Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
NEXT_PUBLIC_SITE_NAME = 开源市集
NEXT_PUBLIC_SITE_SUMMARY =
NEXT_PUBLIC_LOGO = https://github.com/Open-Source-Bazaar.png

NEXT_PUBLIC_LARK_API_HOST = https://open.feishu.cn/open-apis/
NEXT_PUBLIC_LARK_APP_ID = cli_a8094a652022900d
NEXT_PUBLIC_LARK_WIKI_URL = https://open-source-bazaar.feishu.cn/wiki/space/7052192153363054596
87 changes: 61 additions & 26 deletions components/Navigator/MainNavigator.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,57 @@
import { observer } from 'mobx-react';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import { FC, useContext } from 'react';
import { Container, Nav, Navbar } from 'react-bootstrap';
import { AnchorHTMLAttributes, FC, useContext } from 'react';
import { Container, Image, Nav, Navbar, NavDropdown } from 'react-bootstrap';

import { DefaultImage } from '../../models/configuration';
import { i18n, I18nContext } from '../../models/Translation';

const LanguageMenu = dynamic(() => import('./LanguageMenu'), { ssr: false });

export type MenuItem = Record<'href' | 'name', string>;
export interface MenuItem extends Pick<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'title'> {
subs?: MenuItem[];
}

const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [
{ href: '/article/about', name: t('about') },
{ href: '/article/history', name: t('history') },
{ href: '/article/code-of-conduct', name: t('code_of_conduct') },
{ href: '/article/join-us', name: t('join_us') },
{
href: '/article/open-collaborator-award',
name: t('open_collaborator_award'),
title: t('about'),
subs: [
{ href: '/article/about', title: t('about') },
{ href: '/article/history', title: t('history') },
{ href: '/article/code-of-conduct', title: t('code_of_conduct') },
],
},
{
title: t('join_us'),
subs: [
{ href: '/article/join-us', title: t('join_us') },
{
href: '/article/open-collaborator-award',
title: t('open_collaborator_award'),
},
{ href: '/volunteer', title: t('volunteer') },
],
},
{
title: t('open_source_projects'),
subs: [
{ href: '/project', title: t('open_source_projects') },
{ href: '/issue', title: 'GitHub issues' },
{
href: 'https://github.com/Open-Source-Bazaar/Git-Hackathon-scaffold',
title: t('hackathon'),
},
{ href: '/license-filter', title: t('license_filter') },
],
},
{ href: '/volunteer', name: t('volunteer') },
{ href: '/project', name: t('open_source_projects') },
{ href: '/issue', name: 'GitHub issues' },
{
href: 'https://github.com/Open-Source-Bazaar/Git-Hackathon-scaffold',
name: t('hackathon'),
title: t('wiki'),
subs: [
{ href: '/wiki', title: t('wiki') },
{ href: '/policy', title: t('policy') },
],
},
{ href: '/license-filter', name: t('license_filter') },
{ href: '/policy', name: t('policy') },
];

export interface MainNavigatorProps {
Expand All @@ -44,21 +68,32 @@ export const MainNavigator: FC<MainNavigatorProps> = observer(({ menu }) => {
return (
<Navbar bg="dark" variant="dark" fixed="top" expand="lg">
<Container>
<Navbar.Brand href="/" className="fw-bolder">
<Navbar.Brand href="/" className="fw-bolder d-flex align-items-center gap-2">
<Image width={40} src={DefaultImage} alt={t('open_source_bazaar')} />
{t('open_source_bazaar')}
</Navbar.Brand>
<Navbar.Toggle aria-controls="navbarScroll" />
<Navbar.Collapse id="navbarScroll">
<Nav className="me-auto my-2 my-lg-0" navbarScroll>
{menu.map(({ href, name }) => (
<Nav.Link
key={`${href}-${name}`}
href={href}
className={pathname === `${href}` ? 'fw-bolder text-light' : ''}
>
{name}
</Nav.Link>
))}
{menu.map(({ href, title, subs }) =>
subs ? (
<NavDropdown key={title} title={title}>
{subs.map(({ href, title }) => (
<NavDropdown.Item key={href} href={href}>
{title}
</NavDropdown.Item>
))}
</NavDropdown>
) : (
<Nav.Link
key={`${href}-${title}`}
href={href}
className={pathname === `${href}` ? 'fw-bolder text-light' : ''}
>
{title}
</Nav.Link>
),
)}
</Nav>

<LanguageMenu />
Expand Down
15 changes: 15 additions & 0 deletions models/Wiki.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { Content, ContentModel } from 'mobx-github';
import { DocumentModel, WikiNodeModel } from 'mobx-lark';
import { DataObject } from 'mobx-restful';

import { lark } from '../pages/api/Lark/core';
import './Base';
import { LarkWikiDomain, LarkWikiId } from './configuration';

export interface XContent extends Content {
meta?: DataObject;
children?: XContent[];
}

export const policyContentStore = new ContentModel('fpsig', 'open-source-policy');

export class MyWikiNodeModel extends WikiNodeModel {
client = lark.client;
}

export const wikiStore = new MyWikiNodeModel(LarkWikiDomain, LarkWikiId);

export class MyDocumentModel extends DocumentModel {
client = lark.client;
}

export const documentStore = new MyDocumentModel(LarkWikiDomain);
4 changes: 4 additions & 0 deletions models/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ export const LarkAppMeta = {
id: process.env.NEXT_PUBLIC_LARK_APP_ID!,
secret: process.env.LARK_APP_SECRET!,
};
const { hostname, pathname } = new URL(process.env.NEXT_PUBLIC_LARK_WIKI_URL!);

export const LarkWikiDomain = hostname;
export const LarkWikiId = pathname.split('/').pop()!;
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"private": true,
"scripts": {
"prepare": "husky",
"install": "pnpx git-utility download https://github.com/Open-Source-Bazaar/key-vault main Open-Source-Bazaar.github.io || true",
"dev": "next dev",
"build": "next build",
"start": "next start",
Expand All @@ -13,8 +14,8 @@
},
"dependencies": {
"@koa/router": "^14.0.0",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@mdx-js/loader": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@next/mdx": "^15.5.2",
"core-js": "^3.45.1",
"file-type": "^21.0.0",
Expand All @@ -27,7 +28,7 @@
"mobx": "^6.13.7",
"mobx-github": "^0.4.0",
"mobx-i18n": "^0.7.1",
"mobx-lark": "^2.4.0",
"mobx-lark": "^2.4.1",
"mobx-react": "^9.2.0",
"mobx-react-helper": "^0.5.1",
"mobx-restful": "^2.1.0",
Expand All @@ -47,11 +48,11 @@
"@babel/plugin-proposal-decorators": "^7.28.0",
"@babel/plugin-transform-typescript": "^7.28.0",
"@babel/preset-react": "^7.27.1",
"@cspell/eslint-plugin": "^9.2.0",
"@cspell/eslint-plugin": "^9.2.1",
"@eslint/js": "^9.34.0",
"@next/eslint-plugin-next": "^15.5.2",
"@softonus/prettier-plugin-duplicate-remover": "^1.1.2",
"@stylistic/eslint-plugin": "^5.2.3",
"@stylistic/eslint-plugin": "^5.3.1",
"@types/eslint-config-prettier": "^6.11.3",
"@types/koa": "^3.0.0",
"@types/koa__router": "^12.0.4",
Expand All @@ -69,13 +70,13 @@
"jiti": "^2.5.1",
"less": "^4.4.1",
"less-loader": "^12.3.0",
"lint-staged": "^16.1.5",
"lint-staged": "^16.1.6",
"next-with-less": "^3.0.1",
"prettier": "^3.6.2",
"prettier-plugin-css-order": "^2.1.2",
"sass": "^1.91.0",
"typescript": "~5.9.2",
"typescript-eslint": "^8.41.0"
"typescript-eslint": "^8.42.0"
},
"resolutions": {
"next": "$next"
Expand Down
56 changes: 56 additions & 0 deletions pages/wiki/[node_token].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Block, renderBlocks, WikiNode } from 'mobx-lark';
import { GetStaticPaths, GetStaticProps } from 'next';
import { FC } from 'react';
import { Container } from 'react-bootstrap';
import { Minute, Second } from 'web-utility';

import { PageHead } from '../../components/Layout/PageHead';
import { documentStore, wikiStore } from '../../models/Wiki';
import { lark } from '../api/Lark/core';

export const getStaticPaths: GetStaticPaths = async () => {
await lark.getAccessToken();

const nodes = await wikiStore.getAll();

return {
paths: nodes.map(({ node_token }) => ({ params: { node_token } })),
fallback: 'blocking',
};
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
await lark.getAccessToken();

const node = await wikiStore.getOne(params!.node_token as string);

if (node?.obj_type !== 'docx') return { notFound: true };

try {
const blocks = await documentStore.getOneBlocks(
node.obj_token,
token => `/api/Lark/file/${token}/placeholder`,
);

return { props: { node, blocks } };
} catch (error) {
console.error(error);

return { notFound: true, revalidate: Minute / Second };
}
};

interface WikiDocumentPageProps {
node: WikiNode;
blocks: Block<any, any, any>[];
}

const WikiDocumentPage: FC<WikiDocumentPageProps> = ({ node, blocks }) => (
<Container>
<PageHead title={node.title} />

{renderBlocks(blocks)}
</Container>
);

export default WikiDocumentPage;
53 changes: 53 additions & 0 deletions pages/wiki/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { WikiNode } from 'mobx-lark';
import { observer } from 'mobx-react';
import { GetStaticProps } from 'next';
import { FC, useContext } from 'react';
import { Container } from 'react-bootstrap';
import { treeFrom } from 'web-utility';

import { PageHead } from '../../components/Layout/PageHead';
import { I18nContext } from '../../models/Translation';
import { wikiStore } from '../../models/Wiki';
import { lark } from '../api/Lark/core';

export const getStaticProps: GetStaticProps = async () => {
await lark.getAccessToken();

const nodes = await wikiStore.getAll();

return { props: { nodes } };
};

interface XWikiNode extends WikiNode {
// eslint-disable-next-line no-restricted-syntax
children?: XWikiNode[];
}

const renderTree = (children?: XWikiNode[]) =>
children && (
<ol>
{children.map(({ node_token, title, children }) => (
<li key={node_token}>
<a href={`/wiki/${node_token}`}>{title}</a>

{renderTree(children)}
</li>
))}
</ol>
);

const WikiIndexPage: FC<{ nodes: XWikiNode[] }> = observer(({ nodes }) => {
const { t } = useContext(I18nContext);

return (
<Container>
<PageHead title={t('wiki')} />

<h1>{t('wiki')}</h1>

{renderTree(treeFrom(nodes, 'node_token', 'parent_node_token', 'children'))}
</Container>
);
});

export default WikiIndexPage;
Loading