@arshdelight/practi 0.10.0 → 0.11.1

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/dist/cmd/show.js CHANGED
@@ -99,7 +99,8 @@ function renderNode(hash, nodes, opts) {
99
99
  console.log(JSON.stringify(exportSubtree(node, nodes), null, 2));
100
100
  return 0;
101
101
  }
102
- const view = aggregateView(hash, nodes);
102
+ // --json full:机器视图带每步正文,AI 一次读全免逐哈希往返;人类文本仍走紧凑骨架
103
+ const view = aggregateView(hash, nodes, opts.json ? { full: true } : undefined);
103
104
  if (opts.json) {
104
105
  console.log(JSON.stringify(view, null, 2));
105
106
  return 0;
@@ -1 +1 @@
1
- {"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/cmd/show.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/cmd/show.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arshdelight/practi",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "description": "Local registry CLI for POP (Protocol of Practice): manage a personal, content-addressed collection of practice documents on top of @arshdelight/pop-sdk. Syncs to PractiHub.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -126,7 +126,8 @@ practi edit <hash> doc.json --remote
126
126
  # against your remote claims) swaps to the new document; nothing local
127
127
  practi remove <hash> # take a direct pop out of the local directory (registry op; GCs
128
128
  # unreachable nodes — shared indirect nodes survive)
129
- practi show <hash> # aggregate view; --json machine view; --doc full document form
129
+ practi show <hash> # aggregate view; --json machine view (steps carry content
130
+ # the one-read reproduction view); --doc full document form
130
131
  # local first (hash prefix OK); a full hash missing locally falls
131
132
  # back to the hub — content is re-hashed before display, a
132
133
  # found-but-mismatched hash is an error, never shown
@@ -143,6 +144,7 @@ practi migrate [path] [--keep] # cut: move the workspace (old dir removed after
143
144
 
144
145
  - `practi new` validates through the SDK, persists the content-addressed tree, registers the root as direct, and prints the root hash with `status: valid, registered as direct`. On validation issues the tree is stored but **not** registered — read the printed `E_*` issues, fix the JSON, re-run.
145
146
  - `practi show` accepts a unique hash prefix (≥4 hex digits); `--doc` emits the expanded document — the starting point for forking or refining.
147
+ - **Reading vs editing**: to follow or reproduce a practice, read the aggregate view (`show`, or `show --json` — steps come with their content, one read is enough); to edit, fork, or reason about the op structure (choice branches, loop bodies, set sections), read `--doc` — the aggregate flattens those by design.
146
148
 
147
149
  ## Remote workflow
148
150
 
@@ -31,6 +31,7 @@
31
31
  </div>
32
32
  <script src="/lang.js" defer></script>
33
33
  <script src="/marked.min.js"></script>
34
+ <script src="/highlight.min.js"></script>
34
35
  <script src="/detail.js" defer></script>
35
36
  </body>
36
37
  </html>
@@ -83,7 +83,9 @@ function render() {
83
83
  var node = walk(path);
84
84
  // 三视图:向导正文 / StandardView JSON / document JSON——侧栏大纲始终是导航脊柱,
85
85
  // 底部 Prev/Next 也常驻(在 JSON 视图里换节点=高亮跟着走)
86
- app.innerHTML = (view === 'wizard' ? nodeHtml(node) : jsonSectionHtml()) + navHtml();
86
+ app.innerHTML = topbarHtml(node) + (view === 'wizard' ? nodeHtml(node) : jsonSectionHtml()) + navHtml();
87
+ enhanceCodeBlocks(app);
88
+ enhanceMedia(app);
87
89
  updateTabs();
88
90
  markSide();
89
91
  renderNoteSide(); // 右栏跟人走:每次导航换节点都重渲染(草稿保真在 renderNoteSide 内部处理)
@@ -105,14 +107,19 @@ function render() {
105
107
  // set 孩子的后代不递归聚合、天生不在视图里——老实不高亮
106
108
 
107
109
  function jsonSectionHtml() {
110
+ // label 行右侧复制按钮:复用 data-code-copy 委托(编码 JSON 文本)
111
+ function labelRow(text, json) {
112
+ return '<div class="label-row"><div class="label">' + POP_I18N.t(text) + '</div>' +
113
+ '<button type="button" class="codeblock-copy json-copy" data-code-copy="' + encodeURIComponent(JSON.stringify(json, null, 2)) + '" aria-label="copy json">' + COPY_SVG + '</button></div>';
114
+ }
108
115
  if (view === 'doc') {
109
- return '<div class="section-sm"><div class="label">' + POP_I18N.t('labelDocJson') + '</div>' +
116
+ return '<div class="section-sm">' + labelRow('labelDocJson', doc) +
110
117
  '<pre class="json-pre">' + jsonHtml(doc, markDoc) + '</pre></div>';
111
118
  }
112
119
  if (svJson === null) {
113
120
  return '<div class="section-sm"><div class="label">' + POP_I18N.t('labelSvJson') + '</div><p class="op-note">' + POP_I18N.t('loading') + '</p></div>';
114
121
  }
115
- return '<div class="section-sm"><div class="label">' + POP_I18N.t('labelSvJson') + '</div>' +
122
+ return '<div class="section-sm">' + labelRow('labelSvJson', svJson) +
116
123
  '<pre class="json-pre">' + jsonHtml(svJson, markSv) + '</pre></div>';
117
124
  }
118
125
 
@@ -291,7 +298,7 @@ function nodeHtml(node) {
291
298
 
292
299
  if (node.type === 'action') html += inputsHtml(node);
293
300
  html += proseHtml(node);
294
- if (node.type === 'action') html += outputsHtml(node) + attHtml(node);
301
+ if (node.type === 'action') html += outputsHtml(node);
295
302
  html += childrenHtml(node) + choiceHtml(node) + setHtml(node);
296
303
  // 笔记只住右侧栏(不进内容区):revisions 已挪到左侧大纲底部(全文档汇总);refines 同处
297
304
  return html;
@@ -478,6 +485,306 @@ function proseHtml(node) {
478
485
  return sectionWrap(proseLite(content, node));
479
486
  }
480
487
 
488
+ // ── 围栏代码块增强:highlight.js 按语言高亮(未标注自动探测,relevance>=3 才信——
489
+ // 与 hub PopCodeBlock / D:/Dev/practi 桌面端同款逻辑),外包深色卡:
490
+ // 顶栏=语言标签 + 复制按钮(委托 data-code-copy)。hljs 缺库(DIY 覆盖前端)时跳过。
491
+ function enhanceCodeBlocks(root) {
492
+ if (typeof hljs === 'undefined') return;
493
+ var codes = root.querySelectorAll('.prose pre > code, .prose pre');
494
+ for (var i = 0; i < codes.length; i++) {
495
+ var el = codes[i];
496
+ var pre = el.tagName === 'PRE' ? el : el.parentElement;
497
+ if (!pre || pre.parentElement && pre.parentElement.classList.contains('codeblock')) continue;
498
+ if (el.tagName !== 'CODE') {
499
+ // proseLite 回落路径:<pre> 直接包文本——包一层 code 再走同一条路
500
+ var codeEl = document.createElement('code');
501
+ codeEl.textContent = pre.textContent;
502
+ pre.textContent = '';
503
+ pre.appendChild(codeEl);
504
+ el = codeEl;
505
+ }
506
+ var raw = el.textContent.replace(/^[`]{3}[\w-]*\r?\n/, '').replace(/\r?\n[`]{3}$/, '');
507
+ var m = /language-([\w-]+)/.exec(el.className);
508
+ var lang = m && hljs.getLanguage(m[1]) ? m[1] : '';
509
+ var detected = '';
510
+ var html;
511
+ if (lang) {
512
+ html = hljs.highlight(raw, { language: lang }).value;
513
+ detected = lang;
514
+ } else {
515
+ var r = hljs.highlightAuto(raw, AUTO_LANGS);
516
+ html = r.value;
517
+ detected = r.relevance >= 3 ? (r.language || '') : '';
518
+ }
519
+ var wrap = document.createElement('div');
520
+ wrap.className = 'codeblock';
521
+ var head = document.createElement('div');
522
+ head.className = 'codeblock-head';
523
+ head.innerHTML = '<span></span><button type="button" class="codeblock-copy" data-code-copy="' +
524
+ encodeURIComponent(raw) + '" aria-label="copy code">' + COPY_SVG + '</button>';
525
+ head.querySelector('span').textContent = detected;
526
+ var newPre = document.createElement('pre');
527
+ var newCode = document.createElement('code');
528
+ newCode.className = 'hljs';
529
+ newCode.innerHTML = html;
530
+ newPre.appendChild(newCode);
531
+ wrap.appendChild(head);
532
+ wrap.appendChild(newPre);
533
+ pre.parentElement.replaceChild(wrap, pre);
534
+ }
535
+ }
536
+
537
+ // ── 内联媒体增强 + 灯箱:marked 输出的独占 <p><img></p> 与 proseLite 的 figure
538
+ // 统一成 figure.media,限高不撑爆内容列;图注(alt)走 data-tip 悬停气泡,
539
+ // 不再常驻图片下方。点击开灯箱看原图,滚轮在图片列表内循环切换。向导视图
540
+ // 一次只渲染一个节点,列表收集自当前内容区——天然只含本节点的图,滚不出其它节点 ──
541
+ function enhanceMedia(root) {
542
+ var figs = [];
543
+ var imgs = root.querySelectorAll('.prose img');
544
+ for (var i = 0; i < imgs.length; i++) {
545
+ var img = imgs[i];
546
+ var fig = img.closest('figure');
547
+ if (!fig) {
548
+ // marked 路径:img 独占一个 <p> 才升格 figure(行内混排的小图不动)
549
+ var p = img.parentElement;
550
+ if (!p || p.tagName !== 'P' || p.childNodes.length !== 1) continue;
551
+ fig = document.createElement('figure');
552
+ p.replaceWith(fig);
553
+ fig.appendChild(img);
554
+ } else {
555
+ // proseLite 路径自带 figcaption:文字转进 data-tip,元素撤下
556
+ var capEl = fig.querySelector('figcaption');
557
+ if (capEl) capEl.remove();
558
+ }
559
+ fig.classList.add('media');
560
+ var alt = img.getAttribute('alt') || '';
561
+ if (alt) fig.setAttribute('data-cap', alt); // 图注走跟随光标的浮层气泡(#media-tip)
562
+ figs.push(fig);
563
+ }
564
+ for (var j = 0; j < figs.length; j++) {
565
+ (function (fig, idx) {
566
+ var im = fig.querySelector('img');
567
+ // 悬停判定只挂 img:figure 占满整列,挂 figure 会让空白处也出气泡
568
+ im.addEventListener('mouseenter', function (e) { showMediaTip(fig.getAttribute('data-cap'), e); });
569
+ im.addEventListener('mousemove', function (e) { moveMediaTip(e); });
570
+ im.addEventListener('mouseleave', hideMediaTip);
571
+ im.addEventListener('click', function () {
572
+ openLightbox(figs.map(function (f) { return f.querySelector('img'); }), idx);
573
+ });
574
+ })(figs[j], j);
575
+ }
576
+ }
577
+
578
+ // 跟随光标的图注气泡:单例浮层 #media-tip,进出图片显示/隐藏、move 跟手
579
+ // ([data-tip]::after 是静态定位,跟不了光标;皮肤同款白底深蓝字)
580
+ function showMediaTip(text, e) {
581
+ if (!text) return;
582
+ var tip = ensureMediaTip();
583
+ tip.textContent = text;
584
+ tip.classList.add('show');
585
+ moveMediaTip(e);
586
+ }
587
+ function moveMediaTip(e) {
588
+ var tip = document.getElementById('media-tip');
589
+ if (!tip || !tip.classList.contains('show')) return;
590
+ // 避让光标:偏移 16/32,且靠近视口边时翻到另一侧(上→下、右→左),
591
+ // 光标怎么移都不会压进气泡(气泡本身 pointer-events:none,只是视觉避让)
592
+ var w = tip.offsetWidth, h = tip.offsetHeight;
593
+ var x = e.clientX + 16;
594
+ var y = e.clientY - h - 16;
595
+ if (x + w > window.innerWidth - 8) x = e.clientX - w - 16;
596
+ if (y < 8) y = e.clientY + 20;
597
+ tip.style.left = x + 'px';
598
+ tip.style.top = y + 'px';
599
+ }
600
+ function hideMediaTip() {
601
+ var tip = document.getElementById('media-tip');
602
+ if (tip) tip.classList.remove('show');
603
+ }
604
+ function ensureMediaTip() {
605
+ var tip = document.getElementById('media-tip');
606
+ if (!tip) {
607
+ tip = document.createElement('div');
608
+ tip.id = 'media-tip';
609
+ document.body.appendChild(tip);
610
+ }
611
+ return tip;
612
+ }
613
+
614
+ var lbKeydown = null;
615
+
616
+ function openLightbox(list, idx) {
617
+ closeLightbox();
618
+ var cur = idx;
619
+ var overlay = document.createElement('div');
620
+ overlay.id = 'lightbox';
621
+ var stage = document.createElement('img');
622
+ var cap = document.createElement('div');
623
+ cap.className = 'lb-cap';
624
+ var count = document.createElement('div');
625
+ count.className = 'lb-count';
626
+ var close = document.createElement('button');
627
+ close.type = 'button';
628
+ close.className = 'lb-close';
629
+ close.setAttribute('aria-label', 'close');
630
+ close.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>';
631
+ function draw() {
632
+ var img = list[cur];
633
+ stage.src = img.getAttribute('src');
634
+ stage.alt = img.getAttribute('alt') || '';
635
+ cap.textContent = img.getAttribute('alt') || '';
636
+ count.textContent = (cur + 1) + ' / ' + list.length;
637
+ }
638
+ overlay.appendChild(close);
639
+ overlay.appendChild(stage);
640
+ overlay.appendChild(cap);
641
+ overlay.appendChild(count);
642
+ // 滚轮循环切换(down=下一张、up=上一张,头尾环绕)
643
+ overlay.addEventListener('wheel', function (e) {
644
+ e.preventDefault();
645
+ cur = (cur + (e.deltaY > 0 ? 1 : list.length - 1)) % list.length;
646
+ draw();
647
+ }, { passive: false });
648
+ overlay.addEventListener('click', function (e) {
649
+ if (e.target === overlay || e.target === close) closeLightbox();
650
+ });
651
+ lbKeydown = function (e) { if (e.key === 'Escape') closeLightbox(); };
652
+ document.addEventListener('keydown', lbKeydown);
653
+ document.body.appendChild(overlay);
654
+ draw();
655
+ }
656
+
657
+ function closeLightbox() {
658
+ var el = document.getElementById('lightbox');
659
+ if (el) el.remove();
660
+ if (lbKeydown) { document.removeEventListener('keydown', lbKeydown); lbKeydown = null; }
661
+ }
662
+
663
+ function flashCopied(btn) {
664
+ if (btn.getAttribute('data-flashing') || btn.textContent.trim() !== '') return;
665
+ btn.setAttribute('data-flashing', '1');
666
+ btn.innerHTML = CHECK_SVG;
667
+ setTimeout(function () {
668
+ btn.innerHTML = COPY_SVG;
669
+ btn.removeAttribute('data-flashing');
670
+ }, 1500);
671
+ }
672
+
673
+ // 自动探测语言子集(与 hub PopCodeBlock 的 AUTO_LANGS 同步):css/html/xml/markdown
674
+ // 极易误报(winget 命令被判成 css),排除后未命中就退回无标签
675
+ var AUTO_LANGS = ['bash', 'shell', 'powershell', 'json', 'javascript', 'typescript', 'python', 'sql', 'yaml', 'ini', 'rust', 'go', 'java', 'c', 'cpp', 'csharp', 'diff', 'makefile']
676
+ .filter(function (l) { return hljs.getLanguage(l); });
677
+
678
+ var COPY_SVG = '<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>';
679
+ var CHECK_SVG = '<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>';
680
+
681
+ // 轻量 toast(hub notify 同观感):底部居中深色胶囊,1.6s 自动消退
682
+ function showToast(text) {
683
+ var el = document.getElementById('web-toast');
684
+ if (!el) {
685
+ el = document.createElement('div');
686
+ el.id = 'web-toast';
687
+ el.className = 'web-toast';
688
+ document.body.appendChild(el);
689
+ }
690
+ el.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg><span></span>';
691
+ el.querySelector('span').textContent = text;
692
+ el.classList.add('show');
693
+ clearTimeout(el.__timer);
694
+ el.__timer = setTimeout(function () { el.classList.remove('show'); }, 1600);
695
+ }
696
+
697
+ // ── 内容列顶栏(hub 同款):返回(历史 back,兜底回目录)+ 文档信息弹窗。
698
+ // 本地文档没有状态/上传者/认领/浏览这些 hub 数据,弹窗只列节点数与根哈希 ──
699
+ var FP_SVG = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4"/><path d="M14 13.12c0 2.38 0 6.38-1 8.88"/><path d="M17.29 21.02c.12-.6.43-2.3.5-3.02"/><path d="M2 12a10 10 0 0 1 18-6"/><path d="M2 16h.01"/><path d="M21.8 16c.2-2 .131-5.354 0-6"/><path d="M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2"/><path d="M8.65 22c.21-.66.45-1.32.57-2"/><path d="M9 6.8a6 6 0 0 1 9 5.2v2"/></svg>';
700
+ var INFO_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>';
701
+ var CLIP_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>';
702
+ var BACK_SVG = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>';
703
+
704
+ /** 节点附件平铺(附件跟随节点:practice 无附件表,action 列自己的) */
705
+ function sizeLabel(bytes) {
706
+ if (bytes === undefined || bytes === null) return '';
707
+ if (bytes < 1024) return bytes + ' B';
708
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
709
+ return (bytes / 1024 / 1024).toFixed(1) + ' MB';
710
+ }
711
+
712
+ function collectAttachments(node) {
713
+ var out = [];
714
+ var atts = node && node.type === 'action' && node.attachments ? node.attachments : [];
715
+ for (var i = 0; i < atts.length; i++) {
716
+ var a = atts[i];
717
+ out.push({
718
+ name: a.name,
719
+ href: a.url || ('/blobs/' + a.hash),
720
+ meta: [a.mime, sizeLabel(a.size), a.hash ? shortHash(a.hash) : ''].filter(Boolean).join(' · ')
721
+ });
722
+ }
723
+ return out;
724
+ }
725
+
726
+ function topbarHtml(node) {
727
+ var atts = collectAttachments(node);
728
+ var attachBtn = atts.length
729
+ ? '<button type="button" class="topbar-btn" data-top="attach" aria-label="' + POP_I18N.t('docAttach') + '" data-tip="' + escapeHtml(POP_I18N.t('docAttach')) + '">' + CLIP_SVG + '<span>(' + atts.length + ')</span></button>'
730
+ : '';
731
+ return '<div class="detail-topbar">' +
732
+ '<button type="button" class="topbar-btn" data-top="back">' + BACK_SVG + '<span>' + POP_I18N.t('back') + '</span></button>' +
733
+ '<div class="topbar-right">' + attachBtn +
734
+ '<button type="button" class="topbar-btn topbar-icon" data-top="info" aria-label="' + POP_I18N.t('metaInfo') + '" data-tip="' + escapeHtml(POP_I18N.t('metaInfo')) + '">' + INFO_SVG + '</button></div>' +
735
+ '</div>';
736
+ }
737
+
738
+ /** 附件弹窗:固定尺寸,列表超出内滚(多附件不撑屏) */
739
+ function openAttDialog(node) {
740
+ closeAttDialog();
741
+ var atts = collectAttachments(node);
742
+ var rows = atts.map(function (a) {
743
+ return '<div class="att-row"><a href="' + escapeHtml(a.href) + '" target="_blank" rel="noopener">' + escapeHtml(a.name) + '</a>' +
744
+ '<span class="att-meta">' + escapeHtml(a.meta) + '</span></div>';
745
+ }).join('');
746
+ var overlay = document.createElement('div');
747
+ overlay.id = 'att-overlay';
748
+ overlay.innerHTML =
749
+ '<div class="info-card att-card" role="dialog" aria-label="' + POP_I18N.t('docAttach') + '">' +
750
+ '<div class="info-head"><div class="info-title">' + POP_I18N.t('docAttach') + '</div>' +
751
+ '<button type="button" class="info-close" aria-label="close">' + INFO_SVG + '</button></div>' +
752
+ '<div class="att-scroll">' + rows + '</div>' +
753
+ '</div>';
754
+ overlay.addEventListener('click', function (e) {
755
+ if (e.target === overlay) closeAttDialog();
756
+ });
757
+ document.body.appendChild(overlay);
758
+ }
759
+
760
+ function closeAttDialog() {
761
+ var el = document.getElementById('att-overlay');
762
+ if (el) el.remove();
763
+ }
764
+
765
+ function openInfoDialog() {
766
+ closeInfoDialog();
767
+ var overlay = document.createElement('div');
768
+ overlay.id = 'info-overlay';
769
+ overlay.innerHTML =
770
+ '<div class="info-card" role="dialog" aria-label="' + POP_I18N.t('metaInfo') + '">' +
771
+ '<div class="info-head"><div class="info-title">' + POP_I18N.t('metaInfo') + '</div>' +
772
+ '<button type="button" class="info-close" aria-label="close">' + INFO_SVG + '</button></div>' +
773
+ '<div class="info-row"><span class="info-key">' + POP_I18N.t('nodeCount', countNodes(doc)) + '</span></div>' +
774
+ '<button type="button" class="info-hash-btn" data-code-copy="' + encodeURIComponent(HASH) + '" title="' + POP_I18N.t('copied') + '">' +
775
+ FP_SVG + '<span class="info-hash">' + escapeHtml(HASH) + '</span>' + COPY_SVG + '</button>' +
776
+ '</div>';
777
+ overlay.addEventListener('click', function (e) {
778
+ if (e.target === overlay) closeInfoDialog();
779
+ });
780
+ document.body.appendChild(overlay);
781
+ }
782
+
783
+ function closeInfoDialog() {
784
+ var el = document.getElementById('info-overlay');
785
+ if (el) el.remove();
786
+ }
787
+
481
788
  function sectionWrap(body) {
482
789
  return '<div class="section-sm"><div class="label">' + POP_I18N.t('secContent') + '</div><div class="prose">' + body + '</div></div>';
483
790
  }
@@ -495,6 +802,11 @@ function renderMarkdown(content, node) {
495
802
  // 只转义 < 与 &(足以挡内联 HTML 注入);> 保留——行首 "> " 是块引用语法
496
803
  src = src.replace(/&/g, '&amp;').replace(/</g, '&lt;');
497
804
  var html = marked.parse(src, { gfm: true, breaks: false, async: false });
805
+ // marked 在 code 段内会把预转义产生的 & 再转一次(&lt; 变 &amp;lt;,页面显示字面
806
+ // &lt;)——code 段内 &amp; 还原回 &:预转义的 &lt; 复原、原文的字面 & 仍只剩一层
807
+ html = html.replace(/(<code[^>]*>)([\s\S]*?)(<\/code>)/g, function (m0, open, body, close) {
808
+ return open + body.replace(/&amp;/g, '&') + close;
809
+ });
498
810
  return html.replace(/<a href="mailto:[^"]*">([^<]*)<\/a>/g, '$1');
499
811
  }
500
812
 
@@ -575,16 +887,7 @@ function outputsHtml(node) {
575
887
  return '<div class="section-sm"><div class="label">' + POP_I18N.t('secOutputs') + '</div><ol class="io-list">' + outs.map(ioItem).join('') + '</ol></div>';
576
888
  }
577
889
 
578
- function attHtml(node) {
579
- var atts = node.attachments || [];
580
- if (!atts.length) return '';
581
- return '<div class="section-sm"><div class="label">' + POP_I18N.t('secAttachments') + '</div><ul class="att-list">' + atts.map(function (a) {
582
- var href = a.url || ('/blobs/' + a.hash);
583
- var meta = [a.mime, a.hash ? shortHash(a.hash) : ''].filter(Boolean).join(' · ');
584
- return '<li><a href="' + escapeHtml(href) + '">' + escapeHtml(a.name) + '</a>' +
585
- (meta ? ' <span class="att-meta">' + escapeHtml(meta) + '</span>' : '') + '</li>';
586
- }).join('') + '</ul></div>';
587
- }
890
+ // 正文的附件小节已撤(信息进顶栏附件按钮弹窗,附件跟随节点)
588
891
 
589
892
  // ── practice 的孩子呈现(按 op 分形态) ──
590
893
 
@@ -649,6 +952,37 @@ function navHtml() {
649
952
  // ── 事件(委托:innerHTML 重渲染不需要重复绑监听) ──
650
953
 
651
954
  document.addEventListener('click', function (e) {
955
+ var top = e.target.closest('[data-top]');
956
+ if (top) {
957
+ if (top.getAttribute('data-top') === 'back') {
958
+ if (history.length > 1) history.back();
959
+ else location.href = '/';
960
+ } else if (top.getAttribute('data-top') === 'info') {
961
+ openInfoDialog();
962
+ } else if (top.getAttribute('data-top') === 'attach') {
963
+ openAttDialog(walk(path));
964
+ }
965
+ return;
966
+ }
967
+ if (e.target.closest('#info-overlay .info-close')) {
968
+ closeInfoDialog();
969
+ return;
970
+ }
971
+ if (e.target.closest('#att-overlay .info-close')) {
972
+ closeAttDialog();
973
+ return;
974
+ }
975
+ var copyBtn = e.target.closest('[data-code-copy]');
976
+ if (copyBtn) {
977
+ var text = decodeURIComponent(copyBtn.getAttribute('data-code-copy'));
978
+ if (navigator.clipboard && navigator.clipboard.writeText) {
979
+ navigator.clipboard.writeText(text).then(function () {
980
+ flashCopied(copyBtn);
981
+ showToast(POP_I18N.t('copied'));
982
+ }, function () {});
983
+ }
984
+ return;
985
+ }
652
986
  var tab = e.target.closest('[data-view]');
653
987
  if (tab) {
654
988
  view = tab.getAttribute('data-view');