Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/node_modules
/.pnp
.pnp.js
package-lock.json

# testing
/coverage
Expand Down
2 changes: 1 addition & 1 deletion components/Git/Issue/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const IssueCard: FC<IssueCardProps> = ({
</Card.Header>
<Card.Body
as="article"
dangerouslySetInnerHTML={{ __html: marked(body || '') }}
dangerouslySetInnerHTML={{ __html: marked(body || '') as string }}
/>
<Card.Footer className="d-flex justify-content-between align-items-center">
{user && <Nameplate name={user.name || ''} avatar={user.avatar_url} />}
Expand Down
84 changes: 84 additions & 0 deletions models/Wiki.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Issue, RepositoryModel } from 'mobx-github';
import { Filter, ListModel } from 'mobx-restful';
import { buildURLData } from 'web-utility';

import { githubClient, GithubSearchData, makeGithubSearchCondition } from './Base';

export interface WikiNode {
id: number;
title: string;
body: string;
html_url: string;
created_at: string;
updated_at: string;
labels: string[];
number: number;
repository?: {
name: string;
full_name: string;
};
}

export type WikiFilter = Filter<WikiNode>;

export class WikiModel extends ListModel<WikiNode, WikiFilter> {
baseURI = 'search/issues';
client = githubClient;

async loadPage(
page = this.pageIndex,
per_page = this.pageSize,
filter: WikiFilter = {},
) {
// Search for issues labeled as 'wiki' or 'policy' in the Open-Source-Bazaar organization
const searchQuery = [
'org:Open-Source-Bazaar',
'is:issue',
'label:wiki OR label:policy',
'state:open',
].join(' ');

const { body } = await this.client.get<GithubSearchData<Issue>>(
`${this.baseURI}?${buildURLData({
page,
per_page,
q: searchQuery,
sort: 'updated',
order: 'desc'
})}`,
);

const items: WikiNode[] = body!.items.map(issue => ({
id: issue.id,
title: issue.title,
body: issue.body || '',
html_url: issue.html_url,
created_at: issue.created_at,
updated_at: issue.updated_at,
labels: issue.labels.map(label => typeof label === 'string' ? label : label.name || ''),
number: issue.number,
repository: issue.repository ? {
name: issue.repository.name,
full_name: issue.repository.full_name,
} : undefined,
}));

return { pageData: items, totalCount: body!.total_count };
}

async getAll(): Promise<WikiNode[]> {
const { pageData } = await this.loadPage(1, 100);
return pageData;
}

async getOne(id: string | number): Promise<WikiNode> {
const all = await this.getAll();
const found = all.find(item => item.id.toString() === id.toString() || item.number.toString() === id.toString());
if (!found) {
throw new Error(`Wiki node with id ${id} not found`);
}
return found;
}
}

export const wikiStore = new WikiModel();
Comment thread
TechQuery marked this conversation as resolved.
Outdated
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"marked": "^16.2.0",
"mime": "^4.0.7",
"mobx": "^6.13.7",
"mobx-github": "^0.3.11",
"mobx-github": "^0.4.0-rc.1",
"mobx-i18n": "^0.7.1",
"mobx-lark": "^2.4.0",
"mobx-react": "^9.2.0",
Expand Down
153 changes: 126 additions & 27 deletions pages/wiki/[...slug].tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,150 @@
import { marked } from 'marked';
import { DataObject } from 'mobx-restful';
import { GetStaticPaths, GetStaticProps, NextPage } from 'next';
import { GetStaticPaths, GetStaticProps } from 'next';
import Link from 'next/link';
import { ParsedUrlQuery } from 'querystring';
import { FC } from 'react';
import { Badge, Container } from 'react-bootstrap';

import { pageListOf, splitFrontMatter, traverseTree } from '../api/core';
import { PageHead } from '../../components/Layout/PageHead';
import { WikiNode, wikiStore } from '../../models/Wiki';

interface WikiPageParams extends ParsedUrlQuery {
slug: string[];
}

export const getStaticPaths: GetStaticPaths<WikiPageParams> = async () => {
const tree = await Array.fromAsync(pageListOf('wiki', 'public/'));
const list = tree.map(root => [...traverseTree(root, 'subs')]).flat();
const paths = list
.map(({ path }) => path && { params: { slug: path.split('/') } })
.filter(Boolean) as { params: WikiPageParams }[];
try {
const nodes = await wikiStore.getAll();
const paths = nodes.map(({ id }) => ({
params: { slug: [id.toString()] }
}));

return { paths, fallback: 'blocking' };
return { paths, fallback: 'blocking' };
} catch (error) {
console.error('Failed to generate static paths:', error);

return { paths: [], fallback: 'blocking' };
}
Comment thread
TechQuery marked this conversation as resolved.
Outdated
};

interface WikiPageProps {
meta?: DataObject;
node?: WikiNode;
markup: string;
}

export const getStaticProps: GetStaticProps<WikiPageProps, WikiPageParams> = async ({ params }) => {
const { slug } = params!;
// https://github.com/vercel/next.js/issues/12851
if (slug[0] !== 'wiki') slug.unshift('wiki');
const [nodeId] = slug;

try {
const node = await wikiStore.getOne(nodeId);

if (!node) {
return { notFound: true };
}

const markup = marked(node.body || '') as string;

return {
props: { node, markup },
revalidate: 300 // Revalidate every 5 minutes
};
} catch (error) {
console.error('Failed to load wiki node:', error);

return { notFound: true };
}
};

const WikiPage: FC<WikiPageProps> = ({ node, markup }) => {
if (!node) {
return (
<Container className="py-4">
<div className="text-center">
<h2>Wiki 页面未找到</h2>
<p>请检查页面链接是否正确。</p>
<Link href="/wiki" className="btn btn-primary">
返回 Wiki 首页
</Link>
</div>
</Container>
);
}

return (
Comment thread
TechQuery marked this conversation as resolved.
Outdated
<Container className="py-4">
<PageHead title={node.title} />

<nav aria-label="breadcrumb" className="mb-4">
<ol className="breadcrumb">
<li className="breadcrumb-item">
<Link href="/wiki">Wiki</Link>
</li>
<li className="breadcrumb-item active" aria-current="page">
{node.title}
</li>
</ol>
</nav>
Comment thread
TechQuery marked this conversation as resolved.
Outdated

<article>
<header className="mb-4">
<h1>{node.title}</h1>

<div className="d-flex flex-wrap align-items-center gap-3 mb-3">
<div>
{node.labels.map(label => (
<Badge key={label} bg="secondary" className="me-1">
{label}
</Badge>
))}
</div>
Comment thread
TechQuery marked this conversation as resolved.
Outdated

{node.repository && (
<small className="text-muted">
来源: {node.repository.full_name}
</small>
)}
</div>

const { meta, markdown } = await splitFrontMatter(`public/${slug.join('/')}.md`);
<div className="d-flex justify-content-between align-items-center text-muted small">
<div>
创建于: {new Date(node.created_at).toLocaleDateString('zh-CN')}
{node.updated_at !== node.created_at && (
<> | 更新于: {new Date(node.updated_at).toLocaleDateString('zh-CN')}</>
)}
</div>
Comment thread
TechQuery marked this conversation as resolved.
Outdated

<div>
<a
className="btn btn-sm btn-outline-primary"
href={node.html_url}
rel="noopener noreferrer"
target="_blank"
>
在 GitHub 编辑
</a>
</div>
Comment thread
TechQuery marked this conversation as resolved.
Outdated
</div>
</header>

const markup = marked(markdown) as string;
<div
dangerouslySetInnerHTML={{ __html: markup }}
className="wiki-content"
/>
</article>

return { props: JSON.parse(JSON.stringify({ meta, markup })) };
<footer className="mt-5 pt-4 border-top">
<div className="text-center">
<p className="text-muted">
这是一个基于 GitHub Issues 的 Wiki 页面。
<a href={node.html_url} target="_blank" rel="noopener noreferrer" className="ms-2">
在 GitHub 上查看或编辑此内容
</a>
</p>
</div>
</footer>
</Container>
);
};

const WikiPage: NextPage<WikiPageProps> = ({ meta, markup }) => (
<>
{meta && (
<blockquote>
<a target="_blank" href={meta.url} rel="noreferrer">
{meta.url}
</a>
</blockquote>
)}
<article dangerouslySetInnerHTML={{ __html: markup }} />
</>
);
export default WikiPage;
Loading
Loading