feat: add basic webui
This commit is contained in:
89
packages/cms/collections.ts
Normal file
89
packages/cms/collections.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { defineCollection, defineConfig } from '@content-collections/core';
|
||||
import { compileMDX } from '@content-collections/mdx';
|
||||
import {
|
||||
type RehypeCodeOptions,
|
||||
rehypeCode,
|
||||
remarkGfm,
|
||||
remarkHeading,
|
||||
} from 'fumadocs-core/mdx-plugins';
|
||||
import readingTime from 'reading-time';
|
||||
import { sqip } from 'sqip';
|
||||
|
||||
const rehypeCodeOptions: RehypeCodeOptions = {
|
||||
themes: {
|
||||
light: 'catppuccin-mocha',
|
||||
dark: 'catppuccin-mocha',
|
||||
},
|
||||
};
|
||||
|
||||
const posts = defineCollection({
|
||||
name: 'posts',
|
||||
directory: 'content/blog',
|
||||
include: '**/*.mdx',
|
||||
schema: (z) => ({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
date: z.string(),
|
||||
image: z.string(),
|
||||
authors: z.array(z.string()),
|
||||
tags: z.array(z.string()),
|
||||
}),
|
||||
transform: async ({ title, ...page }, context) => {
|
||||
const body = await context.cache(page.content, async () =>
|
||||
compileMDX(context, page, {
|
||||
remarkPlugins: [remarkGfm, remarkHeading],
|
||||
rehypePlugins: [[rehypeCode, rehypeCodeOptions]],
|
||||
})
|
||||
);
|
||||
|
||||
const blur = await context.cache(page._meta.path, async () =>
|
||||
sqip({
|
||||
input: `./public/${page.image}`,
|
||||
plugins: [
|
||||
'sqip-plugin-primitive',
|
||||
'sqip-plugin-svgo',
|
||||
'sqip-plugin-data-uri',
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
const blurResult = Array.isArray(blur) ? blur[0] : blur;
|
||||
|
||||
return {
|
||||
...page,
|
||||
_title: title,
|
||||
_slug: page._meta.path,
|
||||
body,
|
||||
image: page.image,
|
||||
imageBlur: blurResult.metadata.dataURIBase64 as string,
|
||||
readingTime: readingTime(page.content).text,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const legals = defineCollection({
|
||||
name: 'legal',
|
||||
directory: 'content/legal',
|
||||
include: '**/*.mdx',
|
||||
schema: (z) => ({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
date: z.string(),
|
||||
}),
|
||||
transform: async ({ title, ...page }, context) => {
|
||||
const body = await context.cache(page.content, async () =>
|
||||
compileMDX(context, page)
|
||||
);
|
||||
|
||||
return {
|
||||
...page,
|
||||
_title: title,
|
||||
_slug: page._meta.path,
|
||||
body,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default defineConfig({
|
||||
collections: [posts, legals],
|
||||
});
|
||||
10
packages/cms/components/body.tsx
Normal file
10
packages/cms/components/body.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { MDXContent } from '@content-collections/mdx/react';
|
||||
import type { ComponentProps } from 'react';
|
||||
|
||||
type BodyProperties = Omit<ComponentProps<typeof MDXContent>, 'code'> & {
|
||||
content: ComponentProps<typeof MDXContent>['code'];
|
||||
};
|
||||
|
||||
export const Body = ({ content, ...props }: BodyProperties) => (
|
||||
<MDXContent {...props} code={content} />
|
||||
);
|
||||
1
packages/cms/components/image.tsx
Normal file
1
packages/cms/components/image.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Image } from 'next/image';
|
||||
31
packages/cms/components/toc.tsx
Normal file
31
packages/cms/components/toc.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { getTableOfContents } from 'fumadocs-core/server';
|
||||
|
||||
type TableOfContentsProperties = {
|
||||
data: string;
|
||||
};
|
||||
|
||||
export const TableOfContents = async ({
|
||||
data,
|
||||
}: TableOfContentsProperties) => {
|
||||
const toc = await getTableOfContents(data);
|
||||
|
||||
return (
|
||||
<ul className="flex list-none flex-col gap-2 text-sm">
|
||||
{toc.map((item) => (
|
||||
<li
|
||||
key={item.url}
|
||||
style={{
|
||||
paddingLeft: `${item.depth - 2}rem`,
|
||||
}}
|
||||
>
|
||||
<a
|
||||
href={item.url}
|
||||
className="line-clamp-3 flex rounded-sm text-foreground text-sm underline decoration-foreground/0 transition-colors hover:decoration-foreground/50"
|
||||
>
|
||||
{item.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
1
packages/cms/components/toolbar.tsx
Normal file
1
packages/cms/components/toolbar.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export const Toolbar = () => null;
|
||||
23
packages/cms/index.ts
Normal file
23
packages/cms/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { allPosts, allLegals } from 'content-collections';
|
||||
|
||||
export const blog = {
|
||||
postsQuery: null,
|
||||
latestPostQuery: null,
|
||||
postQuery: (slug: string) => null,
|
||||
getPosts: async () => allPosts,
|
||||
getLatestPost: async () =>
|
||||
allPosts.sort((a, b) => a.date.getTime() - b.date.getTime()).at(0),
|
||||
getPost: async (slug: string) =>
|
||||
allPosts.find(({ _meta }) => _meta.path === slug),
|
||||
};
|
||||
|
||||
export const legal = {
|
||||
postsQuery: null,
|
||||
latestPostQuery: null,
|
||||
postQuery: (slug: string) => null,
|
||||
getPosts: async () => allLegals,
|
||||
getLatestPost: async () =>
|
||||
allLegals.sort((a, b) => a.date.getTime() - b.date.getTime()).at(0),
|
||||
getPost: async (slug: string) =>
|
||||
allLegals.find(({ _meta }) => _meta.path === slug),
|
||||
};
|
||||
1
packages/cms/next-config.ts
Normal file
1
packages/cms/next-config.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { withContentCollections as withCMS } from '@content-collections/next';
|
||||
30
packages/cms/package.json
Normal file
30
packages/cms/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@konobangu/cms",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "content-collections build",
|
||||
"analyze": "content-collections build",
|
||||
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@content-collections/mdx": "^0.2.0",
|
||||
"@content-collections/core": "^0.8.0",
|
||||
"@content-collections/next": "^0.2.4",
|
||||
"@konobangu/env": "workspace:*",
|
||||
"acorn": "^8.14.0",
|
||||
"fumadocs-core": "^14.6.0",
|
||||
"react": "^19.0.0",
|
||||
"reading-time": "^1.5.0",
|
||||
"sqip": "1.0.0-alpha.51"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@content-collections/cli": "^0.1.6",
|
||||
"@konobangu/typescript-config": "workspace:*",
|
||||
"@types/node": "22.10.1",
|
||||
"@types/react": "19.0.1",
|
||||
"@types/react-dom": "^19.0.2",
|
||||
"next": "^15.1.3"
|
||||
}
|
||||
}
|
||||
8
packages/cms/tsconfig.json
Normal file
8
packages/cms/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@konobangu/typescript-config/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", "../../apps/web/content-collections.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
7
packages/cms/typescript-config.json
Normal file
7
packages/cms/typescript-config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"content-collections": ["./.content-collections/generated"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user