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