Skip to content
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# documentation
/public/docs/**/sections.*
/public/docs/**/termDefinitions.*
/public/docs/languages.*
/public/docs/revisions-dev.yml
/public/docs/commits-dev.yml
Expand Down
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ Cloudflare Worker のビルドログとステータス表示が見れますが
- このセクションに関連する、想定される質問例。
# question: 自体を省略すると、GitHub ActionがGeminiを呼び出して質問例を生成しpushします。
# 質問例が不要な場合は question: [] にしてください。
term:
- キーワード
- きーわーど
# 他のページで [[キーワード]] と書いた場合に、このセクションの内容がポップアップで表示される。
```
* コード例はそれが配置されているセクションの内容と関連するようにする。
````md
Expand Down Expand Up @@ -185,7 +189,28 @@ Cloudflare Worker のビルドログとステータス表示が見れますが
````
- 練習問題のファイル名は不都合がなければ `practice(章番号)_(問題番号).拡張子` で統一。空でもよいのでファイルコードブロックとexecコードブロックを置く

## markdown仕様
### 内部リンク

```
[[用語]]
```
と書くと、`term: - 用語` が定義されている別のセクションへのリンクになり、ポップアップでその内容も表示されます。

markdown書式も利用可能です。 `[[ほげ**ふが**]]` は `term: - ほげふが` へのリンクになり、表示は「<ins>ほげ**ふが**</ins>」になります。ただし `**[[ふが]]**` はリンクの色が優先されるのに対し `[[**ふが**]]` は強調の色が優先されます。

```
[[./1-foo]]
[[./1]]
```
と書くと、同じ言語のページID `1-foo` またはindex `1` (ページのindexはindex.ymlの記述順で0-based) へのリンクになり、表示は「<ins>第1章</ins>」になります

```
[[./prev]]
[[./next]]
```
と書くと、同じ言語の前のページ・次のページへのリンクになり、表示は「<ins>第1章</ins>」(実際の章番号)になります

### コード実行環境仕様

実行環境の説明は ./packages/runtime/README.md を参照

Expand Down
24 changes: 17 additions & 7 deletions app/(docs)/@chat/chat/[chatId]/chatArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ import { ChatAreaStateUpdater } from "@/(docs)/chatAreaState";
import { useStreamingChatContext } from "@/(docs)/streamingChatContext";
import { deleteChatAction } from "@/actions/deleteChat";
import { ChatWithMessages } from "@/lib/chatHistory";
import { LanguageEntry, MarkdownSection, PageEntry } from "@/lib/docs";
import {
LangId,
LanguageEntry,
MarkdownSection,
PageEntry,
PageSlug,
} from "@/lib/docs";
import { Heading } from "@/markdown/heading";
import { StyledMarkdown } from "@/markdown/markdown";
import { usePagesListForLang } from "@/pagesListContext";
import clsx from "clsx";
import Link from "next/link";
import { useRouter } from "next/navigation";
Expand Down Expand Up @@ -70,12 +77,15 @@ export function ChatAreaContainer(props: {
interface Props {
chatId: string;
chatData: ChatWithMessages;
targetLang: LanguageEntry | undefined;
targetPage: PageEntry | undefined;
langId: LangId;
pageSlug: PageSlug;
targetSection: MarkdownSection | undefined;
}
export function ChatAreaContent(props: Props) {
const { chatId, chatData, targetLang, targetPage, targetSection } = props;
const { chatId, chatData, langId, pageSlug, targetSection } = props;

const langEntry = usePagesListForLang(langId);
const pageEntry = langEntry?.pages.find((p) => p.slug === pageSlug);

const messagesAndDiffs = [
...chatData.messages.map((msg) => ({ type: "message" as const, ...msg })),
Expand All @@ -97,13 +107,13 @@ export function ChatAreaContent(props: Props) {
<div className="flex-none breadcrumbs text-sm">
<ul className="flex-wrap">
<li>
<Link href={`/${targetLang?.id}/${targetLang?.pages[0].slug}`}>
{targetLang?.name}
<Link href={`/${langId}/${langEntry?.pages[0].slug}`}>
{langEntry?.name}
</Link>
</li>
<li>
<Link href={`/${chatData.section.pagePath}`}>
{targetPage?.index}. {targetPage?.name}
{pageEntry?.index}. {pageEntry?.name}
</Link>
</li>
<li>
Expand Down
26 changes: 12 additions & 14 deletions app/(docs)/@chat/chat/[chatId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import {
getChatOne,
initContext,
} from "@/lib/chatHistory";
import { getMarkdownSections, getPagesList } from "@/lib/docs";
import {
getMarkdownSections,
LangId,
PageSlug,
} from "@/lib/docs";
import { ChatAreaContainer, ChatAreaContent } from "./chatArea";
import { cacheLife, cacheTag } from "next/cache";
import { isCloudflare } from "@/lib/detectCloudflare";
Expand All @@ -29,26 +33,20 @@ export default async function ChatPage({
);
}

const pagesList = await getPagesList();
const targetLang = pagesList.find(
(lang) => lang.id === chatData.section.pagePath.split("/")[0]
);
const targetPage = targetLang?.pages.find(
(page) => page.slug === chatData.section.pagePath.split("/")[1]
);
const sections =
targetLang && targetPage
? await getMarkdownSections(targetLang.id, targetPage.slug)
: [];
const [langId, pageSlug] = chatData.section.pagePath.split("/") as [
LangId,
PageSlug,
];
const sections = await getMarkdownSections(langId, pageSlug);
const targetSection = sections.find((sec) => sec.id === chatData.sectionId);

return (
<ChatAreaContainer chatId={chatId}>
<ChatAreaContent
chatId={chatId}
chatData={chatData}
targetLang={targetLang}
targetPage={targetPage}
langId={langId}
pageSlug={pageSlug}
targetSection={targetSection}
/>
</ChatAreaContainer>
Expand Down
40 changes: 19 additions & 21 deletions app/(docs)/@docs/[lang]/[pageId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
} from "@/lib/chatHistory";
import {
getMarkdownSections,
getPagesList,
getPagesListForLang,
getTermDefinitions,
LangId,
PagePath,
PageSlug,
Expand All @@ -18,15 +19,15 @@ import { cacheLife, cacheTag } from "next/cache";
import { isCloudflare } from "@/lib/detectCloudflare";
import { DocsAutoRedirect } from "./autoRedirect";
import { dateReviver } from "@/lib/dateReviver";
import { TermDefinitionProvider } from "@/markdown/term";

export async function generateMetadata({
params,
}: {
params: Promise<{ lang: LangId; pageId: PageSlug }>;
}): Promise<Metadata> {
const { lang, pageId } = await params;
const pagesList = await getPagesList();
const langEntry = pagesList.find((l) => l.id === lang);
const langEntry = await getPagesListForLang(lang);
const pageEntry = langEntry?.pages.find((p) => p.slug === pageId);
if (!langEntry || !pageEntry) notFound();

Expand All @@ -45,15 +46,6 @@ export default async function Page({
params: Promise<{ lang: LangId; pageId: PageSlug }>;
}) {
const { lang, pageId } = await params;
const pagesList = await getPagesList();
const langEntry = pagesList.find((l) => l.id === lang);
const pageEntryIndex =
langEntry?.pages.findIndex((p) => p.slug === pageId) ?? -1;
const pageEntry = langEntry?.pages[pageEntryIndex];
if (!langEntry || !pageEntry) notFound();

const prevPage = langEntry.pages[pageEntryIndex - 1];
const nextPage = langEntry.pages[pageEntryIndex + 1];

// server componentなのでuseMemoいらない
const path = { lang: lang, page: pageId };
Expand All @@ -62,17 +54,23 @@ export default async function Page({
const context = await initContext();
const chatHistories = await getChatFromCache(path, context.userId);

const termDefinitions = await getTermDefinitions(lang);

return (
<>
<PageContent
chatHistories={chatHistories}
splitMdContent={sections}
langEntry={langEntry}
pageEntry={pageEntry}
prevPage={prevPage}
nextPage={nextPage}
path={path}
/>
<TermDefinitionProvider
termDefinitions={termDefinitions}
lang={lang}
page={pageId}
>
<PageContent
chatHistories={chatHistories}
splitMdContent={sections}
langId={lang}
pageSlug={pageId}
path={path}
/>
</TermDefinitionProvider>
<DocsAutoRedirect path={path} />
</>
);
Expand Down
29 changes: 18 additions & 11 deletions app/(docs)/@docs/[lang]/[pageId]/pageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,35 @@ import clsx from "clsx";
import { PageTransition } from "./pageTransition";
import {
DynamicMarkdownSection,
LanguageEntry,
LangId,
MarkdownSection,
PageEntry,
PagePath,
PageSlug,
SectionId,
} from "@/lib/docs";
import { Heading } from "@/markdown/heading";
import Link from "next/link";
import { useChatId } from "@/(docs)/chatAreaState";
import { ChatWithMessages } from "@/lib/chatHistory";
import { usePagesListForLang } from "@/pagesListContext";

interface PageContentProps {
splitMdContent: MarkdownSection[];
langEntry: LanguageEntry;
pageEntry: PageEntry;
prevPage?: PageEntry;
nextPage?: PageEntry;
langId: LangId;
pageSlug: PageSlug;
path: PagePath;
chatHistories: ChatWithMessages[];
}
export function PageContent(props: PageContentProps) {
const { setSidebarMdContent } = useSidebarMdContext();
const { splitMdContent, pageEntry, path, chatHistories } = props;
const { splitMdContent, langId, pageSlug, path, chatHistories } = props;

const langEntry = usePagesListForLang(langId);
const pageEntryIndex =
langEntry?.pages.findIndex((p) => p.slug === pageSlug) ?? -1;
const pageEntry = langEntry?.pages[pageEntryIndex];
const prevPage = langEntry?.pages[pageEntryIndex - 1];
const nextPage = langEntry?.pages[pageEntryIndex + 1];

const [sectionInView, setSectionInView] = useState<boolean[]>([]);
const sectionRefs = useRef<Array<HTMLDivElement | null>>([]);
Expand Down Expand Up @@ -143,7 +149,7 @@ export function PageContent(props: PageContentProps) {
}}
>
<Heading className="max-w-docs" level={1}>
第{pageEntry.index}章: {pageEntry.title}
第{pageEntry?.index}章: {pageEntry?.title}
</Heading>
<div />
{dynamicMdContent.map((section, index) => (
Expand All @@ -159,6 +165,7 @@ export function PageContent(props: PageContentProps) {
<StyledMarkdown
content={section.replacedContent}
replacedRange={section.replacedRange}
interactive
/>
</div>
<div>
Expand All @@ -172,8 +179,8 @@ export function PageContent(props: PageContentProps) {
))}
<PageTransition
lang={path.lang}
prevPage={props.prevPage}
nextPage={props.nextPage}
prevPage={prevPage}
nextPage={nextPage}
/>
<div />
</div>
Expand All @@ -183,7 +190,7 @@ export function PageContent(props: PageContentProps) {
<div className="fixed bottom-4 right-4 left-4 has-sidebar:left-[calc(var(--container-sidebar)+1rem)] z-40">
<ChatForm
path={path}
langName={props.langEntry.name}
langName={langEntry?.name ?? ""}
sectionContent={dynamicMdContent}
close={() => setIsFormVisible(false)}
/>
Expand Down
5 changes: 2 additions & 3 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import {
DynamicMarkdownSectionSchema,
getMarkdownSections,
getPagesList,
getPagesListForLang,
introSectionId,
PagePathSchema,
SectionId,
Expand Down Expand Up @@ -57,8 +57,7 @@ export async function POST(request: NextRequest) {
execResults,
} = parseResult.data;

const pagesList = await getPagesList();
const langEntry = pagesList.find((lang) => lang.id === path.lang);
const langEntry = await getPagesListForLang(path.lang);
const langName = langEntry?.name ?? path.lang;
let targetPath = path;
let targetSectionContent = sectionContent;
Expand Down
21 changes: 13 additions & 8 deletions app/daisyAlertIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import clsx from "clsx";

// https://daisyui.com/components/alert/ のコード例のsvgのコピペ

export function DaisyInfoIcon() {
/**
* 色を変える場合は <DaisyInfoIcon className="text-info" /> のように使う
*/
export function DaisyInfoIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={clsx("h-6 w-6 shrink-0 stroke-current", className)}
fill="none"
viewBox="0 0 24 24"
className="stroke-info h-6 w-6 shrink-0"
>
<path
strokeLinecap="round"
Expand All @@ -18,11 +23,11 @@ export function DaisyInfoIcon() {
);
}

export function DaisySuccessIcon() {
export function DaisySuccessIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 shrink-0 stroke-current"
className={clsx("h-6 w-6 shrink-0 stroke-current", className)}
fill="none"
viewBox="0 0 24 24"
>
Expand All @@ -36,11 +41,11 @@ export function DaisySuccessIcon() {
);
}

export function DaisyWarningIcon() {
export function DaisyWarningIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 shrink-0 stroke-current"
className={clsx("h-6 w-6 shrink-0 stroke-current", className)}
fill="none"
viewBox="0 0 24 24"
>
Expand All @@ -54,11 +59,11 @@ export function DaisyWarningIcon() {
);
}

export function DaisyErrorIcon() {
export function DaisyErrorIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 shrink-0 stroke-current"
className={clsx("h-6 w-6 shrink-0 stroke-current", className)}
fill="none"
viewBox="0 0 24 24"
>
Expand Down
1 change: 1 addition & 0 deletions app/featureCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ Pythonは整数 (\`int\`) と浮動小数点数 (\`float\`) を区別します
浮動小数点数 (\`float\`) は、他の言語の double 型に相当する倍精度浮動小数点数です。
`}
replacedRange={[{ start: 64, end: 120, id: "sample" }]}
interactive
/>
</div>
<div className={clsx(docsReplaced && "hidden")}>
Expand Down
Loading