mirror of
https://github.com/Sevichecc/Urara-Blog.git
synced 2025-04-30 11:29:29 +08:00
remove git cache
This commit is contained in:
parent
1f263d10ac
commit
a060f049cf
270 changed files with 0 additions and 20784 deletions
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"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": true
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaVersion": 2019
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2017": true,
|
|
||||||
"node": true
|
|
||||||
}
|
|
||||||
}
|
|
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -1,2 +0,0 @@
|
||||||
# Auto detect text files and perform LF normalization
|
|
||||||
* text=auto
|
|
35
.github/CONTRIBUTING.md
vendored
35
.github/CONTRIBUTING.md
vendored
|
@ -1,35 +0,0 @@
|
||||||
# Contributing
|
|
||||||
|
|
||||||
Thanks for ur interest in contributing to Urara! Please take a moment to read this document before submitting a pull request.
|
|
||||||
|
|
||||||
## Pull requests
|
|
||||||
|
|
||||||
pls ask before u start working on any important new feature.
|
|
||||||
|
|
||||||
for minor features and bug fixes: I will accept them as long as I think the code quality is good enough.
|
|
||||||
|
|
||||||
### Commit message
|
|
||||||
|
|
||||||
This is not mandatory at this time, but pls use [gitmoji](https://gitmoji.dev) and [Conventional Commits](https://www.conventionalcommits.org) whenever possible.
|
|
||||||
|
|
||||||
### Check the code
|
|
||||||
|
|
||||||
Run this command to check the code:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm check
|
|
||||||
```
|
|
||||||
|
|
||||||
In general, expect to see output like this:
|
|
||||||
|
|
||||||
```text
|
|
||||||
svelte-check found 0 errors, 0 warnings, and 0 hints
|
|
||||||
```
|
|
||||||
|
|
||||||
### Format the code
|
|
||||||
|
|
||||||
run this command to format the code:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm format
|
|
||||||
```
|
|
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
@ -1 +0,0 @@
|
||||||
custom: ['https://giveth.io/project/urara', 'https://donate.lol/eth/0xaBdB3f715198A4d7e6591b6ebBE8Ccf235e5D752']
|
|
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -1,14 +0,0 @@
|
||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
.svelte-kit
|
|
||||||
/package
|
|
||||||
src/routes/**/
|
|
||||||
static/
|
|
||||||
build
|
|
||||||
.vercel_build_output/
|
|
||||||
.netlify/
|
|
||||||
.env.local
|
|
||||||
.env.**.local
|
|
||||||
myblog/urara/2022-06-12-appwrite.md
|
|
||||||
*.config.js
|
|
||||||
urara.js
|
|
1
.npmrc
1
.npmrc
|
@ -1 +0,0 @@
|
||||||
engine-strict=true
|
|
1
.nvmrc
1
.nvmrc
|
@ -1 +0,0 @@
|
||||||
v18.4.0
|
|
|
@ -1,7 +0,0 @@
|
||||||
.svelte-kit/**
|
|
||||||
static/**
|
|
||||||
build/**
|
|
||||||
node_modules/**
|
|
||||||
pnpm-lock.yaml
|
|
||||||
.netlify/**
|
|
||||||
.vercel_build_output/**
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"printWidth": 128,
|
|
||||||
"useTabs": false,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"semi": false,
|
|
||||||
"singleQuote": true,
|
|
||||||
"endOfLine": "lf",
|
|
||||||
"arrowParens": "avoid",
|
|
||||||
"trailingComma": "none",
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"bracketSameLine": true,
|
|
||||||
"htmlWhitespaceSensitivity": "ignore"
|
|
||||||
}
|
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": ["svelte.svelte-vscode", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
|
|
||||||
}
|
|
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"files.eol": "\n",
|
|
||||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
|
||||||
"css.lint.unknownAtRules": "ignore",
|
|
||||||
"svelte.plugin.css.diagnostics.enable": false,
|
|
||||||
"[html]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
||||||
}
|
|
||||||
}
|
|
30
README.md
30
README.md
|
@ -1,30 +0,0 @@
|
||||||
My Tech Blog, base on [Urara](https://github.com/importantimport/urara)
|
|
||||||
|
|
||||||
## Fork from
|
|
||||||
|
|
||||||
- [kwchang083.dev](https://github.com/kwchang0831/kwchang0831.dev)
|
|
||||||
- [kwaa/blog](https://github.com/kwaa/blog)
|
|
||||||
|
|
||||||
### Custom Featrues:
|
|
||||||
|
|
||||||
- Copy code to clipboard
|
|
||||||
- Project page
|
|
||||||
- [zhlint](https://github.com/Jinjiang/zhlint)
|
|
||||||
- Quote Style
|
|
||||||

|
|
||||||
|
|
||||||
## Deploy
|
|
||||||
|
|
||||||
- [Netlify]
|
|
||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
- [ ] Note Page
|
|
||||||
- [ ] Archie Page
|
|
||||||
- [ ] Refactoring the atom feed format
|
|
||||||
- [ ] NeoDB component
|
|
||||||
- [ ] ...
|
|
||||||
|
|
||||||
### License:
|
|
||||||
|
|
||||||
[CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
|
|
@ -1,7 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
echo "1. build locally"
|
|
||||||
netlify build
|
|
||||||
echo "2.deploy test"
|
|
||||||
netlify deploy
|
|
||||||
echo "3.deploy"
|
|
||||||
netlify deploy --prod
|
|
110
mdsvex.config.ts
110
mdsvex.config.ts
|
@ -1,110 +0,0 @@
|
||||||
// mdsvex config type
|
|
||||||
import type { MdsvexOptions } from 'mdsvex'
|
|
||||||
|
|
||||||
// rehype plugins
|
|
||||||
import rehypeSlug from 'rehype-slug'
|
|
||||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
|
|
||||||
import rehypeExternalLinks from 'rehype-external-links'
|
|
||||||
|
|
||||||
// urara remark plugins
|
|
||||||
import type { Node, Data } from 'unist'
|
|
||||||
import { statSync } from 'fs'
|
|
||||||
import { parse, join } from 'path'
|
|
||||||
import { visit } from 'unist-util-visit'
|
|
||||||
import { toString } from 'mdast-util-to-string'
|
|
||||||
import Slugger from 'github-slugger'
|
|
||||||
import remarkFootnotes from 'remark-footnotes'
|
|
||||||
|
|
||||||
// highlighter
|
|
||||||
import { escapeSvelte } from 'mdsvex'
|
|
||||||
import { lex, parse as parseFence } from 'fenceparser'
|
|
||||||
import { renderCodeToHTML, runTwoSlash, createShikiHighlighter } from 'shiki-twoslash'
|
|
||||||
type VALUE = { [key in string | number]: VALUE } | Array<VALUE> | string | boolean | number
|
|
||||||
|
|
||||||
const remarkUraraFm =
|
|
||||||
() =>
|
|
||||||
(tree: Node<Data>, { data, filename }: { data: { fm?: Record<string, unknown> }; filename?: string }) => {
|
|
||||||
const filepath = (filename as string).split('/src/routes')[1]
|
|
||||||
const { dir, name } = parse(filepath)
|
|
||||||
if (!data.fm) data.fm = {}
|
|
||||||
// Generate slug & path
|
|
||||||
data.fm.slug = filepath
|
|
||||||
data.fm.path = join(dir, `/${name}`.replace('/index', '').replace('.svelte', ''))
|
|
||||||
// Generate ToC
|
|
||||||
if (data.fm.toc !== false) {
|
|
||||||
const [slugs, toc]: [slugs: Slugger, toc: { depth: number; title: string; slug: string }[]] = [new Slugger(), []]
|
|
||||||
visit(tree, 'heading', (node: { depth: number }) => {
|
|
||||||
toc.push({
|
|
||||||
depth: node.depth,
|
|
||||||
title: toString(node),
|
|
||||||
slug: slugs.slug(toString(node), false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
data.fm.toc = toc
|
|
||||||
}
|
|
||||||
// Auto-read created & updated
|
|
||||||
if (!data.fm.created || !data.fm.updated) {
|
|
||||||
const { ctime, mtime } = statSync(new URL(`./urara${filepath}`, import.meta.url))
|
|
||||||
if (!data.fm.created) data.fm.created = ctime
|
|
||||||
if (!data.fm.updated) data.fm.updated = mtime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Better type definitions needed
|
|
||||||
const remarkUraraSpoiler = () => (tree: Node<Data>) =>
|
|
||||||
visit(tree, 'paragraph', (node: any) => {
|
|
||||||
const { children } = node
|
|
||||||
const text = children[0].value
|
|
||||||
const re = /\|\|(.{1,}?)\|\|/g
|
|
||||||
if (re.test(children[0].value)) {
|
|
||||||
children[0].type = 'html'
|
|
||||||
children[0].value = text.replace(re, (_match: unknown, p1: string) => `<span class="spoiler">${p1}</span>`)
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
})
|
|
||||||
|
|
||||||
const defineConfig = (config: MdsvexOptions) => config
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
extensions: ['.svelte.md', '.md'],
|
|
||||||
smartypants: {
|
|
||||||
dashes: 'oldschool'
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
_: './src/lib/components/post_layout.svelte'
|
|
||||||
},
|
|
||||||
highlight: {
|
|
||||||
highlighter: async (code, lang, meta) => {
|
|
||||||
let fence: Record<string, VALUE> | null
|
|
||||||
let twoslash: any
|
|
||||||
try {
|
|
||||||
fence = parseFence(lex([lang, meta].filter(Boolean).join(' ')))
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Could not parse the codefence for this code sample \n${code}`)
|
|
||||||
}
|
|
||||||
if (fence?.twoslash === true) twoslash = runTwoSlash(code, lang as string)
|
|
||||||
return `{@html \`${escapeSvelte(
|
|
||||||
renderCodeToHTML(
|
|
||||||
code,
|
|
||||||
lang as string,
|
|
||||||
fence ?? {},
|
|
||||||
{ themeName: 'material-default' },
|
|
||||||
await createShikiHighlighter({ theme: 'material-default' }),
|
|
||||||
twoslash
|
|
||||||
)
|
|
||||||
)}\` }`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
remarkPlugins: [remarkUraraFm, remarkUraraSpoiler, [remarkFootnotes, { inlineNotes: true }]],
|
|
||||||
rehypePlugins: [
|
|
||||||
rehypeSlug,
|
|
||||||
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
|
|
||||||
[
|
|
||||||
rehypeExternalLinks,
|
|
||||||
{
|
|
||||||
rel: ['nofollow', 'noopener', 'noreferrer', 'external'],
|
|
||||||
target: '_blank'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
})
|
|
59
netlify.toml
59
netlify.toml
|
@ -1,59 +0,0 @@
|
||||||
[build]
|
|
||||||
command = "npx pnpm i --store=node_modules/.pnpm-store && npx pnpm build"
|
|
||||||
publish = "build"
|
|
||||||
|
|
||||||
|
|
||||||
[build.environment]
|
|
||||||
NPM_FLAGS = "--version"
|
|
||||||
AWS_LAMBDA_JS_RUNTIME = "nodejs16.x"
|
|
||||||
|
|
||||||
[functions]
|
|
||||||
node_bundler = "esbuild"
|
|
||||||
|
|
||||||
[[headers]]
|
|
||||||
for = "/manifest.webmanifest"
|
|
||||||
[headers.values]
|
|
||||||
Content-Type = "application/manifest+json"
|
|
||||||
|
|
||||||
[[headers]]
|
|
||||||
for = "/assets/*"
|
|
||||||
[headers.values]
|
|
||||||
cache-control = '''
|
|
||||||
max-age=31536000,
|
|
||||||
immutable
|
|
||||||
'''
|
|
||||||
|
|
||||||
[[headers]]
|
|
||||||
for = "/*"
|
|
||||||
[headers.values]
|
|
||||||
Access-Control-Allow-Origin = "https://seviche.cc"
|
|
||||||
X-Frame-Options = "DENY"
|
|
||||||
X-Content-Type-Options = "nosniff"
|
|
||||||
X-XSS-Protection = "1; mode=block"
|
|
||||||
Content-Security-Policy = "style-src 'self' 'unsafe-inline' https://cdn.commento.io/css/commento.css http://fonts.cdnfonts.com/css/lato ; script-src 'self' 'unsafe-inline' https://*.seviche.cc https://giscus.app https://hexoverc.vercel.app/umami.js https://cdn.splitbee.io/sb.js https://cdn.commento.io/js/commento.js"
|
|
||||||
Referrer-Policy = "strict-origin-when-cross-origin"
|
|
||||||
Permissions-Policy = "usb=()"
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/.well-known/host-meta"
|
|
||||||
to = "https://fed.brid.gy/.well-known/host-meta"
|
|
||||||
status = 302
|
|
||||||
force = true
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/.well-known/host-meta.xrd"
|
|
||||||
to = "https://fed.brid.gy/.well-known/host-meta.xrd"
|
|
||||||
status = 302
|
|
||||||
force = true
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/.well-known/host-meta.jrd"
|
|
||||||
to = "https://fed.brid.gy/.well-known/host-meta.jrd"
|
|
||||||
status = 302
|
|
||||||
force = true
|
|
||||||
|
|
||||||
[[redirects]]
|
|
||||||
from = "/.well-known/webfinger"
|
|
||||||
to = "https://fed.brid.gy/.well-known/webfinger"
|
|
||||||
status = 302
|
|
||||||
force = true
|
|
86
package.json
86
package.json
|
@ -1,86 +0,0 @@
|
||||||
{
|
|
||||||
"name": "urara",
|
|
||||||
"type": "module",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"repository": "importantimport/urara",
|
|
||||||
"homepage": "https://github.com/importantimport/urara",
|
|
||||||
"bugs": "https://github.com/importantimport/urara/issues",
|
|
||||||
"author": "藍+85CD",
|
|
||||||
"scripts": {
|
|
||||||
"clean": "node urara.js clean",
|
|
||||||
"tsc": "tsc -p tsconfig.node.json",
|
|
||||||
"tsc:watch": "tsc -w -p tsconfig.node.json",
|
|
||||||
"urara:build": "node urara.js build",
|
|
||||||
"urara:watch": "node urara.js watch",
|
|
||||||
"kit:dev": "cross-env NODE_OPTIONS=--max_old_space_size=7680 MODE=development vite dev",
|
|
||||||
"kit:build": "cross-env NODE_OPTIONS=--max_old_space_size=7680 vite build",
|
|
||||||
"dev:parallel": "npm-run-all -p -r tsc:watch urara:watch \"kit:dev {@} \" --",
|
|
||||||
"dev": "npm-run-all -s tsc \"dev:parallel {@} \" --",
|
|
||||||
"build": "npm-run-all -s tsc urara:build kit:build clean",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"dev:urara": "node urara.js watch",
|
|
||||||
"dev:kit": "export NODE_OPTIONS=--max_old_space_size=8192 && MODE=development svelte-kit dev",
|
|
||||||
"build:urara": "node urara.ts build",
|
|
||||||
"build:kit": "export NODE_OPTIONS=--max_old_space_size=8192 && svelte-kit build",
|
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
||||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
|
||||||
"lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
|
|
||||||
"format": "prettier --write --plugin-search-dir=. .",
|
|
||||||
"zhlint": "zhlint urara/*/*.md --fix && zhlint urara/*.md --fix"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@iconify-json/heroicons-outline": "^1.1.2",
|
|
||||||
"@iconify-json/heroicons-solid": "^1.1.2",
|
|
||||||
"@iconify-json/ic": "^1.1.9",
|
|
||||||
"@iconify-json/simple-icons": "1.1.21",
|
|
||||||
"@iconify-json/material-symbols": "1.1.14",
|
|
||||||
"@iconify-json/mdi": "^1.1.30",
|
|
||||||
"@iconify-json/uil": "^1.1.2",
|
|
||||||
"@sveltejs/adapter-auto": "1.0.0-next.64",
|
|
||||||
"@sveltejs/adapter-node": "1.0.0-next.86",
|
|
||||||
"@sveltejs/adapter-static": "1.0.0-next.39",
|
|
||||||
"@sveltejs/kit": "1.0.0-next.405",
|
|
||||||
"@tailwindcss/typography": "^0.5.4",
|
|
||||||
"@types/node": "^18.7.3",
|
|
||||||
"@types/unist": "^2.0.6",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
|
||||||
"@typescript-eslint/parser": "^5.33.0",
|
|
||||||
"autoprefixer": "^10.4.8",
|
|
||||||
"chalk": "^5.0.1",
|
|
||||||
"chokidar": "^3.5.3",
|
|
||||||
"cross-env": "^7.0.3",
|
|
||||||
"cssnano": "^5.1.13",
|
|
||||||
"daisyui": "^2.24.0",
|
|
||||||
"eslint": "^8.21.0",
|
|
||||||
"eslint-config-prettier": "^8.5.0",
|
|
||||||
"eslint-plugin-svelte3": "^4.0.0",
|
|
||||||
"fenceparser": "^2.2.0",
|
|
||||||
"fff-flavored-frontmatter": "~0.2.1",
|
|
||||||
"github-slugger": "^1.4.0",
|
|
||||||
"mdast-util-to-string": "^3.1.0",
|
|
||||||
"mdsvex": "^0.10.6",
|
|
||||||
"npm-run-all": "^4.1.5",
|
|
||||||
"postcss": "^8.4.16",
|
|
||||||
"prettier": "^2.7.1",
|
|
||||||
"prettier-plugin-svelte": "^2.7.0",
|
|
||||||
"rehype-autolink-headings": "^6.1.1",
|
|
||||||
"rehype-external-links": "^2.0.0",
|
|
||||||
"rehype-slug": "^5.0.1",
|
|
||||||
"remark": "^14.0.2",
|
|
||||||
"remark-footnotes": "~2.0.0",
|
|
||||||
"shiki-twoslash": "^3.1.0",
|
|
||||||
"svelte": "^3.49.0",
|
|
||||||
"svelte-bricks": "^0.1.7",
|
|
||||||
"svelte-check": "^2.8.0",
|
|
||||||
"svelte-preprocess": "^4.10.7",
|
|
||||||
"svelte-typeahead": "^4.2.4",
|
|
||||||
"tailwindcss": "^3.1.8",
|
|
||||||
"tslib": "^2.4.0",
|
|
||||||
"typescript": "^4.7.4",
|
|
||||||
"unist-util-visit": "^4.1.0",
|
|
||||||
"unocss": "^0.45.6",
|
|
||||||
"vite": "^3.0.7",
|
|
||||||
"vite-plugin-pwa": "^0.12.3",
|
|
||||||
"workbox-window": "^6.5.4"
|
|
||||||
}
|
|
||||||
}
|
|
6320
pnpm-lock.yaml
6320
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
184
src/app.css
184
src/app.css
|
@ -1,184 +0,0 @@
|
||||||
/* tailwind */
|
|
||||||
@import url('http://fonts.cdnfonts.com/css/lato');
|
|
||||||
@tailwind base;
|
|
||||||
|
|
||||||
@tailwind components;
|
|
||||||
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
/* global */
|
|
||||||
|
|
||||||
html {
|
|
||||||
@apply !bg-base-200 scroll-smooth overflow-x-hidden overflow-y-scroll;
|
|
||||||
font-family: Lato, pingfang sc, microsoft yahei, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
@apply bg-primary/20;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .urara-prose */
|
|
||||||
|
|
||||||
.urara-prose {
|
|
||||||
@apply !max-w-none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .urara-prose heading */
|
|
||||||
|
|
||||||
.urara-prose > :is(h1, h2, h3, h4, h5) > a {
|
|
||||||
@apply no-underline font-bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.urara-prose > :is(h1, h2, h3, h4, h5) > a::after {
|
|
||||||
@apply pl-2 text-base-200 transition-all content-['#'];
|
|
||||||
}
|
|
||||||
|
|
||||||
.urara-prose > :is(h1, h2, h3, h4, h5):hover > a::after {
|
|
||||||
@apply text-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .urara-prose table */
|
|
||||||
|
|
||||||
.urara-prose div > table > thead {
|
|
||||||
@apply border-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.urara-prose div > table > thead > tr > th {
|
|
||||||
@apply !relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .urara-prose a */
|
|
||||||
|
|
||||||
.urara-prose :is(p, li) > a {
|
|
||||||
@apply bg-[length:100%_0.2em] hover:bg-[length:100%_100%] bg-[position:0_88%] bg-gradient-to-t from-secondary/50 to-primary/25 bg-no-repeat transition-all ease-in-out !no-underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .urara-prose misc */
|
|
||||||
|
|
||||||
.urara-prose > p img {
|
|
||||||
@apply w-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
.urara-prose :is(p, li) > code {
|
|
||||||
@apply bg-base-200 px-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.urara-prose li > input {
|
|
||||||
@apply checkbox checkbox-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
.urara-prose kbd {
|
|
||||||
@apply kbd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.urara-prose hr {
|
|
||||||
@apply border-none divider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* footer a */
|
|
||||||
|
|
||||||
footer a {
|
|
||||||
@apply !no-underline hover:text-primary hover:!underline transition-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spoiler {
|
|
||||||
@apply blur-sm hover:blur-none active:blur-none transition-all select-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .prose pre */
|
|
||||||
|
|
||||||
.prose pre {
|
|
||||||
@apply mockup-code !bg-neutral min-w-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose pre:not(.shiki) {
|
|
||||||
@apply bg-neutral text-neutral-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose pre:not(.shiki)::before {
|
|
||||||
@apply sticky -left-5 -ml-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .urara-prose pre */
|
|
||||||
|
|
||||||
.urara-prose > pre {
|
|
||||||
@apply -mx-8 rounded-none pb-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.urara-prose > pre > div.code-container {
|
|
||||||
@apply pb-5 overflow-x-auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* shiki */
|
|
||||||
|
|
||||||
pre.shiki {
|
|
||||||
@apply px-0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.shiki::before {
|
|
||||||
@apply sticky;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.shiki > div.code-title {
|
|
||||||
@apply absolute -mt-10 ml-20 pt-1.5 pl-1.5 opacity-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.shiki .language-id {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.shiki > .code-container {
|
|
||||||
@apply overflow-auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
:is(pre.shiki[text='true'], pre.shiki[svelte='true']) > div.code-container {
|
|
||||||
@apply mx-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.shiki:not([text='true'], [svelte='true']) > .code-container > code > div.line > span:first-child {
|
|
||||||
@apply pl-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.shiki:not([text='true'], [svelte='true']) > .code-container > code > div.line > span:last-child {
|
|
||||||
@apply pr-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.shiki div.dim {
|
|
||||||
@apply opacity-50 transition-opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.shiki:hover div.dim {
|
|
||||||
@apply opacity-100;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.shiki div.highlight::before {
|
|
||||||
@apply bg-warning/20 absolute content-[''] w-full h-6;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.twoslash data-lsp {
|
|
||||||
@apply border-b border-dashed border-transparent transition-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.twoslash:hover data-lsp {
|
|
||||||
@apply border-neutral-content/30;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.twoslash data-lsp:hover::before {
|
|
||||||
@apply content-[attr(lsp)] absolute rounded translate-y-5 bg-neutral-focus text-neutral-content font-mono whitespace-pre-wrap transition-all px-2 py-1 z-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* your code here */
|
|
||||||
|
|
||||||
.urara-prose blockquote {
|
|
||||||
@apply font-normal text-current not-italic before:content-['“'];
|
|
||||||
border-left: 4px solid rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.urara-prose blockquote:before {
|
|
||||||
vertical-align: -0.4em;
|
|
||||||
@apply mr-2 text-5xl leading-3 italic font-serif opacity-25;
|
|
||||||
}
|
|
||||||
|
|
||||||
.urara-prose blockquote p {
|
|
||||||
@apply inline opacity-80 before:content-[''] after:content-[''];
|
|
||||||
}
|
|
88
src/app.d.ts
vendored
88
src/app.d.ts
vendored
|
@ -1,88 +0,0 @@
|
||||||
/// <reference types="@sveltejs/kit" />
|
|
||||||
|
|
||||||
import { FFFBase, FFFExtra } from 'fff-flavored-frontmatter'
|
|
||||||
|
|
||||||
interface ImportMetaEnv extends Readonly<Record<string, string>> {
|
|
||||||
readonly URARA_SITE_DOMAIN?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ImportMeta {
|
|
||||||
glob<Module = { [key: string]: unknown }>(pattern: string): Record<string, Module>
|
|
||||||
readonly env: ImportMetaEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
namespace Urara {
|
|
||||||
namespace Post {
|
|
||||||
type Frontmatter = Omit<FFFBase, 'created' | 'updated' | 'image' | 'audio' | 'video' | 'flags'> &
|
|
||||||
Pick<FFFExtra, 'in_reply_to'> & {
|
|
||||||
/**
|
|
||||||
* post type.
|
|
||||||
* @remarks auto-generated
|
|
||||||
*/
|
|
||||||
type: 'article' | 'note' | 'photo' | 'reply' | 'audio' | 'video' | 'like' | 'repost' | 'bookmark'
|
|
||||||
/**
|
|
||||||
* post layout.
|
|
||||||
*/
|
|
||||||
layout?: 'article' | 'note' | 'photo' | 'reply'
|
|
||||||
/**
|
|
||||||
* post path.
|
|
||||||
* @remarks auto-generated
|
|
||||||
*/
|
|
||||||
path: string
|
|
||||||
/**
|
|
||||||
* post slug.
|
|
||||||
* @remarks auto-generated
|
|
||||||
*/
|
|
||||||
slug: string
|
|
||||||
/**
|
|
||||||
* table of contents.
|
|
||||||
* @remarks auto-generated, article-only, set to `false` to disable
|
|
||||||
*/
|
|
||||||
toc?: false | Toc[]
|
|
||||||
/**
|
|
||||||
* the created date of the post.
|
|
||||||
* @remarks auto-generated or set manually
|
|
||||||
*/
|
|
||||||
created: string
|
|
||||||
/**
|
|
||||||
* the updated date of the post.
|
|
||||||
* @remarks auto-generated or set manually
|
|
||||||
*/
|
|
||||||
updated: string
|
|
||||||
/**
|
|
||||||
* the featured image for article, or image for "photo" / "multi-photo" posts.
|
|
||||||
* @remarks currently only supports string
|
|
||||||
*/
|
|
||||||
image?: string
|
|
||||||
/** enable some advanced features.
|
|
||||||
* @property hidden - deprecated, transfer to `unlisted`
|
|
||||||
* @property unlisted - hide this post from the homepage and feed.
|
|
||||||
* @property bridgy-fed - add a link to Bridgy Fed in the post. https://fed.brid.gy/
|
|
||||||
* @property bridgy-{target} - add a link to Bridgy in the post. https://brid.gy/publish/{target}
|
|
||||||
*/
|
|
||||||
flags?: string[]
|
|
||||||
}
|
|
||||||
type Toc = {
|
|
||||||
depth: number
|
|
||||||
title?: string
|
|
||||||
slug?: string
|
|
||||||
children?: Toc[]
|
|
||||||
}
|
|
||||||
interface Module {
|
|
||||||
default: {
|
|
||||||
render: () => {
|
|
||||||
html: string
|
|
||||||
head: string
|
|
||||||
css: {
|
|
||||||
code: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
metadata: Frontmatter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type Post = Post.Frontmatter & { html?: string }
|
|
||||||
type Page = { title?: string; path: string }
|
|
||||||
}
|
|
||||||
}
|
|
17
src/app.html
17
src/app.html
|
@ -1,17 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head prefix="og: https://ogp.me/ns#">
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="generator" content="gh:importantimport/urara" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<link rel="manifest" crossorigin="use-credentials" href="/manifest.webmanifest" />
|
|
||||||
<link rel="alternate" type="application/feed+json" href="/feed.json" />
|
|
||||||
<link rel="alternate" type="application/atom+xml" href="/atom.xml" />
|
|
||||||
<link rel="sitemap" type="application/xml" href="/sitemap.xml" />
|
|
||||||
%sveltekit.head%
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body itemscope itemtype="https://schema.org/WebPage">
|
|
||||||
%sveltekit.body%
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,7 +0,0 @@
|
||||||
import type { Handle } from '@sveltejs/kit'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
|
|
||||||
export const handle: Handle = async ({ event, resolve }) =>
|
|
||||||
await resolve(event, {
|
|
||||||
transformPageChunk: ({ html }) => html.replace('<html lang="en">', `<html lang="${site.lang ?? 'en'}">`)
|
|
||||||
})
|
|
|
@ -1,3 +0,0 @@
|
||||||
<a href="#post-comment" class="btn btn-lg btn-circle btn-ghost bg-base-100 shadow-lg hover:shadow-xl">
|
|
||||||
<span class="i-heroicons-outline-chat-alt-2" />
|
|
||||||
</a>
|
|
|
@ -1,12 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
export let post: Urara.Post
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href={`https://translate.google.com/translate?sl=auto&tl=${
|
|
||||||
navigator.languages ? navigator.languages[0] : navigator.language
|
|
||||||
}&u=${site.protocol + site.domain + post.path}`}
|
|
||||||
class="btn btn-lg btn-circle btn-ghost bg-base-100 shadow-lg hover:shadow-xl">
|
|
||||||
<span class="i-heroicons-outline-translate" />
|
|
||||||
</a>
|
|
|
@ -1,12 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
export let post: Urara.Post
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href={`https://www.addtoany.com/share#url=${site.protocol + site.domain + post.path}&title=${encodeURI(
|
|
||||||
post.title ?? post.path.slice(1)
|
|
||||||
)}`}
|
|
||||||
class="btn btn-lg btn-circle btn-ghost bg-base-100 shadow-lg hover:shadow-xl">
|
|
||||||
<span class="i-heroicons-outline-share" />
|
|
||||||
</a>
|
|
|
@ -1,40 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import type { GiscusConfig } from '$lib/types/post'
|
|
||||||
export let config: GiscusConfig
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const giscus = document.createElement('script')
|
|
||||||
Object.entries({
|
|
||||||
src: config.src ?? 'https://giscus.app/client.js',
|
|
||||||
'data-repo': config.repo,
|
|
||||||
'data-repo-id': config.repoID,
|
|
||||||
'data-category': config.category ?? '',
|
|
||||||
'data-category-id': config.categoryID,
|
|
||||||
'data-mapping': 'pathname',
|
|
||||||
'data-reactions-enabled': config.reactionsEnabled === false ? '0' : '1',
|
|
||||||
'data-input-position': config.inputPosition ?? 'bottom',
|
|
||||||
'data-theme': config.theme ?? 'preferred_color_scheme',
|
|
||||||
'data-lang': config.lang ?? site.lang ?? 'en',
|
|
||||||
'data-loading': config.loading ?? '',
|
|
||||||
crossorigin: 'anonymous',
|
|
||||||
async: ''
|
|
||||||
}).forEach(([key, value]) => giscus.setAttribute(key, value))
|
|
||||||
setTimeout(() => {
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
document.getElementById('giscus-loading')!.remove()
|
|
||||||
observer.disconnect()
|
|
||||||
})
|
|
||||||
observer.observe(document.getElementById('giscus')!, {
|
|
||||||
childList: true
|
|
||||||
})
|
|
||||||
document.getElementById('giscus-container')!.appendChild(giscus)
|
|
||||||
}, 1000)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div id="giscus-container">
|
|
||||||
<button id="giscus-loading" class="btn btn-lg flex mx-auto my-4 btn-ghost btn-circle loading" />
|
|
||||||
<div id="giscus" class="giscus" />
|
|
||||||
</div>
|
|
|
@ -1,33 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import type { UtterancesConfig } from '$lib/types/post'
|
|
||||||
export let config: UtterancesConfig
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const utterances = document.createElement('script')
|
|
||||||
Object.entries({
|
|
||||||
src: config.src ?? 'https://utteranc.es/client.js',
|
|
||||||
repo: config.repo,
|
|
||||||
'issue-term': 'pathname',
|
|
||||||
label: config.label ?? '',
|
|
||||||
theme: config.theme ?? 'preferred-color-scheme',
|
|
||||||
crossorigin: 'anonymous',
|
|
||||||
async: ''
|
|
||||||
}).forEach(([key, value]) => utterances.setAttribute(key, value))
|
|
||||||
setTimeout(() => {
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
document.getElementById('utterances-loading')!.remove()
|
|
||||||
observer.disconnect()
|
|
||||||
})
|
|
||||||
observer.observe(document.getElementById('utterances')!, {
|
|
||||||
childList: true
|
|
||||||
})
|
|
||||||
document.getElementById('utterances-container')!.appendChild(utterances)
|
|
||||||
}, 1000)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div id="utterances-container">
|
|
||||||
<button id="utterances-loading" class="btn btn-lg flex mx-auto my-4 btn-ghost btn-circle loading" />
|
|
||||||
<div id="utterances" class="utterances" />
|
|
||||||
</div>
|
|
|
@ -1,117 +0,0 @@
|
||||||
<!-- <script lang="ts">
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import type { WalineConfig } from '$lib/types/post'
|
|
||||||
|
|
||||||
export let post: Urara.Post
|
|
||||||
export let config: WalineConfig
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const waline = document.createElement('script')
|
|
||||||
const [c, s] = [document.createElement('script'), document.createElement('script')]
|
|
||||||
c.id = 'disqus_config'
|
|
||||||
c.type = 'application/javascript'
|
|
||||||
console.log(waline)
|
|
||||||
Object.entries({
|
|
||||||
src: 'https://unpkg.com/@waline/client@v2/dist/waline.js',
|
|
||||||
serverURL: config.serverURL,
|
|
||||||
path: config.path ?? post.path ?? window.location.pathname,
|
|
||||||
lang: config.lang ?? 'en',
|
|
||||||
emoji: config.emoji ?? ['//unpkg.com/@waline/emojis@1.0.1/weibo'],
|
|
||||||
dark: config.dark ?? false,
|
|
||||||
meta: config.meta ?? ['nick', 'mail', 'link'],
|
|
||||||
requiredMeta: config.requiredMeta ?? [],
|
|
||||||
login: config.login ?? 'enable',
|
|
||||||
wordLimit: config.wordLimit ?? 0,
|
|
||||||
pageSize: config.pageSize ?? 10,
|
|
||||||
imageUploader: config.imageUploader,
|
|
||||||
highlighter: config.highlighter,
|
|
||||||
texRender: config.texRender,
|
|
||||||
copyright: config.copyright ?? true,
|
|
||||||
crossorigin: 'anonymous',
|
|
||||||
async: ''
|
|
||||||
}).forEach(([key, value]) => waline.setAttribute(key, value))
|
|
||||||
setTimeout(() => {
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
document.getElementById('giscus-loading').remove()
|
|
||||||
observer.disconnect()
|
|
||||||
})
|
|
||||||
observer.observe(document.getElementById('giscus'), {
|
|
||||||
childList: true
|
|
||||||
})
|
|
||||||
document.getElementById('giscus-container').appendChild(waline)
|
|
||||||
}, 1000)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/@waline/client@v2/dist/waline.css" />
|
|
||||||
<div id="waline" class="waline-container" />
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.waline-container {
|
|
||||||
background-color: var(--card-background);
|
|
||||||
border-radius: var(--card-border-radius);
|
|
||||||
box-shadow: var(--shadow-l1);
|
|
||||||
padding: 2%;
|
|
||||||
}
|
|
||||||
.waline-container .vcount {
|
|
||||||
color: var(--card-text-color-main);
|
|
||||||
}
|
|
||||||
.v[data-class='v'] .vcard {
|
|
||||||
flex: 1;
|
|
||||||
width: 0;
|
|
||||||
padding-bottom: 0.5em;
|
|
||||||
border-bottom: 0; /*删掉回复下面的线*/
|
|
||||||
}
|
|
||||||
.v[data-class='v'] .vcard .vquote {
|
|
||||||
border-left: 1px solid rgba(237, 237, 237, 0.5);
|
|
||||||
}
|
|
||||||
@media (max-width: 580px) {
|
|
||||||
.v[data-class='v'] .vheader .vheader-item:not(:last-child) {
|
|
||||||
border-bottom: 1px solid rgba(237, 237, 237, 0.8); /*输入框分割线*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*日间模式*/
|
|
||||||
:root {
|
|
||||||
--waline-theme-color: #34495e; /*主题色,提交按钮*/
|
|
||||||
--waline-active-color: #246bb1; /*鼠标移到提交按钮上的颜色*/
|
|
||||||
/* 徽章 */
|
|
||||||
--waline-badge-color: #34495e; /*博主徽章色*/
|
|
||||||
--waline-avatar-radius: 5px;
|
|
||||||
--waline-avatar-size: 6rem;
|
|
||||||
--waline-dark-grey: #34495e; /*ID颜色*/
|
|
||||||
--waline-text-color: #34495e; /*字体颜色*/
|
|
||||||
--waline-font-size: 1.7rem; /*字体大小颜色*/
|
|
||||||
}
|
|
||||||
/*夜间模式*/
|
|
||||||
:root[data-scheme='dark'] {
|
|
||||||
--waline-theme-color: #acc6e0;
|
|
||||||
--waline-white: #34495e; /*按键字体颜色*/
|
|
||||||
--waline-active-color: #8ab1d8;
|
|
||||||
--waline-light-grey: #666;
|
|
||||||
--waline-dark-grey: #acc6e0; /*ID颜色*/
|
|
||||||
--waline-badge-color: #acc6e0;
|
|
||||||
/* 布局颜色 */
|
|
||||||
--waline-text-color: rgba(255, 255, 255, 0.7);
|
|
||||||
--waline-bgcolor: #515151;
|
|
||||||
--waline-bgcolor-light: #66696b; /*行内代码块颜色*/
|
|
||||||
--waline-border-color: #9b9c9c;
|
|
||||||
--waline-disable-bgcolor: #444;
|
|
||||||
--waline-disable-color: #272727;
|
|
||||||
/* 特殊颜色 */
|
|
||||||
--waline-bq-color: #9b9c9c; /*quote*/
|
|
||||||
/* 其他颜色 */
|
|
||||||
--waline-info-bgcolor: #acc6e0;
|
|
||||||
--waline-info-color: #9b9c9c;
|
|
||||||
}
|
|
||||||
.v[data-class='v'] .vcontent .vemoji {
|
|
||||||
width: 2.2em; /*表情包大小修改*/
|
|
||||||
margin: 0.25em;
|
|
||||||
}
|
|
||||||
.v[data-class='v'] .vheader {
|
|
||||||
border-bottom: 1px solid rgba(237, 237, 237, 0.8); /*输入框分割线*/
|
|
||||||
}
|
|
||||||
.v[data-class='v'] .vpanel {
|
|
||||||
border-radius: 8px; /*输入框圆角*/
|
|
||||||
}
|
|
||||||
</style> -->
|
|
|
@ -1,205 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import type { WebmentionConfig } from '$lib/types/post'
|
|
||||||
export let config: WebmentionConfig
|
|
||||||
export let post: Urara.Post
|
|
||||||
|
|
||||||
interface WebmentionFeed {
|
|
||||||
type: 'feed'
|
|
||||||
name: 'Webmentions'
|
|
||||||
children: WebmentionEntry[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WebmentionEntry {
|
|
||||||
url: string
|
|
||||||
author?: {
|
|
||||||
name?: string
|
|
||||||
photo?: string
|
|
||||||
url?: string
|
|
||||||
}
|
|
||||||
content?: {
|
|
||||||
html?: string
|
|
||||||
text?: string
|
|
||||||
}
|
|
||||||
rsvp?: string
|
|
||||||
published?: string
|
|
||||||
'wm-received': string
|
|
||||||
'wm-source': string
|
|
||||||
'wm-target': string
|
|
||||||
'wm-id': number
|
|
||||||
'wm-property': 'in-reply-to' | 'like-of' | 'repost-of' | 'bookmark-of' | 'mention-of' | 'rsvp'
|
|
||||||
'wm-private': boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
let [page, loaded, end, mentions, sortDirUp]: [number, boolean, boolean, WebmentionEntry[], boolean] = [
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
[],
|
|
||||||
config?.sortDir === 'up' ? true : false
|
|
||||||
]
|
|
||||||
|
|
||||||
const load = async () =>
|
|
||||||
await fetch(
|
|
||||||
`https://webmention.io/api/mentions.jf2?page=${page}&per-page=${config?.perPage ?? '20'}&sort-by=${
|
|
||||||
config?.sortBy ?? 'created'
|
|
||||||
}&sort-dir=${sortDirUp ? 'up' : 'down'}${
|
|
||||||
config?.property && config.property.forEach(wmProperty => `&wm-property=${wmProperty}`)
|
|
||||||
}&target[]=${site.protocol + site.domain + post.path}&target[]=${site.protocol + site.domain + post.path}/`
|
|
||||||
)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then((feed: WebmentionFeed) => {
|
|
||||||
if (feed.children.length < 10) end = true
|
|
||||||
feed = {
|
|
||||||
...feed,
|
|
||||||
children: feed.children.filter(
|
|
||||||
(entry: WebmentionEntry) => !config.blockList?.includes(new URL(entry['wm-source']).origin)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (feed.children.length > 0) mentions = [...mentions, ...feed.children]
|
|
||||||
page++
|
|
||||||
loaded = true
|
|
||||||
})
|
|
||||||
|
|
||||||
const reset = async () => {
|
|
||||||
page = 0
|
|
||||||
loaded = false
|
|
||||||
end = false
|
|
||||||
mentions = []
|
|
||||||
await load()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => load())
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-8">
|
|
||||||
<div class="flex">
|
|
||||||
<p class="flex-1 m-auto italic opacity-50">
|
|
||||||
<!-- {`Sort by=${config?.sortBy ?? 'Created'}&sort-dir=${sortDirUp ? 'up' : 'down'}`} -->
|
|
||||||
{`Sort ${sortDirUp ? 'up' : 'down'}`}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
class="btn btn-ghost btn-sm float-right"
|
|
||||||
on:click={() => {
|
|
||||||
sortDirUp = !sortDirUp
|
|
||||||
reset()
|
|
||||||
}}>
|
|
||||||
{#if sortDirUp === true}
|
|
||||||
<span class="i-heroicons-outline-sort-ascending" />
|
|
||||||
{:else}
|
|
||||||
<span class="i-heroicons-outline-sort-descending" />
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{#key mentions}
|
|
||||||
{#each mentions as mention}
|
|
||||||
{@const [wmProperty, borderColor, textColor, tooltipColor] = {
|
|
||||||
'in-reply-to': ['💬 Replied', 'border-primary/50', 'text-primary', 'tooltip-primary'],
|
|
||||||
'like-of': ['❤️ liked', 'border-secondary/50', 'text-secondary', 'tooltip-secondary'],
|
|
||||||
'repost-of': ['🔄 Reposted', 'border-accent/50', 'text-accent', 'tooltip-accent'],
|
|
||||||
'bookmark-of': ['⭐️ bookmarked', 'border-neutral/50', 'text-neutral', 'tooltip-neutral'],
|
|
||||||
'mention-of': ['💬 mentioned', 'border-base-300/50', 'text-base-content', 'tooltip-base-content'],
|
|
||||||
rsvp: [
|
|
||||||
`📅 RSVPed ${
|
|
||||||
mention.rsvp &&
|
|
||||||
{
|
|
||||||
yes: '✅',
|
|
||||||
no: '❌',
|
|
||||||
interested: '💡',
|
|
||||||
maybe: '💭'
|
|
||||||
}[mention.rsvp]
|
|
||||||
}`,
|
|
||||||
'border-warning/50',
|
|
||||||
'text-warning',
|
|
||||||
'tooltip-warning'
|
|
||||||
]
|
|
||||||
}[mention['wm-property']]}
|
|
||||||
{#if mention.url !== null}
|
|
||||||
<div class="{borderColor} border-2 rounded-box p-4">
|
|
||||||
<div class="flex bg-base-200 rounded-btn">
|
|
||||||
{#if mention?.author?.photo}
|
|
||||||
<img
|
|
||||||
class="w-12 h-12 flex-none rounded-btn"
|
|
||||||
src={mention.author.photo}
|
|
||||||
alt={mention.author?.name ?? new URL(mention.url).host}
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async" />
|
|
||||||
{/if}
|
|
||||||
<div class="flex-1 px-4 py-2 m-auto">
|
|
||||||
<p>
|
|
||||||
{#if mention?.author?.url}
|
|
||||||
<a class="font-semibold {textColor} hover:underline" href={mention.author.url}>
|
|
||||||
{mention.author?.name ?? new URL(mention.url).host}
|
|
||||||
</a>
|
|
||||||
{:else}
|
|
||||||
{mention?.author?.name ?? new URL(mention.url).host}
|
|
||||||
{/if}
|
|
||||||
<a class="{textColor} hover:underline" href={mention['wm-source']}>
|
|
||||||
{wmProperty}
|
|
||||||
</a>
|
|
||||||
this post on
|
|
||||||
<span
|
|
||||||
class="tooltip tooltip-bottom xl:tooltip-right {tooltipColor}"
|
|
||||||
data-tip={new Date(mention.published ?? mention['wm-received']).toLocaleString()}>
|
|
||||||
{mention.published ? mention.published.slice(0, 10) : mention['wm-received'].slice(0, 10)}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if mention.content}
|
|
||||||
<div class="prose max-w-none break-words mt-4">
|
|
||||||
<p>{@html mention.content?.html ?? mention.content?.text}</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
{/key}
|
|
||||||
{#if loaded === true}
|
|
||||||
{#if end !== true}
|
|
||||||
<button
|
|
||||||
on:click={() => {
|
|
||||||
loaded = false
|
|
||||||
load()
|
|
||||||
}}
|
|
||||||
class="btn btn-primary btn-block">
|
|
||||||
LOAD
|
|
||||||
</button>
|
|
||||||
{:else if config?.form !== true}
|
|
||||||
<div class="divider mt-0 -mb-2">END</div>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<button id="webmention-loading" class="btn btn-lg btn-block flex btn-ghost loading" />
|
|
||||||
{/if}
|
|
||||||
{#if config?.form === true}
|
|
||||||
<form id="webmention-form" method="post" action="https://webmention.io/{config.username}/webmention">
|
|
||||||
<input type="hidden" name="target" value={site.protocol + site.domain + post.path} />
|
|
||||||
<div class="label gap-4">
|
|
||||||
<span class="label-text">send webmentions here:</span>
|
|
||||||
{#if config?.commentParade === true}
|
|
||||||
<span class="label-text-alt text-right">
|
|
||||||
or <a
|
|
||||||
class="hover:!text-primary"
|
|
||||||
href="https://quill.p3k.io/?dontask=1&me=https://commentpara.de/&reply={encodeURI(
|
|
||||||
site.protocol + site.domain + post.path
|
|
||||||
)}">
|
|
||||||
comment anonymously
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<div class="flex-1">
|
|
||||||
<input
|
|
||||||
class="input input-bordered focus:input-primary w-full"
|
|
||||||
type="text"
|
|
||||||
id="reply-url"
|
|
||||||
name="source"
|
|
||||||
placeholder="https://example.com/my-post" />
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-primary flex-none mt-auto" type="submit" id="webmention-submit">Send</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let title: string | undefined = undefined
|
|
||||||
export let description: string | undefined = undefined
|
|
||||||
export let status: 'info' | 'success' | 'warning' | 'error' | undefined = undefined
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class:alert-info={status === 'info'}
|
|
||||||
class:alert-success={status === 'success'}
|
|
||||||
class:alert-warning={status === 'warning'}
|
|
||||||
class:alert-error={status === 'error'}
|
|
||||||
class="alert flex-col shadow-inner my-4">
|
|
||||||
<div class="mr-auto">
|
|
||||||
{#if status === 'success'}
|
|
||||||
<span class="i-heroicons-outline-check-circle" />
|
|
||||||
{:else if status === 'warning'}
|
|
||||||
<span class="i-heroicons-outline-exclamation-circle" />
|
|
||||||
{:else if status === 'error'}
|
|
||||||
<span class="i-heroicons-outline-x-circle" />
|
|
||||||
{:else}
|
|
||||||
<span class="i-heroicons-outline-information-circle" />
|
|
||||||
{/if}
|
|
||||||
<div>
|
|
||||||
<div class:font-bold={description}>{title}</div>
|
|
||||||
{#if description}
|
|
||||||
<div class="text-xs">{description}</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if $$slots.default}
|
|
||||||
<div class="block w-full">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
|
@ -1,64 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
let status: string = ''
|
|
||||||
let statusText: string = ''
|
|
||||||
let input: string = ''
|
|
||||||
const follow = async (event: Event, account = new FormData(event.target as HTMLFormElement).get('account') as string) =>
|
|
||||||
await fetch(
|
|
||||||
account.startsWith('@')
|
|
||||||
? `https://${account.split('@')[2]}/.well-known/webfinger?resource=acct:${account.slice(1)}`
|
|
||||||
: `https://${account.split('@')[1]}/.well-known/webfinger?resource=acct:${account}`,
|
|
||||||
{
|
|
||||||
headers: { Accept: 'application/jrd+json' }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(res => {
|
|
||||||
if (res.ok) {
|
|
||||||
status = 'success'
|
|
||||||
return res.json()
|
|
||||||
} else {
|
|
||||||
status = 'error'
|
|
||||||
statusText = res.status + res.statusText
|
|
||||||
throw Error(res.status + res.statusText)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
({ links }) => links.find((link: { rel: string }) => link.rel === 'http://ostatus.org/schema/1.0/subscribe').template
|
|
||||||
)
|
|
||||||
.then(template => (window.location.href = template.replace('{uri}', `sevichecc@kongwoo.icu`)))
|
|
||||||
.catch(error => console.error(error))
|
|
||||||
$: if (input)
|
|
||||||
input.length < 5 ? (status = '') : input.includes('@') && input.includes('.') ? (status = 'success') : (status = 'warning')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<input type="checkbox" id="remote-follow" class="modal-toggle" />
|
|
||||||
<label for="remote-follow" class="modal modal-bottom sm:modal-middle cursor-pointer">
|
|
||||||
<div class="modal-box relative" for="">
|
|
||||||
<form on:submit|preventDefault={follow} class="form-control gap-2">
|
|
||||||
<div class="label py-0">
|
|
||||||
<span class="label-text">Your fediverse account ID:</span>
|
|
||||||
</div>
|
|
||||||
<label class="input-group">
|
|
||||||
<input
|
|
||||||
bind:value={input}
|
|
||||||
type="text"
|
|
||||||
id="account"
|
|
||||||
name="account"
|
|
||||||
placeholder="username@instance.tld"
|
|
||||||
class:input-success={status === 'success'}
|
|
||||||
class:input-warning={status === 'warning'}
|
|
||||||
class:input-error={status === 'error'}
|
|
||||||
class="input input-bordered transition-all flex-1" />
|
|
||||||
<button type="submit" class="btn btn-square">
|
|
||||||
<span class="i-heroicons-outline-paper-airplane rotate-90" />
|
|
||||||
</button>
|
|
||||||
</label>
|
|
||||||
{#if statusText}
|
|
||||||
<div class="label py-0">
|
|
||||||
<span class="label-text-alt text-error">
|
|
||||||
{statusText}{#if statusText === '404'}: Couldn't find user{/if}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
|
@ -1,50 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { Friend } from '$lib/config/friends'
|
|
||||||
import Footer from '$lib/components/footer.svelte'
|
|
||||||
export let item: unknown
|
|
||||||
let friend = item as unknown as Friend
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if friend.id === 'footer'}
|
|
||||||
<Footer rounded={true} class="p-4 md:p-8" />
|
|
||||||
{:else if friend.html}
|
|
||||||
<a id={friend.id} rel={friend.rel} href={friend.link} class="h-card u-url">
|
|
||||||
{@html friend.html}
|
|
||||||
</a>
|
|
||||||
{:else}
|
|
||||||
<a
|
|
||||||
id={friend.id}
|
|
||||||
rel={friend.rel}
|
|
||||||
href={friend.link}
|
|
||||||
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow h-card u-url">
|
|
||||||
<div class="absolute text-4xl font-bold opacity-5 rotate-6 leading-tight top-4">
|
|
||||||
{friend.name ?? ''}
|
|
||||||
<br />
|
|
||||||
{friend.title ?? ''}
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-4">
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
{#if friend.avatar}
|
|
||||||
<div class="avatar {friend.class?.avatar} shrink-0 w-16 mb-auto">
|
|
||||||
<img class="{friend.class?.img ?? 'rounded-xl'} u-photo" src={friend.avatar} alt={friend.title} />
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="avatar {friend.class?.avatar} placeholder mb-auto">
|
|
||||||
<div class="{friend.class?.img ?? 'bg-neutral-focus text-neutral-content shadow-inner rounded-xl'} w-16">
|
|
||||||
<span class="text-3xl">{(friend.name ?? friend.title).charAt(0)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div class="card-title flex-col gap-0 flex-1 items-end">
|
|
||||||
<span class="text-right p-name">{friend.name ?? ''}</span>
|
|
||||||
<span class="opacity-50 text-right">{friend.title}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if friend.descr}
|
|
||||||
<div class="prose opacity-70 p-note">
|
|
||||||
{friend.descr}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
|
@ -1,83 +0,0 @@
|
||||||
<script lang="ts" context="module">
|
|
||||||
export const prerender = true
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export let user = undefined
|
|
||||||
export let repo = undefined
|
|
||||||
let info: {
|
|
||||||
html_url: string
|
|
||||||
description: string
|
|
||||||
homepage?: string
|
|
||||||
owner: { avatar_url: string }
|
|
||||||
stargazers_count: any
|
|
||||||
license?: { key?: any }
|
|
||||||
}
|
|
||||||
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
onMount(async () => {
|
|
||||||
info = await (await fetch(`https://api.github.com/repos/${user}/${repo}`)).json()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="card bg-base-100 !bg-base-200 my-4 ">
|
|
||||||
<div class="p-6">
|
|
||||||
{#key info}
|
|
||||||
{#if info}
|
|
||||||
<div class="flex">
|
|
||||||
<div class="flex-initial pr-4">
|
|
||||||
<div class="card-title mb-6 !text-3xl font-medium">
|
|
||||||
<a rel="noopener noreferrer external" target="_blank" href={info.html_url}>
|
|
||||||
{user}/
|
|
||||||
<span class="font-semibold">{repo}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<p class="prose">
|
|
||||||
{info.description}
|
|
||||||
<br />
|
|
||||||
<a rel="noopener noreferrer external" target="_blank" href={info.homepage}>{info.homepage}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<img class="w-20 h-20 ml-auto rounded-xl flex-initial" alt="owner_avatar" src={info.owner.avatar_url} />
|
|
||||||
</div>
|
|
||||||
<div class="card-actions -ml-2">
|
|
||||||
<button class="btn btn-sm btn-ghost">
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
version="1.1"
|
|
||||||
data-view-component="true"
|
|
||||||
class="inline-block w-4 h-4 mr-2 fill-current">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z" />
|
|
||||||
</svg>
|
|
||||||
{info.stargazers_count}
|
|
||||||
</button>
|
|
||||||
{#if info.license}
|
|
||||||
<a class="btn btn-sm btn-ghost" href="https://choosealicense.com/licenses/{info.license.key}">
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
version="1.1"
|
|
||||||
data-view-component="true"
|
|
||||||
class="inline-block w-4 h-4 mr-2 fill-current">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M8.75.75a.75.75 0 00-1.5 0V2h-.984c-.305 0-.604.08-.869.23l-1.288.737A.25.25 0 013.984 3H1.75a.75.75 0 000 1.5h.428L.066 9.192a.75.75 0 00.154.838l.53-.53-.53.53v.001l.002.002.002.002.006.006.016.015.045.04a3.514 3.514 0 00.686.45A4.492 4.492 0 003 11c.88 0 1.556-.22 2.023-.454a3.515 3.515 0 00.686-.45l.045-.04.016-.015.006-.006.002-.002.001-.002L5.25 9.5l.53.53a.75.75 0 00.154-.838L3.822 4.5h.162c.305 0 .604-.08.869-.23l1.289-.737a.25.25 0 01.124-.033h.984V13h-2.5a.75.75 0 000 1.5h6.5a.75.75 0 000-1.5h-2.5V3.5h.984a.25.25 0 01.124.033l1.29.736c.264.152.563.231.868.231h.162l-2.112 4.692a.75.75 0 00.154.838l.53-.53-.53.53v.001l.002.002.002.002.006.006.016.015.045.04a3.517 3.517 0 00.686.45A4.492 4.492 0 0013 11c.88 0 1.556-.22 2.023-.454a3.512 3.512 0 00.686-.45l.045-.04.01-.01.006-.005.006-.006.002-.002.001-.002-.529-.531.53.53a.75.75 0 00.154-.838L13.823 4.5h.427a.75.75 0 000-1.5h-2.234a.25.25 0 01-.124-.033l-1.29-.736A1.75 1.75 0 009.735 2H8.75V.75zM1.695 9.227c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327l-1.305 2.9zm10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327l-1.305 2.9z" />
|
|
||||||
</svg>
|
|
||||||
{info.license.key}
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
<button class="btn btn-sm btn-circle btn-ghost ml-auto">
|
|
||||||
<svg aria-hidden="true" viewBox="0 0 16 16" version="1.1" data-view-component="true" class="w-6 h-6 fill-current">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,12 +0,0 @@
|
||||||
<script lang="ts" context="module">
|
|
||||||
export const prerender = true
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export let code: string
|
|
||||||
export let lang = 'text'
|
|
||||||
export let theme = 'material-default'
|
|
||||||
export let highlightedLines = undefined
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<p>TODO {code} {lang} {theme} {highlightedLines}</p>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { dev } from '$app/env'
|
|
||||||
let className = undefined
|
|
||||||
export { className as class }
|
|
||||||
export let src = undefined
|
|
||||||
export let alt = undefined
|
|
||||||
export let loading: 'lazy' | 'eager' = 'lazy'
|
|
||||||
export let decoding: 'async' | 'sync' | 'auto' = 'async'
|
|
||||||
let [name, ext] = [src.split('.').slice(0, -1).join('.'), src.split('.').pop()]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<picture>
|
|
||||||
{#if !dev}
|
|
||||||
<source srcset="{name}_768.{ext} 1x" media="(max-width: 425px)" />
|
|
||||||
<source srcset="{name}_768.{ext} 1x, {src} 2x" media="(min-width: 425px)" />
|
|
||||||
{/if}
|
|
||||||
<img itemprop="image" class={className} {src} {alt} {loading} {decoding} />
|
|
||||||
</picture>
|
|
|
@ -1,7 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let theme = undefined
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{@html `
|
|
||||||
${theme ?? ''}
|
|
||||||
`}
|
|
|
@ -1,36 +0,0 @@
|
||||||
<script lang="ts" context="module">
|
|
||||||
export const prerender = true
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
export let avatar: string
|
|
||||||
export let name: string
|
|
||||||
export let subname: string
|
|
||||||
export let bio: string
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="relative w-auto min-h-48 rounded-box overflow-hidden bg-gradient-to-b from-primary to-secondary text-primary-content transition-shadow duration-200 shadow-xl hover:shadow-2xl p-4 md:p-8 my-4">
|
|
||||||
<div class="absolute -top-4 opacity-10 text-[12rem] text-neutral leading-tight rotate-[30deg]">
|
|
||||||
{name ?? site.author.name}
|
|
||||||
</div>
|
|
||||||
<div class="avatar mb-4">
|
|
||||||
<div class="rounded-full border-2 border-white shadow-xl w-16 h-16">
|
|
||||||
<img
|
|
||||||
class="hover:rotate-[360deg] transition-transform duration-1000 ease-in-out m-0"
|
|
||||||
src={avatar ?? site.author.avatar}
|
|
||||||
alt={name ?? site.author.name}
|
|
||||||
loading="lazy"
|
|
||||||
decoding="async" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if subname}
|
|
||||||
<div class="opacity-50">{subname}</div>
|
|
||||||
{/if}
|
|
||||||
<div class="text-2xl mb-2">{name ?? site.author.name}</div>
|
|
||||||
{#if bio || site.author.bio}
|
|
||||||
<div>{@html bio ?? site.author.bio}</div>
|
|
||||||
{/if}
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
|
@ -1,42 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { Project } from '$lib/config/projects'
|
|
||||||
import Footer from '$lib/components/footer.svelte'
|
|
||||||
export let item: unknown
|
|
||||||
let project = item as unknown as Project
|
|
||||||
let tags = project.tags
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if project.id === 'footer'}
|
|
||||||
<Footer rounded={true} class="max-w-4xl mx-auto p-4 md:p-8" />
|
|
||||||
{:else}
|
|
||||||
<a
|
|
||||||
id={project.id}
|
|
||||||
href={project.link}
|
|
||||||
class="card mx-auto max-w-4xl bg-base-100 shadow-xl transition-shadow mb-7 h-card u-url hover:shadow-2xl">
|
|
||||||
<div class="absolute text-5xl font-bold opacity-5 rotate-6 leading-tight top-2 right-0">
|
|
||||||
{project.feature}
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-4">
|
|
||||||
<div class="flex flex-col md:flex-row items-start gap-4">
|
|
||||||
<div class="mb-auto aspect-video w-full max-w-full shrink-0 md:max-w-xs">
|
|
||||||
<img class="rounded-md " src={project.img} alt={project.description} />
|
|
||||||
</div>
|
|
||||||
<div class="card-title flex-1 flex-col items-start gap-4">
|
|
||||||
<div>
|
|
||||||
<h2 class="p-name text-left text-2xl mb-2">{project.name}</h2>
|
|
||||||
<div class="mb-3 text-base font-normal">
|
|
||||||
{#each tags as tag}
|
|
||||||
<span class="btn btn-sm btn-ghost normal-case border-dotted border-base-content/20 border-2 my-1 mr-1">
|
|
||||||
{tag}
|
|
||||||
</span>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="text-left text-base font-normal opacity-70">
|
|
||||||
{@html project.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
|
@ -1,28 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let id = undefined
|
|
||||||
export let list = undefined
|
|
||||||
export let playlist = undefined
|
|
||||||
export let start = undefined
|
|
||||||
export let autoplay = false
|
|
||||||
export let disablekb = false
|
|
||||||
export let controls = true
|
|
||||||
export let fs = true
|
|
||||||
export let loop = false
|
|
||||||
|
|
||||||
const src = `https://www.youtube.com/embed/${id}?${list ? `listType=playlist&list=${list}&` : ''}${
|
|
||||||
playlist ? `playlist=${playlist}&` : ''
|
|
||||||
}${start ? `start=${start}` : ''}${autoplay ? 'autoplay=1&' : ''}${disablekb ? 'disablekb=1&' : ''}${
|
|
||||||
controls ? '' : 'controls=0&'
|
|
||||||
}${fs ? '' : 'fs=0&'}${loop ? 'loop=1' : ''}`
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="relative pb-[56.25%] mb-2">
|
|
||||||
<iframe
|
|
||||||
{src}
|
|
||||||
class="absolute w-full h-full"
|
|
||||||
title="YouTube video player"
|
|
||||||
frameborder="0"
|
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
||||||
loading="lazy"
|
|
||||||
allowfullscreen />
|
|
||||||
</div>
|
|
|
@ -1,46 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import { footer as footerConfig } from '$lib/config/general'
|
|
||||||
let className: string | undefined = undefined
|
|
||||||
export { className as class }
|
|
||||||
export let sticky: boolean = false
|
|
||||||
export let rounded: boolean = false
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<footer
|
|
||||||
id="footer"
|
|
||||||
class="footer footer-center bg-base-300 text-base-content shadow-inner p-8 {rounded
|
|
||||||
? 'rounded-box'
|
|
||||||
: 'md:rounded-box'} {sticky ? 'sticky bottom-0 z-0 md:static' : ''} {className ?? ''}">
|
|
||||||
<div class="prose">
|
|
||||||
<p>
|
|
||||||
{#if footerConfig.nav}
|
|
||||||
{#each footerConfig.nav as { text, link }, i}
|
|
||||||
<a href={link} rel="noopener external" target="_blank">{text}</a>
|
|
||||||
{#if i + 1 < footerConfig.nav.length}
|
|
||||||
<span class="mr-1">·</span>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
<br />
|
|
||||||
{/if}
|
|
||||||
Copyright © {footerConfig.since && footerConfig.since !== new Date().toJSON().substring(0, 4)
|
|
||||||
? `${footerConfig.since} - ${new Date().toJSON().substring(0, 4)}`
|
|
||||||
: new Date().toJSON().substring(0, 4)}
|
|
||||||
{site.author.name}
|
|
||||||
<br />
|
|
||||||
Powered by
|
|
||||||
<a
|
|
||||||
rel="noopener external"
|
|
||||||
target="_blank"
|
|
||||||
class="tooltip tooltip-secondary hover:text-secondary"
|
|
||||||
data-tip="🌸 [δ] - Based on MDsveX & SvelteKit 🌸"
|
|
||||||
href="https://github.com/importantimport/urara">
|
|
||||||
Urara
|
|
||||||
</a>
|
|
||||||
{#if footerConfig.html}
|
|
||||||
<br />
|
|
||||||
{@html footerConfig.html}
|
|
||||||
{/if}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
|
@ -1,39 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { dev } from '$app/env'
|
|
||||||
import { head } from '$lib/config/general'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import OpenGraph from '$lib/components/head_opengraph.svelte'
|
|
||||||
export let post: Urara.Post | undefined = undefined
|
|
||||||
export let page: Urara.Page | undefined = undefined
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<meta name="author" content={site.author?.name} />
|
|
||||||
{#if post}
|
|
||||||
<link rel="canonical" href={site.protocol + site.domain + post.path} />
|
|
||||||
{#if post.type === 'article'}
|
|
||||||
<title>{post.title} | {site.title}</title>
|
|
||||||
{:else if post.type === 'note'}
|
|
||||||
<title>{post.summary ?? post.path.slice(1)} | {site.title}</title>
|
|
||||||
{/if}
|
|
||||||
{#if post.tags}<meta name="keywords" content={post.tags.join(', ')} />{/if}
|
|
||||||
{#if post.summary}<meta name="description" content={post.summary} />{/if}
|
|
||||||
{:else}
|
|
||||||
<meta name="description" content={site.description} />
|
|
||||||
<meta name="keywords" content={site.keywords?.join(', ')} />
|
|
||||||
{#if page}
|
|
||||||
<title>{page.title ?? page.path.slice(1)} | {site.title}</title>
|
|
||||||
<link rel="canonical" href={site.protocol + site.domain + page.path} />
|
|
||||||
{:else}
|
|
||||||
<title>{site.subtitle ? `${site.title} - ${site.subtitle}` : site.title}</title>
|
|
||||||
<link rel="canonical" href={site.protocol + site.domain} />
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
{#if head.custom}
|
|
||||||
{#each head.custom({ dev, post, page }) as tag}
|
|
||||||
{@html tag}
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<OpenGraph {post} {page} />
|
|
|
@ -1,15 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { favicon, any } from '$lib/config/icon'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
{#if favicon}
|
|
||||||
<link rel="shortcut icon" href={favicon.src} sizes={favicon.sizes} type={favicon.type} />
|
|
||||||
{/if}
|
|
||||||
{#if any['180']}
|
|
||||||
<link rel="apple-touch-icon" href={any['180'].src} sizes={any['180'].sizes} type={any['180'].type} />
|
|
||||||
{/if}
|
|
||||||
{#if any['192']}
|
|
||||||
<link rel="icon" href={any['192'].src} sizes={any['192'].sizes} type={any['192'].type} />
|
|
||||||
{/if}
|
|
||||||
</svelte:head>
|
|
|
@ -1,45 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import { any, maskable } from '$lib/config/icon'
|
|
||||||
export let post: Urara.Post | undefined = undefined
|
|
||||||
export let page: Urara.Page | undefined = undefined
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<meta property="og:site_name" content={site.title} />
|
|
||||||
<meta property="og:locale" content={site.lang} />
|
|
||||||
{#if post}
|
|
||||||
<meta property="og:type" content="article" />
|
|
||||||
<meta property="og:title" content={post.title ?? post.summary ?? post.path.slice(1)} />
|
|
||||||
{#if post.summary}
|
|
||||||
<meta property="og:description" content={post.summary} />
|
|
||||||
{/if}
|
|
||||||
{#if post.image}
|
|
||||||
<meta property="og:image" content={site.protocol + site.domain + post.image} />
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
{:else}
|
|
||||||
<meta property="og:image" content={maskable['512'].src ?? any['512'].src ?? any['192'].src} />
|
|
||||||
<meta name="twitter:card" content="summary" />
|
|
||||||
{/if}
|
|
||||||
{#if post.tags}
|
|
||||||
{#each post.tags as tag}
|
|
||||||
<meta property="article:tag" content={tag} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
<meta property="og:url" content={site.protocol + site.domain + post.path} />
|
|
||||||
<meta property="article:author" content={site.author.name} />
|
|
||||||
<meta property="article:published_time" content={post.published ?? post.created} />
|
|
||||||
<meta property="article:modified_time" content={post.updated ?? post.published ?? post.created} />
|
|
||||||
{:else}
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:image" content={maskable['512'].src ?? any['512'].src ?? any['192'].src} />
|
|
||||||
<meta property="og:description" content={site.description} />
|
|
||||||
{#if page}
|
|
||||||
<meta property="og:title" content={page.title ?? page.path.slice(1)} />
|
|
||||||
<meta property="og:url" content={site.protocol + site.domain + page.path} />
|
|
||||||
{:else}
|
|
||||||
<meta property="og:title" content={site.title} />
|
|
||||||
<meta property="og:url" content={site.protocol + site.domain} />
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</svelte:head>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import { head } from '$lib/config/general'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
{#if site.author.github}
|
|
||||||
<link rel="me" href="https://github.com/{site.author.github}" />
|
|
||||||
{/if}
|
|
||||||
{#if site.author.twitter}
|
|
||||||
<link rel="me" href="https://twitter.com/{site.author.twitter}" />
|
|
||||||
{/if}
|
|
||||||
{#if head.relMe}
|
|
||||||
{#each head.relMe as href}
|
|
||||||
<link rel="me" {href} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</svelte:head>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { head } from '$lib/config/general'
|
|
||||||
import { post } from '$lib/config/post'
|
|
||||||
import Icon from '$lib/components/head_icon.svelte'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
{#if head.me}
|
|
||||||
{#each head.me as href}
|
|
||||||
<link rel="me" {href} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
{#if post.comment?.webmention?.username}
|
|
||||||
<link rel="webmention" href="https://webmention.io/{post.comment.webmention.username}/webmention" />
|
|
||||||
<link rel="pingback" href="https://webmention.io/{post.comment.webmention.username}/xmlrpc" />
|
|
||||||
{/if}
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<Icon />
|
|
|
@ -1,131 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { browser, dev } from '$app/env'
|
|
||||||
import { fly } from 'svelte/transition'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import { theme } from '$lib/config/general'
|
|
||||||
import { title as storedTitle } from '$lib/stores/title'
|
|
||||||
import { header as headerConfig } from '$lib/config/general'
|
|
||||||
import { hslToHex } from '$lib/utils/color'
|
|
||||||
import Nav from '$lib/components/header_nav.svelte'
|
|
||||||
import Search from '$lib/components/header_search.svelte'
|
|
||||||
export let path: string
|
|
||||||
let title: string
|
|
||||||
let currentTheme: string
|
|
||||||
let currentThemeColor: string
|
|
||||||
let search: boolean = false
|
|
||||||
let pin: boolean = true
|
|
||||||
let percent: number
|
|
||||||
let [scrollY, lastY] = [0, 0]
|
|
||||||
|
|
||||||
storedTitle.subscribe(storedTitle => (title = storedTitle as string))
|
|
||||||
|
|
||||||
$: if (browser && currentTheme) {
|
|
||||||
document.documentElement.setAttribute('data-theme', currentTheme)
|
|
||||||
currentThemeColor = hslToHex(
|
|
||||||
...(getComputedStyle(document.documentElement)
|
|
||||||
.getPropertyValue('--b1')
|
|
||||||
.slice(dev ? 1 : 0)
|
|
||||||
.replaceAll('%', '')
|
|
||||||
.split(' ')
|
|
||||||
.map(Number) as [number, number, number])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (scrollY) {
|
|
||||||
pin = lastY - scrollY > 0 || scrollY === 0 ? true : false
|
|
||||||
lastY = scrollY
|
|
||||||
if (browser)
|
|
||||||
percent =
|
|
||||||
Math.round((scrollY / (document.documentElement.scrollHeight - document.documentElement.clientHeight)) * 10000) / 100
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browser)
|
|
||||||
currentTheme =
|
|
||||||
localStorage.getItem('theme') ?? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'night' : 'lemonade')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<meta name="theme-color" content={currentThemeColor} />
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<svelte:window bind:scrollY />
|
|
||||||
|
|
||||||
<header
|
|
||||||
id="header"
|
|
||||||
class:-translate-y-32={!pin && scrollY > 0}
|
|
||||||
class="fixed z-50 w-screen transition-all duration-500 ease-in-out border-b-2 border-transparent max-h-[4.125rem] {scrollY >
|
|
||||||
32 && 'backdrop-blur border-base-content/10 bg-base-100/30 md:bg-base-200/30'}">
|
|
||||||
<div in:fly={{ x: -50, duration: 300, delay: 300 }} out:fly={{ x: -50, duration: 300 }} class="navbar">
|
|
||||||
<div class="navbar-start">
|
|
||||||
{#if headerConfig.nav}
|
|
||||||
<Nav {path} {title} {pin} {scrollY} nav={headerConfig.nav} />
|
|
||||||
{/if}
|
|
||||||
<a href="/" sveltekit:prefetch class="btn btn-ghost normal-case text-lg">{site.title}</a>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-end">
|
|
||||||
<!-- {#if headerConfig.search} -->
|
|
||||||
<!-- The button to open modal -->
|
|
||||||
<!-- <label for="search-modal" class="btn btn-square btn-ghost ml-2"><span class="i-heroicons-outline-search" /></label> -->
|
|
||||||
<!-- <button
|
|
||||||
on:click={() => {
|
|
||||||
search = !search
|
|
||||||
}}
|
|
||||||
type="submit"
|
|
||||||
class="btn btn-square btn-ghost ml-2">
|
|
||||||
<span class="i-heroicons-outline-search" />
|
|
||||||
</button> -->
|
|
||||||
<!-- {/if} -->
|
|
||||||
<div id="change-theme" class="dropdown dropdown-end">
|
|
||||||
<div tabindex="0" class="btn btn-square btn-ghost">
|
|
||||||
<span class="i-heroicons-outline-color-swatch" />
|
|
||||||
</div>
|
|
||||||
<ul
|
|
||||||
tabindex="0"
|
|
||||||
class="flex shadow-2xl menu dropdown-content bg-base-100 text-base-content rounded-box w-52 p-2 gap-2 overflow-y-auto max-h-[21.5rem]"
|
|
||||||
class:hidden={!pin}>
|
|
||||||
{#each theme as { name, text }}
|
|
||||||
<button
|
|
||||||
data-theme={name}
|
|
||||||
on:click={() => {
|
|
||||||
currentTheme = name
|
|
||||||
localStorage.setItem('theme', name)
|
|
||||||
}}
|
|
||||||
class:border-2={currentTheme === name}
|
|
||||||
class:border-primary={currentTheme === name}
|
|
||||||
class="btn btn-ghost hover:bg-primary group rounded-lg flex bg-base-100 p-2 transition-all">
|
|
||||||
<p class="flex-1 text-left text-base-content group-hover:text-primary-content transition-color">
|
|
||||||
{text ?? name}
|
|
||||||
</p>
|
|
||||||
<div class="flex-none m-auto flex gap-1">
|
|
||||||
<div class="bg-primary w-2 h-4 rounded" />
|
|
||||||
<div class="bg-secondary w-2 h-4 rounded" />
|
|
||||||
<div class="bg-accent w-2 h-4 rounded" />
|
|
||||||
<div class="bg-neutral w-2 h-4 rounded" />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<button
|
|
||||||
id="totop"
|
|
||||||
on:click={() => window.scrollTo(0, 0)}
|
|
||||||
class:translate-y-24={!pin || scrollY === 0}
|
|
||||||
aria-label="scroll to top"
|
|
||||||
class="fixed grid group btn btn-circle btn-lg border-none backdrop-blur bottom-6 right-6 z-50 duration-500 ease-in-out {percent >
|
|
||||||
95
|
|
||||||
? 'btn-accent shadow-lg'
|
|
||||||
: 'btn-ghost bg-base-100/30 md:bg-base-200/30'}"
|
|
||||||
class:opacity-100={scrollY}>
|
|
||||||
<div
|
|
||||||
class="radial-progress text-accent transition-all duration-500 ease-in-out group-hover:text-accent-focus col-start-1 row-start-1"
|
|
||||||
style={`--size:4rem; --thickness: 0.25rem; --value:${percent};`} />
|
|
||||||
<div
|
|
||||||
class:border-transparent={percent > 95}
|
|
||||||
class="border-4 border-base-content/10 group-hover:border-transparent col-start-1 row-start-1 rounded-full w-full h-full p-4 grid duration-500 ease-in-out">
|
|
||||||
<span class="i-heroicons-solid-chevron-up !w-6 !h-6" />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
|
@ -1,71 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let nav: { text: string; link?: string; children?: { text: string; link: string }[] }[]
|
|
||||||
export let path: string
|
|
||||||
export let title: string
|
|
||||||
export let scrollY: number
|
|
||||||
export let pin: boolean
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="dropdown lg:hidden">
|
|
||||||
<label for="navbar-dropdown" tabindex="0" class="btn btn-square btn-ghost">
|
|
||||||
<span class="i-heroicons-outline-menu-alt-1" />
|
|
||||||
</label>
|
|
||||||
<ul
|
|
||||||
id="navbar-dropdown"
|
|
||||||
tabindex="0"
|
|
||||||
class:hidden={!pin}
|
|
||||||
class="menu menu-compact dropdown-content bg-base-100 text-base-content shadow-lg rounded-box max-w-52 p-2">
|
|
||||||
{#each nav as { text, link, children }}
|
|
||||||
{#if link && !children}
|
|
||||||
<li>
|
|
||||||
<a sveltekit:prefetch class:font-bold={link === path} href={link}>{text}</a>
|
|
||||||
</li>
|
|
||||||
{:else if children}
|
|
||||||
<li tabindex="0">
|
|
||||||
<span class:font-bold={children.some(({ link }) => link === path)} class="justify-between gap-1 max-w-[13rem]">
|
|
||||||
{text}
|
|
||||||
<span class="i-heroicons-solid-chevron-right mr-2" />
|
|
||||||
</span>
|
|
||||||
<ul class="bg-base-100 text-base-content shadow-lg p-2">
|
|
||||||
{#each children as { text, link }}
|
|
||||||
<li>
|
|
||||||
<a sveltekit:prefetch class:font-bold={link === path} href={link}>{text}</a>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class:swap-active={scrollY > 32 && title} class="swap order-last hidden lg:inline-grid">
|
|
||||||
<button
|
|
||||||
on:click={() => window.scrollTo(0, 0)}
|
|
||||||
class:hidden={scrollY < 32 || !title}
|
|
||||||
class="swap-on btn btn-ghost text-base font-normal normal-case transition-all duration-200">
|
|
||||||
{title}
|
|
||||||
</button>
|
|
||||||
<ul class:hidden={scrollY > 64 && title} class="swap-off menu menu-horizontal p-0">
|
|
||||||
{#each nav as { text, link, children }}
|
|
||||||
{#if link && !children}
|
|
||||||
<li>
|
|
||||||
<a sveltekit:prefetch class:font-bold={link === path} href={link}>{text}</a>
|
|
||||||
</li>
|
|
||||||
{:else if children}
|
|
||||||
<li tabindex="0">
|
|
||||||
<span class:font-bold={children.some(({ link }) => link === path)} class="gap-1">
|
|
||||||
{text}
|
|
||||||
<span class="i-heroicons-solid-chevron-down -mr-1" />
|
|
||||||
</span>
|
|
||||||
<ul class="bg-base-100 text-base-content shadow-lg p-2">
|
|
||||||
{#each children as { text, link }}
|
|
||||||
<li>
|
|
||||||
<a sveltekit:prefetch class:font-bold={link === path} href={link}>{text}</a>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
|
@ -1,102 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { onMount, createEventDispatcher } from 'svelte'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import { header as headerConfig } from '$lib/config/general'
|
|
||||||
import Typeahead from 'svelte-typeahead'
|
|
||||||
import { page } from '$app/stores'
|
|
||||||
// ref : https://github.com/saadeghi/daisyui/blob/master/src/docs/src/components/Search.svelte
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<form>
|
|
||||||
<input type="checkbox" id="search-modal" class="modal-toggle " />
|
|
||||||
<label for="search-modal" class="modal cursor-pointer w-full items-start pt-16 ">
|
|
||||||
<label class="modal-box w-11/12 max-w-4xl " for="">
|
|
||||||
<div class="form-control">
|
|
||||||
<span
|
|
||||||
class="i-heroicons-outline-search text-base-content pointer-events-none absolute z-10 my-2 ml-3 stroke-current opacity-60 w-5" />
|
|
||||||
</div>
|
|
||||||
<Typeahead placeholder="Search is not avaible …" limit={8} label="Search" inputAfterSelect="clear">
|
|
||||||
<div class="py-1 text-sm" />
|
|
||||||
</Typeahead>
|
|
||||||
<div class="pointer-events-none absolute right-14 top-8 gap-1 opacity-50 hidden lg:flex">
|
|
||||||
<kbd class="kbd kbd-sm">ESC</kbd>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- <script lang="ts">
|
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
|
||||||
import { page } from "$app/stores"
|
|
||||||
import { goto } from "$app/navigation"
|
|
||||||
import Typeahead from "svelte-typeahead"
|
|
||||||
import { pages } from "@src/lib/data.js"
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
let searchIndex = []
|
|
||||||
pages.forEach((group) => {
|
|
||||||
group.items.forEach((item) => {
|
|
||||||
searchIndex.push(item)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
let seachboxEl
|
|
||||||
function handleKeydown(e) {
|
|
||||||
if ((e.keyCode === 75 && e.metaKey) || (e.keyCode === 75 && e.ctrlKey)) {
|
|
||||||
e.preventDefault()
|
|
||||||
seachboxEl.querySelector("input[type=search]").focus()
|
|
||||||
dispatch("focus")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function onSelect({ detail }) {
|
|
||||||
goto(searchIndex[detail.originalIndex].href)
|
|
||||||
dispatch("search", detail)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:window on:keydown={handleKeydown} />
|
|
||||||
-->
|
|
||||||
<style global>
|
|
||||||
.searchbox [data-svelte-typeahead] {
|
|
||||||
background: none;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
[data-svelte-search] label {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
[data-svelte-search] input {
|
|
||||||
background-color: transparent;
|
|
||||||
color: inherit;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
padding-left: 2em;
|
|
||||||
}
|
|
||||||
[data-svelte-search] input::placeholder {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
[data-svelte-search] input:focus {
|
|
||||||
outline-color: hsla(var(--bc) / 0.2);
|
|
||||||
background-color: hsl(var(--b1));
|
|
||||||
color: hsla(var(--bc));
|
|
||||||
}
|
|
||||||
[data-svelte-typeahead] .svelte-typeahead-list {
|
|
||||||
transform: translateY(0.5em);
|
|
||||||
background: hsl(var(--b1));
|
|
||||||
border-radius: var(--rounded-btn);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
[data-svelte-typeahead] .svelte-typeahead-list .selected,
|
|
||||||
[data-svelte-typeahead] .svelte-typeahead-list .selected:hover {
|
|
||||||
background: hsl(var(--n));
|
|
||||||
color: hsl(var(--nc));
|
|
||||||
}
|
|
||||||
[data-svelte-typeahead] .svelte-typeahead-list li {
|
|
||||||
color: hsl(var(--bc));
|
|
||||||
}
|
|
||||||
[data-svelte-typeahead] .svelte-typeahead-list li:hover {
|
|
||||||
background: hsl(var(--b2));
|
|
||||||
color: hsl(var(--bc));
|
|
||||||
}
|
|
||||||
[data-svelte-typeahead] .svelte-typeahead-list li:not(:last-of-type) {
|
|
||||||
border-bottom-color: hsla(var(--bc) / 0.1);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,174 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="h-card flex flex-col gap-4 sticky top-24 card card-body p-4 items-right xl:border-2 xl:py-8 border-base-content/10 xl:ml-auto xl:mr-8 xl:max-w-xs">
|
|
||||||
<a href={site.protocol + site.domain} class="hidden u-url u-uid">{site.author.name}</a>
|
|
||||||
<figure class="relative mx-auto group">
|
|
||||||
<img
|
|
||||||
class="rounded-full shadow-xl w-32 h-32 hover:rotate-[360deg] transition-transform duration-1000 ease-in-out"
|
|
||||||
src={site.author.avatar}
|
|
||||||
alt={site.author.name} />
|
|
||||||
{#if site.author.status}
|
|
||||||
<div class="heart absolute rounded-full w-10 h-10 bottom-0 right-0 bg-base-100 shadow-xl text-xl text-center py-1.5">
|
|
||||||
{site.author.status}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</figure>
|
|
||||||
<div class="text-center flex flex-col gap-2">
|
|
||||||
<h2 class="text-2xl font-bold mt-0 mb-2 p-name">{site.author.name}</h2>
|
|
||||||
<p class="opacity-75 p-note">{@html site.author.bio}</p>
|
|
||||||
<label for="remote-follow" class="btn btn-active btn-outline btn-xs modal-button w-fit mx-auto normal-case mt-4 gap-2 ">
|
|
||||||
<span class="i-material-symbols-group-add-rounded" />
|
|
||||||
Remote Follow
|
|
||||||
</label>
|
|
||||||
{#if site.author.metadata}
|
|
||||||
<div class="flex gap-1 flex-wrap justify-center">
|
|
||||||
{#each site.author.metadata as { text, icon, link, rel }}
|
|
||||||
{#if link}
|
|
||||||
<a
|
|
||||||
href={link}
|
|
||||||
rel={rel ?? 'me noopener external'}
|
|
||||||
class:btn-square={!text}
|
|
||||||
class="btn btn-sm btn-ghost normal-case gap-2 u-url"
|
|
||||||
target="_blank">
|
|
||||||
{#if icon}
|
|
||||||
<span class="{icon} !w-5 !h-5" />
|
|
||||||
{/if}
|
|
||||||
{#if text}
|
|
||||||
{text}
|
|
||||||
{/if}
|
|
||||||
</a>
|
|
||||||
{:else}
|
|
||||||
<button class:btn-square={!text} class="btn btn-sm btn-ghost normal-case gap-2" {rel}>
|
|
||||||
{#if icon}
|
|
||||||
<span class="{icon} !w-5 !h-5" />
|
|
||||||
{/if}
|
|
||||||
{#if text}
|
|
||||||
{text}
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.heart {
|
|
||||||
-webkit-animation: heartbeat 2s linear 1s infinite;
|
|
||||||
-o-animation: heartbeat 2s linear 1s infinite;
|
|
||||||
-moz-animation: heartbeat 2s linear 1s infinite;
|
|
||||||
-ms-animation: heartbeat 2s linear 1s infinite;
|
|
||||||
animation: heartbeat 2s linear 1s infinite;
|
|
||||||
}
|
|
||||||
@keyframes heartbeat {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: scale(1);
|
|
||||||
-o-transform: scale(1);
|
|
||||||
-moz-transform: scale(1);
|
|
||||||
-ms-transform: scale(1);
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
2% {
|
|
||||||
-webkit-transform: scale(1);
|
|
||||||
-o-transform: scale(1);
|
|
||||||
-moz-transform: scale(1);
|
|
||||||
-ms-transform: scale(1);
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
4% {
|
|
||||||
-webkit-transform: scale(1.08);
|
|
||||||
-o-transform: scale(1.08);
|
|
||||||
-moz-transform: scale(1.08);
|
|
||||||
-ms-transform: scale(1.08);
|
|
||||||
transform: scale(1.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
8% {
|
|
||||||
-webkit-transform: scale(1.1);
|
|
||||||
-o-transform: scale(1.1);
|
|
||||||
-moz-transform: scale(1.1);
|
|
||||||
-ms-transform: scale(1.1);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
20% {
|
|
||||||
-webkit-transform: scale(0.96);
|
|
||||||
-o-transform: scale(0.96);
|
|
||||||
-moz-transform: scale(0.96);
|
|
||||||
-ms-transform: scale(0.96);
|
|
||||||
transform: scale(0.96);
|
|
||||||
}
|
|
||||||
|
|
||||||
24% {
|
|
||||||
-webkit-transform: scale(1.1);
|
|
||||||
-o-transform: scale(1.1);
|
|
||||||
-moz-transform: scale(1.1);
|
|
||||||
-ms-transform: scale(1.1);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
32% {
|
|
||||||
-webkit-transform: scale(1.08);
|
|
||||||
-o-transform: scale(1.08);
|
|
||||||
-moz-transform: scale(1.08);
|
|
||||||
-ms-transform: scale(1.08);
|
|
||||||
transform: scale(1.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
40% {
|
|
||||||
-webkit-transform: scale(1);
|
|
||||||
-o-transform: scale(1);
|
|
||||||
-moz-transform: scale(1);
|
|
||||||
-ms-transform: scale(1);
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes heartbeat {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: scale(1);
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
2% {
|
|
||||||
-webkit-transform: scale(1);
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
4% {
|
|
||||||
-webkit-transform: scale(1.08);
|
|
||||||
transform: scale(1.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
8% {
|
|
||||||
-webkit-transform: scale(1.1);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
20% {
|
|
||||||
-webkit-transform: scale(0.96);
|
|
||||||
transform: scale(0.96);
|
|
||||||
}
|
|
||||||
|
|
||||||
24% {
|
|
||||||
-webkit-transform: scale(1.1);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
32% {
|
|
||||||
-webkit-transform: scale(1.08);
|
|
||||||
transform: scale(1.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
40% {
|
|
||||||
-webkit-transform: scale(1);
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,12 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let post: Urara.Post
|
|
||||||
const actions = import.meta.glob<{ default: unknown }>('/src/lib/components/actions/*.svelte', { eager: true })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="sticky top-24 hidden xl:flex flex-col gap-4 w-fit h-[calc(100vh-12rem)] ml-auto mr-8 my-8 justify-center">
|
|
||||||
{#if Object.keys(actions).length}
|
|
||||||
{#each Object.values(actions) as action}
|
|
||||||
<svelte:component this={action.default} {post} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
|
@ -1,129 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { browser } from '$app/env'
|
|
||||||
import { post as postConfig } from '$lib/config/post'
|
|
||||||
import { posts as storedPosts } from '$lib/stores/posts'
|
|
||||||
import { title as storedTitle } from '$lib/stores/title'
|
|
||||||
import Reply from '$lib/components/post_reply.svelte'
|
|
||||||
import Status from '$lib/components/post_status.svelte'
|
|
||||||
import Image from '$lib/components/prose/img.svelte'
|
|
||||||
import Pagination from '$lib/components/post_pagination.svelte'
|
|
||||||
import Comment from '$lib/components/post_comment.svelte'
|
|
||||||
export let post: Urara.Post
|
|
||||||
export let preview: boolean = false
|
|
||||||
export let loading: 'eager' | 'lazy' = 'lazy'
|
|
||||||
export let decoding: 'async' | 'sync' | 'auto' = 'async'
|
|
||||||
// pagination
|
|
||||||
let index: number
|
|
||||||
let prev: Urara.Post | undefined = undefined
|
|
||||||
let next: Urara.Post | undefined = undefined
|
|
||||||
if (browser && !preview)
|
|
||||||
storedPosts.subscribe((storedPosts: Urara.Post[]) => {
|
|
||||||
index = storedPosts.findIndex(storedPost => storedPost.path === post.path)
|
|
||||||
prev = storedPosts
|
|
||||||
.slice(0, index)
|
|
||||||
.reverse()
|
|
||||||
.find(post => !post.flags?.includes('unlisted'))
|
|
||||||
next = storedPosts.slice(index + 1).find(post => !post.flags?.includes('unlisted'))
|
|
||||||
storedTitle.set(post.title ?? post.path.slice(1))
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:element
|
|
||||||
this={post.type === 'article' ? 'article' : 'div'}
|
|
||||||
itemscope
|
|
||||||
itemtype="https://schema.org/BlogPosting"
|
|
||||||
itemprop="blogPost"
|
|
||||||
class:md:mb-8={!preview}
|
|
||||||
class:lg:mb-16={!preview}
|
|
||||||
class:h-entry={preview}
|
|
||||||
class:group={preview}
|
|
||||||
class:image-full={preview && post.type === 'article' && post.image}
|
|
||||||
class:before:!rounded-none={preview && post.image}
|
|
||||||
class="card bg-base-100 rounded-none md:rounded-box md:shadow-xl z-10">
|
|
||||||
{#if !preview && postConfig.bridgy}
|
|
||||||
<div id="bridgy" class="hidden">
|
|
||||||
{#each post.flags?.some( flag => flag.startsWith('bridgy') ) ? post.flags.flatMap( flag => (flag.startsWith('bridgy') ? flag.slice(7) : []) ) : [...(postConfig.bridgy.post ?? []), ...(postConfig.bridgy[post.type] ?? [])] as target}
|
|
||||||
{#if target === 'fed'}
|
|
||||||
<a href="https://fed.brid.gy/">fed</a>
|
|
||||||
{:else}
|
|
||||||
<a href="https://brid.gy/publish/{target}">{target}</a>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if post.in_reply_to}
|
|
||||||
<Reply in_reply_to={post.in_reply_to} class="mt-4 mx-4" />
|
|
||||||
{/if}
|
|
||||||
{#if post.image && preview}
|
|
||||||
<figure class="!block">
|
|
||||||
<Image
|
|
||||||
class={post.type === 'article'
|
|
||||||
? 'u-featured object-center h-full w-full absolute group-hover:scale-105 transition-transform duration-500 ease-in-out'
|
|
||||||
: 'u-photo rounded-xl md:rounded-b-none -mb-6 md:-mb-2'}
|
|
||||||
src={post.image}
|
|
||||||
alt={post.image}
|
|
||||||
{loading}
|
|
||||||
{decoding} />
|
|
||||||
</figure>
|
|
||||||
{/if}
|
|
||||||
<div
|
|
||||||
class={`card-body gap-0 ${
|
|
||||||
preview && post.type === 'article' && post.image ? 'md:col-start-1 md:row-start-1 md:text-neutral-content md:z-20' : ''
|
|
||||||
}`}>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
{#if post.image && !preview}
|
|
||||||
<figure
|
|
||||||
class={`md:order-last mb-4 ${post.type === 'article' ? 'flex-col gap-2 -mx-4 -mt-8 md:mt-0' : 'flex-col -mx-8'}`}>
|
|
||||||
<Image
|
|
||||||
class={`${post.type === 'article' ? 'u-featured rounded-box shadow-xl' : 'u-photo'}`}
|
|
||||||
src={post.image}
|
|
||||||
alt={post.image}
|
|
||||||
{loading}
|
|
||||||
{decoding} />
|
|
||||||
</figure>
|
|
||||||
{/if}
|
|
||||||
<Status {post} {preview} />
|
|
||||||
{#if post.title}
|
|
||||||
{#if preview}
|
|
||||||
<h2
|
|
||||||
itemprop="name headline"
|
|
||||||
class="card-title text-2xl mr-auto bg-[length:100%_0%] bg-[position:0_88%] underline decoration-4 decoration-transparent group-hover:decoration-primary hover:bg-[length:100%_100%] hover:text-primary-content bg-gradient-to-t from-primary to-primary bg-no-repeat transition-all ease-in-out duration-300">
|
|
||||||
<a itemprop="url" class="u-url p-name" href={post.path}>{post.title ?? post.path.slice(1)}</a>
|
|
||||||
</h2>
|
|
||||||
{:else}
|
|
||||||
<h1 itemprop="name headline" class="card-title text-3xl mb-8 p-name">{post.title ?? post.path.slice(1)}</h1>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
{#if post.summary}
|
|
||||||
<p itemprop="description" class:hidden={!preview || post.type !== 'article'} class="p-summary mb-auto">
|
|
||||||
{post.summary}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<main itemprop="articleBody" class:mt-4={post.type !== 'article'} class="urara-prose prose e-content">
|
|
||||||
{#if !preview}
|
|
||||||
<slot />
|
|
||||||
{:else if post.html}
|
|
||||||
{@html post.html}
|
|
||||||
{/if}
|
|
||||||
</main>
|
|
||||||
{#if !preview && post.tags}
|
|
||||||
<div class="divider mt-4 mb-0" />
|
|
||||||
<div>
|
|
||||||
{#each post.tags as tag}
|
|
||||||
<a href="/?tags={tag}" class="btn btn-sm btn-ghost normal-case mt-2 mr-2 p-category">
|
|
||||||
#{tag}
|
|
||||||
</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if !preview}
|
|
||||||
{#if (prev || next) && !post.flags?.includes('pagination-disabled') && !post.flags?.includes('unlisted')}
|
|
||||||
<Pagination {next} {prev} />
|
|
||||||
{/if}
|
|
||||||
{#if browser && postConfig.comment && !post.flags?.includes('comment-disabled')}
|
|
||||||
<Comment {post} config={postConfig.comment} />
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</svelte:element>
|
|
|
@ -1,42 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import type { CommentConfig } from '$lib/types/post'
|
|
||||||
import { toSnake } from '$lib/utils/case'
|
|
||||||
export let post: Urara.Post
|
|
||||||
export let config: CommentConfig
|
|
||||||
const comments = import.meta.glob<{ default: unknown }>('/src/lib/components/comments/*.svelte', { eager: true })
|
|
||||||
let currentComment: string | undefined = undefined
|
|
||||||
let currentConfig: unknown | undefined = undefined
|
|
||||||
currentComment = localStorage.getItem('comment') ?? toSnake(config.use[0])
|
|
||||||
// @ts-ignore No index signature with a parameter of type 'string' was found on type 'CommentConfig'. ts(7053)
|
|
||||||
$: if (currentComment) currentConfig = config[currentComment]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if config?.use.length > 0}
|
|
||||||
<div id="post-comment" class="card card-body">
|
|
||||||
{#if config.use.length > 1}
|
|
||||||
<div class="tabs w-full mb-8" class:tabs-boxed={config?.['style'] === 'boxed'}>
|
|
||||||
{#each config.use as name}
|
|
||||||
<span
|
|
||||||
on:click={() => {
|
|
||||||
currentComment = toSnake(name)
|
|
||||||
localStorage.setItem('comment', toSnake(name))
|
|
||||||
}}
|
|
||||||
class="flex-1 tab transition-all"
|
|
||||||
class:tab-bordered={config?.['style'] === 'bordered'}
|
|
||||||
class:tab-lifted={config?.['style'] === 'lifted'}
|
|
||||||
class:tab-active={currentComment === toSnake(name)}>
|
|
||||||
{name}
|
|
||||||
</span>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if currentComment}
|
|
||||||
{#key currentComment}
|
|
||||||
<svelte:component
|
|
||||||
this={comments[`/src/lib/components/comments/${currentComment}.svelte`].default}
|
|
||||||
{post}
|
|
||||||
config={currentConfig} />
|
|
||||||
{/key}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
|
@ -1,46 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { fly } from 'svelte/transition'
|
|
||||||
import { browser } from '$app/env'
|
|
||||||
import Card from '$lib/components/post_card.svelte'
|
|
||||||
import Head from '$lib/components/head.svelte'
|
|
||||||
import Toc from '$lib/components/post_toc.svelte'
|
|
||||||
import Action from '$lib/components/post_action.svelte'
|
|
||||||
import Footer from '$lib/components/footer.svelte'
|
|
||||||
export let post: Urara.Post
|
|
||||||
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { copyCode } from '$lib/utils/copyCode'
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
copyCode()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Head {post} />
|
|
||||||
|
|
||||||
<div class="flex flex-col flex-nowrap justify-center xl:flex-row xl:flex-wrap">
|
|
||||||
<div
|
|
||||||
in:fly={{ x: 25, duration: 300, delay: 500 }}
|
|
||||||
out:fly={{ x: 25, duration: 300 }}
|
|
||||||
class="flex-1 w-full max-w-screen-md order-first ease-out transform mx-auto xl:mr-0">
|
|
||||||
{#if browser}
|
|
||||||
<Action {post} />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
in:fly={{ x: -25, duration: 300, delay: 500 }}
|
|
||||||
out:fly={{ x: -25, duration: 300 }}
|
|
||||||
class="flex-1 w-full max-w-screen-md xl:order-last ease-out transform mx-auto xl:mr-0">
|
|
||||||
{#if browser && post.toc}
|
|
||||||
<div class="h-full hidden xl:block">
|
|
||||||
<Toc toc={post.toc} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="flex-none w-full max-w-screen-md mx-auto xl:mx-0">
|
|
||||||
<Card {post}>
|
|
||||||
<slot />
|
|
||||||
</Card>
|
|
||||||
<Footer sticky={true} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,32 +0,0 @@
|
||||||
<script lang="ts" context="module">
|
|
||||||
import Image from '$lib/components/prose/img.svelte'
|
|
||||||
import table from '$lib/components/prose/table.svelte'
|
|
||||||
export { Image as img, table }
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { typeOfPost } from '$lib/utils/posts'
|
|
||||||
import Container from '$lib/components/post_container.svelte'
|
|
||||||
// auto-generated
|
|
||||||
export let path
|
|
||||||
export let slug
|
|
||||||
export let toc
|
|
||||||
// common
|
|
||||||
export let created
|
|
||||||
export let updated
|
|
||||||
export let published
|
|
||||||
export let summary
|
|
||||||
export let tags
|
|
||||||
export let flags
|
|
||||||
// specify
|
|
||||||
export let title
|
|
||||||
export let image
|
|
||||||
export let in_reply_to
|
|
||||||
// post
|
|
||||||
let fm = { path, slug, toc, created, updated, published, summary, tags, flags, title, image, in_reply_to }
|
|
||||||
let post = { type: typeOfPost(fm), ...fm }
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Container {post}>
|
|
||||||
<slot />
|
|
||||||
</Container>
|
|
|
@ -1,59 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import Image from '$lib/components/prose/img.svelte'
|
|
||||||
export let prev: Urara.Post | undefined = undefined
|
|
||||||
export let next: Urara.Post | undefined = undefined
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<nav class="flex flex-col md:flex-row flex-warp justify-evenly">
|
|
||||||
{#if prev}
|
|
||||||
<div
|
|
||||||
href={prev.path}
|
|
||||||
class:image-full={prev['image']}
|
|
||||||
class:md:rounded-r-box={next && !next['image']}
|
|
||||||
class="flex-1 card group rounded-none before:!rounded-none">
|
|
||||||
{#if prev['image']}
|
|
||||||
<figure class="!block">
|
|
||||||
<Image
|
|
||||||
class="object-center h-full w-full absolute group-hover:scale-105 transition-transform duration-500 ease-in-out"
|
|
||||||
src={prev['image']} />
|
|
||||||
</figure>
|
|
||||||
{/if}
|
|
||||||
<div class="card-body">
|
|
||||||
<span class="i-heroicons-outline-chevron-left opacity-50 group-hover:opacity-100 mr-auto" />
|
|
||||||
<a
|
|
||||||
rel="prev"
|
|
||||||
href={prev.path}
|
|
||||||
class="card-title block text-left mb-0 mr-auto bg-[length:100%_0%] bg-[position:0_88%] underline decoration-3 decoration-transparent group-hover:decoration-primary hover:bg-[length:100%_100%] hover:text-primary-content bg-gradient-to-t from-primary to-primary bg-no-repeat transition-all ease-in-out duration-300">
|
|
||||||
{prev['title'] ?? prev['summary'] ?? prev.path.slice(1)}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if next && !next['image'] && !prev['image']}
|
|
||||||
<div class="flex-0 divider mx-4 md:divider-horizontal md:mx-0 md:my-4" />
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
{#if next}
|
|
||||||
<div
|
|
||||||
href={next.path}
|
|
||||||
class:image-full={next['image']}
|
|
||||||
class:md:rounded-l-box={prev && !prev['image']}
|
|
||||||
class="flex-1 card group rounded-none before:!rounded-none">
|
|
||||||
{#if next['image']}
|
|
||||||
<figure class="!block">
|
|
||||||
<Image
|
|
||||||
class="object-center h-full w-full absolute group-hover:scale-105 transition-transform duration-500 ease-in-out"
|
|
||||||
src={next['image']} />
|
|
||||||
</figure>
|
|
||||||
{/if}
|
|
||||||
<div class="card-body">
|
|
||||||
<a
|
|
||||||
rel="next"
|
|
||||||
href={next.path}
|
|
||||||
class="card-title block text-right mb-0 ml-auto bg-[length:100%_0%] bg-[position:0_88%] underline decoration-3 decoration-transparent group-hover:decoration-primary hover:bg-[length:100%_100%] hover:text-primary-content bg-gradient-to-t from-primary to-primary bg-no-repeat transition-all ease-in-out duration-300">
|
|
||||||
{next['title'] ?? next['summary'] ?? next.path.slice(1)}
|
|
||||||
</a>
|
|
||||||
<span class="i-heroicons-outline-chevron-right opacity-50 group-hover:opacity-100 ml-auto" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</nav>
|
|
|
@ -1,30 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
let className = ''
|
|
||||||
export { className as class }
|
|
||||||
export let in_reply_to: Urara.Post['in_reply_to']
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2 rounded-box outline outline-neutral/10 p-4 {className}">
|
|
||||||
<span class="flex-none font-bold uppercase opacity-30">Reply to: </span>
|
|
||||||
{#if Array.isArray(in_reply_to)}
|
|
||||||
{#each in_reply_to as reply}
|
|
||||||
<a
|
|
||||||
href={reply}
|
|
||||||
rel="noopener external"
|
|
||||||
target="_blank"
|
|
||||||
class="flex-none flex rounded-badge bg-base-200 hover:bg-base-300 transition-all gap-2 px-4 u-in-reply-to">
|
|
||||||
<span class="i-heroicons-outline-reply my-auto !w-4 !h-4" />
|
|
||||||
{reply}
|
|
||||||
</a>
|
|
||||||
{/each}
|
|
||||||
{:else}
|
|
||||||
<a
|
|
||||||
href={in_reply_to}
|
|
||||||
rel="noopener external"
|
|
||||||
target="_blank"
|
|
||||||
class="ml-auto flex-none flex rounded-badge bg-base-200 hover:bg-base-300 transition-all gap-2 px-4 u-in-reply-to">
|
|
||||||
<span class="i-heroicons-outline-reply my-auto !w-4 !h-4" />
|
|
||||||
{in_reply_to}
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { date } from '$lib/config/general'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
export let post: Urara.Post
|
|
||||||
export let preview: boolean = false
|
|
||||||
const stringPublished = new Date(post.published ?? post.created).toLocaleString(date.locales, date.options)
|
|
||||||
const stringUpdated = new Date(post.updated ?? post.published ?? post.created).toLocaleString(date.locales, date.options)
|
|
||||||
const jsonPublished = new Date(post.published ?? post.created).toJSON()
|
|
||||||
const jsonUpdated = new Date(post.updated ?? post.published ?? post.created).toJSON()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class:md:mb-4={!preview && post.type !== 'article'} class="flex font-semibold gap-1.5">
|
|
||||||
<a
|
|
||||||
class:hidden={preview}
|
|
||||||
rel="author"
|
|
||||||
class="opacity-75 hover:opacity-100 hover:text-primary duration-500 ease-in-out p-author h-card"
|
|
||||||
href={site.protocol + site.domain}>
|
|
||||||
{site.author.name}
|
|
||||||
</a>
|
|
||||||
<span class:hidden={preview} class="opacity-50">/</span>
|
|
||||||
<a href={post.path} class="swap hover:swap-active u-url u-uid">
|
|
||||||
<time
|
|
||||||
class="swap-off font-semibold opacity-75 duration-500 ease-in-out mr-auto dt-published"
|
|
||||||
datetime={jsonPublished}
|
|
||||||
itemprop="datePublished">
|
|
||||||
{stringPublished}
|
|
||||||
</time>
|
|
||||||
<time
|
|
||||||
class="swap-on font-semibold text-primary duration-500 ease-in-out mr-auto dt-updated"
|
|
||||||
datetime={jsonUpdated}
|
|
||||||
itemprop="dateModified">
|
|
||||||
{stringUpdated}
|
|
||||||
</time>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
|
@ -1,68 +0,0 @@
|
||||||
<script lang="ts" context="module">
|
|
||||||
export const prerender = true
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { onMount, onDestroy } from 'svelte'
|
|
||||||
import Tree from '$lib/components/post_toc_tree.svelte'
|
|
||||||
export let toc: Urara.Post.Toc[]
|
|
||||||
|
|
||||||
let intersecting: string[] = []
|
|
||||||
let intersectingArticle: boolean = true
|
|
||||||
let bordered: string[] = []
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (window.screen.availWidth >= 1280) {
|
|
||||||
const headingsObserver = new IntersectionObserver(
|
|
||||||
headings =>
|
|
||||||
headings.forEach(heading =>
|
|
||||||
heading.isIntersecting
|
|
||||||
? intersecting.push(heading.target.id)
|
|
||||||
: (intersecting = intersecting.filter(h => h !== heading.target.id))
|
|
||||||
),
|
|
||||||
{ rootMargin: '-64px 0px 0px 0px' }
|
|
||||||
)
|
|
||||||
const articleObserver = new IntersectionObserver(article => (intersectingArticle = article[0].isIntersecting))
|
|
||||||
Array.from(document.querySelectorAll('main h2, main h3, main h4, main h5, main h6')).forEach(element =>
|
|
||||||
headingsObserver.observe(element)
|
|
||||||
)
|
|
||||||
articleObserver.observe(document.getElementsByTagName('main')[0])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
// @ts-ignore: Cannot find name 'headingsObserver'
|
|
||||||
if (typeof headingsObserver !== 'undefined') headingsObserver.disconnect()
|
|
||||||
// @ts-ignore: Cannot find name 'articleObserver'
|
|
||||||
if (typeof headingsObserver !== 'undefined') articleObserver.disconnect()
|
|
||||||
})
|
|
||||||
|
|
||||||
$: if (intersecting.length > 0) bordered = intersecting
|
|
||||||
$: if (intersectingArticle === false) bordered = []
|
|
||||||
$: if (bordered)
|
|
||||||
toc.forEach(heading =>
|
|
||||||
bordered.includes(heading.slug!)
|
|
||||||
? document.getElementById(`toc-link-${heading.slug}`)?.classList.add('!border-accent')
|
|
||||||
: document.getElementById(`toc-link-${heading.slug}`)?.classList.remove('!border-accent')
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<aside class="sticky top-16 py-8">
|
|
||||||
<nav
|
|
||||||
id="post-toc"
|
|
||||||
aria-label="TableOfContent"
|
|
||||||
dir="rtl"
|
|
||||||
class="max-h-[calc(100vh-12rem)] overflow-y-hidden hover:overflow-y-auto">
|
|
||||||
<Tree
|
|
||||||
toc={toc.reduce(
|
|
||||||
(acc, heading) => {
|
|
||||||
let parent = acc
|
|
||||||
// @ts-ignore Type 'Toc | undefined' is not assignable to type 'Toc.' ts(2322)
|
|
||||||
while (parent.depth + 1 < heading.depth) parent = parent.children.at(-1)
|
|
||||||
parent.children = [...(parent.children ?? []), { ...heading, children: [] }]
|
|
||||||
return acc
|
|
||||||
},
|
|
||||||
{ depth: toc[0].depth - 1, children: [] }
|
|
||||||
)} />
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
|
@ -1,33 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let toc: Urara.Post.Toc
|
|
||||||
const { title, slug, depth, children } = toc
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if title}
|
|
||||||
<span
|
|
||||||
dir="ltr"
|
|
||||||
on:click={() =>
|
|
||||||
// @ts-ignore Object is possibly 'null'. ts(2531)
|
|
||||||
document.getElementById(slug).scrollIntoView({ behavior: 'smooth' })}
|
|
||||||
id={`toc-link-${slug}`}
|
|
||||||
class="cursor-pointer border-l-4 border-transparent transition-all hover:border-primary hover:bg-base-content hover:bg-opacity-10 active:bg-primary active:text-primary-content active:font-bold pr-4 {depth <=
|
|
||||||
2
|
|
||||||
? 'py-3'
|
|
||||||
: 'py-2'}"
|
|
||||||
class:pl-4={depth <= 2}
|
|
||||||
class:pl-8={depth === 3}
|
|
||||||
class:pl-12={depth === 4}
|
|
||||||
class:pl-16={depth === 5}
|
|
||||||
class:pl-20={depth === 6}>
|
|
||||||
{title}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
{#if children}
|
|
||||||
<ul dir="ltr" id={`toc-list-${slug ?? 'root'}`}>
|
|
||||||
{#each children as child}
|
|
||||||
<li id={`toc-item-${child.slug}`} class="flex flex-col">
|
|
||||||
<svelte:self toc={child} />
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
|
|
@ -1,10 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
let className: string | undefined = undefined
|
|
||||||
export { className as class }
|
|
||||||
export let src: string
|
|
||||||
export let alt: string = src
|
|
||||||
export let loading: 'eager' | 'lazy' = 'lazy'
|
|
||||||
export let decoding: 'async' | 'sync' | 'auto' = 'async'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<img {src} {alt} class={className ?? 'rounded-lg my-2'} {loading} {decoding} />
|
|
|
@ -1,5 +0,0 @@
|
||||||
<div class="overflow-x-auto mb-4">
|
|
||||||
<table class="table w-full">
|
|
||||||
<slot />
|
|
||||||
</table>
|
|
||||||
</div>
|
|
|
@ -1,90 +0,0 @@
|
||||||
export interface FriendOld {
|
|
||||||
// hCard+XFN
|
|
||||||
id: string // HTML id
|
|
||||||
rel?: string // XFN, contact / acquaintance / friend
|
|
||||||
link?: string // URL
|
|
||||||
html?: string // HTML
|
|
||||||
title?: string // 标题
|
|
||||||
descr?: string // 描述
|
|
||||||
avatar?: string // 头像
|
|
||||||
name?: string // backwards compatibility
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Friend = {
|
|
||||||
id: string // HTML id
|
|
||||||
rel?: string // XHTML Friends Network
|
|
||||||
link?: string // URL
|
|
||||||
html?: string // Custom HTML
|
|
||||||
|
|
||||||
title?: string // 标题
|
|
||||||
name?: string // 人名
|
|
||||||
avatar?: string // 头像
|
|
||||||
descr?: string // 描述
|
|
||||||
class?: {
|
|
||||||
avatar?: string // 头像类名
|
|
||||||
img?: string // 图片类名
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const friends: Friend[] = [
|
|
||||||
{
|
|
||||||
id: 'mantyke',
|
|
||||||
rel: 'friend',
|
|
||||||
name: '塔塔',
|
|
||||||
title: '小球飞鱼',
|
|
||||||
link: 'https://mantyke.icu/',
|
|
||||||
descr: '我们会一起遇见鲸鱼吗?',
|
|
||||||
avatar: 'https://mantyke.icu/images/logo.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'middleofnowhere',
|
|
||||||
rel: 'friend',
|
|
||||||
name: '陆博学',
|
|
||||||
title: 'Middle of No where',
|
|
||||||
link: 'https://notes.midofnowhere.link/',
|
|
||||||
descr: `Welcome to the middle of nowhere. That's right, absolute nowhere.`,
|
|
||||||
avatar: 'https://github.com/yue2/picbed/blob/main/cutepic.png?raw=true'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'chestnut',
|
|
||||||
rel: 'friend',
|
|
||||||
name: '栗',
|
|
||||||
title: '野生栗子🌰',
|
|
||||||
link: 'https://blog.chestnut.monster/'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'nekolas',
|
|
||||||
rel: 'friend',
|
|
||||||
name: 'Nic Tian',
|
|
||||||
title: `Nekolas's blog`,
|
|
||||||
link: 'https://blog.nekolas.cafe/',
|
|
||||||
descr: '欢迎加入锈栓抵抗军',
|
|
||||||
avatar: 'https://blogpic-1308403500.cos.ap-shanghai.myqcloud.com/avatar/nic-avatar-tomato.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'summerblue',
|
|
||||||
rel: 'friend',
|
|
||||||
name: '夏诤',
|
|
||||||
title: 'SummberBlue',
|
|
||||||
link: 'https://summerblue.space/',
|
|
||||||
descr: '早睡早起身体好'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'loikin',
|
|
||||||
rel: 'friend',
|
|
||||||
name: 'Loikin',
|
|
||||||
title: '此生未命名',
|
|
||||||
link: 'https://blog.loikein.one/',
|
|
||||||
descr: '用爱和理性对抗荒谬',
|
|
||||||
avatar: '/assets/loikin.png'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sharktale',
|
|
||||||
rel: 'friend',
|
|
||||||
name: '鲨',
|
|
||||||
title: '一只脆脆鲨',
|
|
||||||
link: 'http://blog.sharktale.xyz/',
|
|
||||||
descr: '遇见一只脆脆鲨',
|
|
||||||
avatar: 'https://s2.loli.net/2022/03/30/xwOzn9G8TIqFPvR.jpg'
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,219 +0,0 @@
|
||||||
import type { ThemeConfig, HeadConfig, HeaderConfig, FooterConfig, DateConfig, FeedConfig } from '$lib/types/general'
|
|
||||||
|
|
||||||
export const theme: ThemeConfig = [
|
|
||||||
{
|
|
||||||
name: 'lemonade',
|
|
||||||
text: 'Light'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'night',
|
|
||||||
text: 'Dark'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cupcake',
|
|
||||||
text: 'Cupcake'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bumblebee',
|
|
||||||
text: 'Bumblebee'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'emerald',
|
|
||||||
text: 'Emerald'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'corporate',
|
|
||||||
text: 'Corporate'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'valentine',
|
|
||||||
text: 'Valentine'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'synthwave',
|
|
||||||
text: 'Synthwave'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'retro',
|
|
||||||
text: 'Retro'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cyberpunk',
|
|
||||||
text: 'Cyberpunk'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'halloween',
|
|
||||||
text: 'Halloween'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'garden',
|
|
||||||
text: 'Garden'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'forest',
|
|
||||||
text: 'Forest'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'aqua',
|
|
||||||
text: 'Aqua'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'lofi',
|
|
||||||
text: 'Lo-Fi'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'pastel',
|
|
||||||
text: 'Pastel'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'fantasy',
|
|
||||||
text: 'Fantasy'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'wirefream',
|
|
||||||
text: 'Wireframe'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'black',
|
|
||||||
text: 'Black'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'luxury',
|
|
||||||
text: 'Luxury'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dracula',
|
|
||||||
text: 'Dracula'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cmyk',
|
|
||||||
text: 'CMYK'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'autumn',
|
|
||||||
text: 'Autumn'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'business',
|
|
||||||
text: 'Business'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'acid',
|
|
||||||
text: 'Acid'
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// name: 'lemonade',
|
|
||||||
// text: 'Lemonade'
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'night',
|
|
||||||
// text: '🌃 Night'
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
name: 'coffee',
|
|
||||||
text: 'Coffee'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'winter',
|
|
||||||
text: 'Winter'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export const head: HeadConfig = {
|
|
||||||
custom: ({ dev }) =>
|
|
||||||
dev
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
// IndieAuth
|
|
||||||
'<link rel="authorization_endpoint" href="https://indieauth.com/auth">',
|
|
||||||
'<link rel="token_endpoint" href="https://tokens.indieauth.com/token">',
|
|
||||||
'<link rel="me" href="https://github.com/sevichecc" />',
|
|
||||||
// Umami Analytics
|
|
||||||
'<script data-cfasync="false" defer data-do-not-track="true" data-website-id="2403ea30-74ff-4ffa-8264-556b9f3b2897" src="https://hexoverc.vercel.app/umami.js"></script>',
|
|
||||||
// splitbee
|
|
||||||
'<script async data-cfasync="false" src="https://cdn.splitbee.io/sb.js"></script>',
|
|
||||||
// Block Baiduspider
|
|
||||||
'<meta name="baiduspider" content="noindex,noarchive">',
|
|
||||||
// Microsub
|
|
||||||
'<link rel="microsub" href="https://aperture.p3k.io/microsub/761">'
|
|
||||||
],
|
|
||||||
me: ['https://kongwoo.icu/@seviche']
|
|
||||||
}
|
|
||||||
|
|
||||||
export const header: HeaderConfig = {
|
|
||||||
search: {
|
|
||||||
provider: 'duckduckgo'
|
|
||||||
},
|
|
||||||
nav: [
|
|
||||||
{
|
|
||||||
text: 'Projects',
|
|
||||||
link: '/projects'
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// text: 'Notes',
|
|
||||||
// link: '/notes'
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
text: 'Friends',
|
|
||||||
link: '/friends'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'About',
|
|
||||||
link: '/about'
|
|
||||||
}
|
|
||||||
// ,
|
|
||||||
// {
|
|
||||||
// text: 'Notes',
|
|
||||||
// link: '/notes'
|
|
||||||
// }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const footer: FooterConfig = {
|
|
||||||
nav: [
|
|
||||||
{
|
|
||||||
text: 'Feed',
|
|
||||||
link: '/atom.xml'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Pravicy',
|
|
||||||
link: '/privacy'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
html: '<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a>'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const date: DateConfig = {
|
|
||||||
// toPublishedString: {
|
|
||||||
// locales: 'en-US',
|
|
||||||
// options: {
|
|
||||||
// year: 'numeric',
|
|
||||||
// weekday: 'short',
|
|
||||||
// month: 'short',
|
|
||||||
// day: 'numeric',
|
|
||||||
// timeZone: 'Asia/Shanghai'
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// toUpdatedString: {
|
|
||||||
// locales: 'en-US',
|
|
||||||
// options: {
|
|
||||||
// year: 'numeric',
|
|
||||||
// weekday: 'short',
|
|
||||||
// month: 'short',
|
|
||||||
// day: 'numeric',
|
|
||||||
// timeZone: 'Asia/Shanghai'
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
locales: 'en-US',
|
|
||||||
options: {
|
|
||||||
year: 'numeric',
|
|
||||||
weekday: 'short',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
timeZone: 'Asia/Shanghai'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const feed: FeedConfig = {
|
|
||||||
hubs: ['https://pubsubhubbub.appspot.com', 'https://bridgy-fed.superfeedr.com']
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
import type { Icon } from '$lib/types/icon'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
|
|
||||||
export const favicon: Icon = {
|
|
||||||
src: site.protocol + site.domain + '/favicon.png',
|
|
||||||
sizes: '48x48',
|
|
||||||
type: 'image/png'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const any: { [key: number]: Icon } = {
|
|
||||||
180: {
|
|
||||||
src: site.protocol + site.domain + '/assets/any@180.png',
|
|
||||||
sizes: '180x180',
|
|
||||||
type: 'image/png'
|
|
||||||
},
|
|
||||||
192: {
|
|
||||||
src: site.protocol + site.domain + '/assets/any@192.png',
|
|
||||||
sizes: '192x192',
|
|
||||||
type: 'image/png'
|
|
||||||
},
|
|
||||||
512: {
|
|
||||||
src: site.protocol + site.domain + '/assets/any@512.png',
|
|
||||||
sizes: '512x512',
|
|
||||||
type: 'image/png'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const maskable: { [key: number]: Icon } = {
|
|
||||||
192: {
|
|
||||||
src: site.protocol + site.domain + '/assets/maskable@192.png',
|
|
||||||
sizes: '192x192',
|
|
||||||
type: 'image/png'
|
|
||||||
},
|
|
||||||
512: {
|
|
||||||
src: site.protocol + site.domain + '/assets/maskable@512.png',
|
|
||||||
sizes: '512x512',
|
|
||||||
type: 'image/png'
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
import type { PostConfig } from '$lib/types/post'
|
|
||||||
|
|
||||||
export const post: PostConfig = {
|
|
||||||
bridgy: {
|
|
||||||
post: ['mastodon']
|
|
||||||
},
|
|
||||||
comment: {
|
|
||||||
use: ['Webmention', 'Giscus'],
|
|
||||||
style: 'boxed',
|
|
||||||
webmention: {
|
|
||||||
username: 'seviche.cc',
|
|
||||||
sortBy: 'created',
|
|
||||||
sortDir: 'down',
|
|
||||||
form: true,
|
|
||||||
commentParade: true
|
|
||||||
},
|
|
||||||
giscus: {
|
|
||||||
// src: 'https://giscus.kwaa.dev/client.js',
|
|
||||||
repo: 'Sevichecc/urara-giscus',
|
|
||||||
repoID: 'R_kgDOHSra4Q',
|
|
||||||
category: 'General',
|
|
||||||
categoryID: 'DIC_kwDOHSra4c4CO9ua',
|
|
||||||
theme: 'light',
|
|
||||||
lang: 'en'
|
|
||||||
}
|
|
||||||
// waline: {
|
|
||||||
// serverURL: 'https://waline-seviche.vercel.app/',
|
|
||||||
// lang: 'en',
|
|
||||||
// emoji: [
|
|
||||||
// 'https://cdn.jsdelivr.net/gh/norevi/waline-blobcatemojis@1.0/blobs',
|
|
||||||
// 'https://cdn.jsdelivr.net/gh/norevi/blob-emoji-for-waline@2.0/blobs-gif',
|
|
||||||
// 'ttps://cdn.jsdelivr.net/gh/norevi/blob-emoji-for-waline@2.0/blobs-png'
|
|
||||||
// ],
|
|
||||||
// requiredMeta: ['nick', 'mail']
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
export type Project = {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
tags?: string[]
|
|
||||||
feature?: string
|
|
||||||
description?: string
|
|
||||||
img: string
|
|
||||||
link?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const projects: Project[] = [
|
|
||||||
// {
|
|
||||||
// id: 'haibian',
|
|
||||||
// name: '海边小站',
|
|
||||||
// tags: ['Vue 3', 'Vue Router', 'Vuex', 'Axios', 'JWT', 'easyMDE', 'BootStrap'],
|
|
||||||
// feature: 'Vue3 + TypeScript',
|
|
||||||
// description:
|
|
||||||
// '海边小站是一个面向成人英语学习的博文平台,为用户提供丰富的英文文摘、英语词汇教程、英语口语教程。网站有用户注册登录、博文发布编辑、用户资料更新等功能。',
|
|
||||||
// img: 'https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/d4d2489936e4f647c25df6982c6ef924.png',
|
|
||||||
// link: 'https://haibian.seviche.cc'
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
id: 'fokify',
|
|
||||||
name: 'Fokify ',
|
|
||||||
tags: ['MVC', 'Vanilla JS', 'ES6', 'Parcel', 'SCSS', 'HTML5'],
|
|
||||||
feature: 'JavaScript',
|
|
||||||
description:
|
|
||||||
'一个基于Web端的菜谱搜索平台,有菜谱搜索、上传、收藏等功能,并使用 LocalStorage 来存储用户数据,让用户在退出页面后仍能浏览所收藏的菜谱。',
|
|
||||||
img: 'https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/06/c3f41e397af1e480f57dd75e82334819.png',
|
|
||||||
link: 'https://forkify.seviche.cc'
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// id: 'Coachlist',
|
|
||||||
// name: 'Find a Coach',
|
|
||||||
// tags: ['Vue 3', 'Vite', 'Vuex', 'Vue Router', 'CSS Animation'],
|
|
||||||
// feature: 'Vue3',
|
|
||||||
// description:
|
|
||||||
// '一个基于Vue3组合式API的师生对接的在线平台,有注册登录、筛选老师、联系老师、注册成为老师等功能。使用Vue3的transition组件和CSS动画为页面提供流畅的过渡效果',
|
|
||||||
// img: '/assets/coach.png',
|
|
||||||
// link: 'https://coachlist.seviche.cc'
|
|
||||||
// }
|
|
||||||
// ,
|
|
||||||
{
|
|
||||||
id: 'bankist',
|
|
||||||
name: 'Bankist',
|
|
||||||
tags: ['Lazy-loading'],
|
|
||||||
feature: 'JavaScript',
|
|
||||||
description: '一个模拟的银行官网页面,用原生JS实现了懒加载、平滑滚动,以及幻灯片等组件',
|
|
||||||
img: 'https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/07/b5e1ff87c3b4ba4cf2f00d4124154472.png',
|
|
||||||
link: 'https://bankist.seviche.cc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'omnifood',
|
|
||||||
name: 'Omnifood',
|
|
||||||
tags: ['CSS5'],
|
|
||||||
feature: 'JavaScript',
|
|
||||||
description: '一个食品订阅APP官网,纯HTML + CSS + JavaScript实现',
|
|
||||||
img: 'https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/07/89424d0b448d105775c1d60346c57c59.png',
|
|
||||||
link: 'https://omnifood.seviche.cc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'piggame',
|
|
||||||
name: 'Pig Game',
|
|
||||||
tags: ['Game'],
|
|
||||||
feature: 'JavaScript',
|
|
||||||
description: '一个投骰子的游戏,先累计到20的人输。',
|
|
||||||
img: 'https://usc1.contabostorage.com/cc0b816231a841b1b0232d5ef0c6deb1:image/2022/07/154ae3bc957a478679f1d9b7e0e0dce1.png',
|
|
||||||
link: 'https://pig-game-101.netlify.app/'
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,49 +0,0 @@
|
||||||
import type { SiteConfig } from '$lib/types/site'
|
|
||||||
|
|
||||||
export const site: SiteConfig = {
|
|
||||||
protocol: 'https://',
|
|
||||||
domain: 'seviche.cc',
|
|
||||||
title: 'Seviche.cc',
|
|
||||||
subtitle: 'Tech / Code / Random Life',
|
|
||||||
lang: 'zh',
|
|
||||||
description: 'Tech / Code / Random Life',
|
|
||||||
author: {
|
|
||||||
name: '酸橘汁腌鱼',
|
|
||||||
avatar: '/assets/avatar.jpg',
|
|
||||||
status: '🖤',
|
|
||||||
bio: ' Code / Tech <br> Living a Random Life ',
|
|
||||||
metadata: [
|
|
||||||
{
|
|
||||||
text: '',
|
|
||||||
icon: 'i-mdi-github',
|
|
||||||
link: 'https://github.com/sevichecc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '',
|
|
||||||
icon: 'i-simple-icons-matrix',
|
|
||||||
link: 'https://matrix.to/#/@seviche:kongwoo.icu'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '',
|
|
||||||
icon: 'i-heroicons-solid-key',
|
|
||||||
link: 'https://keys.openpgp.org/vks/v1/by-fingerprint/76DF9F9CC0C3619AA12CB914AFF18B986818D8AD',
|
|
||||||
rel: 'pgpkey'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '',
|
|
||||||
icon: 'i-ic-twotone-rss-feed',
|
|
||||||
link: '/atom.xml',
|
|
||||||
rel: 'rss'
|
|
||||||
}
|
|
||||||
// ,
|
|
||||||
// {
|
|
||||||
// text: 'Bookmark',
|
|
||||||
// icon: 'i-ic-round-bookmark-border',
|
|
||||||
// link: 'https://airtable.com/shrpftxf6JgRomP2X',
|
|
||||||
// rel: 'bookmark'
|
|
||||||
// }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
keywords: ['Tech', 'Code', 'Random Life'],
|
|
||||||
themeColor: '#3D4451'
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
import type { Writable } from 'svelte/store'
|
|
||||||
import { writable } from 'svelte/store'
|
|
||||||
export const posts: Writable<Urara.Post[]> = writable([])
|
|
||||||
export const tags: Writable<string[]> = writable([])
|
|
|
@ -1,2 +0,0 @@
|
||||||
import { writable } from 'svelte/store'
|
|
||||||
export const title = writable({})
|
|
|
@ -1,42 +0,0 @@
|
||||||
export type ThemeConfig = {
|
|
||||||
text?: string
|
|
||||||
name: string
|
|
||||||
}[]
|
|
||||||
|
|
||||||
export type HeadConfig = {
|
|
||||||
custom?: (params: { dev: boolean; post?: Urara.Post; page?: Urara.Page }) => string[]
|
|
||||||
me?: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HeaderConfig = {
|
|
||||||
nav?: {
|
|
||||||
text: string
|
|
||||||
link?: string
|
|
||||||
children?: {
|
|
||||||
text: string
|
|
||||||
link: string
|
|
||||||
}[]
|
|
||||||
}[]
|
|
||||||
search?: {
|
|
||||||
provider: 'google' | 'duckduckgo'
|
|
||||||
colors?: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FooterConfig = {
|
|
||||||
nav?: {
|
|
||||||
text: string
|
|
||||||
link: string
|
|
||||||
}[]
|
|
||||||
html?: string
|
|
||||||
since?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DateConfig = { locales: string; options: Intl.DateTimeFormatOptions }
|
|
||||||
|
|
||||||
export type FeedConfig = {
|
|
||||||
/** feed entry limit. */
|
|
||||||
limit?: number
|
|
||||||
/** WebSub (formerly PubSubHubbub) hubs. one per line */
|
|
||||||
hubs?: string[]
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
export type Icon = {
|
|
||||||
src: string
|
|
||||||
sizes?: string
|
|
||||||
type?: string
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
import type { WalineEmojiInfo } from '@waline/client'
|
|
||||||
type WalineImageUploader = (image: File) => Promise<string>
|
|
||||||
|
|
||||||
type WalineHighlighter = (code: string, lang: string) => string
|
|
||||||
|
|
||||||
type WalineTexRenderer = (blockMode: boolean, tex: string) => string
|
|
||||||
|
|
||||||
export type PostConfig = {
|
|
||||||
bridgy?: {
|
|
||||||
[kind: string]: ('fed' | 'mastodon' | 'flickr' | 'github' | 'twitter')[]
|
|
||||||
}
|
|
||||||
comment?: CommentConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CommentConfig = {
|
|
||||||
use: string[]
|
|
||||||
/** tab style for multiple comments, preview at https://daisyui.com/components/tab */
|
|
||||||
style?: 'none' | 'bordered' | 'lifted' | 'boxed'
|
|
||||||
/** Webmention.io config, more at https://github.com/aaronpk/webmention.io#api */
|
|
||||||
webmention?: WebmentionConfig
|
|
||||||
/** Giscus config, more at https://giscus.app */
|
|
||||||
giscus?: GiscusConfig
|
|
||||||
/** Utterances config, more at https://utteranc.es */
|
|
||||||
utterances?: UtterancesConfig
|
|
||||||
/** Waline config, more at https://waline.js.org/en/reference/component.html#texrenderer */
|
|
||||||
waline?: WalineConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WebmentionConfig = {
|
|
||||||
/** username you got when you signed up webmention.io. */
|
|
||||||
username: string
|
|
||||||
/** number of results per page. */
|
|
||||||
perPage?: number
|
|
||||||
/** sorting mechanism to return the list of mentions. */
|
|
||||||
sortBy?: 'created' | 'updated' | 'published' | 'rsvp'
|
|
||||||
/** control the ordering. */
|
|
||||||
sortDir?: 'down' | 'up'
|
|
||||||
/** find links of a specific type. */
|
|
||||||
property?: ('in-reply-to' | 'like-of' | 'repost-of' | 'bookmark-of' | 'mention-of' | 'rsvp')[]
|
|
||||||
/** URL array of a webmention you'd like to block. */
|
|
||||||
blockList?: string[]
|
|
||||||
/** show the form for sending the webmention. */
|
|
||||||
form?: boolean
|
|
||||||
/** show `or comment anonymously` label text. */
|
|
||||||
commentParade?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GiscusConfig = {
|
|
||||||
/** self-hosted giscus url. */
|
|
||||||
src?: string
|
|
||||||
/** a public GitHub repository. this repo is where the discussions will be linked to. */
|
|
||||||
repo: string
|
|
||||||
/** a public GitHub repository. this repo is where the discussions will be linked to. */
|
|
||||||
repoID: string
|
|
||||||
/** fill in here if only search for discussions in this category. */
|
|
||||||
category?: string
|
|
||||||
/** choose the discussion category where new discussions will be created. */
|
|
||||||
categoryID: string
|
|
||||||
/** the reactions for the discussion's main post will be shown before the comments. */
|
|
||||||
reactionsEnabled?: boolean
|
|
||||||
/** discussion metadata will be sent periodically to the parent window (the embedding page). */
|
|
||||||
emitMetadata?: boolean
|
|
||||||
/** the comment input box will be placed above the comments, so that users can leave a comment without scrolling to the bottom of the discussion. */
|
|
||||||
inputPosition?: 'top' | 'bottom'
|
|
||||||
/** choose a theme that matches your website. */
|
|
||||||
theme?: string
|
|
||||||
/** choose the language giscus will be displayed in. */
|
|
||||||
lang?: string
|
|
||||||
/** loading of the comments will be deferred until the user scrolls near the comments container. */
|
|
||||||
loading?: 'lazy'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UtterancesConfig = {
|
|
||||||
/** self-hosted utterances url. */
|
|
||||||
src?: string
|
|
||||||
/** choose the repository utterances will connect to. */
|
|
||||||
repo: string
|
|
||||||
/** choose the label that will be assigned to issues created by utterances. */
|
|
||||||
label?: string
|
|
||||||
/** choose an utterances theme that matches your blog. */
|
|
||||||
theme?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DisqusConfig = {
|
|
||||||
shortname: string
|
|
||||||
lang?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ref:https://waline.js.org/reference/component.html
|
|
||||||
export type WalineConfig = {
|
|
||||||
/** Waline server address url */
|
|
||||||
serverURL: string
|
|
||||||
/** Article path id*/
|
|
||||||
path?: string
|
|
||||||
/** Display language. */
|
|
||||||
lang?: string
|
|
||||||
/** Emoji settings, for details see https://waline.js.org/en/guide/client/emoji.html */
|
|
||||||
emoji?: (string | WalineEmojiInfo)[] | false
|
|
||||||
/** Darkmode support */
|
|
||||||
dark?: string | boolean
|
|
||||||
/** Reviewer attributes. Optional values: 'nick', 'mail', 'link' */
|
|
||||||
meta?: string[]
|
|
||||||
/** Set required fields*/
|
|
||||||
requiredMeta?: string[]
|
|
||||||
/** login mode status */
|
|
||||||
login?: string
|
|
||||||
/** Comment word s limit. */
|
|
||||||
wordLimit?: number | [number, number]
|
|
||||||
/**number of comments per page. */
|
|
||||||
pageSize?: number
|
|
||||||
/** Custom image upload method. */
|
|
||||||
imageUploader?: WalineImageUploader | false
|
|
||||||
/** Code highlighting, use hanabi by default */
|
|
||||||
highlighter?: WalineHighlighter | false
|
|
||||||
/** Customize \TeX rendering */
|
|
||||||
texRender?: WalineTexRenderer | false
|
|
||||||
/** Whether show copyright and version in footer. */
|
|
||||||
copyright?: boolean
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
import type { FFFAuthor } from 'fff-flavored-frontmatter'
|
|
||||||
|
|
||||||
export type SiteConfig = {
|
|
||||||
/** @deprecated - use `description` instead */
|
|
||||||
descr?: string
|
|
||||||
/** site protocol. for example: `https://` */
|
|
||||||
protocol: string
|
|
||||||
/** site domain. for example: `example.com` */
|
|
||||||
domain: string
|
|
||||||
/** site title. */
|
|
||||||
title: string
|
|
||||||
/** site subtitle. */
|
|
||||||
subtitle?: string
|
|
||||||
/** site lang. `<html lang={site.lang}>` */
|
|
||||||
lang?: string
|
|
||||||
/** site description. `<meta name="description" content={site.description}>` */
|
|
||||||
description?: string
|
|
||||||
/** site keywords. `<meta name="keywords" content={site.keywords}>` */
|
|
||||||
keywords?: string[]
|
|
||||||
author: Omit<FFFAuthor, 'url'> & {
|
|
||||||
status?: string
|
|
||||||
bio?: string
|
|
||||||
metadata?: (
|
|
||||||
| {
|
|
||||||
text: string
|
|
||||||
icon?: string
|
|
||||||
link?: string
|
|
||||||
rel?: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
text?: string
|
|
||||||
icon: string
|
|
||||||
link?: string
|
|
||||||
rel?: string
|
|
||||||
}
|
|
||||||
)[]
|
|
||||||
}
|
|
||||||
/** for web app manifest only.
|
|
||||||
* ```
|
|
||||||
* "background_color": {site.themeColor},
|
|
||||||
* "theme_color": {site.themeColor}
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
themeColor?: string
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
export const toSnake = (str: string) =>
|
|
||||||
str.charAt(0).toLowerCase() +
|
|
||||||
str
|
|
||||||
.slice(1)
|
|
||||||
.replace(/([A-Z]+)/g, '_$1')
|
|
||||||
.toLowerCase()
|
|
||||||
|
|
||||||
export const toCamel = (str: string) => str.toLowerCase().replace(/([-_][a-z])/g, g => g.slice(-1).toUpperCase())
|
|
|
@ -1,11 +0,0 @@
|
||||||
export const hslToHex = (
|
|
||||||
h: number,
|
|
||||||
s: number,
|
|
||||||
l: number,
|
|
||||||
ll = (l /= 100),
|
|
||||||
a = (s * Math.min(ll, 1 - ll)) / 100,
|
|
||||||
f = (n: number, k = (n + h / 30) % 12) =>
|
|
||||||
Math.round(255 * (ll - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)))
|
|
||||||
.toString(16)
|
|
||||||
.padStart(2, '0')
|
|
||||||
) => `#${f(0)}${f(8)}${f(4)}`
|
|
|
@ -1,55 +0,0 @@
|
||||||
/** copy code block to clipboard
|
|
||||||
* @todo better transition animate
|
|
||||||
* @todo remove dummy code
|
|
||||||
* @todo typecheck
|
|
||||||
*/
|
|
||||||
export const copyCode = () => {
|
|
||||||
const codeBlocks = document.querySelectorAll('pre')
|
|
||||||
const copyText = 'Copy'
|
|
||||||
const copiedText = 'Copied!'
|
|
||||||
|
|
||||||
//copy funciton
|
|
||||||
const copy = async (el: HTMLElement, btn: HTMLElement) => {
|
|
||||||
//select code
|
|
||||||
const range = document.createRange()
|
|
||||||
const end = el.childNodes.length
|
|
||||||
range.setStart(el, 2)
|
|
||||||
range.setEnd(el, end)
|
|
||||||
const selection = window.getSelection()
|
|
||||||
if (!selection) return
|
|
||||||
selection.removeAllRanges()
|
|
||||||
selection.addRange(range)
|
|
||||||
|
|
||||||
// copy to clipboard
|
|
||||||
document.execCommand('copy', false)
|
|
||||||
const clip = async () => navigator.clipboard.writeText(selection.toString())
|
|
||||||
|
|
||||||
if (!clip) return
|
|
||||||
btn.textContent = copiedText
|
|
||||||
setTimeout(() => {
|
|
||||||
btn.textContent = copyText
|
|
||||||
}, 1000)
|
|
||||||
selection.removeRange(range)
|
|
||||||
}
|
|
||||||
|
|
||||||
codeBlocks.forEach((block: HTMLElement) => {
|
|
||||||
// add copy button
|
|
||||||
const copyBtn: HTMLElement = document.createElement('button')
|
|
||||||
copyBtn.textContent = copyText
|
|
||||||
copyBtn.classList.add('btn', 'btn-secondary', 'btn-xs', 'absolute', 'right-2', 'top-3', 'hidden')
|
|
||||||
block.prepend(copyBtn)
|
|
||||||
|
|
||||||
block.addEventListener('mouseenter', () => {
|
|
||||||
copyBtn.classList.remove('hidden')
|
|
||||||
})
|
|
||||||
|
|
||||||
block.addEventListener('mouseleave', () => {
|
|
||||||
copyBtn.classList.add('hidden')
|
|
||||||
})
|
|
||||||
|
|
||||||
copyBtn.addEventListener('click', e => {
|
|
||||||
e.preventDefault()
|
|
||||||
copy(block, copyBtn)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
import type { FFFFlavoredFrontmatter } from 'fff-flavored-frontmatter'
|
|
||||||
|
|
||||||
interface GenPostsOptions {
|
|
||||||
/** import.meta.glob<Urara.Post.Module> https://vitejs.dev/guide/features.html#glob-import */
|
|
||||||
modules?: { [path: string]: Urara.Post.Module }
|
|
||||||
/** set to true to output html */
|
|
||||||
postHtml?: boolean
|
|
||||||
/** limit a certain number of posts */
|
|
||||||
postLimit?: number
|
|
||||||
/** hide posts with 'unlisted' flag */
|
|
||||||
filterUnlisted?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type GenPostsFunction = (options?: GenPostsOptions) => Urara.Post[]
|
|
||||||
|
|
||||||
type GenTagsFunction = (posts: Urara.Post[]) => string[]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect Post Type
|
|
||||||
* @param fm - post frontmatter
|
|
||||||
* @returns - post type string
|
|
||||||
*/
|
|
||||||
export const typeOfPost = (
|
|
||||||
fm: FFFFlavoredFrontmatter
|
|
||||||
): 'note' | 'article' | 'reply' | 'photo' | 'like' | 'video' | 'repost' | 'bookmark' | 'audio' =>
|
|
||||||
fm.title
|
|
||||||
? 'article'
|
|
||||||
: fm.image
|
|
||||||
? 'photo'
|
|
||||||
: fm.audio
|
|
||||||
? 'audio'
|
|
||||||
: fm.video
|
|
||||||
? 'video'
|
|
||||||
: fm.bookmark_of
|
|
||||||
? 'bookmark'
|
|
||||||
: fm.like_of
|
|
||||||
? 'like'
|
|
||||||
: fm.repost_of
|
|
||||||
? 'repost'
|
|
||||||
: fm.in_reply_to
|
|
||||||
? 'reply'
|
|
||||||
: 'note'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate Posts List
|
|
||||||
* @param options - An optional configuration object
|
|
||||||
* @returns - posts list
|
|
||||||
*/
|
|
||||||
export const genPosts: GenPostsFunction = ({
|
|
||||||
modules = import.meta.glob<Urara.Post.Module>('/src/routes/**/*.{md,svelte.md}', { eager: true }),
|
|
||||||
postHtml = false,
|
|
||||||
postLimit = undefined,
|
|
||||||
filterUnlisted = false
|
|
||||||
} = {}) =>
|
|
||||||
Object.entries(modules)
|
|
||||||
.map(([, module]) => ({
|
|
||||||
...module.metadata,
|
|
||||||
type: typeOfPost(module.metadata),
|
|
||||||
html:
|
|
||||||
postHtml || typeOfPost(module.metadata) !== 'article'
|
|
||||||
? module.default
|
|
||||||
.render()
|
|
||||||
.html // eslint-disable-next-line no-control-regex
|
|
||||||
.replace(/[\u0000-\u001F]/g, '')
|
|
||||||
.replace(/[\r\n]/g, '')
|
|
||||||
.match(/<main [^>]+>(.*?)<\/main>/gi)?.[0]
|
|
||||||
.replace(/<main [^>]+>(.*?)<\/main>/gi, '$1')
|
|
||||||
// .replace(/( class=")(.*?)(")/gi, '')
|
|
||||||
.replace(/( style=")(.*?)(")/gi, '')
|
|
||||||
.replace(/(<span>)(.*?)(<\/span>)/gi, '$2')
|
|
||||||
.replace(/(<main>)(.*?)(<\/main>)/gi, '$2')
|
|
||||||
: ''
|
|
||||||
}))
|
|
||||||
.filter((post, index) => (!filterUnlisted || !post.flags?.includes('unlisted')) && (!postLimit || index < postLimit))
|
|
||||||
.sort((a, b) => Date.parse(b.published ?? b.created) - Date.parse(a.published ?? a.created))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate Tags List
|
|
||||||
* @param posts - posts list
|
|
||||||
* @returns - tags list
|
|
||||||
*/
|
|
||||||
export const genTags: GenTagsFunction = posts => [
|
|
||||||
...new Set(posts.reduce((acc, posts) => (posts.tags ? [...acc, ...posts.tags] : acc), ['']).slice(1))
|
|
||||||
]
|
|
|
@ -1,78 +0,0 @@
|
||||||
---
|
|
||||||
title: JavaScript · 判断水仙花数
|
|
||||||
summary: 用JavaScript判断水仙花数
|
|
||||||
created: 2022-03-03T15:07:14.533Z
|
|
||||||
categories:
|
|
||||||
- JavaScript
|
|
||||||
tags:
|
|
||||||
- JavaScript
|
|
||||||
slug: Narcissistic-number-in-JavaScript
|
|
||||||
lastmod: 2022-04-07T07:20:02.340Z
|
|
||||||
---
|
|
||||||
|
|
||||||
题目来源: [“如果”可以“重来” | 百度前端技术学园](http://ife.baidu.com/javascript/if&while.html#%E7%BC%96%E7%A0%81%E4%B8%89)
|
|
||||||
|
|
||||||
## 题目
|
|
||||||
|
|
||||||
根据用户输入的数据,判断水仙花数(三位数),水仙花数是指一个 n 位数 (n≥3),它的每个位上的数字的 n 次幂之和等于它本身。
|
|
||||||
|
|
||||||
```html
|
|
||||||
<label>请输需要判断的水仙花数(三位数):</label>
|
|
||||||
<input type="text" />
|
|
||||||
<br />
|
|
||||||
<button>开始判断</button>
|
|
||||||
<script>
|
|
||||||
function numDaffodils(num) {
|
|
||||||
// 判断是否为水仙花数
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
**需求说明**
|
|
||||||
|
|
||||||
- 当点击 `开始判断` 按钮,就执行 `numDaffodils` 函数判断输入的数字是否为水仙花数.
|
|
||||||
- 如果是,就弹出提示框提示是水仙花数,如果不是,就提示不是水仙花数
|
|
||||||
- 例如输入 153,`153=1* 1*1+5*5*5+3*3*3` , 是水仙花数,就提示 153 是水仙花数。
|
|
||||||
- 请加入输入判断,必须输入数字,不能输入其他类型。
|
|
||||||
|
|
||||||
## 解法
|
|
||||||
|
|
||||||
```html
|
|
||||||
<label>请输需要判断的水仙花数(三位数):</label>
|
|
||||||
<input type="text" />
|
|
||||||
<br />
|
|
||||||
<button>开始判断</button>
|
|
||||||
<script>
|
|
||||||
const btn = document.querySelector('button')
|
|
||||||
|
|
||||||
function numDaffodils() {
|
|
||||||
let num = document.querySelector('input').value
|
|
||||||
|
|
||||||
//输入的是字符串
|
|
||||||
// console.log(typeof num); =>string
|
|
||||||
|
|
||||||
// 检查是否是有效数字
|
|
||||||
if (num.startsWith('0') || num.length !== 3 || isNaN(num)) alert('请输入三位有效数字')
|
|
||||||
|
|
||||||
// 拆分为数组
|
|
||||||
let numArr = num.split('')
|
|
||||||
|
|
||||||
//判断是不是水仙花数字!
|
|
||||||
const numCheck = numArr.reduce((acc, value) => acc + Math.pow(value, 3), 0)
|
|
||||||
|
|
||||||
numCheck == num ? alert('是水仙花数 ✅') : alert('不是水仙花数❗️')
|
|
||||||
}
|
|
||||||
|
|
||||||
btn.addEventListener('click', numDaffodils)
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
这里有一个坑:从 `<input type="text">` 获取输入内容[^1],因为 `type=text` ,所以输出的是 `string` 而不是 `number`,不能直接用`typeof ==='number'`判断输入的是不是数字
|
|
||||||
|
|
||||||
## 参考
|
|
||||||
|
|
||||||
- [learn/task2_3 简单水仙花.html · Homeuh/learn · GitHub](https://github.com/Homeuh/learn/blob/6ed2d79cd6abff09f981c0af21080c38b55b6ef2/out/artifacts/Web0_1_Web_exploded/Task_JS/task2_3%E7%AE%80%E5%8D%95%E6%B0%B4%E4%BB%99%E8%8A%B1.html)
|
|
||||||
|
|
||||||
- [IFE/水仙花数.html · Yaomiaomu/IFE · GitHub](https://github.com/Yaomiaomu/IFE/blob/fed038d6c76b2bf62ee83d6539c927c6fa333b91/JAVASCRIPT/%E6%B0%B4%E4%BB%99%E8%8A%B1%E6%95%B0.html)
|
|
||||||
|
|
||||||
[^1]: [HTML text input allow only numeric input](https://stackoverflow.com/questions/469357/html-text-input-allow-only-numeric-input)
|
|
|
@ -1,76 +0,0 @@
|
||||||
---
|
|
||||||
title: JavaScript · 十进制数转二进制
|
|
||||||
summary: 用JavaScript将十进制数转二进制数
|
|
||||||
created: 2022-03-04T14:57:48.683Z
|
|
||||||
draft: ''
|
|
||||||
tags:
|
|
||||||
- JavaScript
|
|
||||||
categories:
|
|
||||||
- JavaScript
|
|
||||||
lastmod: 2022-04-16T12:54:16.167Z
|
|
||||||
type: default
|
|
||||||
# changelogs:
|
|
||||||
# - tag: "20220308"
|
|
||||||
# summary:
|
|
||||||
# - 将`push` 改为`unshift`
|
|
||||||
# - 使用`padding`填充字符串
|
|
||||||
# - 修改`binNumber.length >= binBit`
|
|
||||||
---
|
|
||||||
|
|
||||||
## 题目
|
|
||||||
|
|
||||||
来源:[“如果”可以“重来” | 百度前端技术学园](http://ife.baidu.com/javascript/if&while.html#:~:text=opens%20new%20window)
|
|
||||||
|
|
||||||
验证工具:[在线进制转换 | 进制转换器 — 在线工具](https://www.sojson.com/hexconvert.html)
|
|
||||||
|
|
||||||
### Task1
|
|
||||||
|
|
||||||
实现当点击转化按钮时,将输入的十进制数字转化为二进制,并显示在 `result` 的 `p` 标签内
|
|
||||||
|
|
||||||
### Task2
|
|
||||||
|
|
||||||
- 转化显示后的二进制数为 bin-bit 中输入的数字宽度,例如 `dec-number` 为 5 ,`bin-bit` 为 5 ,则转化后数字为 `00101`
|
|
||||||
- 如果 `bin-bit` 小于转化后的二进制本身位数,则使用原本的位数,如 `dec-number` 为 5 ,`bin-bit` 为 2 ,依然输出 `101` ,但同时在 console 中报个错。
|
|
||||||
|
|
||||||
## 解法
|
|
||||||
|
|
||||||
```html
|
|
||||||
<input id="dec-number" type="number" placeholder="输入一个十进制非负整数" />
|
|
||||||
<input id="bin-bit" type="number" placeholder="输入转化后二进制数字位数" />
|
|
||||||
<button id="trans-btn">转化为二进制</button>
|
|
||||||
<p id="result">运算结果</p>
|
|
||||||
<script>
|
|
||||||
/////// Task 1
|
|
||||||
const btn = document.querySelector('#trans-btn')
|
|
||||||
const result = document.querySelector('#result')
|
|
||||||
|
|
||||||
function dec2bin() {
|
|
||||||
let decNumber = Number(document.querySelector('#dec-number').value)
|
|
||||||
|
|
||||||
// 判断输入必须为一个非负整数
|
|
||||||
if (decNumber < 0 || !Number.isInteger(decNumber)) {
|
|
||||||
alert('请输入一个非负整数!')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 求余
|
|
||||||
let bin = []
|
|
||||||
let remainder
|
|
||||||
while (decNumber !== 0) {
|
|
||||||
remainder = decNumber % 2
|
|
||||||
decNumber = parseInt(decNumber / 2)
|
|
||||||
bin.unshift(remainder)
|
|
||||||
}
|
|
||||||
let binNumber = bin.join('')
|
|
||||||
|
|
||||||
////// Task2
|
|
||||||
let binBit = Number(document.querySelector('#bin-bit').value)
|
|
||||||
if (binNumber.length >= binBit) {
|
|
||||||
binNumber = binNumber.slice(0, binBit + 1)
|
|
||||||
} else {
|
|
||||||
binNumber = binNumber.padStart(binBit, '0')
|
|
||||||
}
|
|
||||||
result.innerHTML = `运算结果:${binNumber}`
|
|
||||||
}
|
|
||||||
btn.addEventListener('click', dec2bin)
|
|
||||||
</script>
|
|
||||||
```
|
|
|
@ -1,65 +0,0 @@
|
||||||
---
|
|
||||||
title: JavaScript · 字符串去重
|
|
||||||
summary: 编码实现字符串去重
|
|
||||||
created: 2022-03-07T13:55:21.090Z
|
|
||||||
tags:
|
|
||||||
- JavaScript
|
|
||||||
categories:
|
|
||||||
- JavaScript
|
|
||||||
lastmod: 2022-04-07T07:20:30.550Z
|
|
||||||
---
|
|
||||||
|
|
||||||
## 题目
|
|
||||||
|
|
||||||
来源:[百度前端学院](http://ife.baidu.com/javascript/string.html#%E5%AD%97%E7%AC%A6%E4%B8%B2)
|
|
||||||
|
|
||||||
```js
|
|
||||||
/*
|
|
||||||
去掉字符串 str 中,连续重复的地方
|
|
||||||
*/
|
|
||||||
function removeRepetition(str) {
|
|
||||||
// do something
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试用例
|
|
||||||
console.log(removeRepetition('aaa')) // ->a
|
|
||||||
console.log(removeRepetition('abbba')) // ->aba
|
|
||||||
console.log(removeRepetition('aabbaabb')) // ->abab
|
|
||||||
console.log(removeRepetition('')) // ->
|
|
||||||
console.log(removeRepetition('abc')) // ->abc
|
|
||||||
```
|
|
||||||
|
|
||||||
## 解法
|
|
||||||
|
|
||||||
```js
|
|
||||||
function removeRepetition(str) {
|
|
||||||
let strArr = [...str]
|
|
||||||
const result = strArr.filter((s, i, arr) => s !== arr[i + 1]).join('')
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(removeRepetition('aaa')) // ->a
|
|
||||||
console.log(removeRepetition('abbba')) // ->aba
|
|
||||||
console.log(removeRepetition('aabbaabb')) // ->abab
|
|
||||||
console.log(removeRepetition('')) // ->
|
|
||||||
console.log(removeRepetition('abc')) // ->abc
|
|
||||||
```
|
|
||||||
|
|
||||||
如果没有限定条件说是“连续重复”,就可以用 **Set**:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function removeRepetition(str) {
|
|
||||||
let strArr = [...new Set(str)]
|
|
||||||
return strArr.join('')
|
|
||||||
}
|
|
||||||
console.log(removeRepetition('aaa')) // ->a
|
|
||||||
console.log(removeRepetition('abbba')) // ->ab
|
|
||||||
console.log(removeRepetition('aabbaabb')) // ->ab
|
|
||||||
console.log(removeRepetition('')) // ->
|
|
||||||
console.log(removeRepetition('abc')) // ->abc
|
|
||||||
```
|
|
||||||
|
|
||||||
## 其他解法
|
|
||||||
|
|
||||||
- [filter 结合 call Method](https://www.programminghunter.com/article/7794242622/)
|
|
||||||
- [用 for 循环的两种方式](https://www.cnblogs.com/zyc-zsxbh/p/9327364.html)
|
|
|
@ -1,101 +0,0 @@
|
||||||
---
|
|
||||||
title: CSS · Tab选项卡
|
|
||||||
summary: 一个纯 CSS 实现的 Tab 选项卡
|
|
||||||
created: 2022-03-09T07:42:25.299Z
|
|
||||||
tags:
|
|
||||||
- CSS
|
|
||||||
categories:
|
|
||||||
- CSS
|
|
||||||
lastmod: 2022-04-07T07:40:02.371Z
|
|
||||||
---
|
|
||||||
|
|
||||||
一个纯 CSS 实现的 Tab 选项卡
|
|
||||||
|
|
||||||
## 原理
|
|
||||||
|
|
||||||
> 通过隐藏的 `input` 和与之关联的 [label](https://so.csdn.net/so/search?q=label&spm=1001.2101.3001.7020) 点击 `label` 触发 `input` 的 `checked` 状态触发的,再配合使用元素状态的伪类 `:checked `样式就可以实现不同状态的切换,中间的过度效果还可以配合 CSS3 的 `transition`过度效果实现 [^1]。
|
|
||||||
|
|
||||||
## 代码
|
|
||||||
|
|
||||||
- `input` 的`name` 都一样,`id`不同
|
|
||||||
|
|
||||||
### HTML
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="tab-frame">
|
|
||||||
<!--标签页标题栏-->
|
|
||||||
<!-- 设置一个为check -->
|
|
||||||
<input type="radio" name="tab" id="tab1" check />
|
|
||||||
<label for="tab1">TAB1</label>
|
|
||||||
|
|
||||||
<input type="radio" name="tab" id="tab2" />
|
|
||||||
<label for="tab2">TAB2</label>
|
|
||||||
|
|
||||||
<input type="radio" name="tab" id="tab3" />
|
|
||||||
<label for="tab3">TAB2</label>
|
|
||||||
|
|
||||||
<!--Tab内容-->
|
|
||||||
<div class="tab-content">
|
|
||||||
<p>THIS IS TAB1 CONTENT</p>
|
|
||||||
<p>Notice the gap between the content and tab after applying background cololr</p>
|
|
||||||
</div>
|
|
||||||
<div class="tab-content">
|
|
||||||
<p>THIS IS TAB2 CONTENT</p>
|
|
||||||
<p>Notice the gap between the content and tab after applying background cololr</p>
|
|
||||||
</div>
|
|
||||||
<div class="tab-content">
|
|
||||||
<p>THIS IS TAB3 CONTENT</p>
|
|
||||||
<p>Notice the gap between the content and tab after applying background cololr</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### CSS
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* 隐藏input和tab内容 */
|
|
||||||
.tab-frame input,
|
|
||||||
.tab-content {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 导航栏样式:未选中时 */
|
|
||||||
.tab-frame label {
|
|
||||||
color: #555;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-bottom: 1px solid #555;
|
|
||||||
cursor: pointer;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 导航栏样式:选中时 */
|
|
||||||
.tab-frame input:checked + label {
|
|
||||||
color: #0f71aa;
|
|
||||||
border: 1px solid #555;
|
|
||||||
border-bottom: none;
|
|
||||||
border-radius: 4px 4px 0px 0px;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tab内容样式 */
|
|
||||||
.tab-frame .tab-content {
|
|
||||||
color: #0f71aa;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
padding-top: 40px;
|
|
||||||
clear: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 点击的时候显示tab内容,即input checked的时候显示label*/
|
|
||||||
.tab-frame input:nth-of-type(1):checked ~ .tab-content:nth-of-type(1),
|
|
||||||
.tab-frame input:nth-of-type(2):checked ~ .tab-content:nth-of-type(2),
|
|
||||||
.tab-frame input:nth-of-type(3):checked ~ .tab-content:nth-of-type(3) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 参考
|
|
||||||
|
|
||||||
Demo:[Tabs CSS & HTML, no JS & Jquery](https://codepen.io/llgruff/pen/ZGBxOa)
|
|
||||||
|
|
||||||
[^1]: [CSS tab 选项卡 (标签页) 切换](https://blog.csdn.net/baiding1123/article/details/51889201)
|
|
|
@ -1,59 +0,0 @@
|
||||||
---
|
|
||||||
title: JavaScript · 打字机效果生成器
|
|
||||||
summary: 用 JavaScript 实现网页打字机效果
|
|
||||||
created: 2022-03-08T16:19:05.137Z
|
|
||||||
tags:
|
|
||||||
- JavaScript
|
|
||||||
categories:
|
|
||||||
- JavaScript
|
|
||||||
lastmod: 2022-04-07T07:40:27.758Z
|
|
||||||
---
|
|
||||||
|
|
||||||
## 题目
|
|
||||||
|
|
||||||
来源:[百度前端学院](http://ife.baidu.com/javascript/string.html#%E4%BB%BB%E5%8A%A1%E5%9B%9B)
|
|
||||||
|
|
||||||
参照 [打字机效果 DEMO (opens new window)](https://b.bdstatic.com/searchbox/icms/searchbox/img/%E6%89%93%E5%AD%97%E6%9C%BA.gif),实现一个打字机效果生成器
|
|
||||||
|
|
||||||
**需求说明**
|
|
||||||
|
|
||||||
- 在输入框中输入需要实现打字机效果的文本
|
|
||||||
- 实现原理使用定时器间隔一段时间递增地截取字符串的长度
|
|
||||||
- 点击 button 实现打字机效果的生成,将文本输出到 id 为 showText 的标签中
|
|
||||||
|
|
||||||
```html
|
|
||||||
<label>请输入文本:</label>
|
|
||||||
<input type="text" />
|
|
||||||
<button onclick="generateTypeEffect()">生成打字效果</button>
|
|
||||||
<h2 id="showText"></h2>
|
|
||||||
<script>
|
|
||||||
function generateTypeEffect() {
|
|
||||||
//这里实现打字机效果
|
|
||||||
//将内容显示在h2中
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 解法
|
|
||||||
|
|
||||||
```html
|
|
||||||
<label>请输入文本:</label>
|
|
||||||
<input type="text" />
|
|
||||||
<button onclick="generateTypeEffect()">生成打字效果</button>
|
|
||||||
<h2 id="showText"></h2>
|
|
||||||
<script>
|
|
||||||
let i = 0
|
|
||||||
function generateTypeEffect() {
|
|
||||||
const output = document.getElementById('showText')
|
|
||||||
const input = document.querySelector('input').value
|
|
||||||
if (i < input.length) {
|
|
||||||
output.textContent += input[i]
|
|
||||||
setTimeout(generateTypeEffect, 200, ++i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 参考
|
|
||||||
|
|
||||||
[How TO - Typing Effect](https://www.w3schools.com/howto/howto_js_typewriter.asp)
|
|
|
@ -1,30 +0,0 @@
|
||||||
---
|
|
||||||
title: Forty页面仿写
|
|
||||||
summary: 完成 HTML、CSS 代码编写,暂无 JavaScript
|
|
||||||
created: 2022-03-10T08:38:17.227Z
|
|
||||||
preview: ''
|
|
||||||
draft: ''
|
|
||||||
tags:
|
|
||||||
- CSS
|
|
||||||
- HTML
|
|
||||||
lastmod: 2022-04-07T07:39:48.473Z
|
|
||||||
---
|
|
||||||
|
|
||||||
## 题目
|
|
||||||
|
|
||||||
来源:[百度前端学院|浮动实战任务](http://ife.baidu.com/csspart/floatTask.html)
|
|
||||||
|
|
||||||
通过 HTML 及 CSS 参考[示例图](https://b.bdstatic.com/searchbox/icms/searchbox/img/task1.png)实现页面开发,要求实现效果与示例图基本一致
|
|
||||||
|
|
||||||
- 页面宽度固定(定宽), 请应用 CSS 浮动以及前几天所学的 CSS 样式来完成页面效果
|
|
||||||
- 只需要完成 HTML、CSS 代码编写,不需要写 JavaScript
|
|
||||||
|
|
||||||
## 示例图
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Demo
|
|
||||||
|
|
||||||
https://forty-seviche.netlify.app/
|
|
||||||
|
|
||||||
耗时:4 小时(还没有做自适应等很多东西……╮( ̄ ▽  ̄"")╭
|
|
|
@ -1,52 +0,0 @@
|
||||||
---
|
|
||||||
title: CSS · 解决 Chrome 中小于12px的字体不显示的问题
|
|
||||||
lastmod: 2022-04-07T07:36:23.629Z
|
|
||||||
summary: 先用scale总体缩小再补上减少的宽度
|
|
||||||
created: 2022-03-29T13:46:29.228Z
|
|
||||||
tags:
|
|
||||||
- CSS
|
|
||||||
- CSS Trick
|
|
||||||
categories:
|
|
||||||
- CSS
|
|
||||||
toc: false
|
|
||||||
---
|
|
||||||
|
|
||||||
如设置字体大小为 10.2px
|
|
||||||
|
|
||||||
### HTML
|
|
||||||
|
|
||||||
```html
|
|
||||||
<p>
|
|
||||||
I am a frontend developer with a particular interest in making things simple and automating daily tasks. I try to keep up with
|
|
||||||
security and best practices, and am always looking for new things to learn.
|
|
||||||
</p>
|
|
||||||
```
|
|
||||||
|
|
||||||
### CSS
|
|
||||||
|
|
||||||
```css
|
|
||||||
p {
|
|
||||||
color: #dcdcdc;
|
|
||||||
|
|
||||||
/*缩小基准大小,也就是缩小后的字体应该是 10.2px=12px*0.85*/
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
/* 缩小比例 10.2px/12px=0.85 */
|
|
||||||
transform: scale(0.85);
|
|
||||||
|
|
||||||
/*设置缩放中心*/
|
|
||||||
transform-origin: 0 0;
|
|
||||||
|
|
||||||
/*(1-0.85)+1,补上缩小的宽度,这里可以按视觉效果调整一点*/
|
|
||||||
width: 118%;
|
|
||||||
|
|
||||||
/*兼容IE*/
|
|
||||||
*font-size: 10.2px;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
参考:
|
|
||||||
|
|
||||||
- [css 小于 12px 字体\_MAIMIHO 的博客-CSDN 博客](https://blog.csdn.net/maimiho/article/details/121548769)
|
|
||||||
- [css 设置字体小于 12px 的方法 - 代码先锋网](https://www.codeleading.com/article/46263149244/)
|
|
||||||
- [Set CSS font-size less than 12px in webkit browser](https://codepen.io/mjj2000/pen/AYEqwJ)
|
|
|
@ -1,91 +0,0 @@
|
||||||
---
|
|
||||||
title: 实现一个返回页面顶部的 Vue3 组件
|
|
||||||
created: 2022-06-10
|
|
||||||
summary: 结合流畅的动画平滑滚动到页面顶部
|
|
||||||
tags:
|
|
||||||
- Vue3
|
|
||||||
- BootStrap
|
|
||||||
---
|
|
||||||
|
|
||||||
主要参考:[Simple Vue.js and Tailwind.css Scroll To Top Button | Adam Bailey](https://adambailey.io/blog/scroll-to-top-button-vue/)
|
|
||||||
|
|
||||||
CSS 库:[Bootstrap V5.2](https://getbootstrap.com/docs/5.2/getting-started/introduction/)
|
|
||||||
|
|
||||||
- 按钮的布局方式为 sticky
|
|
||||||
- 因为可能需要频繁切换显示状态,所以用`v-show` 而不是 `v-if`来控制按钮可见性
|
|
||||||
- 使用 Vue 中内置的`<transition>`组件实现状态之间的平滑过渡
|
|
||||||
|
|
||||||
```vue title="BackToTop.vue"
|
|
||||||
<template>
|
|
||||||
<div class="position-sticky bottom-0 end-0 w-100 d-flex justify-content-end b-0 pb-3 pe-5">
|
|
||||||
<transition>
|
|
||||||
<button class="btn btn-secondary btn-sm" v-show="isVisible" @click="scrollToTop" aria-label="scroll to top of the page">
|
|
||||||
<img src="../assets/to-top.min.svg" alt="an arrow point to top" />
|
|
||||||
</button>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref, onMounted, onBeforeUnmount } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'BackToTop',
|
|
||||||
setup() {
|
|
||||||
const isVisible = ref(false)
|
|
||||||
const handleScroll = () => {
|
|
||||||
isVisible.value = window.scrollY > 0
|
|
||||||
}
|
|
||||||
const scrollToTop = () => {
|
|
||||||
window.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
behavior: 'smooth'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onMounted(() => {
|
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
})
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
window.removeEventListener('scroll', handleScroll)
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
isVisible,
|
|
||||||
handleScroll,
|
|
||||||
scrollToTop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.v-enter-active,
|
|
||||||
.v-leave-active {
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
.v-enter-from,
|
|
||||||
.v-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
|
||||||
|
|
||||||
```html title="to-top.min.svg"
|
|
||||||
<svg width="20" height="20" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path
|
|
||||||
d="M24.008 14.1V42M12 26l12-12 12 12M12 6h24"
|
|
||||||
stroke="#fff"
|
|
||||||
stroke-width="4"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round" />
|
|
||||||
</svg>
|
|
||||||
```
|
|
||||||
|
|
||||||
其他参考/实现方式:
|
|
||||||
|
|
||||||
- [vue 返回顶部的组件 BackTop](https://blog.csdn.net/m0_46217225/article/details/117933815)
|
|
||||||
- [vue-simple-scroll-up/ScrollToTop.vue · GitHub](https://github.com/asdf1899/vue-simple-scroll-up/blob/master/src/ScrollToTop.vue)
|
|
||||||
- [Vue.js - Scroll Back To Top Button Without Library](https://codepen.io/webty_mizusawa/pen/QWLMeqE)
|
|
||||||
- [Vue3 从 0 到 1 组件开发-基础组件:BackTop 回顶 - 掘金](https://juejin.cn/post/6993729338843594783)
|
|
||||||
- [vue 相同路径刷新怎么回到顶部 - 掘金](https://juejin.cn/post/6873264845915947016)
|
|
||||||
|
|
||||||
题外话:||BootStrap 的文档写得好烂||
|
|
|
@ -1,134 +0,0 @@
|
||||||
---
|
|
||||||
title: SQL 基础笔记
|
|
||||||
created: 2022-07-13
|
|
||||||
summary: 关于增改删查的方式
|
|
||||||
tags:
|
|
||||||
- SQL
|
|
||||||
---
|
|
||||||
|
|
||||||
- 课程:[The Complete 2022 Web Development Bootcamp](https://www.udemy.com/share/1013gG3@wP9ybulEki65OWpaP1-gXEeRPJl4aj8eZNX7YYjFOgXlrxBGQyU6NniyJf2PqDI1EA==/)
|
|
||||||
- 工具:[SQL Online Compiler - for Data Science](https://sqliteonline.com)
|
|
||||||
- 教程:[SQL 教程 | 菜鸟教程](https://www.runoob.com/sql/sql-tutorial.html)
|
|
||||||
|
|
||||||
## SQL vs. NOSQL
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- SQL
|
|
||||||
- 注重结构
|
|
||||||
- NOSQL
|
|
||||||
- 更灵活,也更稳健
|
|
||||||
- MoogoDB
|
|
||||||
|
|
||||||
## CRUD
|
|
||||||
|
|
||||||
### Create
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE product(
|
|
||||||
id Int NOT NULL,
|
|
||||||
name STRING,
|
|
||||||
price MONEY,
|
|
||||||
PRIMARY KEY(id)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
- `NOT NULL` 当此值为 null 时,不创建列
|
|
||||||
- `PRIMARY KEY(id)` 主键必须包含唯一的值,这个不能有重复的值
|
|
||||||
|
|
||||||
**插入数值**
|
|
||||||
第一种形式无需指定要插入数据的列名,只需提供被插入的值即可
|
|
||||||
|
|
||||||
```sql
|
|
||||||
INSERT INTO _table_name_
|
|
||||||
VALUES (_value1_,_value2_,_value3_,...);
|
|
||||||
```
|
|
||||||
|
|
||||||
第二种形式需要指定列名及被插入的值:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
INSERT INTO _table_name_ (_column1_,_column2_,_column3_,...)
|
|
||||||
VALUES (_value1_,_value2_,_value3_,...);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Read
|
|
||||||
|
|
||||||
SELECT 语句用于从数据库中选取数据。
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT _column_name_,_column_name_
|
|
||||||
FROM _table_name_;
|
|
||||||
```
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT * FROM _table_name_;
|
|
||||||
```
|
|
||||||
|
|
||||||
`*` 表示选择全部
|
|
||||||
|
|
||||||
可以用`WHERE` 筛选选择结果,如:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT _column_name_,_column_name_
|
|
||||||
FROM _table_name_
|
|
||||||
WHERE _column_name operator value_;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Update
|
|
||||||
|
|
||||||
```sql
|
|
||||||
UPDATE _tablse_name_
|
|
||||||
SET _column1_=_value1_,_column2_=_value2_,...
|
|
||||||
WHERE _some_column_=_some_value_;
|
|
||||||
```
|
|
||||||
|
|
||||||
**ALTER TABLE 语句用于在已有的表中添加、删除或修改列。**
|
|
||||||
如需在表中添加列,请使用下面的语法:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
ALTER TABLE table_name
|
|
||||||
ADD column_name datatype
|
|
||||||
```
|
|
||||||
|
|
||||||
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
|
|
||||||
|
|
||||||
```sql
|
|
||||||
ALTER TABLE table_name
|
|
||||||
DROP COLUMN column_name
|
|
||||||
```
|
|
||||||
|
|
||||||
### Destory
|
|
||||||
|
|
||||||
```sql
|
|
||||||
DELETE FROM _table_name_
|
|
||||||
WHERE _some_column_=_some_value_;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Understanding SQL Relationships, Foreign Keys and Inner Joins
|
|
||||||
|
|
||||||
### FOREIGN KEY
|
|
||||||
|
|
||||||
- 用`FOREIGN KEY` 来和外部表单链接
|
|
||||||
- 一个表中的 FOREIGN KEY 指向另一个表中的 UNIQUE KEY(唯一约束的键)。
|
|
||||||
|
|
||||||
### INNER JOIN
|
|
||||||
|
|
||||||
选择相应列并合并表,`ON`后面写的是条件
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT _column_name(s)_
|
|
||||||
FROM _table1_
|
|
||||||
INNER JOIN _table2_
|
|
||||||
ON _table1.column_name_=_table2.column_name_;
|
|
||||||
```
|
|
||||||
|
|
||||||
或:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT _column_name(s)_
|
|
||||||
FROM _table1_
|
|
||||||
JOIN _table2_
|
|
||||||
ON _table1.column_name_=_table2.column_name_;
|
|
||||||
```
|
|
|
@ -1,22 +0,0 @@
|
||||||
---
|
|
||||||
created: 2022-07-26
|
|
||||||
tags:
|
|
||||||
- XSS
|
|
||||||
- Notes
|
|
||||||
---
|
|
||||||
|
|
||||||
### XSS 学习
|
|
||||||
|
|
||||||
介绍文章:
|
|
||||||
|
|
||||||
- [前端安全系列(一):如何防止 XSS 攻击? - 美团技术团队](https://tech.meituan.com/2018/09/27/fe-security.html)
|
|
||||||
- [Cross-site scripting(跨站脚本攻击) - 术语表 | MDN](https://developer.mozilla.org/zh-CN/docs/Glossary/Cross-site_scripting)
|
|
||||||
|
|
||||||
练习:
|
|
||||||
|
|
||||||
- [prompt(1) to win](https://prompt.ml/)
|
|
||||||
- 解答:[prompt.ml](https://github.com/cure53/XSSChallengeWiki/wiki/prompt.ml#level-1)
|
|
||||||
|
|
||||||
相关文章:
|
|
||||||
|
|
||||||
- [我自己博客的一个 XSS 的故事 | 离别歌](https://www.leavesongs.com/PENETRATION/xss-from-my-blog.html)
|
|
|
@ -1,31 +0,0 @@
|
||||||
---
|
|
||||||
title: JS中的二进制数字
|
|
||||||
created: 2022-07-27
|
|
||||||
summary: 0b/0B和paresInt
|
|
||||||
tags:
|
|
||||||
- JavsScript
|
|
||||||
---
|
|
||||||
|
|
||||||
参考:[How to Represent Binary Numbers in JavaScript? - Designcise](https://www.designcise.com/web/tutorial/how-to-represent-binary-numbers-in-javascript)
|
|
||||||
|
|
||||||
## ES6+
|
|
||||||
|
|
||||||
在 ES6 之后的版本,在二进制数字前加`0b` 或者`0B`来标识这是一个二进制数字,比如:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let number5 = ob101
|
|
||||||
let number5 = oB101
|
|
||||||
```
|
|
||||||
|
|
||||||
## Before ES6
|
|
||||||
|
|
||||||
- 通过字符串和 parseInt 来转换
|
|
||||||
- parseInt 可以在字符串中提取数字,第一个参数是要提取的字符串,第二个是基准的计算进制
|
|
||||||
|
|
||||||
```js
|
|
||||||
const number = '0101'
|
|
||||||
|
|
||||||
Number.parseInt(number, 2)
|
|
||||||
```
|
|
||||||
|
|
||||||
相关:[JavaScript · 十进制数转二进制](/2022-03-04-decbin)
|
|
|
@ -1,38 +0,0 @@
|
||||||
<script lang="ts" context="module">
|
|
||||||
import type { Load } from './__types'
|
|
||||||
export const load: Load = ({ url: { pathname }, error, status }) => ({ props: { pathname, error, status } })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Head from '$lib/components/head.svelte'
|
|
||||||
import Footer from '$lib/components/footer.svelte'
|
|
||||||
export let pathname: string
|
|
||||||
export let error: Error
|
|
||||||
export let status: string
|
|
||||||
console.error(status, error.message)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Head page={{ title: status ?? '404', path: pathname ?? '/404' }} />
|
|
||||||
|
|
||||||
<div class="flex flex-col flex-nowrap justify-center xl:flex-row xl:flex-wrap">
|
|
||||||
<div class="flex-none w-full max-w-screen-md mx-auto xl:mx-0">
|
|
||||||
<article
|
|
||||||
itemscope
|
|
||||||
itemtype="https://schema.org/BlogPosting"
|
|
||||||
class="card bg-base-100 rounded-none md:rounded-box shadow-xl md:mb-8 z-10">
|
|
||||||
<main itemprop="articleBody" class="card-body prose urara-prose">
|
|
||||||
<h1 class="opacity-20 text-6xl md:text-[12rem] -mt-2 mb-0">
|
|
||||||
{status ?? '404'}
|
|
||||||
</h1>
|
|
||||||
<h2 class="-mt-12 md:-mt-24">{error.message ?? 'Not found'}</h2>
|
|
||||||
<div class="card-actions">
|
|
||||||
<a href="/" class="btn btn-neutral no-underline shadow-xl hover:shadow-2xl mt-8">
|
|
||||||
<span class="i-heroicons-outline-home -ml-1 mr-2" />
|
|
||||||
Back to Home
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</article>
|
|
||||||
<Footer sticky={true} class="flex-1 md:flex-initial" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,49 +0,0 @@
|
||||||
<script lang="ts" context="module">
|
|
||||||
import type { Load } from './__types'
|
|
||||||
export const prerender = true
|
|
||||||
export const load: Load = async ({ url, fetch }) => ({
|
|
||||||
props: {
|
|
||||||
path: url.pathname,
|
|
||||||
res: await fetch('/posts.json').then(res => res.json())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { browser, dev } from '$app/env'
|
|
||||||
import { fly } from 'svelte/transition'
|
|
||||||
import { genTags } from '$lib/utils/posts'
|
|
||||||
import { posts, tags } from '$lib/stores/posts'
|
|
||||||
import { registerSW } from 'virtual:pwa-register'
|
|
||||||
import Head from '$lib/components/head_static.svelte'
|
|
||||||
import Header from '$lib/components/header.svelte'
|
|
||||||
import 'uno.css'
|
|
||||||
import '../app.css'
|
|
||||||
export let res: Urara.Post[]
|
|
||||||
export let path: string
|
|
||||||
posts.set(res)
|
|
||||||
tags.set(genTags(res))
|
|
||||||
onMount(
|
|
||||||
() =>
|
|
||||||
!dev &&
|
|
||||||
browser &&
|
|
||||||
registerSW({
|
|
||||||
onRegistered: r => r && setInterval(async () => await r.update(), 198964),
|
|
||||||
onRegisterError: error => console.error(error)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Head />
|
|
||||||
|
|
||||||
<Header {path} />
|
|
||||||
|
|
||||||
{#key path}
|
|
||||||
<div
|
|
||||||
class="bg-base-100 md:bg-base-200 min-h-screen pt-16 md:pb-8 lg:pb-16"
|
|
||||||
in:fly={{ y: 100, duration: 300, delay: 300 }}
|
|
||||||
out:fly={{ y: -100, duration: 300 }}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
{/key}
|
|
|
@ -1,67 +0,0 @@
|
||||||
---
|
|
||||||
title: CSS · Argon主题的CSS修改
|
|
||||||
created: 2022-01-16 14:04:17
|
|
||||||
tags:
|
|
||||||
- CSS
|
|
||||||
slug: free-axure-cloud
|
|
||||||
summary: 基于最近所学,对当前Argon主题做了一些微小的调整
|
|
||||||
lastmod: 2022-05-07T05:30:35.639Z
|
|
||||||
---
|
|
||||||
|
|
||||||
然后已经不用 Wordpress 了,这个主题加载太慢了(也可能是我自己的问题
|
|
||||||
|
|
||||||
```css
|
|
||||||
/*删掉tag图标*/
|
|
||||||
|
|
||||||
.fa-tags {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*左侧菜单居中*/
|
|
||||||
.leftbar-menu-item {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
/*删掉日历图标*/
|
|
||||||
.fa-calendar-o {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*说说字体统一大小*/
|
|
||||||
.shuoshuo-date-date {
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shuoshuo-date-month {
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*banner字体改为思源宋体*/
|
|
||||||
.banner-title.text-white {
|
|
||||||
font-family: 'Noto Serif SC', serif;
|
|
||||||
}
|
|
||||||
/*删掉左侧搜索栏*/
|
|
||||||
.card-body.text-center.leftbar-search-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*删掉左侧栏站点名称*/
|
|
||||||
.leftbar-banner.card-body {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*作者相关链接居中*/
|
|
||||||
.site-author-links a {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*去掉页脚卡片外形,并缩减边距*/
|
|
||||||
#footer.card {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.darkmode #footer {
|
|
||||||
background: none !important;
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,49 +0,0 @@
|
||||||
import type { RequestHandler } from '@sveltejs/kit'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import { feed } from '$lib/config/general'
|
|
||||||
import { favicon } from '$lib/config/icon'
|
|
||||||
import { genPosts, genTags } from '$lib/utils/posts'
|
|
||||||
|
|
||||||
const render = async (
|
|
||||||
posts = genPosts({ postHtml: true, postLimit: feed.limit, filterUnlisted: true })
|
|
||||||
): Promise<string> => `<?xml version='1.0' encoding='utf-8'?>
|
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
|
||||||
<id>${site.protocol + site.domain}/</id>
|
|
||||||
<title><![CDATA[${site.title}]]></title>${site.subtitle ? `\n <subtitle><![CDATA[${site.subtitle}]]></subtitle>` : ''}${
|
|
||||||
favicon ? `\n <icon>${favicon.src}</icon>` : ''
|
|
||||||
}
|
|
||||||
<link href="${site.protocol + site.domain}" />
|
|
||||||
<link href="${site.protocol + site.domain}/atom.xml" rel="self" type="application/atom+xml" />${
|
|
||||||
feed.hubs?.map(hub => `\n <link href="${hub}" rel="hub"/>`).join('') ?? ''
|
|
||||||
}
|
|
||||||
<updated>${new Date().toJSON()}</updated>
|
|
||||||
<author>
|
|
||||||
<name><![CDATA[${site.author.name}]]></name>
|
|
||||||
</author>${genTags(posts)
|
|
||||||
.map(tag => `\n <category term="${tag}" scheme="${site.protocol + site.domain}/?tags=${encodeURI(tag)}" />`)
|
|
||||||
.join('')}${posts
|
|
||||||
.map(
|
|
||||||
post => `\n <entry>
|
|
||||||
<title type="html"><![CDATA[${post.title}]]></title>
|
|
||||||
<link href="${site.protocol + site.domain + post.path}" />
|
|
||||||
<id>${site.protocol + site.domain + post.path}</id>
|
|
||||||
<published>${new Date(post.published ?? post.created).toJSON()}</published>
|
|
||||||
<updated>${new Date(post.updated ?? post.published ?? post.created).toJSON()}</updated>${
|
|
||||||
post.summary ? `\n <summary type="html"><![CDATA[${post.summary.toString()}]]></summary>` : ''
|
|
||||||
}
|
|
||||||
<content type="html">
|
|
||||||
<![CDATA[${post.html}]]>
|
|
||||||
</content>${post.tags
|
|
||||||
?.map(tag => `\n <category term="${tag}" scheme="${site.protocol + site.domain}/?tags=${encodeURI(tag)}" />`)
|
|
||||||
.join('')}
|
|
||||||
</entry>`
|
|
||||||
)
|
|
||||||
.join('')}
|
|
||||||
</feed>`
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async () => ({
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/atom+xml; charset=utf-8'
|
|
||||||
},
|
|
||||||
body: await render()
|
|
||||||
})
|
|
|
@ -1,32 +0,0 @@
|
||||||
---
|
|
||||||
title: 两种免费发布Axure原型的方式
|
|
||||||
created: 2022-01-21 00:11:17
|
|
||||||
tags:
|
|
||||||
- 实用技巧
|
|
||||||
slug: free-axure-cloud
|
|
||||||
summary: 通过Netlify和Vercel发布
|
|
||||||
lastmod: 2022-04-07T07:24:20.692Z
|
|
||||||
---
|
|
||||||
|
|
||||||
好像很少看到有人提,这里简单记一下思路,我有用 Vercel 成功试验过,跟本地预览效果是一样的,还可以改域名。不过了解过 Netlify 之后,我觉得用 Netlify 更简单,有兴趣的可以再研究一下。
|
|
||||||
|
|
||||||
## Netlify
|
|
||||||
|
|
||||||
需要了解 Netlify 的使用方式
|
|
||||||
|
|
||||||
1. 在 Axure 中导出文档的 HTML 文件:发布>生成 HTML 文件
|
|
||||||
2. 注册并托管到 Netlify
|
|
||||||
3. 上传 HTML 文件:site -> 拖动 HTML 文件夹到下面的虚线区域
|
|
||||||
|
|
||||||
更新方式:再导入一次更新后的文件包
|
|
||||||
|
|
||||||
## Vercel +Github
|
|
||||||
|
|
||||||
需要了解 Github 的基本使用方式和 Vercel
|
|
||||||
|
|
||||||
1. 在 Axure 中导出文档的 HTML 文件:发布>生成 HTML 文件
|
|
||||||
2. 创建一个 Github repo,并下载到本地
|
|
||||||
3. 复制 HTML 文件到本地 Github repo 的文件夹中,并 commit->push 到云端
|
|
||||||
4. 导入相应 repo 到 Vercel 中
|
|
||||||
|
|
||||||
更新方式:复制更新后的 HTML 文件夹到相应的 Github repo 文件夹中,覆盖原来的
|
|
|
@ -1,49 +0,0 @@
|
||||||
import type { RequestHandler } from '@sveltejs/kit'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import { feed } from '$lib/config/general'
|
|
||||||
import { favicon, any } from '$lib/config/icon'
|
|
||||||
import { genPosts } from '$lib/utils/posts'
|
|
||||||
|
|
||||||
const render = async (posts = genPosts({ postHtml: true, postLimit: feed.limit, filterUnlisted: true })) => ({
|
|
||||||
version: 'https://jsonfeed.org/version/1.1',
|
|
||||||
title: site.title,
|
|
||||||
home_page_url: site.protocol + site.domain,
|
|
||||||
feed_url: site.protocol + site.domain + '/feed.json',
|
|
||||||
description: site.description,
|
|
||||||
icon: any['512'].src ?? any['192'].src,
|
|
||||||
favicon: favicon?.src,
|
|
||||||
authors: [
|
|
||||||
{
|
|
||||||
name: site.author.name,
|
|
||||||
url: site.protocol + site.domain,
|
|
||||||
avatar: site.author.avatar
|
|
||||||
}
|
|
||||||
],
|
|
||||||
language: site.lang ?? 'en',
|
|
||||||
hubs: feed.hubs?.map(hub => ({
|
|
||||||
type: 'WebSub',
|
|
||||||
url: hub
|
|
||||||
})),
|
|
||||||
items: posts.map(post => ({
|
|
||||||
id: post.path.slice(1),
|
|
||||||
url: site.protocol + site.domain + post.path,
|
|
||||||
title: post.title,
|
|
||||||
content_html: post.html,
|
|
||||||
summary: post['summary'],
|
|
||||||
image: post['image'],
|
|
||||||
date_published: post.published ?? post.created,
|
|
||||||
date_modified: post.updated ?? post.published ?? post.created,
|
|
||||||
tags: post.tags,
|
|
||||||
_indieweb: {
|
|
||||||
type: post.type,
|
|
||||||
'in-reply-to': post.in_reply_to
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async () => ({
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/feed+json; charset=utf-8'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(await render(), null, 2)
|
|
||||||
})
|
|
|
@ -1,45 +0,0 @@
|
||||||
---
|
|
||||||
title: CSS · Reusable Grid
|
|
||||||
slug: css-grid
|
|
||||||
tags:
|
|
||||||
- CSS
|
|
||||||
created: 2022-01-13T10:01:44.000Z
|
|
||||||
summary: 可复用的CSS Grid设置
|
|
||||||
lastmod: 2022-05-07T05:32:52.372Z
|
|
||||||
---
|
|
||||||
|
|
||||||
1. 先设置一个 class 为 grid,并设定 gap
|
|
||||||
2. 子元素中再设置具体的列数
|
|
||||||
|
|
||||||
如设一个上为 2 列和下为 3 列的栅格布局:
|
|
||||||
|
|
||||||
#### CSS:
|
|
||||||
|
|
||||||
```css
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 1.2rem;
|
|
||||||
}
|
|
||||||
.grid--2--cols {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid--3--cols {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### HTML:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<section class="grid grid--2--cols">
|
|
||||||
<div>Text1</div>
|
|
||||||
<div>Text2</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="grid grid--3--cols">
|
|
||||||
<div>Text1</div>
|
|
||||||
<div>Text2</div>
|
|
||||||
<div>Text3</div>
|
|
||||||
</section>
|
|
||||||
```
|
|
|
@ -1,129 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import { fly } from 'svelte/transition'
|
|
||||||
import { page } from '$app/stores'
|
|
||||||
import { browser } from '$app/env'
|
|
||||||
import { posts as storedPosts, tags as storedTags } from '$lib/stores/posts'
|
|
||||||
import { title as storedTitle } from '$lib/stores/title'
|
|
||||||
import Head from '$lib/components/head.svelte'
|
|
||||||
import Footer from '$lib/components/footer.svelte'
|
|
||||||
import Post from '$lib/components/post_card.svelte'
|
|
||||||
// import Post from '$lib/components/index_post.svelte'
|
|
||||||
import Profile from '$lib/components/index_profile.svelte'
|
|
||||||
import RemoteFollow from '$lib/components/extra/follow.svelte'
|
|
||||||
|
|
||||||
let allPosts: Urara.Post[]
|
|
||||||
let allTags: string[]
|
|
||||||
let loaded: boolean
|
|
||||||
let [posts, tags, years]: [Urara.Post[], string[], number[]] = [[], [], []]
|
|
||||||
|
|
||||||
storedTitle.set('')
|
|
||||||
|
|
||||||
$: storedPosts.subscribe(
|
|
||||||
storedPosts => (allPosts = (storedPosts as Urara.Post[]).filter(post => !post.flags?.includes('unlisted')))
|
|
||||||
)
|
|
||||||
|
|
||||||
$: storedTags.subscribe(storedTags => (allTags = storedTags as string[]))
|
|
||||||
|
|
||||||
$: if (posts.length > 1) years = [new Date(posts[0].published ?? posts[0].created).getFullYear()]
|
|
||||||
|
|
||||||
$: if (tags) {
|
|
||||||
posts = !tags ? allPosts : allPosts.filter(post => tags.every(tag => post.tags?.includes(tag)))
|
|
||||||
if (browser && window.location.pathname === '/')
|
|
||||||
window.history.replaceState({}, '', tags.length > 0 ? `?tags=${tags.toString()}` : `/`)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (browser) {
|
|
||||||
if ($page.url.searchParams.get('tags')) tags = $page.url.searchParams.get('tags')?.split(',') ?? []
|
|
||||||
loaded = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Head />
|
|
||||||
|
|
||||||
<div class="flex flex-col flex-nowrap justify-center xl:flex-row xl:flex-wrap h-feed">
|
|
||||||
<div
|
|
||||||
in:fly={{ x: 25, duration: 300, delay: 500 }}
|
|
||||||
out:fly={{ x: 25, duration: 300 }}
|
|
||||||
class="flex-1 w-full max-w-screen-md order-first mx-auto xl:mr-0 xl:ml-8 xl:max-w-md">
|
|
||||||
<Profile />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
in:fly={{ x: -25, duration: 300, delay: 500 }}
|
|
||||||
out:fly={{ x: -25, duration: 300 }}
|
|
||||||
class="flex-1 w-full max-w-screen-md xl:order-last mx-auto xl:ml-0 xl:mr-8 xl:max-w-md">
|
|
||||||
{#if allTags && Object.keys(allTags).length > 0}
|
|
||||||
<div
|
|
||||||
class="flex xl:flex-wrap gap-2 overflow-x-auto xl:overflow-x-hidden overflow-y-hidden max-h-24 my-auto xl:max-h-fit max-w-fit xl:max-w-full pl-8 md:px-0 xl:pl-8 xl:pt-8">
|
|
||||||
{#each allTags as tag}
|
|
||||||
<button
|
|
||||||
id={tag}
|
|
||||||
on:click={() => (tags.includes(tag) ? (tags = tags.filter(tagName => tagName != tag)) : (tags = [...tags, tag]))}
|
|
||||||
class:!btn-secondary={tags.includes(tag)}
|
|
||||||
class:shadow-lg={tags.includes(tag)}
|
|
||||||
class="btn btn-sm btn-ghost normal-case border-dotted border-base-content/20 border-2 mt-4 mb-8 xl:m-0">
|
|
||||||
#{tag}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="flex-none w-full max-w-screen-md mx-auto xl:mx-0">
|
|
||||||
{#key posts}
|
|
||||||
<!-- {:else} is not used because there is a problem with the transition -->
|
|
||||||
{#if loaded && posts.length === 0}
|
|
||||||
<div
|
|
||||||
in:fly={{ x: 100, duration: 300, delay: 500 }}
|
|
||||||
out:fly={{ x: -100, duration: 300 }}
|
|
||||||
class="bg-base-300 text-base-content shadow-inner text-center md:rounded-box p-10 -mb-2 md:mb-0 relative z-10">
|
|
||||||
<div class="prose items-center">
|
|
||||||
<h2>
|
|
||||||
Not found: [{#each tags as tag, i}
|
|
||||||
'{tag}'{#if i + 1 < tags.length},{/if}
|
|
||||||
{/each}]
|
|
||||||
</h2>
|
|
||||||
<button on:click={() => (tags = [])} class="btn btn-secondary">
|
|
||||||
<span class="i-heroicons-outline-trash mr-2" />
|
|
||||||
tags = []
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<main
|
|
||||||
class="flex flex-col relative bg-base-100 md:bg-transparent md:gap-8 z-10"
|
|
||||||
itemprop="mainEntityOfPage"
|
|
||||||
itemscope
|
|
||||||
itemtype="https://schema.org/Blog">
|
|
||||||
{#each posts as post, index}
|
|
||||||
{@const year = new Date(post.published ?? post.created).getFullYear()}
|
|
||||||
{#if !years.includes(year)}
|
|
||||||
<div
|
|
||||||
in:fly={{ x: index % 2 ? 100 : -100, duration: 300, delay: 500 }}
|
|
||||||
out:fly={{ x: index % 2 ? -100 : 100, duration: 300 }}
|
|
||||||
class="divider my-4 md:my-0">
|
|
||||||
{years.push(year) && year}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div
|
|
||||||
in:fly={{ x: index % 2 ? 100 : -100, duration: 300, delay: 500 }}
|
|
||||||
out:fly={{ x: index % 2 ? -100 : 100, duration: 300 }}
|
|
||||||
class="rounded-box transition-all duration-500 ease-in-out hover:z-30 hover:shadow-lg md:shadow-xl md:hover:shadow-2xl md:hover:-translate-y-0.5">
|
|
||||||
<Post {post} preview={true} loading={index < 5 ? 'eager' : 'lazy'} decoding={index < 5 ? 'auto' : 'async'} />
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</main>
|
|
||||||
<div
|
|
||||||
class:hidden={!loaded}
|
|
||||||
class="sticky bottom-0 md:static md:mt-8"
|
|
||||||
in:fly={{ x: posts.length + (1 % 2) ? 100 : -100, duration: 300, delay: 500 }}
|
|
||||||
out:fly={{ x: posts.length + (1 % 2) ? -100 : 100, duration: 300 }}>
|
|
||||||
<div class="divider mt-0 mb-8 hidden lg:flex" />
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<RemoteFollow />
|
|
|
@ -1,32 +0,0 @@
|
||||||
import type { RequestHandler } from '@sveltejs/kit'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import { any, maskable } from '$lib/config/icon'
|
|
||||||
|
|
||||||
export const GET: RequestHandler = () => ({
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/manifest+json; charset=utf-8'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(
|
|
||||||
{
|
|
||||||
name: site.title,
|
|
||||||
short_name: site.title,
|
|
||||||
lang: site.lang,
|
|
||||||
description: site.description,
|
|
||||||
id: site.protocol + site.domain + '/',
|
|
||||||
start_url: '/',
|
|
||||||
scope: '/',
|
|
||||||
display: 'standalone',
|
|
||||||
orientation: 'portrait',
|
|
||||||
background_color: site.themeColor,
|
|
||||||
theme_color: site.themeColor,
|
|
||||||
icons: [
|
|
||||||
...Object.values(any)
|
|
||||||
.filter(icon => icon.sizes !== '180x180')
|
|
||||||
.map(icon => ({ ...icon, purpose: 'any' })),
|
|
||||||
...Object.values(maskable).map(icon => ({ ...icon, purpose: 'maskable' }))
|
|
||||||
]
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)
|
|
||||||
})
|
|
|
@ -1,34 +0,0 @@
|
||||||
---
|
|
||||||
title: CSS · Add margin to buttons
|
|
||||||
slug: css-margin
|
|
||||||
tags:
|
|
||||||
- CSS Trick
|
|
||||||
- CSS
|
|
||||||
created: 2022-01-13T10:04:21.000Z
|
|
||||||
summary: 用helper class为单个按钮加margin,防止元素复用时产生不必要的margin
|
|
||||||
lastmod: 2022-05-07T05:32:49.156Z
|
|
||||||
---
|
|
||||||
|
|
||||||
用 helper class 为单个按钮加 margin,防止元素复用时产生不必要的 margin
|
|
||||||
|
|
||||||
## HTML
|
|
||||||
|
|
||||||
```html
|
|
||||||
<button class="btn helper">Text</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
## CSS
|
|
||||||
|
|
||||||
```css
|
|
||||||
.helper {
|
|
||||||
margin-right: 1.6rem;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 为按钮增加内边距
|
|
||||||
|
|
||||||
如果设置 border 数值为负,周边空间则会收到影响,但用 box-shadow 就不会:
|
|
||||||
|
|
||||||
所以可以这样设置:
|
|
||||||
|
|
||||||
`box-shadow: inset 0 0 0 3px #fff;`
|
|
|
@ -1,23 +0,0 @@
|
||||||
---
|
|
||||||
title: 翻译 · Material Design3
|
|
||||||
created: 2022-01-03 13:02:17
|
|
||||||
tags:
|
|
||||||
- 翻译
|
|
||||||
categories:
|
|
||||||
- 翻译
|
|
||||||
slug: material-design-3
|
|
||||||
summary: 部分翻译内容
|
|
||||||
lastmod: 2022-05-07T05:30:39.510Z
|
|
||||||
---
|
|
||||||
|
|
||||||
上个月参与了 [Material Design 3 的中文版](https://www.yuque.com/advancedux/xr6e1n)翻译,我和搭档一起翻译了 [FAB (浮动操作按钮)](https://www.yuque.com/advancedux/xr6e1n/xip12y) 的部分内容。
|
|
||||||
|
|
||||||
这份翻译还存在一些问题,部分专有词汇还没统一翻译,但大概看看还是可以的。
|
|
||||||
|
|
||||||
相关内容:
|
|
||||||
|
|
||||||
- 推文:[Material Design 最新改版,变化好大!](https://mp.weixin.qq.com/s/fm7RKvCSgBz0jCIWlr8mWg)
|
|
||||||
- [Material Design3 英文版](https://m3.material.io/)
|
|
||||||
- [Material Design 3 中文版](https://www.yuque.com/advancedux/xr6e1n)
|
|
||||||
- [Material Design 旧版](https://material.io/)
|
|
||||||
- [CSDC 共享语言库](https://csdcachieve.notion.site/a1fb571707784c809b508a4e63a6ce81)
|
|
|
@ -1,9 +0,0 @@
|
||||||
import type { RequestHandler } from '@sveltejs/kit'
|
|
||||||
import { genPosts } from '$lib/utils/posts'
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async () => ({
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json; charset=utf-8'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(genPosts(), null, 2)
|
|
||||||
})
|
|
|
@ -1,27 +0,0 @@
|
||||||
import type { RequestHandler } from '@sveltejs/kit'
|
|
||||||
import { site } from '$lib/config/site'
|
|
||||||
import { genPosts } from '$lib/utils/posts'
|
|
||||||
|
|
||||||
const render = async (): Promise<string> => `<?xml version='1.0' encoding='utf-8'?>
|
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
||||||
<url>
|
|
||||||
<loc>${site.protocol + site.domain}</loc>
|
|
||||||
</url>
|
|
||||||
${genPosts()
|
|
||||||
.map(
|
|
||||||
post => `
|
|
||||||
<url>
|
|
||||||
<loc>${site.protocol + site.domain + post.path}</loc>
|
|
||||||
<lastmod>${new Date(post.updated ?? post.published ?? post.created).toISOString()}</lastmod>
|
|
||||||
<priority>0.5</priority>
|
|
||||||
</url>`
|
|
||||||
)
|
|
||||||
.join('')}
|
|
||||||
</urlset>`
|
|
||||||
|
|
||||||
export const GET: RequestHandler = async () => ({
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/xml; charset=utf-8'
|
|
||||||
},
|
|
||||||
body: await render()
|
|
||||||
})
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue