├── .nvmrc ├── .npmrc ├── screenshots.png ├── static └── favicon.png ├── .github ├── auto-merge.yml ├── workflows │ ├── auto-merge.yml │ ├── lint.yml │ └── codeql-analysis.yml ├── dependabot.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .markdownlint.json ├── postcss.config.cjs ├── src ├── index.d.ts ├── lib │ ├── types │ │ ├── page.ts │ │ ├── post.ts │ │ └── base.ts │ ├── components │ │ ├── Header.svelte │ │ ├── BackButton.svelte │ │ ├── Github.svelte │ │ ├── Email.svelte │ │ ├── Twitter.svelte │ │ ├── Article.svelte │ │ ├── LabelsSection.svelte │ │ ├── HomeHeader.svelte │ │ ├── Footer.svelte │ │ ├── Label.svelte │ │ ├── Giscus.svelte │ │ ├── PageMeta.svelte │ │ ├── Labels.svelte │ │ ├── PostsSection.svelte │ │ ├── Nav.svelte │ │ ├── AboutSection.svelte │ │ └── Pagination.svelte │ ├── helper │ │ ├── readableDate.ts │ │ ├── matcher.ts │ │ ├── fetchPage.ts │ │ └── fetchPosts.ts │ └── constants.ts ├── routes │ ├── __layout-md.svelte │ ├── test.md │ ├── _page │ │ └── __error.md │ ├── __layout@withoutHeader.svelte │ ├── __error.svelte │ ├── [page].svelte │ ├── __layout-withoutHeader.svelte │ ├── atom.xml.ts │ ├── post │ │ └── [post]@withoutHeader.svelte │ └── [...index=pageMatcher].svelte ├── app.d.ts ├── app.html ├── params │ └── pageMatcher.ts ├── app.css ├── hooks.ts └── code.css ├── prettier.config.cjs ├── .eslintignore ├── .prettierignore ├── .gitignore ├── playwright.config.ts ├── .scripts ├── post │ └── index.ts └── pre │ ├── types.ts │ ├── converter.ts │ ├── filter.ts │ ├── writer.ts │ ├── index.ts │ └── fetcher.ts ├── tailwind.config.cjs ├── vite.config.js ├── tsconfig.json ├── .eslintrc.cjs ├── svelte.config.js ├── package.json ├── README.md └── LICENSE /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YeungKC/Hakuba/HEAD/screenshots.png -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YeungKC/Hakuba/HEAD/static/favicon.png -------------------------------------------------------------------------------- /.github/auto-merge.yml: -------------------------------------------------------------------------------- 1 | - match: 2 | dependency_type: all 3 | update_type: all 4 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD041": false, 3 | "MD033": false, 4 | "MD013": false 5 | } 6 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.md' { 2 | export { SvelteComponentDev as default } from 'svelte/internal'; 3 | export { metadata }; 4 | } 5 | -------------------------------------------------------------------------------- /src/lib/types/page.ts: -------------------------------------------------------------------------------- 1 | import type BasePageType from './base'; 2 | 3 | export default interface Page extends BasePageType { 4 | priority?: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/routes/__layout-md.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('prettier-plugin-tailwindcss')], 3 | useTabs: true, 4 | singleQuote: true, 5 | trailingComma: 'none', 6 | printWidth: 100 7 | }; 8 | -------------------------------------------------------------------------------- /src/routes/test.md: -------------------------------------------------------------------------------- 1 | Here is test page. 2 | 3 | > I don't know why, but if there is no such page, then there will be a packaging failure, which I suspect is caused by a routing priority issue. 4 | -------------------------------------------------------------------------------- /src/lib/types/post.ts: -------------------------------------------------------------------------------- 1 | import type BasePageType from './base'; 2 | 3 | export default interface Post extends BasePageType { 4 | labels?: { name: string; color: string }[]; 5 | timezone?: string; 6 | } 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | yarn-error.log 11 | report.html 12 | 13 | /src/routes/post/_source 14 | 15 | /src/routes/_page/*.md 16 | !/src/routes/_page/__error.md 17 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 3000 7 | } 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /src/routes/_page/__error.md: -------------------------------------------------------------------------------- 1 | --- 2 | comment: true 3 | --- 4 | 5 | 8 | 9 | > Oops!! 10 | > 11 | > Here an error seems to have occurred, please [contact me.]({REPOSITORY_ISSUES_URL}) 12 | -------------------------------------------------------------------------------- /src/lib/components/Header.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |

9 | 10 |

11 | -------------------------------------------------------------------------------- /src/lib/components/BackButton.svelte: -------------------------------------------------------------------------------- 1 | POSTS / 6 | -------------------------------------------------------------------------------- /src/lib/components/Github.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Github 8 | 9 | -------------------------------------------------------------------------------- /src/lib/types/base.ts: -------------------------------------------------------------------------------- 1 | export default interface BasePageType { 2 | // from github 3 | title: string; 4 | published: string; 5 | number: number; 6 | url: string; 7 | updated?: string | null; 8 | 9 | // custom 10 | path?: string; 11 | lang?: string; 12 | comment?: boolean; 13 | excerpt?: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/components/Email.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Email 8 | 9 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: AutoMerge 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | auto-merge: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: ahmadnassri/action-dependabot-auto-merge@v2 12 | with: 13 | github-token: ${{ secrets.TOKEN }} 14 | -------------------------------------------------------------------------------- /src/lib/components/Twitter.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | Twitter 8 | 9 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // See https://kit.svelte.dev/docs/types#app 4 | // for information about these interfaces 5 | // and what to do when importing types 6 | declare namespace App { 7 | // interface Locals {} 8 | // interface Platform {} 9 | // interface Session {} 10 | // interface Stuff {} 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/components/Article.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /src/lib/helper/readableDate.ts: -------------------------------------------------------------------------------- 1 | import { TIMEZONE } from '../constants'; 2 | 3 | // Date format in Swedish is the same as ISO 4 | export const readableDate = (dateText: string, timezone?: string) => 5 | new Date(dateText).toLocaleDateString('sv', { 6 | timeZone: timezone ?? TIMEZONE, 7 | month: '2-digit', 8 | day: '2-digit', 9 | year: 'numeric' 10 | }); 11 | -------------------------------------------------------------------------------- /.scripts/post/index.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import { createSitemap } from 'svelte-sitemap/src/index.js'; 3 | dotenv.config({ path: '.env.local' }); 4 | 5 | const domain = process.env.VITE_DOMAIN; 6 | 7 | if (!domain) { 8 | console.log("DOMAIN is not defined, don't create sitemap"); 9 | } else { 10 | createSitemap(domain); 11 | console.log('sitemap created'); 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/helper/matcher.ts: -------------------------------------------------------------------------------- 1 | import { match } from 'path-to-regexp'; 2 | export const generateMatcher =

>(param: string) => { 3 | const m = match

(param, { decode: decodeURIComponent }); 4 | return (path: string) => { 5 | const result = m(path); 6 | if (!result) { 7 | return undefined; 8 | } 9 | return result.params; 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/lib/components/LabelsSection.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | {#if labels.length} 9 |

Labels

10 | {/if} 11 | 12 | 13 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | mode: 'jit', 4 | content: ['./src/**/*.{html,js,svelte,ts}'], 5 | theme: { 6 | container: { 7 | center: true 8 | }, 9 | extend: {} 10 | }, 11 | plugins: [ 12 | ({ addVariant }) => { 13 | addVariant('child', '& > *'); 14 | }, 15 | require('@tailwindcss/typography') 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | - run: npm i 19 | - run: npm run lint 20 | -------------------------------------------------------------------------------- /src/lib/components/HomeHeader.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |
{BLOG_NAME}
12 |
14 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { visualizer } from 'rollup-plugin-visualizer'; 3 | 4 | const lifecycle = process.env.npm_lifecycle_event; 5 | 6 | const plugins = [sveltekit()]; 7 | 8 | if (lifecycle === 'report') { 9 | plugins.push(visualizer({ open: true, gzipSize: true, filename: 'report.html' })); 10 | } 11 | /** @type {import('vite').UserConfig} */ 12 | const config = { 13 | plugins: plugins 14 | }; 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 | 11 |
14 | %sveltekit.body% 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /src/lib/components/Footer.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
12 |
Powered by Hakuba
13 |
15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: 'npm' # See documentation for possible values 9 | directory: '/' # Location of package manifests 10 | schedule: 11 | interval: 'daily' 12 | -------------------------------------------------------------------------------- /src/lib/components/Label.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 | #{label}{count ? `(${count})` : ''} 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | }, 13 | "ts-node": { 14 | "esm": true, 15 | "compilerOptions": { 16 | "isolatedModules": false, 17 | "importsNotUsedAsValues": "remove", 18 | "strictFunctionTypes": false, 19 | "noImplicitAny": false 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/lib/components/Giscus.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | {#if config.comment ?? COMMENT} 9 |
10 | 11 | 23 | {/if} 24 | -------------------------------------------------------------------------------- /src/params/pageMatcher.ts: -------------------------------------------------------------------------------- 1 | import type { ParamMatcher } from '@sveltejs/kit'; 2 | import { generateMatcher } from '$lib/helper/matcher'; 3 | 4 | const labelsMatcher = generateMatcher('label/:label{/page/:page(\\d+)}?'); 5 | const indexMatcher = generateMatcher('{page/:page(\\d+)}?'); 6 | export const listMatcher = (param: string) => indexMatcher(param) || labelsMatcher(param); 7 | 8 | export const match: ParamMatcher = (param) => { 9 | const result = listMatcher(param); 10 | if (result && !result?.page) return true; 11 | if (Number.parseInt(result?.page ?? '') <= 1) return false; 12 | return !!result; 13 | }; 14 | -------------------------------------------------------------------------------- /src/routes/__layout@withoutHeader.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/lib/components/PageMeta.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | {metadata.title} 10 | 11 | {#if 'excerpt' in metadata && metadata.excerpt} 12 | 13 | 14 | {/if} 15 | {#if 'labels' in metadata && metadata.labels?.length} 16 | 17 | {/if} 18 | 19 | -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | a > span.icon.icon-link { 7 | @apply relative opacity-0 transition before:absolute before:-left-[1.5ch] before:pr-[1.5ch] before:content-['#'] group-hover:opacity-100 [@media(any-hover:none)]:opacity-100; 8 | } 9 | 10 | /* css page scrollbar toggle center no jumping */ 11 | html { 12 | @apple overflow-y-scroll; 13 | } 14 | 15 | :root { 16 | @apply overflow-y-auto overflow-x-hidden; 17 | } 18 | 19 | :root body { 20 | @apply absolute; 21 | } 22 | 23 | body { 24 | @apply w-screen overflow-hidden; 25 | } 26 | /* css page scrollbar toggle center no jumping */ 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/components/Labels.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | {#if labels?.length} 12 |
13 | {#each labels ?? [] as label, index (typeof label === 'string' ? label : label?.[0])} 14 | 15 | 17 | {/each} 18 |
19 | {/if} 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.scripts/pre/types.ts: -------------------------------------------------------------------------------- 1 | export interface FetchViewerType { 2 | viewer: { 3 | login: string; 4 | url: string; 5 | bio: string; 6 | }; 7 | } 8 | 9 | export interface DiscussionsType { 10 | number: number; 11 | title: string; 12 | createdAt: string; 13 | publishedAt: string; 14 | lastEditedAt?: string; 15 | url: string; 16 | body: string; 17 | category: { 18 | name: string; 19 | }; 20 | labels: { 21 | nodes: { 22 | name: string; 23 | color: string; 24 | }[]; 25 | }; 26 | } 27 | 28 | export interface PageInfo { 29 | hasNextPage: boolean; 30 | endCursor?: string; 31 | } 32 | export interface FetchDiscussionsType { 33 | repository: { 34 | discussions: { 35 | pageInfo: PageInfo; 36 | nodes: DiscussionsType[]; 37 | }; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/lib/components/PostsSection.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | {#if showTitle && posts.length} 10 |

Posts

11 | {/if} 12 |
    13 | {#each posts as post (post.number)} 14 |
  • 15 |
    {readableDate(post.published)}
    16 | {post.title} 21 |
  • 22 | {/each} 23 |
24 | -------------------------------------------------------------------------------- /src/routes/__error.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/lib/helper/fetchPage.ts: -------------------------------------------------------------------------------- 1 | import type { SvelteComponent } from 'svelte'; 2 | import type Page from '../types/page'; 3 | 4 | export const fetchPages = async () => 5 | ( 6 | await Promise.all( 7 | Object.entries(import.meta.glob('../../routes/_page/*.md')).map(async ([, page]) => { 8 | const { metadata, default: component } = await page(); 9 | return { 10 | metadata: metadata as Page, 11 | component: component as SvelteComponent 12 | }; 13 | }) 14 | ) 15 | ) 16 | .filter(({ metadata: { title } }) => title && title !== '__error') 17 | .sort((a, b) => (b.metadata?.priority ?? 0) - (a.metadata?.priority ?? 0)); 18 | 19 | export const fetchPage = async (path: string) => 20 | (await fetchPages()).find( 21 | ({ metadata: { path: identifyPath, title } }) => 22 | path === identifyPath || path === title.toLowerCase() 23 | ); 24 | -------------------------------------------------------------------------------- /src/hooks.ts: -------------------------------------------------------------------------------- 1 | import type { Handle } from '@sveltejs/kit'; 2 | import { LANGUAGE } from '$lib/constants'; 3 | import { fetchPage } from '$lib/helper/fetchPage'; 4 | import { fetchPost } from '$lib/helper/fetchPosts'; 5 | 6 | const getLang = async ({ 7 | routeId, 8 | params 9 | }: { 10 | routeId: string | null; 11 | params: Record; 12 | }) => { 13 | if (routeId === 'posts/[post]@withoutHeader') { 14 | return (await fetchPost(params.post))?.metadata.lang ?? LANGUAGE; 15 | } 16 | if (routeId === '[page]') { 17 | return (await fetchPage(params.page))?.metadata.lang ?? LANGUAGE; 18 | } 19 | 20 | return LANGUAGE; 21 | }; 22 | 23 | export const handle: Handle = async ({ event, resolve }) => { 24 | const response = await resolve(event, { 25 | transformPage: async ({ html }) => html.replace('%lang%', await getLang(event)) 26 | }); 27 | return response; 28 | }; 29 | -------------------------------------------------------------------------------- /src/routes/[page].svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/lib/components/Nav.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 32 | -------------------------------------------------------------------------------- /src/lib/components/AboutSection.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
16 |

{BIO}

17 | {#if components.length} 18 |

19 | Find me on {#each components as component, index (component)} 20 | 21 | {index < components.length - 2 ? ', ' : ''} 22 | {index === components.length - 2 ? ' and ' : ''} 23 | {/each} 24 | . 25 |

26 | {/if} 27 |
28 | -------------------------------------------------------------------------------- /.scripts/pre/converter.ts: -------------------------------------------------------------------------------- 1 | import YAML from 'yaml'; 2 | import { DiscussionsType } from './types'; 3 | 4 | const splitMdx = (mdx: string) => { 5 | const arr = mdx.split(/^(-{3}(?:\n|\r)([\w\W]+?)(?:\n|\r)-{3})/); 6 | if (arr.length === 1) return [mdx]; 7 | const frontMatter = YAML.parse(arr[2].trim()); 8 | return [arr[3], frontMatter]; 9 | }; 10 | 11 | export const convertFrontMatter = (list: DiscussionsType[]) => 12 | list.map((node) => { 13 | const [md, originalFrontMatter] = splitMdx(node.body); 14 | 15 | const frontMatter = { 16 | number: node.number, 17 | title: node.title, 18 | published: node.publishedAt, 19 | updated: node.lastEditedAt, 20 | url: node.url, 21 | labels: node.labels.nodes, 22 | ...originalFrontMatter 23 | }; 24 | 25 | const frontMatterText = `---\n${YAML.stringify(frontMatter)}---${ 26 | originalFrontMatter ? '' : '\n' 27 | }`; 28 | 29 | return { ...node, body: `${frontMatterText}${md}` }; 30 | }); 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const HAKUBA_GITHUB_URL = 'https://github.com/YeungKC/Hakuba'; 2 | 3 | const env = import.meta.env; 4 | 5 | export const USER_NAME = env.VITE_NAME; 6 | export const GITHUB_URL = env.VITE_GITHUB_URL; 7 | 8 | // Configurable 9 | export const PAGE_SIZE = env.VITE_PAGE_SIZE || 10; 10 | export const BLOG_NAME = env.VITE_BLOG_NAME || `${USER_NAME}'s Blog`; 11 | export const BIO = env.VITE_BIO || env.VITE_DESCRIPTION; 12 | export const EMAIL = env.VITE_EMAIL; 13 | export const TWITTER = env.VITE_TWITTER; 14 | export const DOMAIN = env.VITE_DOMAIN; 15 | export const DESCRIPTION = env.VITE_DESCRIPTION || env.VITE_BIO; 16 | export const KEYWORDS = env.VITE_KEYWORDS; 17 | export const REPOSITORY = env.VITE_REPOSITORY; 18 | export const LANGUAGE = env.VITE_LANGUAGE || 'en'; 19 | export const COMMENT = env.VITE_COMMENT || true; 20 | export const TIMEZONE = env.VITE_TIMEZONE || 'GMT'; 21 | 22 | export const REPOSITORY_URL = `https://github.com/${USER_NAME}/${REPOSITORY}`; 23 | export const REPOSITORY_ISSUES_URL = `${REPOSITORY_URL}/issues`; 24 | -------------------------------------------------------------------------------- /.scripts/pre/filter.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import { DiscussionsType } from './types'; 3 | dotenv.config(); 4 | 5 | const configCategoryName = process.env.CONFIG_CATEGORY || 'Config'; 6 | const postCategoryName = process.env.POST_CATEGORY || 'Post'; 7 | const pageCategoryName = process.env.PAGE_CATEGORY || 'Page'; 8 | 9 | export const findConfig = (list: DiscussionsType[]) => { 10 | const configText = list.find( 11 | (e) => e.category.name === configCategoryName && e.title === 'index' 12 | )?.body; 13 | 14 | if (!configText) return {}; 15 | 16 | return dotenv.parse(configText); 17 | }; 18 | 19 | export const filterPage = (list: DiscussionsType[]) => { 20 | const pages = list.filter((e) => e.category.name === pageCategoryName); 21 | console.log(`filtered pages: ${pages.map(({ title }) => title).join(', ')}`); 22 | return pages; 23 | }; 24 | 25 | export const filterPost = (list: DiscussionsType[]) => { 26 | const posts = list.filter((e) => e.category.name === postCategoryName); 27 | console.log(`filtered posts: ${posts.length}`); 28 | return posts; 29 | }; 30 | -------------------------------------------------------------------------------- /.scripts/pre/writer.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { mkdirSync, promises } from 'fs'; 3 | import { DiscussionsType } from './types'; 4 | 5 | export const writePosts = async (list: DiscussionsType[]) => { 6 | const dir = path.join('./src/routes/post/_source'); 7 | mkdirSync(dir, { recursive: true }); 8 | 9 | await Promise.all( 10 | list.map(({ number, body }) => { 11 | const p = path.resolve(dir, `${number}.md`); 12 | return promises.writeFile(p, body); 13 | }) 14 | ); 15 | }; 16 | 17 | export const writePages = async (list: DiscussionsType[]) => { 18 | const dir = path.join('./src/routes/_page'); 19 | mkdirSync(dir, { recursive: true }); 20 | await Promise.all( 21 | list.map(({ title, body }) => { 22 | const p = path.resolve(dir, `${title}.md`); 23 | return promises.writeFile(p, body); 24 | }) 25 | ); 26 | }; 27 | 28 | export const writeEnv = async (config: Record) => { 29 | const content = Object.entries(config) 30 | .map(([key, value]) => `VITE_${key}=${value}`) 31 | .join('\n'); 32 | return promises.writeFile('./.env.local', content); 33 | }; 34 | -------------------------------------------------------------------------------- /.scripts/pre/index.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import { convertFrontMatter } from './converter.js'; 3 | import { fetchUser, fetchAllDiscussions } from './fetcher.js'; 4 | import { findConfig, filterPage as filterPage, filterPost } from './filter.js'; 5 | import { writePosts, writePages, writeEnv } from './writer.js'; 6 | 7 | dotenv.config(); 8 | const env = process.env; 9 | 10 | const { login: user, url: githubUrl, bio } = await fetchUser(); 11 | let list = await fetchAllDiscussions(user); 12 | 13 | const config = findConfig(list); 14 | 15 | config.NAME = user; 16 | config.GITHUB_URL = githubUrl; 17 | 18 | [ 19 | ['PAGE_SIZE'], 20 | ['BLOG_NAME'], 21 | ['BIO', bio], 22 | ['EMAIL'], 23 | ['TWITTER'], 24 | ['DOMAIN'], 25 | ['DESCRIPTION'], 26 | ['KEYWORDS'], 27 | ['REPOSITORY'], 28 | ['LANGUAGE'], 29 | ['COMMENT'], 30 | ['TIMEZONE'] 31 | ].forEach(([key, value]) => { 32 | const finalValue = config[key] || env[key] || value; 33 | if (!finalValue) return; 34 | config[key] = finalValue; 35 | }); 36 | 37 | list = convertFrontMatter(list); 38 | 39 | const pages = filterPage(list); 40 | const posts = filterPost(list); 41 | 42 | console.log(`writing...`); 43 | 44 | await writePosts(posts); 45 | await writePages(pages); 46 | await writeEnv(config); 47 | 48 | console.log(`done`); 49 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import preprocess from 'svelte-preprocess'; 3 | import { mdsvex } from 'mdsvex'; 4 | import rehypeSlug from 'rehype-slug'; 5 | import rehypeAutolinkHeadings from 'rehype-autolink-headings'; 6 | import rehypeExternalLinks from 'rehype-external-links'; 7 | import addClasses from 'rehype-add-classes'; 8 | 9 | /** @type {import('@sveltejs/kit').Config} */ 10 | const config = { 11 | extensions: ['.svelte', '.md'], 12 | 13 | // Consult https://github.com/sveltejs/svelte-preprocess 14 | // for more information about preprocessors 15 | 16 | preprocess: [ 17 | preprocess({ 18 | preserve: ['ld+json'], 19 | postcss: true 20 | }), 21 | mdsvex({ 22 | extensions: ['.md'], 23 | highlight: { 24 | alias: { vue: 'html' } 25 | }, 26 | rehypePlugins: [ 27 | rehypeSlug, 28 | rehypeAutolinkHeadings, 29 | [rehypeExternalLinks, { target: '_blank' }], 30 | [addClasses, { 'h1,h2,h3,h4,h5,h6': 'group' }] 31 | ], 32 | layout: 'src/routes/__layout-md.svelte' 33 | }) 34 | ], 35 | 36 | kit: { 37 | adapter: adapter({ fallback: '404.html' }), 38 | prerender: { 39 | default: true 40 | }, 41 | trailingSlash: 'always', 42 | inlineStyleThreshold: 1024 * 32 43 | } 44 | }; 45 | 46 | export default config; 47 | -------------------------------------------------------------------------------- /src/routes/__layout-withoutHeader.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | 23 | 24 | {BLOG_NAME} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {#if TWITTER} 34 | 35 | 36 | {/if} 37 | 38 | 39 |
40 | 41 |
42 | 43 |