mirror of
https://iceshrimp.dev/Crimekillz/jointrashposs.git
synced 2024-11-22 08:53:49 +01:00
Merge branch 'master' of https://github.com/misskey-dev/misskey-hub-next
This commit is contained in:
commit
980ebab9db
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,6 +3,7 @@
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
.vercel
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
|
14
app.vue
14
app.vue
@ -2,19 +2,19 @@
|
||||
import type { LocaleObject } from '@nuxtjs/i18n/dist/runtime/composables';
|
||||
import NProgress from 'nprogress';
|
||||
import type { Graph, Thing } from 'schema-dts';
|
||||
import { normalizeURL, withTrailingSlash } from 'ufo';
|
||||
|
||||
const nuxtApp = useNuxtApp();
|
||||
|
||||
const { t, locale, locales } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const colorMode = useColorMode();
|
||||
const baseUrl = useRuntimeConfig().public.baseUrl as string;
|
||||
|
||||
router.beforeEach((to, from) => {
|
||||
if (to.path === from.path) return;
|
||||
nuxtApp.hook('page:start', () => {
|
||||
NProgress.start();
|
||||
});
|
||||
router.afterEach((to, from) => {
|
||||
if (to.path === from.path) return;
|
||||
nuxtApp.hook('page:finish', () => {
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
NProgress.done();
|
||||
@ -70,7 +70,7 @@ const getLdJson = (additionalGraphes: Thing[] = []): string => {
|
||||
const currentLocaleIso = computed(() => (locales.value as LocaleObject[]).find((e) => e?.code === locale.value)?.iso);
|
||||
|
||||
const head = useLocaleHead({
|
||||
addSeoAttributes: true
|
||||
addSeoAttributes: true,
|
||||
});
|
||||
|
||||
/**
|
||||
@ -118,7 +118,7 @@ useHead((): Record<string, any> => ({
|
||||
...(head.value.meta?.map((e) => ({ property: e.property, content: e.content, })) || []),
|
||||
],
|
||||
link: [
|
||||
...(head.value.link?.map((e) => ({ rel: e.rel, href: (e.href.endsWith('/') ? e.href : e.href + '/'), hreflang: e.hreflang, })) || []),
|
||||
...(head.value.link?.map((e) => ({ rel: e.rel, href: normalizeURL(withTrailingSlash(e.href)), hreflang: e.hreflang, })) || []),
|
||||
...cnHead,
|
||||
],
|
||||
script: [
|
||||
|
@ -37,6 +37,13 @@ VuePressでは末尾が`.md`となるパス形式でしたが、Misskey-Hub-Next
|
||||
date: 2023-11-11
|
||||
```
|
||||
|
||||
### `thumbnail`
|
||||
(現状ブログのみ)サムネイル画像を設定できます。URLまたはサイトディレクトリの絶対パスを入力してください
|
||||
|
||||
```yml
|
||||
thumbnail: /img/blog/foo/bar.png
|
||||
```
|
||||
|
||||
### `maxTocDepth`
|
||||
(Docsのみ)もくじの見出しを遡る限度を指定できます。`<h2>`タグまでにしたい場合は`2`を指定します。
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
import yaml from '@rollup/plugin-yaml';
|
||||
import svgLoader from 'vite-svg-loader';
|
||||
import { cpus } from 'node:os';
|
||||
import genSitemap from './scripts/gen-sitemap';
|
||||
import { genApiTranslationFiles } from './scripts/gen-api-translations';
|
||||
import type { LocaleObject } from '@nuxtjs/i18n/dist/runtime/composables';
|
||||
import { genLocalesJson } from './scripts/gen-locales';
|
||||
import { getStaticEndpoints } from './scripts/get-static-endpoints';
|
||||
import type { NuxtConfig } from 'nuxt/schema';
|
||||
|
||||
// 公開時のドメイン(末尾スラッシュなし)
|
||||
const baseUrl = 'https://misskey-hub.net';
|
||||
@ -27,6 +27,41 @@ export type LocaleCodes = typeof localesConst[number]['code'];
|
||||
|
||||
export const locales = localesConst as unknown as LocaleObject[];
|
||||
|
||||
function getRouteRules(): NuxtConfig['routeRules'] {
|
||||
// 言語ごとに割り当てる必要のないRouteRules
|
||||
const staticRules: NuxtConfig['routeRules'] = {
|
||||
'/ja/blog/**': { isr: true },
|
||||
'/ns/': { prerender: true },
|
||||
};
|
||||
|
||||
// それぞれの言語について割り当てる必要のあるRouteRules
|
||||
const localeBasedRules: NuxtConfig['routeRules'] = {
|
||||
'/docs/**': { isr: true },
|
||||
};
|
||||
|
||||
// 静的ページをすべて追加
|
||||
getStaticEndpoints().forEach((route) => {
|
||||
if (!route.includes('ns')) {
|
||||
localeBasedRules[route] = { prerender: true };
|
||||
staticRules[route] = { prerender: true };
|
||||
}
|
||||
});
|
||||
|
||||
// 言語ごとにすべて割り当てていく
|
||||
const _localeBasedRules: NuxtConfig['routeRules'] = {};
|
||||
const localeCodes = locales.map((v) => v.code);
|
||||
Object.keys(localeBasedRules).forEach((route) => {
|
||||
localeCodes.forEach((code) => {
|
||||
_localeBasedRules[`/${code}${route}`] = localeBasedRules[route];
|
||||
});
|
||||
})
|
||||
|
||||
return {
|
||||
...staticRules,
|
||||
..._localeBasedRules,
|
||||
};
|
||||
}
|
||||
|
||||
export default defineNuxtConfig({
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
@ -114,17 +149,7 @@ export default defineNuxtConfig({
|
||||
],
|
||||
},
|
||||
nitro: {
|
||||
hooks: {
|
||||
'compiled': genSitemap,
|
||||
},
|
||||
prerender: {
|
||||
concurrency: cpus().length * 8 ?? 12,
|
||||
routes: [
|
||||
"/404.html"
|
||||
],
|
||||
// 【一時対応】とりあえずビルドできるようにする
|
||||
failOnError: false,
|
||||
},
|
||||
preset: 'vercel',
|
||||
plugins: [
|
||||
'@/server/plugins/appendComment.ts',
|
||||
'@/server/plugins/i18nRedirector.ts',
|
||||
@ -141,4 +166,5 @@ export default defineNuxtConfig({
|
||||
payloadExtraction: true,
|
||||
componentIslands: true,
|
||||
},
|
||||
})
|
||||
routeRules: getRouteRules(),
|
||||
});
|
||||
|
@ -17,9 +17,6 @@
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^20.9.1",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/rellax": "^1.7.7",
|
||||
"@types/three": "^0.158.2",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.5",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"bootstrap": "^5.3.2",
|
||||
@ -30,15 +27,12 @@
|
||||
"mfm-js": "^0.23.3",
|
||||
"misskey-js": "^0.0.16",
|
||||
"nprogress": "^0.2.0",
|
||||
"nuxt": "^3.8.1",
|
||||
"nuxt": "^3.8.2",
|
||||
"postcss": "^8.4.31",
|
||||
"rellax": "^1.12.1",
|
||||
"sass": "^1.69.5",
|
||||
"schema-dts": "^1.1.2",
|
||||
"sitemap": "^7.1.1",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"three": "^0.158.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"ufo": "^1.3.2",
|
||||
"vite-svg-loader": "^4.0.0"
|
||||
},
|
||||
|
@ -5,8 +5,8 @@
|
||||
<LeftIco class="mr-2 stroke-1 stroke-current" />
|
||||
</GNuxtLink>
|
||||
<p class="text-center mb-4">{{ $t('_blog.title') }}</p>
|
||||
<h1 class="text-center font-bold text-2xl lg:text-3xl mb-4">{{ data.title }}</h1>
|
||||
<p class="text-center">{{ $d(new Date(data.date)) }}</p>
|
||||
<h1 class="text-center font-bold text-2xl lg:text-3xl mb-4">{{ data?.title }}</h1>
|
||||
<p class="text-center">{{ $d(new Date(data?.date)) }}</p>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-slate-950 pb-12 lg:mt-12 pt-6 px-6">
|
||||
<div class="mx-auto container max-w-screen-md markdown-body">
|
||||
@ -23,6 +23,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import LeftIco from 'bi/arrow-left.svg';
|
||||
import { joinURL, parseURL } from 'ufo';
|
||||
import type { MiBlogParsedContent } from '~/types/content';
|
||||
// 日本語でしか提供されない
|
||||
defineI18nRoute({
|
||||
locales: ['ja'],
|
||||
@ -31,7 +33,16 @@ defineI18nRoute({
|
||||
const localePath = useLocalePath();
|
||||
|
||||
const route = useRoute();
|
||||
const { data } = await useAsyncData(`blog-${route.params.slug}`, () => queryContent(`/blog/${route.params.slug}`).findOne())
|
||||
const runtimeConfig = useRuntimeConfig();
|
||||
const { data } = await useAsyncData(`blog-${route.params.slug}`, () => queryContent<MiBlogParsedContent>(`/blog/${route.params.slug}`).findOne());
|
||||
|
||||
if (data.value?.thumbnail) {
|
||||
route.meta.thumbnail = (parseURL(data.value.thumbnail).host == null) ? joinURL(runtimeConfig.public.baseUrl, data.value.thumbnail) : data.value.thumbnail;
|
||||
}
|
||||
|
||||
if (!data.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'page not found' });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="grid docs-main">
|
||||
<div class="lg:hidden sticky top-16 -mx-6 -mt-6 overflow-y-auto bg-slate-50 dark:bg-slate-900 z-[9890] border-b dark:border-slate-700 text-sm flex items-start">
|
||||
<details v-if="data?.body && data.body.toc.links.length > 0" class="peer order-2 flex-grow flex-shrink-0" :open="openState">
|
||||
<details v-if="data?.body && data.body.toc?.links.length > 0" class="peer order-2 flex-grow flex-shrink-0" :open="openState">
|
||||
<summary class="py-4 cursor-pointer">
|
||||
{{ $t('_docs._toc.title') }}
|
||||
</summary>
|
||||
<div class="pb-4 px-6 max-h-[65vh] overflow-y-auto">
|
||||
<DocsTocLinks :links="data?.body.toc.links" :max-depth="data?.maxTocDepth ?? undefined" @child-click="openState = false" />
|
||||
<DocsTocLinks :links="data?.body.toc?.links" :max-depth="data?.maxTocDepth ?? undefined" @child-click="openState = false" />
|
||||
</div>
|
||||
</details>
|
||||
<button @click="isAsideNavOpen = !isAsideNavOpen" class="p-4 order-1 dark:border-slate-800 border-r peer-open:border-b mr-2">
|
||||
@ -42,6 +42,7 @@
|
||||
<script setup lang="ts">
|
||||
import AsideNavIco from 'bi/text-indent-left.svg';
|
||||
import ExtIco from 'bi/box-arrow-up-right.svg';
|
||||
import type { MiDocsParsedContent } from '~/types/content';
|
||||
|
||||
const isAsideNavOpen = useState<boolean>('miHub_docs_asideNav_openState', () => false);
|
||||
|
||||
@ -55,7 +56,7 @@ definePageMeta({
|
||||
const route = useRoute();
|
||||
const slugs = (route.params.slug as string[]).filter((v) => v !== '');
|
||||
|
||||
const { data } = await useAsyncData(`docs-${locale.value}-${slugs.join('-')}`, () => queryContent(`/${locale.value}/docs/${slugs.join('/')}`).findOne());
|
||||
const { data } = await useAsyncData(`docs-${locale.value}-${slugs.join('-')}`, () => queryContent<MiDocsParsedContent>(`/${locale.value}/docs/${slugs.join('/')}`).findOne());
|
||||
|
||||
if (!data.value) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'page not found' });
|
||||
|
1370
pnpm-lock.yaml
1370
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,9 @@ export default async function genSitemap(nitro: Nitro) {
|
||||
priority: .7,
|
||||
} as SitemapItem;
|
||||
});
|
||||
|
||||
if (routes.length === 0) return;
|
||||
|
||||
const smStream = new SitemapStream({ hostname: domain });
|
||||
Readable.from(routes).pipe(smStream);
|
||||
|
||||
|
29
scripts/get-static-endpoints.ts
Normal file
29
scripts/get-static-endpoints.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import fs from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
export function getStaticEndpoints(): string[] {
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const dir = resolve(`${__dirname}/../pages`);
|
||||
const files = getFiles(dir);
|
||||
const filtered = files
|
||||
.filter((file) => !file.includes('slug')) // exclude dynamic content
|
||||
.map((file) => file.split('pages')[1])
|
||||
.map((file) => file.replaceAll('\\', '/'))
|
||||
.map((file) => {
|
||||
return (file.endsWith('index.vue') ? file.replace(/\/index.vue$/, '') : file.split('.vue')[0]) + '/';
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* recursively get all files from /pages folder
|
||||
*/
|
||||
function getFiles(dir: string): string[] {
|
||||
const dirents = fs.readdirSync(dir, { withFileTypes: true });
|
||||
const files = dirents.map((dirent) => {
|
||||
const res = resolve(dir, dirent.name);
|
||||
return dirent.isDirectory() ? getFiles(res) : res;
|
||||
})
|
||||
return files.flat();
|
||||
}
|
29
types/content.ts
Normal file
29
types/content.ts
Normal file
@ -0,0 +1,29 @@
|
||||
// Misskey Docs Frontmatter Types
|
||||
import type { ParsedContent, MarkdownParsedContent } from '@nuxt/content/dist/runtime/types';
|
||||
|
||||
/**
|
||||
* Docs Frontmatter の型定義
|
||||
*
|
||||
* `/content/<lang>/docs/` のフロントマターはこの形式で入力してください
|
||||
*/
|
||||
export interface MiDocsParsedContent extends MarkdownParsedContent {
|
||||
/** もくじの見出しをさかのぼる限度 */
|
||||
maxTocDepth?: number;
|
||||
|
||||
/** 前へ・次へボタンの階層考慮を無視 */
|
||||
ignoreDirBasedNav?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blog Frontmatter の型定義
|
||||
*
|
||||
* `/content/blog/` のフロントマターはこの形式で入力してください
|
||||
*/
|
||||
export interface MiBlogParsedContent extends MarkdownParsedContent {
|
||||
/** サムネイル画像のURL・絶対パス */
|
||||
thumbnail?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Docs API の型定義
|
||||
*/
|
@ -1,15 +0,0 @@
|
||||
// Misskey Docs Frontmatter Types
|
||||
import type { ParsedContent, MarkdownParsedContent } from '@nuxt/content/dist/runtime/types';
|
||||
|
||||
/**
|
||||
* Docs Frontmatter の型定義
|
||||
*
|
||||
* `/content/<lang>/docs/` のフロントマターはこの形式で入力してください
|
||||
*/
|
||||
export interface MiDocsParsedContent extends MarkdownParsedContent {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Docs API の型定義
|
||||
*/
|
3
vercel.json
Normal file
3
vercel.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"trailingSlash": true
|
||||
}
|
Loading…
Reference in New Issue
Block a user