concept-atlas-dense-explain 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.mjs +26 -2
- package/package.json +1 -1
- package/skill/SKILL.md +5 -4
- package/template/guides/atlas-guide.mdx +2 -0
- package/template/index.html +41 -16
- package/template/scripts/build.mjs +46 -24
- package/template/scroll.html +25 -0
- package/template/src/app/App.jsx +232 -239
- package/template/src/app/use-appearance.js +82 -0
- package/template/src/components/MDXComponents.jsx +62 -13
- package/template/src/components/SkinPicker.jsx +90 -0
- package/template/src/model/concept-schema.js +169 -169
- package/template/src/model/relation-types.js +90 -90
- package/template/src/model/skins.js +44 -0
- package/template/src/scroll-main.jsx +6 -8
- package/template/src/styles/concept-explain.css +266 -168
- package/template/src/styles/skins.css +194 -0
- package/template/src/styles/tokens.css +229 -0
- package/template/src/views/NodeExplorer.jsx +668 -668
- package/template/src/views/RelationGraph.jsx +722 -723
- package/template/vite.config.js +43 -0
package/bin/cli.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { build } from 'vite';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { validateMdxSource, countBySeverity, detectFeatures } from '../template/src/model/validate-content.js';
|
|
8
|
+
import { SKINS, normalizeSkin, COMPONENT_STYLES, normalizeStyle } from '../template/src/model/skins.js';
|
|
8
9
|
|
|
9
10
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
11
|
const templateRoot = path.join(packageRoot, 'template');
|
|
@@ -13,7 +14,7 @@ let buildCounter = 0;
|
|
|
13
14
|
|
|
14
15
|
function usage() {
|
|
15
16
|
console.log('Usage:');
|
|
16
|
-
console.log(' npx concept-atlas-dense-explain <input.mdx>... [--mode atlas|scroll] [-o output.html|dir] [--force] [--concurrency N] [--link-assets] [--json] [--no-validate]');
|
|
17
|
+
console.log(' npx concept-atlas-dense-explain <input.mdx>... [--mode atlas|scroll] [--skin <id>] [--default-mode dark|light|system] [--style <id>] [-o output.html|dir] [--force] [--concurrency N] [--link-assets] [--json] [--no-validate]');
|
|
17
18
|
console.log(' npx concept-atlas-dense-explain render <input.mdx>... [-o output.html|dir]');
|
|
18
19
|
console.log(' npx concept-atlas-dense-explain validate <input.mdx> [--mode atlas|scroll] [--strict] [--json]');
|
|
19
20
|
console.log(' npx concept-atlas-dense-explain create <output.mdx> [--mode atlas|scroll] [--force]');
|
|
@@ -21,6 +22,7 @@ function usage() {
|
|
|
21
22
|
console.log('');
|
|
22
23
|
console.log(' Multiple inputs build in parallel (default 2 at a time, cap 4); -o is then a directory.');
|
|
23
24
|
console.log(' --link-assets keeps figures as relative links instead of inlining them as base64.');
|
|
25
|
+
console.log(` --skin bakes a default palette (${SKINS.map(skin => skin.id).join(', ')}); --default-mode bakes a default dark/light mode; --style bakes a default component style (${COMPONENT_STYLES.map(style => style.id).join(', ')}). Readers can still switch in the UI.`);
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
async function exists(filePath) {
|
|
@@ -41,7 +43,7 @@ function fail(message) {
|
|
|
41
43
|
process.exit(1);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
const VALUE_FLAGS = new Set(['--mode', '-o', '--output', '--concurrency']);
|
|
46
|
+
const VALUE_FLAGS = new Set(['--mode', '-o', '--output', '--concurrency', '--skin', '--default-mode', '--style']);
|
|
45
47
|
|
|
46
48
|
/** Splits argv into flags, flag values and positional arguments. */
|
|
47
49
|
function parseFlags(argv) {
|
|
@@ -201,6 +203,25 @@ const force = parsed.flags.has('--force');
|
|
|
201
203
|
const linkAssets = parsed.flags.has('--link-assets');
|
|
202
204
|
const modeFlag = parsed.values.get('--mode') || null;
|
|
203
205
|
|
|
206
|
+
// Compile-time appearance defaults. Invalid values fail fast with the valid
|
|
207
|
+
// options instead of silently baking a broken default into every page.
|
|
208
|
+
let skinFlag = null;
|
|
209
|
+
if (parsed.values.has('--skin')) {
|
|
210
|
+
skinFlag = normalizeSkin(parsed.values.get('--skin'));
|
|
211
|
+
if (!skinFlag) fail(`Unknown skin: ${parsed.values.get('--skin')} (available: ${SKINS.map(skin => skin.id).join(', ')})`);
|
|
212
|
+
}
|
|
213
|
+
let defaultModeFlag = null;
|
|
214
|
+
if (parsed.values.has('--default-mode')) {
|
|
215
|
+
const raw = parsed.values.get('--default-mode');
|
|
216
|
+
if (!['dark', 'light', 'system'].includes(raw)) fail(`Invalid --default-mode: ${raw} (use dark, light or system)`);
|
|
217
|
+
defaultModeFlag = raw;
|
|
218
|
+
}
|
|
219
|
+
let styleFlag = null;
|
|
220
|
+
if (parsed.values.has('--style')) {
|
|
221
|
+
styleFlag = normalizeStyle(parsed.values.get('--style'));
|
|
222
|
+
if (!styleFlag) fail(`Unknown component style: ${parsed.values.get('--style')} (available: ${COMPONENT_STYLES.map(style => style.id).join(', ')})`);
|
|
223
|
+
}
|
|
224
|
+
|
|
204
225
|
if (command === 'validate') {
|
|
205
226
|
const target = parsed.positional[0] ? path.resolve(parsed.positional[0]) : null;
|
|
206
227
|
if (!target || path.extname(target).toLowerCase() !== '.mdx' || !(await exists(target))) {
|
|
@@ -305,6 +326,9 @@ async function buildOne(job) {
|
|
|
305
326
|
await mkdir(scratch, { recursive: true });
|
|
306
327
|
const define = { __ATLAS_FEATURES__: JSON.stringify(features) };
|
|
307
328
|
if (link) define.__ATLAS_INLINE_ASSETS__ = 'false';
|
|
329
|
+
if (skinFlag) define.__ATLAS_DEFAULT_SKIN__ = JSON.stringify(skinFlag);
|
|
330
|
+
if (defaultModeFlag) define.__ATLAS_DEFAULT_MODE__ = JSON.stringify(defaultModeFlag);
|
|
331
|
+
if (styleFlag) define.__ATLAS_DEFAULT_STYLE__ = JSON.stringify(styleFlag);
|
|
308
332
|
try {
|
|
309
333
|
await build({
|
|
310
334
|
root: templateRoot,
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -26,14 +26,15 @@ If the user only wants the prompt/methodology and not files, still choose a shel
|
|
|
26
26
|
npx concept-atlas-dense-explain validate <file>.mdx --json
|
|
27
27
|
```
|
|
28
28
|
Every diagnostic is `CODE line:column message`. Fix all `error`s and re-run; warnings are quality signals you should also address when cheap.
|
|
29
|
-
6. Compile: `npx concept-atlas-dense-explain <file>.mdx --mode atlas|scroll [-o out.html]`. Output is a standalone HTML beside the MDX unless `-o` is given. Validation errors abort the build; use `--no-validate` only to force a knowingly broken build.
|
|
30
|
-
7.
|
|
31
|
-
8.
|
|
29
|
+
6. Compile: `npx concept-atlas-dense-explain <file>.mdx --mode atlas|scroll [-o out.html] [--skin <id>] [--default-mode dark|light|system] [--style <id>]`. Output is a standalone HTML beside the MDX unless `-o` is given. Validation errors abort the build; use `--no-validate` only to force a knowingly broken build.
|
|
30
|
+
7. **Appearance (optional)**: pages ship with a reader-facing appearance menu — palette (`aurora` cool blue, `ember` warm gold), a dark/light toggle, and a component style pack (`manuscript` editorial marginalia, `classic` boxed cards). The shipped default is aurora × manuscript × light; choices persist in localStorage across both carriers. You can bake different compile-time defaults: `--skin ember --default-mode dark --style classic` (or env `CONCEPT_ATLAS_SKIN` / `CONCEPT_ATLAS_DEFAULT_MODE` / `CONCEPT_ATLAS_STYLE` on the repo build). Bake a default only when the user asks for one. Content MDX never sets appearance — it is carrier/tooling territory, not content.
|
|
31
|
+
8. For several documents, pass them all in one call: `npx concept-atlas-dense-explain a.mdx b.mdx c.mdx -o dist --force [--concurrency 3]`. `-o` is then a directory. The batch validates everything first and builds in parallel. Builds only bundle the heavy renderers the content uses: a page with no `<Math>`/`<Mermaid>` skips KaTeX (its ~1.4MB inlined fonts) and Mermaid, shrinking a typical scroll article from ~5MB to ~250KB. Do not add dummy `<Math>`/`<Mermaid>` nodes to "enable" them — write the components only when the content needs them. Add `--link-assets` when the page carries many screenshots and size matters.
|
|
32
|
+
9. Report the shell, output path, validation result (errors/warnings), and limitations. Do not claim interactions you did not verify.
|
|
32
33
|
|
|
33
34
|
## Carriers
|
|
34
35
|
|
|
35
36
|
- `atlas`: `ExplainPage` → `ConceptGraph` → `ConceptNode`, plus `Children`/`ConceptRef` and cross-branch `Relation`s. Exactly one `L0` root, several `L1` branches, depth to `L3`/`L4`. Shared components live inside nodes.
|
|
36
|
-
- `scroll`: `ScrollDocument` → `ScrollHeader` + `ScrollSection` (+ `ScrollProse`, `ScrollGrid`). Shared components live inside sections. The shell auto-builds a
|
|
37
|
+
- `scroll`: `ScrollDocument` → `ScrollHeader` + `ScrollSection` (+ `ScrollProse`, `ScrollGrid`). Shared components live inside sections. The shell auto-builds a table of contents and reading progress from section titles — do not hand-build navigation.
|
|
37
38
|
- Never make one MDX file both shells. When switching shells, convert only the outer structure.
|
|
38
39
|
|
|
39
40
|
## Component families
|
|
@@ -89,10 +89,12 @@
|
|
|
89
89
|
<Children><ConceptRef id="layout-demo" /><ConceptRef id="compact-demo" /></Children>
|
|
90
90
|
</ConceptNode>
|
|
91
91
|
<ConceptNode id="layout-demo" title="布局原语:Stack、Grid、Split、Columns 让信息有秩序" level="L2" parent="presentation-family" summary="布局组件只表达空间意图,具体样式由模板统一接管。">
|
|
92
|
+
<Definition>布局原语表达信息之间的空间关系;它们负责组织内容,不负责改变内容本身的语义。</Definition>
|
|
92
93
|
<Columns><Split ratio="1fr 1fr"><Callout title="左列">结论与定义。</Callout><Callout title="右列">证据与边界。</Callout></Split></Columns>
|
|
93
94
|
<Grid columns="auto" gap="sm"><Insight title="局部判断">Grid 中的短信息。</Insight><Insight title="另一判断" tone="warn">不要混用职责。</Insight></Grid>
|
|
94
95
|
</ConceptNode>
|
|
95
96
|
<ConceptNode id="compact-demo" title="压缩阅读:Tabs、Details、Callout、Insight、NoteGrid" level="L2" parent="presentation-family" summary="这些组件适合把结论、提示、细节和短信息压缩在首屏。">
|
|
97
|
+
<Definition>压缩组件通过折叠、分组和短提示降低首屏负担,同时保留继续阅读所需的上下文。</Definition>
|
|
96
98
|
<Tabs items={[{label:'结论',content:'先展示最重要的判断。'},{label:'证据',content:'再展开可验证依据。'}]} />
|
|
97
99
|
<Details summary="展开更多">Details 将次要信息收起,避免首屏过载。</Details>
|
|
98
100
|
<Insight title="关键判断" tone="success">一个组件只承担一种信息组织方式。</Insight>
|
package/template/index.html
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>Concept Atlas · 概念缩放式知识图谱</title>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Concept Atlas · 概念缩放式知识图谱</title>
|
|
7
|
+
<script>
|
|
8
|
+
(function () {
|
|
9
|
+
var doc = document.documentElement;
|
|
10
|
+
var stored = null;
|
|
11
|
+
try { stored = JSON.parse(localStorage.getItem('concept_atlas_appearance') || 'null'); } catch (e) {}
|
|
12
|
+
var legacy = localStorage.getItem('concept_atlas_theme');
|
|
13
|
+
// Placeholders are replaced at build time when --skin/--default-mode
|
|
14
|
+
// (or CONCEPT_ATLAS_* env vars) are configured; otherwise the runtime
|
|
15
|
+
// fallbacks below keep the carrier defaults.
|
|
16
|
+
var skin = '__ATLAS_DEFAULT_SKIN__';
|
|
17
|
+
if (/^__.+__$/.test(skin)) skin = 'aurora';
|
|
18
|
+
var mode = '__ATLAS_DEFAULT_MODE__';
|
|
19
|
+
if (/^__.+__$/.test(mode)) mode = 'light';
|
|
20
|
+
var style = '__ATLAS_DEFAULT_STYLE__';
|
|
21
|
+
if (/^__.+__$/.test(style)) style = 'manuscript';
|
|
22
|
+
if (stored && (stored.mode === 'light' || stored.mode === 'dark')) {
|
|
23
|
+
mode = stored.mode;
|
|
24
|
+
} else if (mode !== 'light' && mode !== 'dark') {
|
|
25
|
+
mode = legacy === 'light' || legacy === 'dark' ? legacy : 'light';
|
|
26
|
+
}
|
|
27
|
+
doc.setAttribute('data-skin', stored && stored.skin ? stored.skin : skin);
|
|
28
|
+
doc.setAttribute('data-theme', mode);
|
|
29
|
+
doc.setAttribute('data-style', stored && stored.style ? stored.style : style);
|
|
30
|
+
})();
|
|
31
|
+
</script>
|
|
32
|
+
<!-- Google Fonts for Academic / Editorial Knowledge style -->
|
|
33
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
34
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
35
|
+
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600&family=Lora:ital,wght@0,500;0,600;0,700;1,400&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Noto+Serif+SC:wght@500;600;700;900&display=swap" rel="stylesheet">
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<div id="root"></div>
|
|
39
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|
|
@@ -1,30 +1,52 @@
|
|
|
1
|
-
import { build } from 'vite';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
1
|
+
import { build } from 'vite';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { normalizeSkin, normalizeStyle } from '../src/model/skins.js';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const rootDir = path.resolve(__dirname, '..');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Optional compile-time appearance defaults, read from the environment and
|
|
13
|
+
* forwarded as `define`s. The carrier HTML plugin replaces the placeholders
|
|
14
|
+
* inside the anti-flash inline script only when these are configured.
|
|
15
|
+
*/
|
|
16
|
+
function appearanceDefines() {
|
|
17
|
+
const define = {};
|
|
18
|
+
const skin = normalizeSkin(process.env.CONCEPT_ATLAS_SKIN || '');
|
|
19
|
+
const mode = process.env.CONCEPT_ATLAS_DEFAULT_MODE;
|
|
20
|
+
const style = normalizeStyle(process.env.CONCEPT_ATLAS_STYLE || '');
|
|
21
|
+
if (skin) define.__ATLAS_DEFAULT_SKIN__ = JSON.stringify(skin);
|
|
22
|
+
if (['dark', 'light', 'system'].includes(mode)) define.__ATLAS_DEFAULT_MODE__ = JSON.stringify(mode);
|
|
23
|
+
if (style) define.__ATLAS_DEFAULT_STYLE__ = JSON.stringify(style);
|
|
24
|
+
return define;
|
|
25
|
+
}
|
|
26
|
+
|
|
10
27
|
async function runBuild() {
|
|
11
28
|
console.log('🚀 开始构建 Concept Atlas 知识讲解页面...');
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
// 确保 dist 目录存在
|
|
15
|
-
const distDir = path.resolve(rootDir, 'dist');
|
|
16
|
-
if (!fs.existsSync(distDir)) {
|
|
17
|
-
fs.mkdirSync(distDir, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// 确保 dist 目录存在
|
|
32
|
+
const distDir = path.resolve(rootDir, 'dist');
|
|
33
|
+
if (!fs.existsSync(distDir)) {
|
|
34
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
const mode = process.env.CONCEPT_ATLAS_MODE;
|
|
21
38
|
const carriers = mode === 'atlas' ? ['index.html'] : mode === 'scroll' ? ['scroll.html'] : ['index.html', 'scroll.html'];
|
|
39
|
+
const define = appearanceDefines();
|
|
40
|
+
if (define.__ATLAS_DEFAULT_SKIN__ || define.__ATLAS_DEFAULT_MODE__) {
|
|
41
|
+
console.log(`🎨 默认外观:skin=${define.__ATLAS_DEFAULT_SKIN__ || '(carrier 默认)'} mode=${define.__ATLAS_DEFAULT_MODE__ || '(carrier 默认)'}`);
|
|
42
|
+
}
|
|
22
43
|
|
|
23
44
|
// vite-plugin-singlefile supports one HTML input per build. Build each
|
|
24
45
|
// requested carrier separately so every output remains a standalone file.
|
|
25
46
|
for (const [index, entry] of carriers.entries()) {
|
|
26
47
|
await build({
|
|
27
48
|
root: rootDir,
|
|
49
|
+
define,
|
|
28
50
|
build: {
|
|
29
51
|
outDir: 'dist',
|
|
30
52
|
emptyOutDir: index === 0,
|
|
@@ -34,10 +56,10 @@ async function runBuild() {
|
|
|
34
56
|
}
|
|
35
57
|
|
|
36
58
|
console.log(`✅ 构建成功!产物已生成到 ${carriers.map(entry => `dist/${entry}`).join(' 和 ')}。`);
|
|
37
|
-
} catch (err) {
|
|
38
|
-
console.error('❌ 构建失败:', err);
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
runBuild();
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error('❌ 构建失败:', err);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
runBuild();
|
package/template/scroll.html
CHANGED
|
@@ -4,6 +4,31 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Concept Atlas · 连续阅读示例</title>
|
|
7
|
+
<script>
|
|
8
|
+
(function () {
|
|
9
|
+
var doc = document.documentElement;
|
|
10
|
+
var stored = null;
|
|
11
|
+
try { stored = JSON.parse(localStorage.getItem('concept_atlas_appearance') || 'null'); } catch (e) {}
|
|
12
|
+
var legacy = localStorage.getItem('concept_atlas_scroll_theme');
|
|
13
|
+
// Placeholders are replaced at build time when --skin/--default-mode
|
|
14
|
+
// (or CONCEPT_ATLAS_* env vars) are configured; otherwise the runtime
|
|
15
|
+
// fallbacks below keep the carrier defaults.
|
|
16
|
+
var skin = '__ATLAS_DEFAULT_SKIN__';
|
|
17
|
+
if (/^__.+__$/.test(skin)) skin = 'aurora';
|
|
18
|
+
var mode = '__ATLAS_DEFAULT_MODE__';
|
|
19
|
+
if (/^__.+__$/.test(mode)) mode = 'light';
|
|
20
|
+
var style = '__ATLAS_DEFAULT_STYLE__';
|
|
21
|
+
if (/^__.+__$/.test(style)) style = 'manuscript';
|
|
22
|
+
if (stored && (stored.mode === 'light' || stored.mode === 'dark')) {
|
|
23
|
+
mode = stored.mode;
|
|
24
|
+
} else if (mode !== 'light' && mode !== 'dark') {
|
|
25
|
+
mode = legacy === 'light' || legacy === 'dark' ? legacy : 'light';
|
|
26
|
+
}
|
|
27
|
+
doc.setAttribute('data-skin', stored && stored.skin ? stored.skin : skin);
|
|
28
|
+
doc.setAttribute('data-theme', mode);
|
|
29
|
+
doc.setAttribute('data-style', stored && stored.style ? stored.style : style);
|
|
30
|
+
})();
|
|
31
|
+
</script>
|
|
7
32
|
</head>
|
|
8
33
|
<body>
|
|
9
34
|
<div id="root"></div>
|