skill-forge-hub 1.0.7 → 1.2.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.
Files changed (153) hide show
  1. package/CHANGELOG.en.md +19 -0
  2. package/CHANGELOG.md +19 -0
  3. package/README.en.md +20 -7
  4. package/README.md +20 -7
  5. package/SKILLS_CATALOG.md +119 -0
  6. package/annual-report/SKILL.md +13 -3
  7. package/annual-report/resources/disk-and-html.md +1 -0
  8. package/annual-report/resources/reference.html +3 -3
  9. package/annual-report/resources/skillforge-guide.html +409 -0
  10. package/annual-report/resources/skillforge-guide.txt +132 -0
  11. package/annual-report/user-config.md +1 -1
  12. package/article-to-html/SKILL.md +13 -1
  13. package/article-to-html/resources/skillforge-guide.html +409 -0
  14. package/article-to-html/resources/skillforge-guide.txt +132 -0
  15. package/article-to-html/user-config.md +1 -1
  16. package/cli.js +60 -19
  17. package/commit-heatmap/SKILL.md +70 -0
  18. package/commit-heatmap/resources/collect.md +22 -0
  19. package/commit-heatmap/resources/generate.md +15 -0
  20. package/commit-heatmap/resources/html-rules.md +21 -0
  21. package/commit-heatmap/resources/reference.html +228 -0
  22. package/commit-heatmap/resources/skillforge-guide.html +409 -0
  23. package/commit-heatmap/resources/skillforge-guide.txt +132 -0
  24. package/commit-heatmap/resources/usage-guide.md +23 -0
  25. package/commit-heatmap/user-config.md +12 -0
  26. package/daily-report/SKILL.md +12 -2
  27. package/daily-report/resources/disk-and-html.md +1 -1
  28. package/daily-report/resources/reference.html +1 -1
  29. package/daily-report/resources/skillforge-guide.html +409 -0
  30. package/daily-report/resources/skillforge-guide.txt +132 -0
  31. package/daily-report/user-config.md +1 -1
  32. package/data-prepper/SKILL.md +54 -0
  33. package/data-prepper/resources/clean-rules.md +18 -0
  34. package/data-prepper/resources/overview.md +17 -0
  35. package/data-prepper/resources/read-data.md +13 -0
  36. package/data-prepper/resources/skillforge-guide.html +409 -0
  37. package/data-prepper/resources/skillforge-guide.txt +132 -0
  38. package/data-prepper/resources/usage-guide.md +24 -0
  39. package/data-prepper/user-config.md +11 -0
  40. package/decision-log/SKILL.md +53 -0
  41. package/decision-log/resources/index.md +21 -0
  42. package/decision-log/resources/record.md +19 -0
  43. package/decision-log/resources/skillforge-guide.html +409 -0
  44. package/decision-log/resources/skillforge-guide.txt +132 -0
  45. package/decision-log/resources/usage-guide.md +23 -0
  46. package/decision-log/user-config.md +9 -0
  47. package/deck-stage/SKILL.md +14 -11
  48. package/deck-stage/resources/cover-template.html +1 -1
  49. package/deck-stage/resources/skillforge-guide.html +409 -0
  50. package/deck-stage/resources/skillforge-guide.txt +132 -0
  51. package/docs/architecture.en.md +2 -1
  52. package/docs/architecture.md +2 -1
  53. package/docs/articles/ai-daily-report-correct-pose.md +39 -0
  54. package/docs/articles/ai-prompt-management-skill-pouch.md +37 -0
  55. package/docs/directory-submission.md +106 -0
  56. package/docs/install.en.md +57 -1
  57. package/docs/install.md +57 -1
  58. package/docs/usage-examples.en.md +50 -0
  59. package/docs/usage-examples.md +50 -0
  60. package/magic-deck/SKILL.md +15 -6
  61. package/magic-deck/resources/deck-spec.md +1 -1
  62. package/magic-deck/resources/reference.html +1 -1
  63. package/magic-deck/resources/skillforge-guide.html +409 -0
  64. package/magic-deck/resources/skillforge-guide.txt +132 -0
  65. package/magic-deck/user-config.md +1 -1
  66. package/meeting-minutes/SKILL.md +11 -1
  67. package/meeting-minutes/resources/disk-and-html.md +1 -0
  68. package/meeting-minutes/resources/reference.html +1 -1
  69. package/meeting-minutes/resources/skillforge-guide.html +409 -0
  70. package/meeting-minutes/resources/skillforge-guide.txt +132 -0
  71. package/meeting-minutes/user-config.md +1 -1
  72. package/meta-forge/SKILL.md +13 -3
  73. package/meta-forge/resources/forge-rules.md +4 -1
  74. package/meta-forge/resources/skillforge-guide.html +409 -0
  75. package/meta-forge/resources/skillforge-guide.txt +132 -0
  76. package/meta-forge/user-config.md +1 -1
  77. package/monthly-report/SKILL.md +11 -1
  78. package/monthly-report/resources/disk-and-html.md +1 -0
  79. package/monthly-report/resources/reference.html +1 -1
  80. package/monthly-report/resources/skillforge-guide.html +409 -0
  81. package/monthly-report/resources/skillforge-guide.txt +132 -0
  82. package/monthly-report/user-config.md +1 -1
  83. package/one-pager/SKILL.md +83 -0
  84. package/one-pager/resources/content.md +18 -0
  85. package/one-pager/resources/design-process.md +117 -0
  86. package/one-pager/resources/generate.md +28 -0
  87. package/one-pager/resources/html-rules.md +66 -0
  88. package/one-pager/resources/reference.html +1290 -0
  89. package/one-pager/resources/skillforge-guide.html +409 -0
  90. package/one-pager/resources/skillforge-guide.txt +132 -0
  91. package/one-pager/resources/usage-guide.md +37 -0
  92. package/one-pager/resources/user-template-example.html +63 -0
  93. package/one-pager/user-config.md +14 -0
  94. package/package.json +12 -2
  95. package/prompt-pouch/SKILL.md +16 -16
  96. package/prompt-pouch/resources/reference.html +181 -43
  97. package/prompt-pouch/resources/skillforge-guide.html +409 -0
  98. package/prompt-pouch/resources/skillforge-guide.txt +132 -0
  99. package/prompt-pouch/resources/usage-guide.md +3 -3
  100. package/quarterly-report/SKILL.md +11 -1
  101. package/quarterly-report/resources/disk-and-html.md +1 -0
  102. package/quarterly-report/resources/reference.html +1 -1
  103. package/quarterly-report/resources/skillforge-guide.html +409 -0
  104. package/quarterly-report/resources/skillforge-guide.txt +132 -0
  105. package/quarterly-report/user-config.md +1 -1
  106. package/release-notes/SKILL.md +54 -0
  107. package/release-notes/resources/collect.md +17 -0
  108. package/release-notes/resources/publish-checklist.md +11 -0
  109. package/release-notes/resources/skillforge-guide.html +409 -0
  110. package/release-notes/resources/skillforge-guide.txt +132 -0
  111. package/release-notes/resources/usage-guide.md +25 -0
  112. package/release-notes/resources/write.md +27 -0
  113. package/release-notes/user-config.md +15 -0
  114. package/skill-doctor/SKILL.md +53 -0
  115. package/skill-doctor/resources/checks.md +27 -0
  116. package/skill-doctor/resources/report.md +26 -0
  117. package/skill-doctor/resources/skillforge-guide.html +409 -0
  118. package/skill-doctor/resources/skillforge-guide.txt +132 -0
  119. package/skill-doctor/resources/usage-guide.md +22 -0
  120. package/skill-doctor/user-config.md +9 -0
  121. package/spec-doc2spec/SKILL.md +13 -9
  122. package/spec-doc2spec/resources/skillforge-guide.html +409 -0
  123. package/spec-doc2spec/resources/skillforge-guide.txt +132 -0
  124. package/spec-doc2spec/user-config.md +1 -1
  125. package/spec-spec2doc/SKILL.md +13 -12
  126. package/spec-spec2doc/resources/skillforge-guide.html +409 -0
  127. package/spec-spec2doc/resources/skillforge-guide.txt +132 -0
  128. package/spec-spec2doc/user-config.md +1 -1
  129. package/team-report-collector/SKILL.md +70 -0
  130. package/team-report-collector/resources/collect.md +23 -0
  131. package/team-report-collector/resources/generate.md +14 -0
  132. package/team-report-collector/resources/html-rules.md +22 -0
  133. package/team-report-collector/resources/reference.html +384 -0
  134. package/team-report-collector/resources/skillforge-guide.html +409 -0
  135. package/team-report-collector/resources/skillforge-guide.txt +132 -0
  136. package/team-report-collector/resources/usage-guide.md +25 -0
  137. package/team-report-collector/user-config.md +12 -0
  138. package/template/SKILL.md +2 -2
  139. package/template/user-config.md +1 -1
  140. package/twin-docs/SKILL.md +53 -0
  141. package/twin-docs/resources/glossary.md +12 -0
  142. package/twin-docs/resources/skillforge-guide.html +409 -0
  143. package/twin-docs/resources/skillforge-guide.txt +132 -0
  144. package/twin-docs/resources/sync-check.md +15 -0
  145. package/twin-docs/resources/translate.md +20 -0
  146. package/twin-docs/resources/usage-guide.md +24 -0
  147. package/twin-docs/user-config.md +11 -0
  148. package/weekly-report/SKILL.md +12 -2
  149. package/weekly-report/resources/disk-and-html.md +1 -1
  150. package/weekly-report/resources/reference.html +1 -1
  151. package/weekly-report/resources/skillforge-guide.html +409 -0
  152. package/weekly-report/resources/skillforge-guide.txt +132 -0
  153. package/weekly-report/user-config.md +1 -1
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <meta name="sf-template" content="prompt-pouch@2">
6
+ <meta name="sf-template" content="prompt-pouch@9">
7
7
  <meta name="pouch-id" content="REF-POUCH-0000">
8
8
  <title>锦囊 Prompt Pouch — 提示词管理台</title>
9
9
  <style>
@@ -113,7 +113,7 @@ html,body{ height:100%; }
113
113
  body{
114
114
  background:var(--bg); /* 主体纯色:光晕/160° 渐变移入 fixed 氛围层 body::before——避免背景传播到画布后滚动出现上下颜色断层(html,body 锁 height:100% 时 body 背景只覆盖第一屏,跨浏览器传播行为不一致);四层氛围与光晕层均 fixed 覆盖视口,滚动恒定 */
115
115
  color:var(--text);
116
- font-family:ui-monospace,'SF Mono',SFMono-Regular,Menlo,Consolas,'PingFang SC','Microsoft YaHei',monospace;
116
+ font-family:ui-monospace,'SF Mono',SFMono-Regular,Menlo,Consolas,monospace;
117
117
  min-height:100%;
118
118
  }
119
119
 
@@ -200,12 +200,13 @@ header{
200
200
  /* 侧栏 */
201
201
  aside{ width:200px; flex:0 0 auto;
202
202
  display:flex; flex-direction:column;
203
- position:sticky; top:80px; align-self:flex-start;
204
- height:calc(100vh - 100px);
203
+ align-self:flex-start; /* 自然流式随页面整体滚动:说明卡完整展示,页面滚到底即可读完 */
205
204
  }
206
- /* 侧栏分组滚动区:收藏 + 分组局部滚动,分组下方紧跟「+ 新建分组」,滚动流末尾附「锦囊用法」说明——滚动区占满「全部」与侧栏底缘之间的剩余屏幕高度(最大占满屏幕),说明被顶出首屏(滚到底可见);窄滚动条 */
205
+ /* 侧栏分组滚动区:收藏 + 分组局部滚动——滚动区最大高度 = 视口高度减去顶栏、「全部」行与底部「锦囊用法」说明卡(完整展示不裁剪),分组再多说明卡也完整可见;窄滚动条 */
207
206
  .nav-scroll{
208
- flex:1 1 auto; min-height:0; overflow-y:auto;
207
+ flex:0 0 auto; min-height:0;
208
+ max-height:calc(100vh - 360px);
209
+ overflow-y:auto;
209
210
  margin:0 -4px 0 0; padding-right:4px;
210
211
  scrollbar-width:thin; scrollbar-color:var(--border) transparent;
211
212
  }
@@ -231,8 +232,8 @@ aside{ width:200px; flex:0 0 auto;
231
232
  }
232
233
  .nav-item.on .cnt{ color:var(--gold); background:var(--gold-dim); }
233
234
  .side-tip{
234
- flex:0 0 auto;
235
- margin:16px 4px 0 0; padding:12px 14px; border-radius:10px; /* margin-right 补偿 nav-scroll 负 margin(-4px)的右侧裁切,虚线边框完整不裁;滚动流末尾,分组多时被顶出首屏 */
235
+ flex:0 0 auto; /* 完整展示不裁剪:分组滚动区为其留出空间,侧栏随页面整体滚动即可读完 */
236
+ margin:12px 0 0 0; padding:12px 14px 14px; border-radius:10px;
236
237
  border:1px dashed var(--border); font-size:11px; line-height:1.9; color:var(--muted);
237
238
  }
238
239
  .side-tip b{ color:var(--gold); font-weight:700; }
@@ -246,13 +247,38 @@ main{ flex:1; min-width:0; }
246
247
  font-weight:900;
247
248
  }
248
249
  .bar .total{ font-size:12px; color:var(--muted); }
249
- .sort-box{ margin-left:auto; display:flex; align-items:center; gap:6px; font-size:11px; color:var(--muted); }
250
- .sort-box select{
250
+ .sort-box{ margin-left:auto; display:flex; align-items:center; gap:6px; font-size:11px; color:var(--muted); position:relative; }
251
+ .sort-sel{
252
+ display:flex; align-items:center; gap:7px;
251
253
  background:var(--panel2); border:1px solid var(--border); color:var(--text);
252
254
  font-family:inherit; font-size:12px; letter-spacing:1px;
253
- border-radius:999px; padding:5px 12px; outline:none; cursor:pointer;
254
- }
255
- .sort-box select:focus{ border-color:var(--gold); }
255
+ border-radius:999px; padding:5px 13px; outline:none; cursor:pointer;
256
+ transition:border-color .15s ease;
257
+ }
258
+ .sort-sel:hover,.sort-sel.open{ border-color:var(--gold); }
259
+ .sort-arrow{ font-size:9px; color:var(--gold); line-height:1; transition:transform .2s ease; }
260
+ .sort-sel.open .sort-arrow{ transform:rotate(180deg); }
261
+ .sort-menu{
262
+ position:absolute; top:calc(100% + 6px); right:0; z-index:130;
263
+ display:flex; flex-direction:column; gap:2px; min-width:136px;
264
+ background:var(--panel2); border:1px solid var(--border); border-radius:12px;
265
+ padding:5px; box-shadow:0 12px 32px rgba(0,0,0,.45);
266
+ animation:sortMenuIn .16s ease;
267
+ }
268
+ .sort-menu[hidden]{ display:none; }
269
+ @keyframes sortMenuIn{ from{ opacity:0; transform:translateY(-4px); } to{ opacity:1; transform:none; } }
270
+ .sort-opt{
271
+ display:flex; align-items:center; gap:8px; width:100%;
272
+ background:transparent; border:none; border-radius:8px;
273
+ color:var(--text); font-family:inherit; font-size:12px; letter-spacing:1px;
274
+ padding:7px 10px; cursor:pointer; text-align:left;
275
+ transition:background .12s ease,color .12s ease;
276
+ }
277
+ .sort-opt:hover{ background:var(--gold-dim); color:var(--gold); }
278
+ .sort-check{ width:14px; font-size:11px; color:var(--gold); opacity:0; flex:0 0 auto; }
279
+ .sort-opt.on{ color:var(--gold); font-weight:600; }
280
+ .sort-opt.on .sort-check{ opacity:1; }
281
+ [data-theme="light"] .sort-menu{ box-shadow:0 10px 28px rgba(30,31,34,.16); }
256
282
  .sort-status{
257
283
  font-size:12px; color:var(--muted);
258
284
  display:flex; align-items:center; gap:10px;
@@ -521,9 +547,10 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
521
547
  }
522
548
  /* 页面直写:侧栏分组管理 / 提示词表单 / toast */
523
549
  .nav-add{
524
- display:flex; align-items:center; justify-content:center; gap:6px;
525
- margin-top:10px; padding:10px 14px; border-radius:10px; cursor:pointer;
526
- font-size:13px; letter-spacing:1px; color:var(--gold);
550
+ flex:0 0 auto; /* 「全部」行右侧迷你 + 图标(新建分组),行内不占独立高度 */
551
+ width:22px; height:22px; margin-left:6px;
552
+ display:flex; align-items:center; justify-content:center;
553
+ border-radius:6px; cursor:pointer; font-size:14px; line-height:1; color:var(--gold);
527
554
  border:1px dashed var(--border); background:var(--gold-dim);
528
555
  transition:all .18s ease; user-select:none;
529
556
  }
@@ -636,13 +663,16 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
636
663
  <span class="tag-filter" id="tagFilterChip" style="display:none"><span class="tf-ico">🏷</span><b id="tagFilterName"></b><button class="tf-x" id="tagFilterClear" title="清除标签过滤">✕</button></span>
637
664
  <button class="btn primary" id="btnAddItem">+ 新增提示词</button>
638
665
  <div class="sort-box">
639
- <label for="sortSel">排序</label>
640
- <select id="sortSel" title="切换卡片排序方式">
641
- <option value="added">最新添加</option>
642
- <option value="recent">最近使用</option>
643
- <option value="freq">使用频率</option>
644
- <option value="manual">手动顺序</option>
645
- </select>
666
+ <label>排序</label>
667
+ <button class="sort-sel" id="sortSel" type="button" title="切换卡片排序方式">
668
+ <span class="sort-sel-txt" id="sortTxt">最新添加</span><span class="sort-arrow">▾</span>
669
+ </button>
670
+ <div class="sort-menu" id="sortMenu" hidden>
671
+ <button class="sort-opt on" data-mode="added" type="button"><span class="sort-check">✓</span>最新添加</button>
672
+ <button class="sort-opt" data-mode="recent" type="button"><span class="sort-check">✓</span>最近使用</button>
673
+ <button class="sort-opt" data-mode="freq" type="button"><span class="sort-check">✓</span>使用频率</button>
674
+ <button class="sort-opt" data-mode="manual" type="button"><span class="sort-check">✓</span>手动顺序</button>
675
+ </div>
646
676
  </div>
647
677
  <div class="sort-status" id="sortStatus">
648
678
  <button class="btn" id="btnSortCmd" disabled>📋 复制变更指令</button>
@@ -812,6 +842,9 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
812
842
  var searchClear = document.getElementById('searchClear');
813
843
  var sortStatus = document.getElementById('sortStatus');
814
844
  var sortSel = document.getElementById('sortSel');
845
+ var sortMenu = document.getElementById('sortMenu');
846
+ var sortTxt = document.getElementById('sortTxt');
847
+ var SORT_LABELS = { added:'最新添加', recent:'最近使用', freq:'使用频率', manual:'手动顺序' };
815
848
  var btnSortCmd = document.getElementById('btnSortCmd');
816
849
  var btnMarkSaved = document.getElementById('btnMarkSaved');
817
850
  var bkStatus = document.getElementById('bkStatus');
@@ -944,16 +977,15 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
944
977
  var html = '';
945
978
  var all = state.data.prompts.length;
946
979
  var favN = state.data.prompts.filter(function(p){ return p.favorite; }).length;
947
- html += '<div class="nav-item' + (state.curGroup==='all'?' on':'') + '" data-g="all"><span class="icon">🗂️</span>全部<span class="cnt">' + all + '</span></div>';
980
+ html += '<div class="nav-item' + (state.curGroup==='all'?' on':'') + '" data-g="all"><span class="icon">🗂️</span>全部<span class="cnt">' + all + '</span><span class="nav-add" id="navAdd" title="新建分组">+</span></div>';
948
981
  html += '<div class="nav-scroll">';
949
982
  html += '<div class="nav-item' + (state.curGroup==='fav'?' on':'') + '" data-g="fav"><span class="icon">⭐</span>收藏<span class="cnt">' + favN + '</span></div>';
950
983
  state.data.groups.forEach(function(g){
951
984
  var n = state.data.prompts.filter(function(p){ return p.group === g.id; }).length;
952
985
  html += '<div class="nav-item' + (state.curGroup===g.id?' on':'') + '" data-g="' + g.id + '" draggable="true" title="拖拽调整分组顺序"><span class="icon">' + g.icon + '</span><span class="g-name">' + escapeHtml(g.name) + '</span><span class="cnt">' + n + '</span><span class="g-ops"><button class="g-edit" title="编辑分组">✎</button><button class="g-del" title="删除分组">🗑</button></span></div>';
953
986
  });
954
- html += '<div class="nav-add" id="navAdd">+ 新建分组</div>';
955
- html += '<div class="side-tip"><b>锦囊用法</b><br>· 页面直接增删改:+ 新建分组 / + 新增提示词 / ✎ 编辑 / 🗑 删除(授权目录即写即存)<br>· 也可对 AI 说「把 XX 加到写作输出分组」<br>· 顶栏切换排序;⇄ 移组、拖卡片排序自动保存;拖分组条调整分组顺序<br>· 点卡片标签按标签筛选(再点取消);标签悬停 ✕ 删 / + 加;⤓ 导出 / ⤒ 导入 JSON 备份<br>· ⭐ 收藏常用 · 复制次数在卡片右上角</div>';
956
987
  html += '</div>';
988
+ html += '<div class="side-tip"><b>锦囊用法</b><br>· 页面直接增删改:+ 新建分组 / + 新增提示词 / ✎ 编辑 / 🗑 删除(授权目录即写即存)<br>· 也可对 AI 说「把 XX 加到写作输出分组」<br>· 顶栏切换排序;⇄ 移组、拖卡片排序自动保存;拖分组条调整分组顺序<br>· 点卡片标签按标签筛选(再点取消);标签悬停 ✕ 删 / + 加;⤓ 导出 / ⤒ 导入 JSON 备份<br>· ⭐ 收藏常用 · 复制次数在卡片右上角</div>';
957
989
  sideNav.innerHTML = html;
958
990
  sideNav.querySelectorAll('.nav-item').forEach(function(el){
959
991
  el.addEventListener('click', function(){
@@ -1001,7 +1033,7 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
1001
1033
  });
1002
1034
  });
1003
1035
  var addBtn = sideNav.querySelector('#navAdd');
1004
- if (addBtn) addBtn.addEventListener('click', function(){ openGroupForm(null); });
1036
+ if (addBtn) addBtn.addEventListener('click', function(e){ e.stopPropagation(); openGroupForm(null); });
1005
1037
  }
1006
1038
 
1007
1039
  function render(){
@@ -1198,7 +1230,12 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
1198
1230
  persistUserState();
1199
1231
  scheduleBackup();
1200
1232
  }
1201
- function syncSortUI(){ if (sortSel) sortSel.value = state.sortMode; }
1233
+ function syncSortUI(){
1234
+ if (sortTxt) sortTxt.textContent = SORT_LABELS[state.sortMode] || SORT_LABELS.added;
1235
+ if (sortMenu) sortMenu.querySelectorAll('.sort-opt').forEach(function(o){
1236
+ o.classList.toggle('on', o.getAttribute('data-mode') === state.sortMode);
1237
+ });
1238
+ }
1202
1239
  function ensureManual(){
1203
1240
  if (state.sortMode === 'manual') return;
1204
1241
  state.sortMode = 'manual';
@@ -1279,10 +1316,30 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
1279
1316
  if (!gpopEl.contains(e.target) && !onAnchor){ closeGpop(); }
1280
1317
  }
1281
1318
  });
1282
- sortSel.addEventListener('change', function(){
1283
- state.sortMode = sortSel.value;
1319
+ function toggleSortMenu(force){
1320
+ if (!sortMenu) return;
1321
+ var willOpen = (force !== undefined) ? force : sortMenu.hidden;
1322
+ sortMenu.hidden = !willOpen;
1323
+ sortSel.classList.toggle('open', willOpen);
1324
+ }
1325
+ sortSel.addEventListener('click', function(e){
1326
+ e.stopPropagation();
1327
+ toggleSortMenu();
1328
+ });
1329
+ sortMenu.addEventListener('click', function(e){
1330
+ var opt = e.target.closest('.sort-opt');
1331
+ if (!opt) return;
1332
+ state.sortMode = opt.getAttribute('data-mode');
1284
1333
  persistUserState();
1285
1334
  render();
1335
+ syncSortUI();
1336
+ toggleSortMenu(false);
1337
+ });
1338
+ document.addEventListener('click', function(e){
1339
+ if (!sortMenu.hidden){
1340
+ var onSel = e.target.closest && e.target.closest('#sortSel');
1341
+ if (!sortMenu.contains(e.target) && !onSel){ toggleSortMenu(false); }
1342
+ }
1286
1343
  });
1287
1344
 
1288
1345
  /* ---------- 模板插值({{变量}} / {变量}) ---------- */
@@ -1448,6 +1505,7 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
1448
1505
  if (next === null) return false;
1449
1506
  return bkWrite('prompts.json', 'data', JSON.stringify(next, null, 2)).then(function(){
1450
1507
  state.data = next;
1508
+ syncMirror(next);
1451
1509
  applyUserState();
1452
1510
  state.dirty = false; state.moves = [];
1453
1511
  persistUserState(); refreshDirtyUI();
@@ -1484,6 +1542,7 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
1484
1542
  });
1485
1543
  return bkWrite('prompts.json', 'data', JSON.stringify(latest, null, 2)).then(function(){
1486
1544
  state.data = latest;
1545
+ syncMirror(latest);
1487
1546
  applyUserState();
1488
1547
  state.dirty = false; state.moves = [];
1489
1548
  persistUserState(); refreshDirtyUI();
@@ -1868,6 +1927,7 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
1868
1927
  if (e.key === 'Escape'){
1869
1928
  closeFillPanel();
1870
1929
  closeGpop();
1930
+ toggleSortMenu(false);
1871
1931
  closeItemForm();
1872
1932
  closeGroupForm();
1873
1933
  document.getElementById('bkMask').classList.remove('open');
@@ -2030,6 +2090,19 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
2030
2090
  });
2031
2091
  });
2032
2092
  }
2093
+ /* 数据镜像:localStorage 存全量数据快照(file:// 下 IndexedDB 偶发不可用时兜底恢复,键按 pouch-id 隔离) */
2094
+ function syncMirror(data){
2095
+ try{ localStorage.setItem(SF_NS + '-mirror', JSON.stringify(data)); }catch(e){}
2096
+ }
2097
+ function readMirror(){
2098
+ try{
2099
+ var raw = localStorage.getItem(SF_NS + '-mirror');
2100
+ if (!raw) return null;
2101
+ var d = JSON.parse(raw);
2102
+ if (d && d.groups && d.prompts) return d;
2103
+ }catch(e){}
2104
+ return null;
2105
+ }
2033
2106
  /* 备份文件读:backup/ 优先,读不到回退根目录旧文件(旧目录结构兼容) */
2034
2107
  function readBk(name){
2035
2108
  return bkRead(name, 'backup').then(function(text){
@@ -2122,6 +2195,7 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
2122
2195
  try{ localStorage.removeItem(SF_NS + '-bk-declined'); }catch(e){}
2123
2196
  idbSet('bkHandle', bkDirHandle);
2124
2197
  updateBkUI();
2198
+ reloadFromDirIfNeeded();
2125
2199
  return true;
2126
2200
  })
2127
2201
  .catch(function(){ pendingAuth = false; updateBkUI(); return false; });
@@ -2168,6 +2242,43 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
2168
2242
  pendingAuth = false;
2169
2243
  }).catch(function(){});
2170
2244
  }
2245
+ /* file:// 直开补洞:fetch 被浏览器拦截读不到数据文件时,授权目录后直读 data/prompts.json 替换示例/快照 */
2246
+ function reloadFromDirIfNeeded(){
2247
+ if (!bkDirHandle || bootMode === 'live') return;
2248
+ readPouch().then(function(text){
2249
+ if (text === null) return;
2250
+ var data = JSON.parse(text);
2251
+ state.data = data;
2252
+ idbSet('lastData', data);
2253
+ syncMirror(data);
2254
+ bootMode = 'live';
2255
+ cacheBadge.style.display = 'none';
2256
+ applyUserState();
2257
+ renderNav(); render();
2258
+ toast('✓ 已直读 data/prompts.json,你的分组与提示词已载入');
2259
+ }).catch(function(){});
2260
+ }
2261
+ /* 「🔓 恢复数据」:已授权过目录但刷新后权限需重新确认(file:// 权限不跨会话持久)→ 一键 requestPermission 后直读,免重新选目录 */
2262
+ function restoreAuth(){
2263
+ if (!bkDirHandle || typeof bkDirHandle.requestPermission !== 'function') return;
2264
+ bkDirHandle.requestPermission({ mode: 'readwrite' }).then(function(st){
2265
+ if (st === 'granted'){
2266
+ pendingAuth = false; updateBkUI();
2267
+ reloadFromDirIfNeeded();
2268
+ } else {
2269
+ toast('未获授权:点「💾 授权目录」重新选择目录', true);
2270
+ }
2271
+ }).catch(function(){ toast('权限恢复失败,请重新授权目录', true); });
2272
+ }
2273
+ function updateBootBadge(){
2274
+ if (bootMode === 'live' || !bkDirHandle || !bootRendered) return;
2275
+ var link = ' <a id="bootRestore" style="cursor:pointer;text-decoration:underline">🔓 恢复数据</a>';
2276
+ cacheBadge.innerHTML = (bootMode === 'cache'
2277
+ ? '⚠️ 快照模式:当前显示上次快照,目录权限需确认后直读最新数据'
2278
+ : '📌 示例模式:检测到曾授权过目录,点击确认权限即可载入你的数据') + link;
2279
+ cacheBadge.style.display = '';
2280
+ document.getElementById('bootRestore').addEventListener('click', restoreAuth);
2281
+ }
2171
2282
  btnBkEnable.addEventListener('click', function(){
2172
2283
  if (bkDirHandle && !pendingAuth){
2173
2284
  writeBackupNow().then(function(ok){ toast(ok ? '✓ 备份完成 · backup/pouch-backup.json' : '备份失败,请重新授权目录', !ok); });
@@ -2179,7 +2290,7 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
2179
2290
  }
2180
2291
  if (bkDirHandle && pendingAuth){
2181
2292
  bkDirHandle.requestPermission({ mode: 'readwrite' }).then(function(st){
2182
- if (st === 'granted'){ pendingAuth = false; updateBkUI(); writeBackupNow(); }
2293
+ if (st === 'granted'){ pendingAuth = false; updateBkUI(); writeBackupNow(); reloadFromDirIfNeeded(); }
2183
2294
  else bkStatus.textContent = '未获授权,备份暂停';
2184
2295
  }).catch(function(){});
2185
2296
  return;
@@ -2191,6 +2302,7 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
2191
2302
  idbSet('bkHandle', bkDirHandle);
2192
2303
  updateBkUI();
2193
2304
  writeBackupNow();
2305
+ reloadFromDirIfNeeded();
2194
2306
  })
2195
2307
  .catch(function(){});
2196
2308
  });
@@ -2204,6 +2316,7 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
2204
2316
  idbSet('bkHandle', bkDirHandle);
2205
2317
  updateBkUI();
2206
2318
  writeBackupNow();
2319
+ reloadFromDirIfNeeded();
2207
2320
  })
2208
2321
  .catch(function(){});
2209
2322
  });
@@ -2267,6 +2380,7 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
2267
2380
  // 先把当前状态固化进备份(目录备份滚入 .prev.json / 快照环记一份),再应用所选备份
2268
2381
  (bkDirHandle ? writeBackupNow() : writeSnapNow()).then(function(){
2269
2382
  state.data = snap.data;
2383
+ syncMirror(snap.data);
2270
2384
  applyUserStateFrom(snap.userState || {});
2271
2385
  state.dirty = false; state.moves = [];
2272
2386
  persistUserState();
@@ -2303,7 +2417,7 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
2303
2417
  render();
2304
2418
  if (bootMode === 'cache') cacheBadge.style.display = '';
2305
2419
  if (bootMode === 'demo'){
2306
- cacheBadge.textContent = '📌 示例模式:未读到 data/prompts.json,当前展示系统自带示例提示词(file:// 直开或数据文件尚未生成)。经 HTTP 打开页面并生成数据文件后即为你的提示词,示例可随时删除。';
2420
+ cacheBadge.textContent = '📌 示例模式:未读到 data/prompts.json,当前展示系统自带示例。若你有自己的数据文件:点顶部「💾 授权目录」选 prompt-pouch.html 所在文件夹即可直读载入;或经 HTTP 打开页面(file:// 直开会被浏览器拦截文件读取)。';
2307
2421
  cacheBadge.style.display = '';
2308
2422
  }
2309
2423
  }
@@ -2319,26 +2433,49 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
2319
2433
  .then(function(data){
2320
2434
  state.data = data;
2321
2435
  idbSet('lastData', data);
2436
+ syncMirror(data);
2322
2437
  bootMode = 'live';
2323
2438
  firstRender();
2324
2439
  })
2325
2440
  .catch(function(){
2326
- idbGet('lastData').then(function(cached){
2327
- if (cached){
2328
- state.data = cached;
2329
- bootMode = 'cache';
2330
- firstRender();
2331
- } else {
2332
- state.data = DEFAULT_POUCH;
2333
- bootMode = 'demo';
2334
- firstRender();
2335
- }
2441
+ // file:// 场景:fetch 被浏览器拦截 → 已授权目录则直读 data/prompts.json(File System Access API)
2442
+ return dirHandleReady.then(function(){
2443
+ return readPouch().then(function(text){
2444
+ if (text !== null) return JSON.parse(text);
2445
+ throw new Error('no-fs');
2446
+ });
2447
+ }).then(function(data){
2448
+ state.data = data;
2449
+ idbSet('lastData', data);
2450
+ syncMirror(data);
2451
+ bootMode = 'live';
2452
+ firstRender();
2453
+ }).catch(function(){
2454
+ idbGet('lastData').then(function(cached){
2455
+ if (cached){
2456
+ state.data = cached;
2457
+ bootMode = 'cache';
2458
+ firstRender();
2459
+ } else {
2460
+ var mirror = readMirror();
2461
+ if (mirror){
2462
+ state.data = mirror;
2463
+ bootMode = 'cache';
2464
+ firstRender();
2465
+ } else {
2466
+ state.data = DEFAULT_POUCH;
2467
+ bootMode = 'demo';
2468
+ firstRender();
2469
+ }
2470
+ }
2471
+ });
2336
2472
  });
2337
2473
  });
2338
2474
  // 本地态并行:可用则应用收藏与排序并刷新备份区(不阻塞数据展示)
2475
+ var dirHandleReady = idbOpen().then(function(){ return loadDirHandle(); }).catch(function(){});
2339
2476
  idbOpen()
2340
2477
  .then(loadLocalState)
2341
- .then(loadDirHandle)
2478
+ .then(function(){ return dirHandleReady; })
2342
2479
  .catch(function(){})
2343
2480
  .then(function(){
2344
2481
  applyUserState();
@@ -2346,6 +2483,7 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
2346
2483
  refreshDirtyUI();
2347
2484
  if (bootRendered){ renderNav(); render(); }
2348
2485
  updateBkUI();
2486
+ updateBootBadge();
2349
2487
  });
2350
2488
  })();
2351
2489