feat: initial commit
This commit is contained in:
commit
8f7b88a01a
13
.eslintignore
Normal file
13
.eslintignore
Normal file
@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
20
.eslintrc.cjs
Normal file
20
.eslintrc.cjs
Normal file
@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||
plugins: ['svelte3', '@typescript-eslint'],
|
||||
ignorePatterns: ['*.cjs'],
|
||||
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||
settings: {
|
||||
'svelte3/typescript': () => require('typescript')
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
}
|
||||
};
|
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
pnpm-global
|
13
.prettierignore
Normal file
13
.prettierignore
Normal file
@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100
|
||||
}
|
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
FROM node:16
|
||||
|
||||
# install dependencies
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN pnpm install
|
||||
|
||||
# Copy all local files into the image.
|
||||
COPY . .
|
||||
|
||||
RUN pnpm build
|
||||
|
||||
###
|
||||
# Only copy over the Node pieces we need
|
||||
# ~> Saves 35MB
|
||||
###
|
||||
FROM node:16-slim
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=0 /app/build .
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["node", "./index.js"]
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Damillora
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
38
README.md
Normal file
38
README.md
Normal file
@ -0,0 +1,38 @@
|
||||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npm init svelte
|
||||
|
||||
# create a new project in my-app
|
||||
npm init svelte my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
48
package.json
Normal file
48
package.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@damillora/shallie",
|
||||
"repository": "https://github.com/Damillora/Shallie",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"package": "svelte-kit package",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check --plugin-search-dir=. . && eslint .",
|
||||
"format": "prettier --write --plugin-search-dir=. ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/adapter-node": "^1.0.0-next.83",
|
||||
"@sveltejs/kit": "next",
|
||||
"@types/fitvids": "^2.1.1",
|
||||
"@types/tryghost__content-api": "^1.3.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
||||
"@typescript-eslint/parser": "^5.30.7",
|
||||
"dayjs": "^1.11.4",
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"sass": "^1.54.0",
|
||||
"svelte": "^3.49.0",
|
||||
"svelte-check": "^2.8.0",
|
||||
"svelte-infinite-scroll": "^2.0.1",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"svelte2tsx": "^0.5.12",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.2"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@damillora/plachta": "^1.0.0",
|
||||
"@tryghost/content-api": "^1.11.0",
|
||||
"fitvids": "^2.1.1",
|
||||
"svelte-material-icons": "^2.0.2",
|
||||
"svelte-themes": "^0.0.98"
|
||||
}
|
||||
}
|
2128
pnpm-lock.yaml
Normal file
2128
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
11
src/app.d.ts
vendored
Normal file
11
src/app.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
// interface Locals {}
|
||||
// interface Platform {}
|
||||
// interface Session {}
|
||||
// interface Stuff {}
|
||||
}
|
14
src/app.html
Normal file
14
src/app.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
42
src/lib/components/SEO/AuthorSEO.svelte
Normal file
42
src/lib/components/SEO/AuthorSEO.svelte
Normal file
@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
export let author: any;
|
||||
|
||||
let schemaOrg = `
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "Damillora",
|
||||
"url": "${author.url}",
|
||||
"image": {
|
||||
"@type": "ImageObject",
|
||||
"url": "${author.feature_image}"
|
||||
},
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": "https://blog.nanao.moe/"
|
||||
},
|
||||
"description": "${author.bio}"
|
||||
}
|
||||
\</script\>
|
||||
`;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta property="og:site_name" content="Damillora's Virtual Memoir" />
|
||||
<meta property="og:type" content="profile" />
|
||||
<meta property="og:title" content="{author.name} - Damillora's Virtual Memoir" />
|
||||
<meta property="og:description" content={author.bio} />
|
||||
<meta property="og:url" content="https://blog.nanao.moe/author/${author.slug}/" />
|
||||
<meta property="og:image" content={author.feature_image ?? `https://blog.nanao.moe/images/default-feature.jpg`} />
|
||||
<meta property="article:publisher" content="https://nanao.moe" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="{author.name} - Damillora's Virtual Memoir" />
|
||||
<meta name="twitter:description" content={author.bio} />
|
||||
<meta name="twitter:url" content="https://blog.nanao.moe/author/${author.slug}/" />
|
||||
<meta name="twitter:image" content={author.feature_image ?? `https://blog.nanao.moe/images/default-feature.jpg`} />
|
||||
<meta name="twitter:site" content="@Damillora" />
|
||||
<meta name="twitter:creator" content={author.twitter} />
|
||||
|
||||
{@html schemaOrg}
|
||||
</svelte:head>
|
47
src/lib/components/SEO/IndexSEO.svelte
Normal file
47
src/lib/components/SEO/IndexSEO.svelte
Normal file
@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
let schemaOrg = `
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": "Damillora's Virtual Memoir",
|
||||
"url": "https://blog.nanao.moe/",
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "https://blog.nanao.moe/favicon.png"
|
||||
}
|
||||
},
|
||||
"url": "https://blog.nanao.moe/",
|
||||
"image": {
|
||||
"@type": "ImageObject",
|
||||
"url": "https://blog.nanao.moe/images/default-header.jpg"
|
||||
},
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": "https://blog.nanao.moe/"
|
||||
},
|
||||
"description": "I talk about code, games, and music."
|
||||
}
|
||||
\</script\>
|
||||
`;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta property="og:site_name" content="Damillora's Virtual Memoir" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="Damillora's Virtual Memoir" />
|
||||
<meta property="og:description" content="I talk about code, games, and music." />
|
||||
<meta property="og:url" content="https://blog.nanao.moe/" />
|
||||
<meta property="og:image" content="https://blog.nanao.moe/images/default-header.jpg" />
|
||||
<meta property="article:publisher" content="https://nanao.moe" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Damillora's Virtual Memoir" />
|
||||
<meta name="twitter:description" content="I talk about code, games, and music." />
|
||||
<meta name="twitter:url" content="https://blog.nanao.moe/" />
|
||||
<meta name="twitter:image" content="https://blog.nanao.moe/images/default-header.jpg" />
|
||||
<meta name="twitter:site" content="@Damillora" />
|
||||
|
||||
{@html schemaOrg}
|
||||
</svelte:head>
|
75
src/lib/components/SEO/PostSEO.svelte
Normal file
75
src/lib/components/SEO/PostSEO.svelte
Normal file
@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import Post from "../../../../../Plachta/package/components/PageTypes/Post.svelte";
|
||||
|
||||
|
||||
export let post: any;
|
||||
|
||||
let schemaOrg = `
|
||||
<script type="application/ld+json">
|
||||
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": "Damillora's Virtual Memoir",
|
||||
"url": "https://blog.nanao.moe/",
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "https://blog.nanao.moe/images/default-feature.jpg"
|
||||
}
|
||||
},
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "${post.primary_author.name}",
|
||||
"image": {
|
||||
"@type": "ImageObject",
|
||||
"url": "${post.primary_author.profile_image ?? 'https://blog.nanao.moe/images/default-feature.jpg'}"
|
||||
},
|
||||
"url": "${post.primary_author.url}/"
|
||||
},
|
||||
"headline": "${post.title}",
|
||||
"url": "${post.url}",
|
||||
"datePublished": "${post.published_at}",
|
||||
"dateModified": "${post.updated_at}",
|
||||
"image": {
|
||||
"@type": "ImageObject",
|
||||
"url": "${post.feature_image ?? 'https://blog.nanao.moe/images/default-feature.jpg'}"
|
||||
},
|
||||
"keywords": "${post.primary_tag.name}",
|
||||
"description": "${post.excerpt}",
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": "https://blog.nanao.moe/"
|
||||
}
|
||||
}
|
||||
\</script\>
|
||||
`;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta property="og:site_name" content="Damillora's Virtual Memoir" />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content={post.title} />
|
||||
<meta property="og:description" content={post.excerpt}/>
|
||||
<meta property="og:url" content={post.url} />
|
||||
<meta property="og:image" content={post.feature_image ?? 'https://blog.nanao.moe/images/default-feature.jpg'} />
|
||||
<meta property="article:published_time" content="2022-07-22T12:42:23.000Z" />
|
||||
<meta property="article:modified_time" content="2022-07-22T12:47:19.000Z" />
|
||||
<meta property="article:tag" content={post.primary_tag.name} />
|
||||
|
||||
<meta property="article:publisher" content="https://www.facebook.com/Damillora" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={post.title} />
|
||||
<meta name="twitter:description" content={post.excerpt} />
|
||||
<meta name="twitter:url" content={post.url} />
|
||||
<meta name="twitter:image" content={post.feature_image ?? 'https://blog.nanao.moe/images/default-feature.jpg'} />
|
||||
<meta name="twitter:label1" content="Written by" />
|
||||
<meta name="twitter:data1" content={post.primary_author.name} />
|
||||
<meta name="twitter:label2" content="Filed under" />
|
||||
<meta name="twitter:data2" content={post.primary_tag.name} />
|
||||
<meta name="twitter:site" content="@Damillora" />
|
||||
<meta name="twitter:creator" content={post.primary_author.twitter} />
|
||||
|
||||
{@html schemaOrg}
|
||||
</svelte:head>
|
49
src/lib/components/SEO/TagSEO.svelte
Normal file
49
src/lib/components/SEO/TagSEO.svelte
Normal file
@ -0,0 +1,49 @@
|
||||
<script lang="ts">
|
||||
export let tag: any;
|
||||
|
||||
let schemaOrg = `
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Series",
|
||||
"publisher": {
|
||||
"@type": "Organization",
|
||||
"name": "Damillora's Virtual Memoir",
|
||||
"url": "https://blog.nanao.moe/",
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "https://blog.nanao.moe/images/favicon.png"
|
||||
}
|
||||
},
|
||||
"url": "${tag.url}",
|
||||
"image": {
|
||||
"@type": "ImageObject",
|
||||
"url": "${tag.feature_image}"
|
||||
},
|
||||
"name": "${tag.name}",
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": "https://blog.nanao.moe/"
|
||||
},
|
||||
"description": "${tag.description}"
|
||||
}
|
||||
\</script\>
|
||||
`;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta property="og:site_name" content="Damillora's Virtual Memoir" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content={`${tag.name} - Damillora's Virtual Memoir`} />
|
||||
<meta property="og:description" content={tag.description} />
|
||||
<meta property="og:url" content={`https://blog.nanao.moe/${tag.slug}/`} />
|
||||
<meta property="og:image" content={tag.feature_image ?? `https://blog.nanao.moe/images/default-feature.jpg`} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={`${tag.name} - Damillora's Virtual Memoir`} />
|
||||
<meta name="twitter:description" content={tag.description} />
|
||||
<meta name="twitter:url" content={`https://blog.nanao.moe/${tag.slug}/`} />
|
||||
<meta name="twitter:image" content={tag.feature_image ?? `https://blog.nanao.moe/images/default-feature.jpg`} />
|
||||
<meta name="twitter:site" content="@Damillora" />
|
||||
|
||||
{@html schemaOrg}
|
||||
</svelte:head>
|
68
src/lib/content/contentApi.ts
Normal file
68
src/lib/content/contentApi.ts
Normal file
@ -0,0 +1,68 @@
|
||||
|
||||
import GhostContentAPI from '@tryghost/content-api';
|
||||
|
||||
export const GHOST_URL = 'https://admin.blog.nanao.moe';
|
||||
const GHOST_KEY = '8c86e852c31b39b9d4148b2088';
|
||||
const GHOST_VERSION = 'v5.0';
|
||||
|
||||
const api = GhostContentAPI({
|
||||
url: GHOST_URL,
|
||||
key: GHOST_KEY,
|
||||
version: GHOST_VERSION
|
||||
})
|
||||
|
||||
export const browsePost = async (page = 1) => {
|
||||
const posts = await api.posts.browse({ page: page, limit: 10, include: ['tags', 'authors'] });
|
||||
|
||||
return posts
|
||||
}
|
||||
export const browseAllPost = async (page = 1) => {
|
||||
const posts = await api.posts.browse({ limit: 'all' });
|
||||
|
||||
return posts
|
||||
}
|
||||
export const browsePostWithTag = async (slug: string, page = 1) => {
|
||||
const posts = await api.posts.browse({ page: page, limit: 10, include: ['tags', 'authors'], filter: [`tag:${slug}`] });
|
||||
|
||||
return posts
|
||||
}
|
||||
export const browsePostWithAuthor = async (slug: string, page = 1) => {
|
||||
const posts = await api.posts.browse({ page: page, limit: 10, include: ['tags', 'authors'], filter: [`author:${slug}`] });
|
||||
|
||||
return posts
|
||||
}
|
||||
export const readPost = async (slug: string) => {
|
||||
const post = await api.posts.read({ slug }, { include: ['tags', 'authors'] });
|
||||
|
||||
return post;
|
||||
}
|
||||
|
||||
export const readTag = async (slug: string) => {
|
||||
const tag = await api.tags.read({ slug });
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
export const readAuthor = async (slug: string) => {
|
||||
const author = await api.authors.read({ slug });
|
||||
|
||||
return author;
|
||||
}
|
||||
|
||||
export const browseLatestPost = async () => {
|
||||
const post = await api.posts.browse({ limit: 1 });
|
||||
|
||||
return post[0];
|
||||
}
|
||||
export const browseLatestTag = async () => {
|
||||
const tag = await api.tags.browse({ limit: 1, order: 'updated_at DESC' })
|
||||
}
|
||||
export const browseLatestAuthor = async () => {
|
||||
const tag = await api.authors.browse({ limit: 1, order: 'updated_at DESC' })
|
||||
}
|
||||
export const browseSettings = async () => {
|
||||
return {
|
||||
codeinjection_head: `<script async defer data-domain="blog.nanao.moe" src="https://stats.nanao.moe/js/plausible.js"></script>`,
|
||||
codeinjection_foot: ``,
|
||||
};
|
||||
}
|
15
src/lib/content/searchApi.ts
Normal file
15
src/lib/content/searchApi.ts
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
export const search = async (q: string, page = 1) => {
|
||||
const yurikoEndpoint = 'https://search.blog.nanao.moe';
|
||||
const apiEndpoint = '/api/article/search?';
|
||||
const queryString = 'q=';
|
||||
const pageString = '&page=';
|
||||
|
||||
const search = await fetch(
|
||||
`${yurikoEndpoint}${apiEndpoint}${queryString}${q}${pageString}${page}`
|
||||
);
|
||||
const result = await search.json();
|
||||
|
||||
return result;
|
||||
}
|
132
src/routes/[tag]/[slug].svelte
Normal file
132
src/routes/[tag]/[slug].svelte
Normal file
@ -0,0 +1,132 @@
|
||||
<script lang="ts" context="module">
|
||||
export const load: Load = async ({ params }) => {
|
||||
const postSlug = params.slug;
|
||||
const post = await readPost(postSlug);
|
||||
|
||||
return {
|
||||
props: {
|
||||
post: post
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
import fitvids from 'fitvids';
|
||||
|
||||
import Hero from '@damillora/plachta/components/Hero/Hero.svelte';
|
||||
import PostHeader from '@damillora/plachta/components/Post/PostHeader.svelte';
|
||||
import Post from '@damillora/plachta/components/PageTypes/Post.svelte';
|
||||
import PostMain from '@damillora/plachta/components/Post/PostMain.svelte';
|
||||
import PostNavigator from '@damillora/plachta/components/Post/PostNavigator.svelte';
|
||||
import Container from '@damillora/plachta/components/Container/Container.svelte';
|
||||
import PostRelated from '@damillora/plachta/components/Post/PostRelated.svelte';
|
||||
import PostCard from '@damillora/plachta/components/PostCard/PostCard.svelte';
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { readPost } from '$lib/content/contentApi';
|
||||
import { browser } from '$app/env';
|
||||
import PostSeo from '$lib/components/SEO/PostSEO.svelte';
|
||||
|
||||
export let post: any;
|
||||
|
||||
if (browser) {
|
||||
fitvids();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{post.title}</title>
|
||||
</svelte:head>
|
||||
|
||||
<PostSeo post={post} />
|
||||
<Hero background={post.feature_image ?? `/images/default-feature.jpg`} />
|
||||
|
||||
<Container>
|
||||
<Post>
|
||||
<PostHeader
|
||||
title={post.title}
|
||||
authors={post.authors}
|
||||
primary_tag={post.primary_tag}
|
||||
date={dayjs(post.published_at).format('DD MMM YYYY')}
|
||||
reading_time={`${post.reading_time} min read`}
|
||||
/>
|
||||
<PostMain>
|
||||
{@html post.html}
|
||||
<svelte:fragment slot="comments" />
|
||||
</PostMain>
|
||||
<PostNavigator
|
||||
prev_post={{
|
||||
url: '/examples/post-page',
|
||||
title: 'Sophie!'
|
||||
}}
|
||||
next_post={{
|
||||
url: '/examples/post-page',
|
||||
title: 'Sophie!'
|
||||
}}
|
||||
/>
|
||||
</Post>
|
||||
|
||||
<PostRelated name="Sophie" url="https://google.com" accent_color="#3b34dc">
|
||||
<PostCard
|
||||
authors={[
|
||||
{
|
||||
profile_image: 'https://images.nanao.moe/r/GDuirR.jpg',
|
||||
url: '/examples/post-page',
|
||||
name: 'Sophie Neuenmuller'
|
||||
}
|
||||
]}
|
||||
date="2022 November 2020"
|
||||
excerpt="Sophie!"
|
||||
feature_image="https://images.nanao.moe/r/7jETH3.png"
|
||||
primary_tag={{
|
||||
accent_color: '#3b7b9c',
|
||||
name: 'Sound Voltex',
|
||||
url: '/examples/post-page'
|
||||
}}
|
||||
reading_time="3 min"
|
||||
title="Sophie!"
|
||||
url="/examples/post-page"
|
||||
/>
|
||||
<PostCard
|
||||
authors={[
|
||||
{
|
||||
profile_image: 'https://images.nanao.moe/r/GDuirR.jpg',
|
||||
url: '/examples/post-page',
|
||||
name: 'Sophie Neuenmuller'
|
||||
}
|
||||
]}
|
||||
date="2022 November 2020"
|
||||
excerpt="Sophie!"
|
||||
feature_image="https://images.nanao.moe/r/7jETH3.png"
|
||||
primary_tag={{
|
||||
accent_color: '#3b7b9c',
|
||||
name: 'Sound Voltex',
|
||||
url: '/examples/post-page'
|
||||
}}
|
||||
reading_time="3 min"
|
||||
title="Sophie!"
|
||||
url="/examples/post-page"
|
||||
/>
|
||||
<PostCard
|
||||
authors={[
|
||||
{
|
||||
profile_image: 'https://images.nanao.moe/r/GDuirR.jpg',
|
||||
url: '/examples/post-page',
|
||||
name: 'Sophie Neuenmuller'
|
||||
}
|
||||
]}
|
||||
date="2022 November 2020"
|
||||
excerpt="Sophie!"
|
||||
feature_image="https://images.nanao.moe/r/7jETH3.png"
|
||||
primary_tag={{
|
||||
accent_color: '#3b7b9c',
|
||||
name: 'Sound Voltex',
|
||||
url: '/examples/post-page'
|
||||
}}
|
||||
reading_time="3 min"
|
||||
title="Sophie!"
|
||||
url="/examples/post-page"
|
||||
/>
|
||||
</PostRelated>
|
||||
</Container>
|
87
src/routes/[tag]/index.svelte
Normal file
87
src/routes/[tag]/index.svelte
Normal file
@ -0,0 +1,87 @@
|
||||
<script lang="ts" context="module">
|
||||
export const load: Load = async ({ params }) => {
|
||||
const tagSlug = params.tag;
|
||||
const tagObj = await readTag(tagSlug);
|
||||
const posts = await browsePostWithTag(tagSlug);
|
||||
|
||||
return {
|
||||
props: {
|
||||
tag: tagObj,
|
||||
posts: posts
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Hero from '@damillora/plachta/components/Hero/Hero.svelte';
|
||||
import PostCard from '@damillora/plachta/components/PostCard/PostCard.svelte';
|
||||
import Index from '@damillora/plachta/components/PageTypes/Index.svelte';
|
||||
import TagHeader from '@damillora/plachta/components/Post/TagHeader.svelte';
|
||||
import Post from '@damillora/plachta/components/PageTypes/Post.svelte';
|
||||
import Container from '@damillora/plachta/components/Container/Container.svelte';
|
||||
import { browsePostWithTag, readTag } from '$lib/content/contentApi';
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/env';
|
||||
import dayjs from 'dayjs';
|
||||
import TagSeo from '$lib/components/SEO/TagSEO.svelte';
|
||||
|
||||
export let tag: any;
|
||||
export let posts: any[];
|
||||
let newPosts: any[] = [];
|
||||
let page = 1;
|
||||
$: posts = [...posts, ...newPosts];
|
||||
|
||||
async function loadPage() {
|
||||
const posts = await browsePostWithTag(tag.slug, page);
|
||||
newPosts = posts;
|
||||
}
|
||||
|
||||
let footer: HTMLElement;
|
||||
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
const handleIntersect: IntersectionObserverCallback = (entries, observer) => {
|
||||
const first = entries[0];
|
||||
if (first.isIntersecting) {
|
||||
page++;
|
||||
loadPage();
|
||||
}
|
||||
};
|
||||
const options = { threshold: 0.125, rootMargin: '-100% 0% 100%' };
|
||||
const observer = new IntersectionObserver(handleIntersect, options);
|
||||
observer.observe(footer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Tag: {tag.name} - Damillora's Virtual Memoir</title>
|
||||
</svelte:head>
|
||||
|
||||
<TagSeo {tag} />
|
||||
|
||||
<Hero background={tag.feature_image ?? `/images/default-feature.jpg`} />
|
||||
|
||||
<Container>
|
||||
<Post>
|
||||
<TagHeader accent_color={tag.accent_color} name={tag.name} description={tag.description} />
|
||||
</Post>
|
||||
|
||||
<Index>
|
||||
{#each posts as post}
|
||||
<PostCard
|
||||
title={post.title}
|
||||
authors={post.authors}
|
||||
primary_tag={post.primary_tag}
|
||||
date={dayjs(post.published_at).format('DD MMM YYYY')}
|
||||
reading_time={`${post.reading_time} min read`}
|
||||
excerpt={post.excerpt}
|
||||
feature_image={post.feature_image ?? '/images/default-feature.jpg'}
|
||||
url={post.url}
|
||||
/>
|
||||
{/each}
|
||||
<div bind:this={footer} />
|
||||
</Index>
|
||||
</Container>
|
58
src/routes/__layout.svelte
Normal file
58
src/routes/__layout.svelte
Normal file
@ -0,0 +1,58 @@
|
||||
<script lang="ts" context="module">
|
||||
export const load: Load = async () => {
|
||||
const settings = await browseSettings();
|
||||
|
||||
return {
|
||||
props: {
|
||||
header: settings.codeinjection_head,
|
||||
footer: settings.codeinjection_foot
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { browseSettings } from '$lib/content/contentApi';
|
||||
|
||||
import Base from '@damillora/plachta/components/Base/Base.svelte';
|
||||
import Footer from '@damillora/plachta/components/Footer.svelte/Footer.svelte';
|
||||
import Header from '@damillora/plachta/components/Header/Header.svelte';
|
||||
import NavDarkMode from '@damillora/plachta/components/Nav/NavDarkMode.svelte';
|
||||
import NavMenu from '@damillora/plachta/components/Nav/NavMenu.svelte';
|
||||
import NavSearch from '@damillora/plachta/components/Nav/NavSearch.svelte';
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
|
||||
const doSearch = (e: any) => {
|
||||
if (e.detail.query) {
|
||||
goto('/search?q=' + e.detail.query, { replaceState: true, keepfocus: true });
|
||||
} else {
|
||||
goto('/', { replaceState: true, keepfocus: true });
|
||||
}
|
||||
};
|
||||
|
||||
export let header = '';
|
||||
export let footer = '';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{@html header}
|
||||
</svelte:head>
|
||||
<Base>
|
||||
<Header>
|
||||
<svelte:fragment slot="title">
|
||||
<a href="/"> <strong>Damillora</strong>'s Virtual Memoir </a>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="nav">
|
||||
<NavMenu label="Home" url="/" />
|
||||
<NavDarkMode />
|
||||
<NavSearch on:search={doSearch} />
|
||||
</svelte:fragment>
|
||||
</Header>
|
||||
|
||||
<slot />
|
||||
{@html footer}
|
||||
<Footer>
|
||||
<p>Copyright (c) 2021 Damillora</p>
|
||||
</Footer>
|
||||
</Base>
|
95
src/routes/author/[author].svelte
Normal file
95
src/routes/author/[author].svelte
Normal file
@ -0,0 +1,95 @@
|
||||
<script lang="ts" context="module">
|
||||
export const load: Load = async ({ params }) => {
|
||||
const authorSlug = params.author;
|
||||
const authorObj = await readAuthor(authorSlug);
|
||||
const posts = await browsePostWithAuthor(authorSlug);
|
||||
|
||||
return {
|
||||
props: {
|
||||
author: authorObj,
|
||||
posts: posts
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import PostCard from '@damillora/plachta/components/PostCard/PostCard.svelte';
|
||||
import Index from '@damillora/plachta/components/PageTypes/Index.svelte';
|
||||
import Post from '@damillora/plachta/components/PageTypes/Post.svelte';
|
||||
import AuthorHeader from '@damillora/plachta/components/Post/AuthorHeader.svelte';
|
||||
import Container from '@damillora/plachta/components/Container/Container.svelte';
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { browsePostWithAuthor, readAuthor } from '$lib/content/contentApi';
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/env';
|
||||
import dayjs from 'dayjs';
|
||||
import AuthorSeo from '$lib/components/SEO/AuthorSEO.svelte';
|
||||
import Hero from '@damillora/plachta/components/Hero/Hero.svelte';
|
||||
|
||||
export let author: any;
|
||||
|
||||
export let posts: any[];
|
||||
let newPosts: any[] = [];
|
||||
let page = 1;
|
||||
$: posts = [...posts, ...newPosts];
|
||||
|
||||
async function loadPage() {
|
||||
const posts = await browsePostWithAuthor(author.slug, page);
|
||||
newPosts = posts;
|
||||
}
|
||||
|
||||
let footer: HTMLElement;
|
||||
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
const handleIntersect: IntersectionObserverCallback = (entries, observer) => {
|
||||
const first = entries[0];
|
||||
if (first.isIntersecting) {
|
||||
page++;
|
||||
loadPage();
|
||||
}
|
||||
};
|
||||
const options = { threshold: 0.125, rootMargin: '-100% 0% 100%' };
|
||||
const observer = new IntersectionObserver(handleIntersect, options);
|
||||
observer.observe(footer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Author: {author.name} - Damillora's Virtual Memoir</title>
|
||||
</svelte:head>
|
||||
|
||||
<Hero background={author.cover_image ?? `/images/default-feature.jpg`} />
|
||||
|
||||
<AuthorSeo {author} />
|
||||
|
||||
<Container>
|
||||
<Post>
|
||||
<AuthorHeader
|
||||
profile_image={author.profile_image}
|
||||
name={author.name}
|
||||
bio={author.bio}
|
||||
website={author.website}
|
||||
facebook={author.facebook}
|
||||
twitter={author.twitter}
|
||||
/>
|
||||
</Post>
|
||||
|
||||
<Index>
|
||||
{#each posts as post}
|
||||
<PostCard
|
||||
title={post.title}
|
||||
authors={post.authors}
|
||||
primary_tag={post.primary_tag}
|
||||
date={dayjs(post.published_at).format('DD MMM YYYY')}
|
||||
reading_time={`${post.reading_time} min read`}
|
||||
excerpt={post.excerpt}
|
||||
feature_image={post.feature_image ?? '/images/default-feature.jpg'}
|
||||
url={post.url}
|
||||
/>
|
||||
{/each}
|
||||
<div bind:this={footer} />
|
||||
</Index>
|
||||
</Container>
|
77
src/routes/index.svelte
Normal file
77
src/routes/index.svelte
Normal file
@ -0,0 +1,77 @@
|
||||
<script lang="ts" context="module">
|
||||
import { browsePost } from '$lib/content/contentApi';
|
||||
|
||||
export async function load() {
|
||||
const posts = await browsePost();
|
||||
|
||||
return {
|
||||
props: {
|
||||
posts: posts
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/env';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import Container from '@damillora/plachta/components/Container/Container.svelte';
|
||||
import Hero from '@damillora/plachta/components/Hero/Hero.svelte';
|
||||
import Index from '@damillora/plachta/components/PageTypes/Index.svelte';
|
||||
import PostCard from '@damillora/plachta/components/PostCard/PostCard.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import IndexSeo from '$lib/components/SEO/IndexSEO.svelte';
|
||||
|
||||
export let posts: any[] = [];
|
||||
let newPosts: any[] = [];
|
||||
let page = 1;
|
||||
$: posts = [...posts, ...newPosts];
|
||||
|
||||
async function loadPage() {
|
||||
const posts = await browsePost(page);
|
||||
newPosts = posts;
|
||||
}
|
||||
|
||||
let footer: HTMLElement;
|
||||
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
const handleIntersect: IntersectionObserverCallback = (entries, observer) => {
|
||||
const first = entries[0];
|
||||
if (first.isIntersecting) {
|
||||
page++;
|
||||
loadPage();
|
||||
}
|
||||
};
|
||||
const options = { threshold: 0.125, rootMargin: '-100% 0% 100%' };
|
||||
const observer = new IntersectionObserver(handleIntersect, options);
|
||||
observer.observe(footer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Damillora's Virtual Memoir</title>
|
||||
</svelte:head>
|
||||
<IndexSeo />
|
||||
|
||||
<Hero background="/images/default-feature.jpg" />
|
||||
|
||||
<Container>
|
||||
<Index>
|
||||
{#each posts as post}
|
||||
<PostCard
|
||||
title={post.title}
|
||||
authors={post.authors}
|
||||
primary_tag={post.primary_tag}
|
||||
date={dayjs(post.published_at).format('DD MMM YYYY')}
|
||||
reading_time={`${post.reading_time} min read`}
|
||||
excerpt={post.excerpt}
|
||||
feature_image={post.feature_image ?? '/images/default-feature.jpg'}
|
||||
url={post.url}
|
||||
/>
|
||||
{/each}
|
||||
</Index>
|
||||
<div bind:this={footer} />
|
||||
</Container>
|
79
src/routes/search.svelte
Normal file
79
src/routes/search.svelte
Normal file
@ -0,0 +1,79 @@
|
||||
<script lang="ts" context="module">
|
||||
export const load: Load = async ({ url, fetch }) => {
|
||||
const yurikoEndpoint = 'https://search.blog.nanao.moe';
|
||||
const apiEndpoint = '/api/article/search?';
|
||||
const queryString = 'q=';
|
||||
const pageString = '&page=';
|
||||
|
||||
const q = url.searchParams.get('q') ?? '';
|
||||
const page = url.searchParams.get('page') ?? 1;
|
||||
|
||||
const search = await fetch(
|
||||
`${yurikoEndpoint}${apiEndpoint}${queryString}${q}${pageString}${page}`
|
||||
);
|
||||
const result = await search.json();
|
||||
|
||||
return {
|
||||
props: {
|
||||
query: q,
|
||||
posts: result.result
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/env';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import Container from '@damillora/plachta/components/Container/Container.svelte';
|
||||
import Hero from '@damillora/plachta/components/Hero/Hero.svelte';
|
||||
import Index from '@damillora/plachta/components/PageTypes/Index.svelte';
|
||||
import SearchCard from '@damillora/plachta/components/PostCard/SearchCard.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { search } from '$lib/content/searchApi';
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
export let query = '';
|
||||
export let posts: any[] = [];
|
||||
let newPosts: any[] = [];
|
||||
let page = 1;
|
||||
$: posts = [...posts, ...newPosts];
|
||||
|
||||
async function loadPage() {
|
||||
const posts = await search(query, page);
|
||||
|
||||
newPosts = posts.result;
|
||||
}
|
||||
|
||||
let footer: HTMLElement;
|
||||
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
const handleIntersect: IntersectionObserverCallback = (entries, observer) => {
|
||||
const first = entries[0];
|
||||
if (first.isIntersecting) {
|
||||
page++;
|
||||
loadPage();
|
||||
}
|
||||
};
|
||||
const options = { threshold: 0.125, rootMargin: '-100% 0% 100%' };
|
||||
const observer = new IntersectionObserver(handleIntersect, options);
|
||||
observer.observe(footer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Damillora's Virtual Memoir</title>
|
||||
</svelte:head>
|
||||
|
||||
<Hero background="/images/default-feature.jpg" />
|
||||
|
||||
<Container>
|
||||
<Index>
|
||||
{#each posts as post}
|
||||
<SearchCard title={post.title} url={post.url} excerpt={post.excerpt} />
|
||||
{/each}
|
||||
</Index>
|
||||
<div bind:this={footer} />
|
||||
</Container>
|
39
src/routes/sitemap.xml.ts
Normal file
39
src/routes/sitemap.xml.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { browseAllPost } from "$lib/content/contentApi";
|
||||
|
||||
export async function GET() {
|
||||
|
||||
const allPosts = await browseAllPost();
|
||||
|
||||
|
||||
/** @type {import('@sveltejs/kit').RequestHandler} */
|
||||
const createSitemap = () => {
|
||||
|
||||
let xml = ''
|
||||
xml += '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
|
||||
|
||||
|
||||
allPosts.map((_post) => {
|
||||
xml += '<url>'
|
||||
xml += `<loc>${_post.url}</loc>`
|
||||
xml += `<lastmod>${_post.updated_at}</lastmod>`
|
||||
xml += `<changefreq>always</changefreq>`
|
||||
xml += `<priority>0.5</priority>`
|
||||
xml += '</url>'
|
||||
});
|
||||
|
||||
xml += '</urlset>'
|
||||
|
||||
console.log(`Wrote Sitemap`);
|
||||
return xml;
|
||||
|
||||
}
|
||||
const xml = createSitemap();
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
'access-control-allow-origin': '*'
|
||||
},
|
||||
body: xml,
|
||||
};
|
||||
}
|
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 263 KiB |
BIN
static/images/default-feature.jpg
Normal file
BIN
static/images/default-feature.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 493 KiB |
15
svelte.config.js
Normal file
15
svelte.config.js
Normal file
@ -0,0 +1,15 @@
|
||||
import adapter from '@sveltejs/adapter-node';
|
||||
import preprocess from 'svelte-preprocess';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: preprocess(),
|
||||
|
||||
kit: {
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
}
|
||||
}
|
11
vite.config.js
Normal file
11
vite.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import preprocess from 'svelte-preprocess';
|
||||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
plugins: [sveltekit({
|
||||
useVitePreprocess: true,
|
||||
})],
|
||||
};
|
||||
|
||||
export default config;
|
Loading…
Reference in New Issue
Block a user