concept-atlas-dense-explain 0.10.0 → 1.0.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/package.json
CHANGED
|
@@ -100,6 +100,38 @@ npm run dev`}</CodeBlock>
|
|
|
100
100
|
<RelationMap title="关系速览" items={[{from:'MDX',type:'produces',to:'Graph',note:'生成关系图'},{from:'Node',type:'uses',to:'Mermaid',note:'展示局部流程'}]} />
|
|
101
101
|
</ScrollSection>
|
|
102
102
|
|
|
103
|
+
<ScrollSection title="附录:大图的框内适配与放大阅读">
|
|
104
|
+
<ScrollProse>较大的流程图会被等比收进框内,而不是画出边框。点击图面或标题栏的「放大阅读」可以打开全屏视图,用滚轮缩放、拖拽平移、Esc 关闭。</ScrollProse>
|
|
105
|
+
<Mermaid title="图谱生成管线" width="100%" height="360px" chart={`flowchart TD
|
|
106
|
+
S[MDX 源文件] --> P[解析与校验]
|
|
107
|
+
P --> G[概念图谱]
|
|
108
|
+
P --> F[图表与公式]
|
|
109
|
+
G --> D[节点探索]
|
|
110
|
+
G --> R[关系图谱]
|
|
111
|
+
D --> M[Mermaid 局部流程图]
|
|
112
|
+
R --> M
|
|
113
|
+
F --> M
|
|
114
|
+
M --> Z[框内自适应缩放]
|
|
115
|
+
Z --> O[点击放大阅读]
|
|
116
|
+
O --> W[拖拽平移]
|
|
117
|
+
O --> K[滚轮缩放]
|
|
118
|
+
O --> E[Esc 关闭]
|
|
119
|
+
style S fill:#172554,stroke:#38bdf8,color:#e0f2fe
|
|
120
|
+
style Z fill:#14532d,stroke:#34d399,color:#ecfdf5
|
|
121
|
+
style O fill:#78350f,stroke:#fbbf24,color:#fffbeb`} />
|
|
122
|
+
<ScrollGrid columns="2">
|
|
123
|
+
<Mermaid title="窄栏中的纵向长图" width="100%" height="220px" chart={`flowchart TD
|
|
124
|
+
A[读取] --> B[解析]
|
|
125
|
+
B --> C[建模]
|
|
126
|
+
C --> D[渲染]
|
|
127
|
+
D --> E[校验]
|
|
128
|
+
E --> F[发布]
|
|
129
|
+
F --> G[归档]
|
|
130
|
+
G --> H[复盘]`} />
|
|
131
|
+
<RelationPath title="阅读路径" steps={[{level:'L0',node:'总览',note:'建立直觉',tone:'info'},{level:'L1',node:'机制',note:'理解过程',tone:'success'},{level:'L2',node:'边界',note:'检查反例',tone:'warn'}]} />
|
|
132
|
+
</ScrollGrid>
|
|
133
|
+
</ScrollSection>
|
|
134
|
+
|
|
103
135
|
<ScrollSection title="参考文献">
|
|
104
136
|
<References title="本页引用" items={[{id:'tufte1983',authors:'Tufte, E. R.',year:'1983',title:'The Visual Display of Quantitative Information',source:'Graphics Press',note:'关于以图形压缩与呈现证据的经典论述。'}]} />
|
|
105
137
|
</ScrollSection>
|
|
@@ -726,13 +726,48 @@ export function Tabs({ items = [], children }) {
|
|
|
726
726
|
}
|
|
727
727
|
Tabs.displayName = 'Tabs';
|
|
728
728
|
|
|
729
|
+
/**
|
|
730
|
+
* Enlarged reader for a diagram. It re-renders the chart under a fresh id so
|
|
731
|
+
* the full-size copy never collides with the inline SVG, then hands the result
|
|
732
|
+
* to the shared zoom overlay (wheel zoom, drag to pan, Esc to close).
|
|
733
|
+
*/
|
|
734
|
+
function MermaidZoom({ chart, title, onClose }) {
|
|
735
|
+
const ref = React.useRef(null);
|
|
736
|
+
const appearanceKey = useAppearanceKey();
|
|
737
|
+
const [error, setError] = React.useState('');
|
|
738
|
+
|
|
739
|
+
React.useEffect(() => {
|
|
740
|
+
let cancelled = false;
|
|
741
|
+
async function renderZoom() {
|
|
742
|
+
if (!ref.current || !chart.trim()) return;
|
|
743
|
+
try {
|
|
744
|
+
configureMermaid();
|
|
745
|
+
const renderId = `mermaid-zoom-${Math.random().toString(36).slice(2, 10)}`;
|
|
746
|
+
const { svg } = await mermaid.render(renderId, chart.trim());
|
|
747
|
+
if (!cancelled && ref.current) ref.current.innerHTML = svg;
|
|
748
|
+
} catch (err) {
|
|
749
|
+
if (!cancelled) setError(err.message || 'Mermaid 图表语法错误');
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
renderZoom();
|
|
753
|
+
return () => { cancelled = true; };
|
|
754
|
+
}, [chart, appearanceKey]);
|
|
755
|
+
|
|
756
|
+
return (
|
|
757
|
+
<ZoomOverlay title={title || '关系草图'} caption={title} onClose={onClose}>
|
|
758
|
+
{error
|
|
759
|
+
? <pre className="mermaid-error">{error}</pre>
|
|
760
|
+
: <div className="mermaid-zoom-canvas" ref={ref} />}
|
|
761
|
+
</ZoomOverlay>
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
|
|
729
765
|
export function Mermaid({ chart = '', title = '关系草图', width = 'auto', height = 'auto', x = 0, y = 0, position = 'flow' }) {
|
|
730
766
|
const ref = React.useRef(null);
|
|
731
767
|
const id = React.useId().replace(/:/g, '');
|
|
732
768
|
const appearanceKey = useAppearanceKey();
|
|
733
769
|
const [error, setError] = React.useState('');
|
|
734
|
-
const [
|
|
735
|
-
const [pan, setPan] = React.useState({ x: 0, y: 0 });
|
|
770
|
+
const [zoomed, setZoomed] = React.useState(false);
|
|
736
771
|
|
|
737
772
|
React.useEffect(() => {
|
|
738
773
|
let cancelled = false;
|
|
@@ -754,15 +789,41 @@ export function Mermaid({ chart = '', title = '关系草图', width = 'auto', he
|
|
|
754
789
|
return () => { cancelled = true; };
|
|
755
790
|
}, [chart, id, appearanceKey]);
|
|
756
791
|
|
|
757
|
-
const
|
|
758
|
-
const
|
|
792
|
+
const canZoom = Boolean(chart.trim()) && !error;
|
|
793
|
+
const openZoom = () => { if (canZoom) setZoomed(true); };
|
|
794
|
+
const onKeyDown = event => {
|
|
795
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
796
|
+
event.preventDefault();
|
|
797
|
+
openZoom();
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
|
|
759
801
|
return (
|
|
760
802
|
<div className={`semantic-mermaid ${widgetClass(position)}`} style={widgetStyle({ width, height, x, y, position })}>
|
|
761
803
|
<div className="semantic-widget-head">
|
|
762
804
|
<span>{title}</span>
|
|
763
|
-
<div className="mermaid-tools"
|
|
805
|
+
<div className="mermaid-tools">
|
|
806
|
+
<code>MERMAID</code>
|
|
807
|
+
<button type="button" onClick={openZoom} disabled={!canZoom} aria-label={`放大阅读:${title}`}>
|
|
808
|
+
<ZoomIn size={12} />放大阅读
|
|
809
|
+
</button>
|
|
810
|
+
</div>
|
|
764
811
|
</div>
|
|
765
|
-
{error ?
|
|
812
|
+
{error ? (
|
|
813
|
+
<pre className="mermaid-error">{error}</pre>
|
|
814
|
+
) : (
|
|
815
|
+
<div
|
|
816
|
+
className="mermaid-canvas"
|
|
817
|
+
role="button"
|
|
818
|
+
tabIndex={0}
|
|
819
|
+
aria-label={`放大阅读:${title}`}
|
|
820
|
+
onClick={openZoom}
|
|
821
|
+
onKeyDown={onKeyDown}
|
|
822
|
+
>
|
|
823
|
+
<div className="mermaid-canvas-inner" ref={ref} />
|
|
824
|
+
</div>
|
|
825
|
+
)}
|
|
826
|
+
{zoomed && <MermaidZoom chart={chart} title={title} onClose={() => setZoomed(false)} />}
|
|
766
827
|
</div>
|
|
767
828
|
);
|
|
768
829
|
}
|
|
@@ -1047,12 +1108,12 @@ const ZOOM_MIN = 0.5;
|
|
|
1047
1108
|
const ZOOM_MAX = 6;
|
|
1048
1109
|
|
|
1049
1110
|
/**
|
|
1050
|
-
* Full-viewport
|
|
1051
|
-
* transformed/rotated ancestor surfaces (`position:
|
|
1052
|
-
* contained by them). Supports wheel zoom, drag to
|
|
1053
|
-
* 1x/2x, and Escape to close.
|
|
1111
|
+
* Full-viewport zoom viewer shared by images and diagrams. Rendered through a
|
|
1112
|
+
* portal so it escapes the transformed/rotated ancestor surfaces (`position:
|
|
1113
|
+
* fixed` would otherwise be contained by them). Supports wheel zoom, drag to
|
|
1114
|
+
* pan, double-click to toggle 1x/2x, and Escape to close.
|
|
1054
1115
|
*/
|
|
1055
|
-
function
|
|
1116
|
+
function ZoomOverlay({ title, caption, label, onClose, children }) {
|
|
1056
1117
|
const [scale, setScale] = React.useState(1);
|
|
1057
1118
|
const [offset, setOffset] = React.useState({ x: 0, y: 0 });
|
|
1058
1119
|
const [dragging, setDragging] = React.useState(false);
|
|
@@ -1126,8 +1187,6 @@ function ImageZoom({ src, alt, caption, label, onClose }) {
|
|
|
1126
1187
|
}
|
|
1127
1188
|
};
|
|
1128
1189
|
|
|
1129
|
-
const title = alt || caption || '图片';
|
|
1130
|
-
|
|
1131
1190
|
return createPortal(
|
|
1132
1191
|
<div
|
|
1133
1192
|
className="image-zoom-overlay"
|
|
@@ -1146,14 +1205,11 @@ function ImageZoom({ src, alt, caption, label, onClose }) {
|
|
|
1146
1205
|
onDoubleClick={() => { const next = scaleRef.current > 1 ? 1 : 2; setScale(next); settle(next); }}
|
|
1147
1206
|
onClick={event => { if (movedRef.current) { event.stopPropagation(); movedRef.current = false; } }}
|
|
1148
1207
|
>
|
|
1149
|
-
<
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
draggable="false"
|
|
1153
|
-
style={{ transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})` }}
|
|
1154
|
-
/>
|
|
1208
|
+
<div className="image-zoom-target" style={{ transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})` }}>
|
|
1209
|
+
{children}
|
|
1210
|
+
</div>
|
|
1155
1211
|
</div>
|
|
1156
|
-
{title && <p className="image-zoom-caption">{label && <b>{label}</b>}{caption
|
|
1212
|
+
{title && <p className="image-zoom-caption">{label && <b>{label}</b>}{caption}</p>}
|
|
1157
1213
|
<div className="image-zoom-toolbar">
|
|
1158
1214
|
<button type="button" onClick={() => zoomBy(-0.25)} aria-label="缩小" title="缩小(−)">−</button>
|
|
1159
1215
|
<span aria-live="polite">{Math.round(scale * 100)}%</span>
|
|
@@ -1166,6 +1222,15 @@ function ImageZoom({ src, alt, caption, label, onClose }) {
|
|
|
1166
1222
|
);
|
|
1167
1223
|
}
|
|
1168
1224
|
|
|
1225
|
+
function ImageZoom({ src, alt, caption, label, onClose }) {
|
|
1226
|
+
const title = alt || caption || '图片';
|
|
1227
|
+
return (
|
|
1228
|
+
<ZoomOverlay title={title} caption={caption || alt} label={label} onClose={onClose}>
|
|
1229
|
+
<img src={src} alt={alt || ''} draggable="false" />
|
|
1230
|
+
</ZoomOverlay>
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1169
1234
|
export function Figure({ src, alt = '', caption, label, width = 'auto', height = 'auto', x = 0, y = 0, position = 'flow' }) {
|
|
1170
1235
|
const [zoomed, setZoomed] = React.useState(false);
|
|
1171
1236
|
const title = alt || caption || '图片';
|
|
@@ -94,11 +94,45 @@ button:focus-visible, a:focus-visible, [role="button"]:focus-visible {
|
|
|
94
94
|
.gap-sm { gap: 8px; } .gap-md { gap: 16px; } .gap-lg { gap: 28px; }
|
|
95
95
|
.semantic-tabs details { border-bottom: 1px solid var(--border-subtle); padding: 8px 0; }
|
|
96
96
|
.semantic-tabs summary { cursor: pointer; font-weight: 700; color: var(--text-primary); }
|
|
97
|
-
.mermaid-tools { display: inline-flex; align-items: center; gap:
|
|
98
|
-
.mermaid-tools button {
|
|
97
|
+
.mermaid-tools { display: inline-flex; align-items: center; gap: 6px; }
|
|
98
|
+
.mermaid-tools button {
|
|
99
|
+
display: inline-flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 4px;
|
|
102
|
+
height: 22px;
|
|
103
|
+
padding: 0 8px;
|
|
104
|
+
border: 1px solid var(--border-medium);
|
|
105
|
+
border-radius: var(--radius-1);
|
|
106
|
+
background: var(--bg-subtle);
|
|
107
|
+
color: var(--text-secondary);
|
|
108
|
+
font-size: 11px;
|
|
109
|
+
font-weight: 700;
|
|
110
|
+
}
|
|
99
111
|
.mermaid-tools button:hover { color: var(--primary); border-color: var(--primary); }
|
|
100
|
-
.mermaid-
|
|
101
|
-
|
|
112
|
+
.mermaid-tools button:disabled { opacity: .5; cursor: not-allowed; }
|
|
113
|
+
|
|
114
|
+
/* The diagram frame is a bounded viewport, not a loose box: the head keeps its
|
|
115
|
+
own height and the canvas takes the rest, so a fixed `height` prop contains
|
|
116
|
+
the drawing instead of letting it paint past the border. The SVG is scaled to
|
|
117
|
+
fit with its own preserveAspectRatio; clicking opens the full-size viewer. */
|
|
118
|
+
.mermaid-canvas {
|
|
119
|
+
position: relative;
|
|
120
|
+
flex: 1 1 auto;
|
|
121
|
+
min-height: 0;
|
|
122
|
+
min-width: 0;
|
|
123
|
+
max-width: 100%;
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
padding: 4px;
|
|
126
|
+
cursor: zoom-in;
|
|
127
|
+
}
|
|
128
|
+
.mermaid-canvas-inner {
|
|
129
|
+
display: grid;
|
|
130
|
+
place-items: center;
|
|
131
|
+
width: 100%;
|
|
132
|
+
height: 100%;
|
|
133
|
+
transform-origin: center center;
|
|
134
|
+
}
|
|
135
|
+
.mermaid-canvas svg { display: block; width: 100%; height: 100%; max-width: 100%; max-height: 100%; }
|
|
102
136
|
|
|
103
137
|
/* Scrollbar beautification */
|
|
104
138
|
::-webkit-scrollbar {
|
|
@@ -2070,6 +2104,16 @@ button.flow-box {
|
|
|
2070
2104
|
min-width: 0;
|
|
2071
2105
|
}
|
|
2072
2106
|
|
|
2107
|
+
/* The diagram frame clips and hands its remaining height to the canvas, so a
|
|
2108
|
+
fixed `height` prop can never let the drawing escape the border. */
|
|
2109
|
+
.semantic-mermaid {
|
|
2110
|
+
display: flex;
|
|
2111
|
+
flex-direction: column;
|
|
2112
|
+
overflow: hidden;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
.semantic-mermaid > .semantic-widget-head { flex: 0 0 auto; }
|
|
2116
|
+
|
|
2073
2117
|
.draft-board .custom-section {
|
|
2074
2118
|
padding: 12px 14px;
|
|
2075
2119
|
}
|
|
@@ -2130,9 +2174,10 @@ button.flow-box {
|
|
|
2130
2174
|
.draft-board .semantic-relation-map {
|
|
2131
2175
|
width: fit-content;
|
|
2132
2176
|
max-width: 100%;
|
|
2133
|
-
overflow: visible;
|
|
2134
2177
|
}
|
|
2135
2178
|
|
|
2179
|
+
.draft-board .semantic-relation-map { overflow: visible; }
|
|
2180
|
+
|
|
2136
2181
|
.draft-board .semantic-insight,
|
|
2137
2182
|
.draft-board .semantic-note-grid {
|
|
2138
2183
|
width: fit-content;
|
|
@@ -2156,22 +2201,18 @@ button.flow-box {
|
|
|
2156
2201
|
font-size: 9px;
|
|
2157
2202
|
}
|
|
2158
2203
|
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
|
-
.mermaid-canvas svg {
|
|
2168
|
-
max-width: none;
|
|
2169
|
-
min-width: 100%;
|
|
2170
|
-
height: auto;
|
|
2204
|
+
/* The inline diagram is a preview: clicking it, or the head button, opens the
|
|
2205
|
+
full-size viewer. The cursor is the only affordance on the drawing itself, so
|
|
2206
|
+
nothing competes with the diagram for attention. */
|
|
2207
|
+
.mermaid-canvas:focus-visible {
|
|
2208
|
+
outline: 2px solid var(--accent-cyan);
|
|
2209
|
+
outline-offset: 2px;
|
|
2171
2210
|
}
|
|
2172
2211
|
|
|
2173
2212
|
.mermaid-error {
|
|
2174
2213
|
margin: 0;
|
|
2214
|
+
min-height: 0;
|
|
2215
|
+
overflow: auto;
|
|
2175
2216
|
color: var(--accent-rose);
|
|
2176
2217
|
font-size: 11px;
|
|
2177
2218
|
white-space: pre-wrap;
|
|
@@ -3760,6 +3801,10 @@ html[data-carrier='scroll'] #root {
|
|
|
3760
3801
|
place-items: center;
|
|
3761
3802
|
padding: 28px;
|
|
3762
3803
|
background: var(--overlay-scrim);
|
|
3804
|
+
/* Clip at the viewport, not at the media box: the zoom transform does not
|
|
3805
|
+
change layout size, so a stage-sized clip would only ever reveal what the
|
|
3806
|
+
un-zoomed element already covered. */
|
|
3807
|
+
overflow: hidden;
|
|
3763
3808
|
}
|
|
3764
3809
|
|
|
3765
3810
|
.image-zoom-stage {
|
|
@@ -3767,13 +3812,22 @@ html[data-carrier='scroll'] #root {
|
|
|
3767
3812
|
place-items: center;
|
|
3768
3813
|
max-width: 100%;
|
|
3769
3814
|
max-height: 100%;
|
|
3770
|
-
overflow:
|
|
3815
|
+
overflow: visible;
|
|
3771
3816
|
cursor: grab;
|
|
3772
3817
|
touch-action: none;
|
|
3773
3818
|
}
|
|
3774
3819
|
|
|
3775
3820
|
.image-zoom-stage.is-dragging { cursor: grabbing; }
|
|
3776
3821
|
|
|
3822
|
+
/* The pan/zoom transform rides a wrapper so the viewer can hold an <img> or a
|
|
3823
|
+
rendered diagram without duplicating the gesture logic. */
|
|
3824
|
+
.image-zoom-target {
|
|
3825
|
+
display: block;
|
|
3826
|
+
transition: transform 0.12s ease-out;
|
|
3827
|
+
will-change: transform;
|
|
3828
|
+
user-select: none;
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3777
3831
|
.image-zoom-stage img {
|
|
3778
3832
|
display: block;
|
|
3779
3833
|
width: auto;
|
|
@@ -3783,12 +3837,22 @@ html[data-carrier='scroll'] #root {
|
|
|
3783
3837
|
border-radius: var(--radius-2);
|
|
3784
3838
|
background: var(--bg-surface);
|
|
3785
3839
|
box-shadow: var(--shadow-modal);
|
|
3786
|
-
transition: transform 0.12s ease-out;
|
|
3787
|
-
will-change: transform;
|
|
3788
3840
|
user-select: none;
|
|
3789
3841
|
}
|
|
3790
3842
|
|
|
3791
|
-
.image-zoom-stage.is-dragging
|
|
3843
|
+
.image-zoom-stage.is-dragging .image-zoom-target { transition: none; }
|
|
3844
|
+
|
|
3845
|
+
/* A diagram keeps its natural aspect inside the viewer and paints on the
|
|
3846
|
+
Mermaid canvas tone, so labels stay legible over the dark scrim. */
|
|
3847
|
+
.mermaid-zoom-canvas svg {
|
|
3848
|
+
display: block;
|
|
3849
|
+
width: min(94vw, 1800px);
|
|
3850
|
+
height: auto;
|
|
3851
|
+
max-height: 84vh;
|
|
3852
|
+
border-radius: var(--radius-3);
|
|
3853
|
+
background: var(--mermaid-canvas);
|
|
3854
|
+
box-shadow: var(--shadow-modal);
|
|
3855
|
+
}
|
|
3792
3856
|
|
|
3793
3857
|
.image-zoom-caption {
|
|
3794
3858
|
position: absolute;
|
|
@@ -3847,14 +3911,14 @@ html[data-carrier='scroll'] #root {
|
|
|
3847
3911
|
}
|
|
3848
3912
|
|
|
3849
3913
|
@media (prefers-reduced-motion: reduce) {
|
|
3850
|
-
.image-zoom-
|
|
3914
|
+
.image-zoom-target { transition: none; }
|
|
3851
3915
|
}
|
|
3852
3916
|
|
|
3853
3917
|
@media (max-width: 760px) {
|
|
3854
3918
|
.image-zoom-overlay { padding: 12px; }
|
|
3855
3919
|
.image-zoom-stage img { max-height: 74vh; }
|
|
3920
|
+
.mermaid-zoom-canvas svg { max-height: 74vh; }
|
|
3856
3921
|
.image-zoom-caption { top: 10px; font-size: 12px; }
|
|
3857
|
-
.image-zoom-hint { opacity: 0.9; }
|
|
3858
3922
|
}
|
|
3859
3923
|
|
|
3860
3924
|
.semantic-figure figcaption {
|