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
67 changes: 55 additions & 12 deletions app/[docs_id]/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import remarkGfm from "remark-gfm";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { PythonEmbeddedTerminal } from "../terminal/python/embedded";
import { Heading } from "./section";
import { EditorComponent } from "../terminal/editor";
import { ExecFile } from "../terminal/exec";
import { AceLang, EditorComponent } from "../terminal/editor";
import { ExecFile, ExecLang } from "../terminal/exec";

export function StyledMarkdown({ content }: { content: string }) {
return (
Expand Down Expand Up @@ -34,6 +34,11 @@ const components: Components = {
strong: ({ node, ...props }) => (
<strong className="text-primary" {...props} />
),
table: ({ node, ...props }) => (
<div className="w-max max-w-full overflow-x-auto mx-auto my-2 rounded-lg border border-base-content/5 shadow-sm">
<table className="table w-max" {...props} />
</div>
),
hr: ({ node, ...props }) => <hr className="border-primary my-4" {...props} />,
pre: ({ node, ...props }) => props.children,
code: ({ node, className, ref, style, ...props }) => {
Expand All @@ -52,21 +57,59 @@ const components: Components = {
hello, world!
---------------------------
*/
return (
<div className="border border-primary border-2 shadow-md m-2 rounded-lg">
<ExecFile
language={match[1]}
filename={match[3]}
content={String(props.children || "").replace(/\n$/, "")}
/>
</div>
);
let execLang: ExecLang | undefined = undefined;
switch (match[1]) {
case "python":
execLang = "python";
break;
case "cpp":
case "c++":
execLang = "cpp";
break;
default:
console.warn(`Unsupported language for exec: ${match[1]}`);
break;
}
if (execLang) {
return (
<div className="border border-primary border-2 shadow-md m-2 rounded-lg">
<ExecFile
language={execLang}
filenames={match[3].split(",")}
content={String(props.children || "").replace(/\n$/, "")}
/>
</div>
);
}
} else if (match[3]) {
// ファイル名指定がある場合、ファイルエディター
let aceLang: AceLang | undefined = undefined;
switch (match[1]) {
case "python":
aceLang = "python";
break;
case "cpp":
case "c++":
aceLang = "c_cpp";
break;
case "json":
aceLang = "json";
break;
case "csv":
aceLang = "csv";
break;
case "text":
case "txt":
aceLang = "text";
break;
default:
console.warn(`Unsupported language for editor: ${match[1]}`);
break;
}
return (
<div className="border border-primary border-2 shadow-md m-2 rounded-lg">
<EditorComponent
language={match[1]}
language={aceLang}
tabSize={4}
filename={match[3]}
readonly={match[2] === "-readonly"}
Expand Down
1 change: 1 addition & 0 deletions app/[docs_id]/section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useFile } from "../terminal/file";

// セクション内に埋め込まれているターミナルとファイルエディターの内容をSection側から取得できるよう、
// Contextに保存する
// TODO: C++では複数ファイルを実行する場合がありうるが、ここではfilenameを1つしか受け付けない想定になっている
interface ISectionCodeContext {
addReplOutput: (command: string, output: ReplOutput[]) => void;
addFile: (filename: string) => void;
Expand Down
5 changes: 4 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Sidebar } from "./sidebar";
import { ReactNode } from "react";
import { PyodideProvider } from "./terminal/python/pyodide";
import { FileProvider } from "./terminal/file";
import { WandboxProvider } from "./terminal/wandbox/wandbox";

export const metadata: Metadata = {
title: "Create Next App",
Expand All @@ -22,7 +23,9 @@ export default function RootLayout({
<div className="drawer-content flex flex-col">
<Navbar />
<FileProvider>
<PyodideProvider>{children}</PyodideProvider>
<PyodideProvider>
<WandboxProvider>{children}</WandboxProvider>
</PyodideProvider>
</FileProvider>
</div>
<div className="drawer-side shadow-md">
Expand Down
80 changes: 77 additions & 3 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,79 @@
import Link from "next/link";
import { pagesList } from "./pagesList";

export default function Home() {
return <div className="p-4">
This is root page
</div>;
return (
<div className="p-4">
<h1 className="text-4xl font-bold my-4">my.code(); へようこそ</h1>
<p>
my.code();
はプログラミング言語のチュートリアルを提供するウェブサイトです。
</p>
<div
className="grid items-center gap-4 my-4"
style={{
gridTemplateColumns: "repeat(auto-fill, minmax(20rem, 1fr))",
}}
>
{pagesList.map((group) => {
return (
<div
key={group.id}
className="card card-border bg-base-100 card-md shadow-md"
>
<div className="card-body">
<h2 className="card-title">{group.lang}</h2>
<p>{group.description}</p>
<div className="justify-end card-actions">
<Link
href={`${group.id}-${group.pages[0].id}`}
className="btn btn-primary"
>
はじめる
</Link>
</div>
</div>
</div>
);
})}
</div>
<h2 className="text-2xl font-bold my-4">主な特徴</h2>
{/* TODO: デザインがダサい */}
<ul className="list-disc list-inside space-y-4">
<li>
豊富なチュートリアル
<p className="ml-8">
my.code();
ではさまざまなプログラミング言語やフレームワークのチュートリアルを提供しています。
初心者向けの基礎から上級者向けの応用まで、幅広いレベルに対応したチュートリアルが揃っています。
{/* ほんまか? */}
</p>
</li>
<li>
すぐに動かせる実行環境
<p className="ml-8">
my.code();
ではブラウザ上でコードを実行できる環境を整備しており、環境構築の手間なくすぐにコードを実行することができます。
チュートリアル内のサンプルコードはそのまま実行するだけでなく、自由に編集して試すことも可能です。
</p>
</li>
<li>
AIアシスタントによるサポート
<p className="ml-8">
my.code(); ではAIアシスタントが学習をサポートします。
チュートリアルを読んでいてわからないことがあれば、AIアシスタントに質問してみてください。
さらに、実行したサンプルコードの解説やエラーの原因調査、改善提案まで、AIアシスタントがあなたの学習を強力に支援します。
</p>
</li>
<li>
実践的な練習問題
<p className="ml-8">
各チュートリアルには練習問題が含まれており、学んだ内容を実際に試すことができます。
練習問題は段階的に難易度が上がるように設計されており、理解度を深めるのに役立ちます。
書いたコードはその場ですぐにAIアシスタントがレビューし、フィードバックを提供します。
</p>
</li>
</ul>
</div>
);
}
32 changes: 32 additions & 0 deletions app/pagesList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// docs_id = `${group.id}-${page.id}`
export const pagesList = [
{
id: "python",
lang: "Python",
// TODO: これをいい感じの文章に変える↓
description: "Pythonの基礎から応用までを学べるチュートリアル",
pages: [
{ id: 1, title: "環境構築と基本思想" },
{ id: 2, title: "基本構文とデータ型" },
{ id: 3, title: "リスト、タプル、辞書、セット" },
{ id: 4, title: "制御構文と関数" },
{ id: 5, title: "モジュールとパッケージ" },
{ id: 6, title: "オブジェクト指向プログラミング" },
{
id: 7,
title: "ファイルの入出力とコンテキストマネージャ",
},
{ id: 8, title: "例外処理" },
{ id: 9, title: "ジェネレータとデコレータ" },
],
},
{
id: "cpp",
lang: "C++",
description: "C++の基本から高度な機能までを学べるチュートリアル",
pages: [
{ id: 2, title: "型システムとメモリ" },
{ id: 3, title: "関数と参照" },
],
},
] as const;
81 changes: 38 additions & 43 deletions app/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,60 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import useSWR, { Fetcher } from 'swr'
import useSWR, { Fetcher } from "swr";
import { splitMarkdown } from "./[docs_id]/splitMarkdown";
import { pagesList } from "./pagesList";

const fetcher: Fetcher<string, string> = (url) => fetch(url).then((r) => r.text())
const fetcher: Fetcher<string, string> = (url) =>
fetch(url).then((r) => r.text());

export function Sidebar() {
const pathname = usePathname();
const docs_id = pathname.replace(/^\//, "");
const { data, error, isLoading } = useSWR(
`/docs/${docs_id}.md`,
fetcher
)
const { data, error, isLoading } = useSWR(`/docs/${docs_id}.md`, fetcher);

const pages = [
{ id: "python-1", title: "1. 環境構築と基本思想" },
{ id: "python-2", title: "2. 基本構文とデータ型" },
{ id: "python-3", title: "3. リスト、タプル、辞書、セット" },
{ id: "python-4", title: "4. 制御構文と関数" },
{ id: "python-5", title: "5. モジュールとパッケージ" },
{ id: "python-6", title: "6. オブジェクト指向プログラミング" },
{ id: "python-7", title: "7. ファイルの入出力とコンテキストマネージャ" },
{ id: "python-8", title: "8. 例外処理" },
{ id: "python-9", title: "9. ジェネレータとデコレータ" },
];
if (error) console.error("Sidebar fetch error:", error);

if (error) console.error("Sidebar fetch error:", error)




const splitmdcontent = splitMarkdown(data ?? "")
const splitmdcontent = splitMarkdown(data ?? "");
return (
<div className="bg-base-200 min-h-full w-80 p-4">
<div className="bg-base-200 h-full w-80 overflow-y-auto">
{/* todo: 背景色ほんとにこれでいい? */}
<h2 className="hidden lg:block text-xl font-bold mb-4">
<h2 className="hidden lg:block text-xl font-bold p-4">
{/* サイドバーが常時表示されている場合のみ */}
Navbar Title
</h2>

<ol className="menu w-full list-outside">
{pages.map((page) => (
<li key={page.id}>
<Link href={`/${page.id}`}>{page.title}</Link>
{page.id === docs_id && !isLoading &&(
<ul className="ml-4 mt-2 text-sm">
{splitmdcontent
.slice(1)
.map((section, idx) => (
<li key={idx} style={{ marginLeft: (section.level - 2) + "em"}}>
<Link href={`#${idx+1}`}>{section.title}</Link>
</li>
))}
</ul>
)}

<ul className="menu w-full">
{pagesList.map((group) => (
<li key={group.id}>
<details open={docs_id.startsWith(`${group.id}-`)}>
<summary>{group.lang}</summary>
<ul>
{group.pages.map((page) => (
<li key={page.id}>
<Link href={`${group.id}-${page.id}`}>
<span className="mr-0">{page.id}.</span>
{page.title}
</Link>
{`${group.id}-${page.id}` === docs_id && !isLoading && (
<ul className="ml-4 text-sm">
{splitmdcontent.slice(1).map((section, idx) => (
<li
key={idx}
style={{ marginLeft: section.level - 2 + "em" }}
>
<Link href={`#${idx + 1}`}>{section.title}</Link>
</li>
))}
</ul>
)}
</li>
))}
</ul>
</details>
</li>
))}
</ol>
</ul>
</div>
);
}

6 changes: 5 additions & 1 deletion app/terminal/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "ace-builds/src-min-noconflict/theme-twilight";
import "ace-builds/src-min-noconflict/ext-language_tools";
import "ace-builds/src-min-noconflict/ext-searchbox";
import "ace-builds/src-min-noconflict/mode-python";
import "ace-builds/src-min-noconflict/mode-c_cpp";
import "ace-builds/src-min-noconflict/mode-json";
import "ace-builds/src-min-noconflict/mode-csv";
import "ace-builds/src-min-noconflict/mode-text";
Expand All @@ -17,8 +18,11 @@ import { useSectionCode } from "../[docs_id]/section";
import clsx from "clsx";
// snippetを有効化するにはsnippetもimportする必要がある: import "ace-builds/src-min-noconflict/snippets/python";

// mode-xxxx.js のファイル名と、AceEditorの mode プロパティの値が対応する
export type AceLang = "python" | "c_cpp" | "json" | "csv" | "text";

interface EditorProps {
language?: string;
language?: AceLang;
tabSize: number;
filename: string;
initContent: string;
Expand Down
Loading