diff --git a/docs/1-trial-session/04-javascript/index.mdx b/docs/1-trial-session/04-javascript/index.mdx
index 790162f7..a0ce3afc 100644
--- a/docs/1-trial-session/04-javascript/index.mdx
+++ b/docs/1-trial-session/04-javascript/index.mdx
@@ -4,16 +4,16 @@ title: JavaScriptことはじめ
import helloWorldByJavascriptVideo from "./hello-world-by-javascript.mp4";
-## JavaScript
+## [[JavaScript]]
{/* prettier-ignore */}
-HTMLがウェブサイトの構造を表す言語だとすれば、**JavaScript**はウェブサイトに振る舞いを与える言語といえます。ブラウザさえあれば環境に関係なく同じように実行可能な、強力なプログラミング言語です。
+[[HTML]]がウェブサイトの構造を表す言語だとすれば、[[**JavaScript**]]はウェブサイトに振る舞いを与える言語といえます。ブラウザさえあれば環境に関係なく同じように実行可能な、強力なプログラミング言語です。

-## JavaScriptでHello World!
+## [[JavaScript]]でHello World!
-プログラミングの世界では、まず画面に`Hello World!`と表示させることが慣例になっています。JavaScriptを用いて画面に`Hello World!`を表示してみましょう。
+プログラミングの世界では、まず画面に`Hello World!`と表示させることが慣例になっています。[[JavaScript]]を用いて画面に`Hello World!`を表示してみましょう。
まずは、`index.html`を次のように書き換えます。
@@ -40,26 +40,26 @@ document.write("Hello World!");
-## JavaScriptが動く仕組み
+## [[JavaScript]]が動く仕組み
-HTMLファイルの中に、以下のような記述があります。
+[[HTML]]ファイルの中に、以下のような記述があります。
```html title="index.html"
```
-まず、この記述によって、`script.js`ファイルがブラウザによって読み込まれます。この`script.js`に記述されているのがJavaScriptです。
+まず、この記述によって、`script.js`ファイルがブラウザによって読み込まれます。この`script.js`に記述されているのが[[JavaScript]]です。
:::info
-この講座の中では、`script`要素を常に **`body`要素の末尾** に記述するようにします。これは、JavaScriptが読み込まれるタイミングで他のすべてのHTML要素がすでに表示されていることを保証するためです。
+この講座の中では、`script`[[要素]]を常に **`body`[[要素]]の末尾** に記述するようにします。これは、[[JavaScript]]が読み込まれるタイミングで他のすべての[[HTML要素]]がすでに表示されていることを保証するためです。
:::
-## JavaScript の基本文法
+## [[JavaScript]] の基本文法
{/* prettier-ignore */}
-JavaScriptのプログラムで、セミコロンで区切られた部分を文と呼びます。JavaScriptの実行環境は、プログラム中に含まれる文を上から下に向けて順番に実行していきます。
+[[JavaScript]]のプログラムで、セミコロンで区切られた部分を[[文]]と呼びます。[[JavaScript]]の実行環境は、プログラム中に含まれる[[文]]を上から下に向けて順番に実行していきます。
`document.write`はブラウザの画面に出力するための命令です。
```javascript title="script.js"
diff --git a/docusaurus.config.ts b/docusaurus.config.ts
index 8b8204d1..c2aad3a4 100644
--- a/docusaurus.config.ts
+++ b/docusaurus.config.ts
@@ -4,6 +4,7 @@ import type * as Preset from "@docusaurus/preset-classic";
import { execSync } from "node:child_process";
import math from "remark-math";
import katex from "rehype-katex";
+import remarkTerm from "./src/remark/remark-term";
const config: Config = {
title: "ut.code(); Learn",
@@ -46,7 +47,7 @@ const config: Config = {
showLastUpdateTime: true,
sidebarPath: "./sidebars.ts",
editUrl: "https://github.com/ut-code/utcode-learn/blob/main/",
- remarkPlugins: [math],
+ remarkPlugins: [math, remarkTerm],
rehypePlugins: [katex],
},
theme: {
diff --git a/package-lock.json b/package-lock.json
index 65de0617..f84042b3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,8 @@
"@tippyjs/react": "^4.2.6",
"clsx": "^2.1.1",
"hast-util-is-element": "^3.0.0",
+ "mdast-util-mdx-jsx": "^3.2.0",
+ "mdast-util-phrasing": "^4.1.0",
"outdent": "^0.8.0",
"prism-react-renderer": "^2.4.1",
"react": "^19.1.1",
@@ -26,12 +28,14 @@
"react-slick": "^0.31.0",
"rehype-katex": "^7.0.1",
"remark-math": "^6.0.0",
- "slick-carousel": "^1.8.1"
+ "slick-carousel": "^1.8.1",
+ "unified": "^11.0.5"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.9.1",
"@docusaurus/tsconfig": "^3.9.1",
"@docusaurus/types": "^3.9.1",
+ "@types/mdast": "^4.0.4",
"@types/react-slick": "^0.23.13",
"prettier": "^3.6.2",
"typescript": "^5.9.3"
@@ -245,7 +249,6 @@
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.39.0.tgz",
"integrity": "sha512-/IYpF10BpthGZEJQZMhMqV4AqWr5avcWfZm/SIKK1RvUDmzGqLoW/+xeJVX9C8ZnNkIC8hivbIQFaNaRw0BFZQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@algolia/client-common": "5.39.0",
"@algolia/requester-browser-xhr": "5.39.0",
@@ -393,7 +396,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
@@ -2228,7 +2230,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -2251,7 +2252,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -2361,7 +2361,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -2783,7 +2782,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -3647,7 +3645,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.1.tgz",
"integrity": "sha512-DyLk9BIA6I9gPIuia8XIL+XIEbNnExam6AHzRsfrEq4zJr7k/DsWW7oi4aJMepDnL7jMRhpVcdsCxdjb0/A9xg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@docusaurus/core": "3.9.1",
"@docusaurus/logger": "3.9.1",
@@ -3916,7 +3913,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.1.tgz",
"integrity": "sha512-j9adi961F+6Ps9d0jcb5BokMcbjXAAJqKkV43eo8nh4YgmDj7KUNDX4EnOh/MjTQeO06oPY5cxp3yUXdW/8Ggw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@docusaurus/mdx-loader": "3.9.1",
"@docusaurus/module-type-aliases": "3.9.1",
@@ -4545,7 +4541,6 @@
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz",
"integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/mdx": "^2.0.0"
},
@@ -5148,7 +5143,6 @@
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz",
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0",
@@ -5808,7 +5802,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.14.tgz",
"integrity": "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -6158,7 +6151,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6244,7 +6236,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -6290,7 +6281,6 @@
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.39.0.tgz",
"integrity": "sha512-DzTfhUxzg9QBNGzU/0kZkxEV72TeA4MmPJ7RVfLnQwHNhhliPo7ynglEWJS791rNlLFoTyrKvkapwr/P3EXV9A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@algolia/abtesting": "1.5.0",
"@algolia/client-abtesting": "5.39.0",
@@ -6766,7 +6756,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001741",
@@ -7066,7 +7055,6 @@
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
@@ -7777,7 +7765,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -8097,7 +8084,6 @@
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10"
}
@@ -8507,7 +8493,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
- "peer": true,
"engines": {
"node": ">=12"
}
@@ -9704,7 +9689,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -14548,7 +14532,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -15127,7 +15110,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -16031,7 +16013,6 @@
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
"integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@@ -16870,7 +16851,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -16889,7 +16869,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -16954,7 +16933,6 @@
"resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz",
"integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/react": "*"
},
@@ -16983,7 +16961,6 @@
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
"integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.12.13",
"history": "^4.9.0",
@@ -18891,8 +18868,7 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD",
- "peer": true
+ "license": "0BSD"
},
"node_modules/type-fest": {
"version": "2.19.0",
@@ -18955,7 +18931,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -19340,7 +19315,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -19601,7 +19575,6 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz",
"integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -20209,7 +20182,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz",
"integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/package.json b/package.json
index 95510043..0bcf921a 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,8 @@
"@tippyjs/react": "^4.2.6",
"clsx": "^2.1.1",
"hast-util-is-element": "^3.0.0",
+ "mdast-util-mdx-jsx": "^3.2.0",
+ "mdast-util-phrasing": "^4.1.0",
"outdent": "^0.8.0",
"prism-react-renderer": "^2.4.1",
"react": "^19.1.1",
@@ -31,7 +33,8 @@
"react-slick": "^0.31.0",
"rehype-katex": "^7.0.1",
"remark-math": "^6.0.0",
- "slick-carousel": "^1.8.1"
+ "slick-carousel": "^1.8.1",
+ "unified": "^11.0.5"
},
"browserslist": {
"production": [
@@ -52,6 +55,7 @@
"@docusaurus/module-type-aliases": "^3.9.1",
"@docusaurus/tsconfig": "^3.9.1",
"@docusaurus/types": "^3.9.1",
+ "@types/mdast": "^4.0.4",
"@types/react-slick": "^0.23.13",
"prettier": "^3.6.2",
"typescript": "^5.9.3"
diff --git a/src/remark/remark-term.ts b/src/remark/remark-term.ts
new file mode 100644
index 00000000..3f2c9086
--- /dev/null
+++ b/src/remark/remark-term.ts
@@ -0,0 +1,129 @@
+import type { Plugin } from "unified";
+import type { Nodes, Root, RootContent } from "mdast";
+import "mdast-util-mdx-jsx";
+import { phrasing } from "mdast-util-phrasing";
+
+/**
+ * `[[用語]]`を`用語`に変換するプラグイン。
+ * `[[**用語**]]`のように中身が単一のASTノードの場合も変換可能。
+ *
+ * @example
+ * // returns "**HTML**とCSS、そしてJavaScriptです。"
+ * String(
+ * await remark()
+ * .use(remarkMdx)
+ * .use(remarkTerm)
+ * .process("[[**HTML**]]と[[CSS]]、そして[[JavaScript]]です。"),
+ * );
+ */
+const remarkTerm: Plugin<[], Root> = () => (tree) => transform(tree);
+
+export default remarkTerm;
+
+function isParent(node: Nodes) {
+ return "children" in node;
+}
+
+function transform(node: Nodes) {
+ if (!isParent(node)) return;
+
+ for (const child of node.children) {
+ transform(child);
+ }
+
+ node.children = transformChildren(
+ node.children.flatMap((child) => transformChild(child)),
+ );
+}
+
+function transformChild(node: RootContent): RootContent[] {
+ if (node.type !== "text") return [node];
+
+ const transformedChildren: RootContent[] = [];
+ let text = node.value;
+
+ if (text.startsWith("]]")) {
+ transformedChildren.push({ type: "text", value: "]]" });
+ text = text.slice(2);
+ }
+
+ if (text.endsWith("[[")) {
+ text = text.slice(0, -2);
+ }
+
+ for (const segmentedText of text.split(/(\[\[.*?\]\])/)) {
+ if (segmentedText === "") {
+ continue;
+ }
+ if (segmentedText.startsWith("[[")) {
+ transformedChildren.push({
+ type: "mdxJsxTextElement",
+ name: "Term",
+ attributes: [],
+ children: [
+ {
+ type: "text",
+ value: segmentedText.slice(2, -2),
+ },
+ ],
+ });
+ } else {
+ transformedChildren.push({
+ type: "text",
+ value: segmentedText,
+ });
+ }
+ }
+
+ if (node.value.endsWith("[[")) {
+ transformedChildren.push({ type: "text", value: "[[" });
+ }
+
+ return transformedChildren;
+}
+
+function transformChildren(children: RootContent[]): RootContent[] {
+ const transformedChildren: RootContent[] = [];
+
+ let i = 0;
+ while (i < children.length) {
+ const openingBracket = children[i];
+ const innerElement = children[i + 1];
+ const closingBracket = children[i + 2];
+ if (
+ openingBracket.type !== "text" ||
+ openingBracket.value !== "[[" ||
+ !innerElement
+ ) {
+ transformedChildren.push(openingBracket);
+ i++;
+ continue;
+ }
+
+ if (
+ !closingBracket ||
+ closingBracket.type !== "text" ||
+ closingBracket.value !== "]]"
+ ) {
+ transformedChildren.push(openingBracket, innerElement);
+ i += 2;
+ continue;
+ }
+
+ if (!phrasing(innerElement)) {
+ transformedChildren.push(openingBracket, innerElement, closingBracket);
+ i += 3;
+ continue;
+ }
+
+ transformedChildren.push({
+ type: "mdxJsxTextElement",
+ name: "Term",
+ attributes: [],
+ children: [innerElement],
+ });
+ i += 3;
+ }
+
+ return transformedChildren;
+}