feat: initial commit

This commit is contained in:
Damillora 2022-07-24 03:52:23 +07:00
commit 8f7b88a01a
31 changed files with 3247 additions and 0 deletions

13
.eslintignore Normal file
View 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
View 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
View File

@ -0,0 +1,9 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
pnpm-global

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

13
.prettierignore Normal file
View 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
View File

@ -0,0 +1,6 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
}

23
Dockerfile Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

11
src/app.d.ts vendored Normal file
View 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
View 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>

View 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&#x27;s Virtual Memoir" />
<meta property="og:type" content="profile" />
<meta property="og:title" content="{author.name} - Damillora&#x27;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&#x27;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>

View 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&#x27;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&#x27;s Virtual Memoir" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Damillora&#x27;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&#x27;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>

View 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&#x27;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&#x27;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>

View 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&#x27;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&#x27;s Virtual Memoir" />
<meta property="og:type" content="website" />
<meta property="og:title" content={`${tag.name} - Damillora&#x27;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&#x27;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>

View 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: ``,
};
}

View 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;
}

View 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>

View 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>

View 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>

View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

15
svelte.config.js Normal file
View 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
View 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
View 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;