skill-forge-hub 1.1.0 → 1.3.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/CHANGELOG.en.md +18 -0
- package/CHANGELOG.md +18 -0
- package/README.en.md +3 -1
- package/README.md +3 -1
- package/SKILLS_CATALOG.md +119 -0
- package/annual-report/SKILL.md +3 -3
- package/annual-report/resources/reference.html +2 -2
- package/annual-report/resources/skillforge-guide.html +1 -0
- package/annual-report/resources/skillforge-guide.txt +1 -0
- package/annual-report/user-config.md +1 -1
- package/article-to-html/SKILL.md +1 -1
- package/article-to-html/resources/skillforge-guide.html +1 -0
- package/article-to-html/resources/skillforge-guide.txt +1 -0
- package/article-to-html/user-config.md +1 -1
- package/cli.js +54 -14
- package/commit-heatmap/SKILL.md +2 -2
- package/commit-heatmap/resources/skillforge-guide.html +1 -0
- package/commit-heatmap/resources/skillforge-guide.txt +1 -0
- package/commit-heatmap/user-config.md +1 -1
- package/daily-report/SKILL.md +2 -2
- package/daily-report/resources/skillforge-guide.html +1 -0
- package/daily-report/resources/skillforge-guide.txt +1 -0
- package/daily-report/user-config.md +1 -1
- package/data-prepper/SKILL.md +2 -2
- package/data-prepper/resources/skillforge-guide.html +1 -0
- package/data-prepper/resources/skillforge-guide.txt +1 -0
- package/data-prepper/user-config.md +1 -1
- package/decision-log/SKILL.md +2 -2
- package/decision-log/resources/skillforge-guide.html +1 -0
- package/decision-log/resources/skillforge-guide.txt +1 -0
- package/decision-log/user-config.md +1 -1
- package/deck-stage/SKILL.md +3 -10
- package/deck-stage/resources/skillforge-guide.html +1 -0
- package/deck-stage/resources/skillforge-guide.txt +1 -0
- package/docs/articles/ai-daily-report-correct-pose.md +39 -0
- package/docs/articles/ai-prompt-management-skill-pouch.md +37 -0
- package/docs/articles/csdn-skillforge-21-skills.md +89 -0
- package/docs/directory-submission.md +106 -0
- package/docs/install.en.md +3 -3
- package/docs/install.md +3 -3
- package/docs/usage-examples.en.md +1 -1
- package/docs/usage-examples.md +1 -1
- package/magic-deck/SKILL.md +4 -5
- package/magic-deck/resources/skillforge-guide.html +1 -0
- package/magic-deck/resources/skillforge-guide.txt +1 -0
- package/magic-deck/user-config.md +1 -1
- package/meeting-minutes/SKILL.md +1 -1
- package/meeting-minutes/resources/skillforge-guide.html +1 -0
- package/meeting-minutes/resources/skillforge-guide.txt +1 -0
- package/meeting-minutes/user-config.md +1 -1
- package/meta-forge/SKILL.md +2 -2
- package/meta-forge/resources/skillforge-guide.html +1 -0
- package/meta-forge/resources/skillforge-guide.txt +1 -0
- package/meta-forge/user-config.md +1 -1
- package/monthly-report/SKILL.md +1 -1
- package/monthly-report/resources/skillforge-guide.html +1 -0
- package/monthly-report/resources/skillforge-guide.txt +1 -0
- package/monthly-report/user-config.md +1 -1
- package/one-pager/SKILL.md +20 -8
- package/one-pager/resources/design-process.md +117 -0
- package/one-pager/resources/generate.md +19 -7
- package/one-pager/resources/html-rules.md +55 -10
- package/one-pager/resources/reference.html +1192 -111
- package/one-pager/resources/skillforge-guide.html +1 -0
- package/one-pager/resources/skillforge-guide.txt +1 -0
- package/one-pager/resources/usage-guide.md +18 -4
- package/one-pager/resources/user-template-example.html +63 -0
- package/one-pager/user-config.md +7 -4
- package/package.json +3 -2
- package/prompt-pouch/SKILL.md +4 -15
- package/prompt-pouch/resources/reference.html +388 -126
- package/prompt-pouch/resources/skillforge-guide.html +1 -0
- package/prompt-pouch/resources/skillforge-guide.txt +1 -0
- package/prompt-pouch/resources/usage-guide.md +4 -3
- package/quarterly-report/SKILL.md +1 -1
- package/quarterly-report/resources/skillforge-guide.html +1 -0
- package/quarterly-report/resources/skillforge-guide.txt +1 -0
- package/quarterly-report/user-config.md +1 -1
- package/release-notes/SKILL.md +2 -2
- package/release-notes/resources/skillforge-guide.html +1 -0
- package/release-notes/resources/skillforge-guide.txt +1 -0
- package/release-notes/user-config.md +1 -1
- package/skill-doctor/SKILL.md +2 -2
- package/skill-doctor/resources/skillforge-guide.html +1 -0
- package/skill-doctor/resources/skillforge-guide.txt +1 -0
- package/skill-doctor/user-config.md +1 -1
- package/spec-doc2spec/SKILL.md +3 -9
- package/spec-doc2spec/resources/skillforge-guide.html +1 -0
- package/spec-doc2spec/resources/skillforge-guide.txt +1 -0
- package/spec-doc2spec/user-config.md +1 -1
- package/spec-spec2doc/SKILL.md +3 -12
- package/spec-spec2doc/resources/skillforge-guide.html +1 -0
- package/spec-spec2doc/resources/skillforge-guide.txt +1 -0
- package/spec-spec2doc/user-config.md +1 -1
- package/team-report-collector/SKILL.md +2 -2
- package/team-report-collector/resources/skillforge-guide.html +1 -0
- package/team-report-collector/resources/skillforge-guide.txt +1 -0
- package/team-report-collector/user-config.md +1 -1
- package/template/SKILL.md +2 -2
- package/template/user-config.md +1 -1
- package/twin-docs/SKILL.md +2 -2
- package/twin-docs/resources/skillforge-guide.html +1 -0
- package/twin-docs/resources/skillforge-guide.txt +1 -0
- package/twin-docs/user-config.md +1 -1
- package/weekly-report/SKILL.md +2 -2
- package/weekly-report/resources/skillforge-guide.html +1 -0
- package/weekly-report/resources/skillforge-guide.txt +1 -0
- 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@
|
|
6
|
+
<meta name="sf-template" content="prompt-pouch@10">
|
|
7
7
|
<meta name="pouch-id" content="REF-POUCH-0000">
|
|
8
8
|
<title>锦囊 Prompt Pouch — 提示词管理台</title>
|
|
9
9
|
<style>
|
|
@@ -200,12 +200,13 @@ header{
|
|
|
200
200
|
/* 侧栏 */
|
|
201
201
|
aside{ width:200px; flex:0 0 auto;
|
|
202
202
|
display:flex; flex-direction:column;
|
|
203
|
-
|
|
204
|
-
height:calc(100vh - 100px);
|
|
203
|
+
align-self:flex-start; /* 自然流式随页面整体滚动:说明卡完整展示,页面滚到底即可读完 */
|
|
205
204
|
}
|
|
206
|
-
/* 侧栏分组滚动区:收藏 +
|
|
205
|
+
/* 侧栏分组滚动区:收藏 + 分组局部滚动——滚动区最大高度 = 视口高度减去顶栏、「全部」行与底部「锦囊用法」说明卡(完整展示不裁剪),分组再多说明卡也完整可见;窄滚动条 */
|
|
207
206
|
.nav-scroll{
|
|
208
|
-
flex:
|
|
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:
|
|
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-
|
|
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
|
|
254
|
-
|
|
255
|
-
|
|
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;
|
|
@@ -449,6 +475,38 @@ mark.hl{ background:var(--gold-dim); color:var(--gold); border-radius:3px; paddi
|
|
|
449
475
|
color:var(--muted); font-family:inherit; font-size:12px; line-height:1.7;
|
|
450
476
|
}
|
|
451
477
|
.fp-ops{ display:flex; justify-content:flex-end; gap:10px; }
|
|
478
|
+
/* ============ 统一确认弹框(替代浏览器原生弹框) ============ */
|
|
479
|
+
.cf-panel{
|
|
480
|
+
width:460px; max-width:92vw; max-height:82vh; display:flex; flex-direction:column;
|
|
481
|
+
background:linear-gradient(180deg, var(--panel) 0%, var(--panel2) 100%);
|
|
482
|
+
border:1px solid var(--border); border-radius:16px; padding:20px 22px;
|
|
483
|
+
box-shadow:0 20px 60px rgba(0,0,0,.5);
|
|
484
|
+
animation:cfPop .18s ease;
|
|
485
|
+
}
|
|
486
|
+
@keyframes cfPop{ from{ transform:translateY(10px) scale(.97); opacity:0; } to{ transform:none; opacity:1; } }
|
|
487
|
+
.cf-title{
|
|
488
|
+
font-size:15px; font-weight:900; letter-spacing:1px; margin-bottom:10px;
|
|
489
|
+
background:linear-gradient(90deg,var(--star),var(--gold));
|
|
490
|
+
-webkit-background-clip:text; background-clip:text; color:transparent;
|
|
491
|
+
}
|
|
492
|
+
.cf-body{
|
|
493
|
+
font-size:13px; line-height:1.9; color:var(--text); white-space:pre-wrap; word-break:break-word;
|
|
494
|
+
max-height:46vh; overflow-y:auto; padding-right:4px;
|
|
495
|
+
}
|
|
496
|
+
.cf-field{ display:flex; flex-direction:column; gap:4px; margin-top:12px; }
|
|
497
|
+
.cf-field label{ font-size:12px; font-weight:700; color:var(--gold); letter-spacing:.5px; }
|
|
498
|
+
.cf-field input{
|
|
499
|
+
width:100%; padding:9px 12px; border-radius:9px;
|
|
500
|
+
background:rgba(0,0,0,.25); border:1px solid var(--border); outline:none;
|
|
501
|
+
color:var(--text); font-family:inherit; font-size:13px; transition:border-color .18s ease, box-shadow .18s ease;
|
|
502
|
+
}
|
|
503
|
+
.cf-field input:focus{ border-color:var(--gold); box-shadow:0 0 0 3px var(--gold-dim); }
|
|
504
|
+
.cf-ops{ display:flex; justify-content:flex-end; gap:10px; margin-top:16px; }
|
|
505
|
+
.btn.danger{
|
|
506
|
+
background:linear-gradient(135deg, #a63d34, #e06c5b); color:#fff;
|
|
507
|
+
font-weight:900; border:none;
|
|
508
|
+
}
|
|
509
|
+
.btn.danger:hover{ box-shadow:0 0 16px rgba(224,108,91,.45); }
|
|
452
510
|
/* 备份与数据安全 */
|
|
453
511
|
.bk-wrap{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
|
|
454
512
|
.bk-status{ font-size:12px; color:var(--muted); letter-spacing:1px; }
|
|
@@ -521,9 +579,10 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
521
579
|
}
|
|
522
580
|
/* 页面直写:侧栏分组管理 / 提示词表单 / toast */
|
|
523
581
|
.nav-add{
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
582
|
+
flex:0 0 auto; /* 「全部」行右侧迷你 + 图标(新建分组),行内不占独立高度 */
|
|
583
|
+
width:22px; height:22px; margin-left:6px;
|
|
584
|
+
display:flex; align-items:center; justify-content:center;
|
|
585
|
+
border-radius:6px; cursor:pointer; font-size:14px; line-height:1; color:var(--gold);
|
|
527
586
|
border:1px dashed var(--border); background:var(--gold-dim);
|
|
528
587
|
transition:all .18s ease; user-select:none;
|
|
529
588
|
}
|
|
@@ -636,13 +695,16 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
636
695
|
<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
696
|
<button class="btn primary" id="btnAddItem">+ 新增提示词</button>
|
|
638
697
|
<div class="sort-box">
|
|
639
|
-
<label
|
|
640
|
-
<
|
|
641
|
-
<
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
<
|
|
645
|
-
|
|
698
|
+
<label>排序</label>
|
|
699
|
+
<button class="sort-sel" id="sortSel" type="button" title="切换卡片排序方式">
|
|
700
|
+
<span class="sort-sel-txt" id="sortTxt">最新添加</span><span class="sort-arrow">▾</span>
|
|
701
|
+
</button>
|
|
702
|
+
<div class="sort-menu" id="sortMenu" hidden>
|
|
703
|
+
<button class="sort-opt on" data-mode="added" type="button"><span class="sort-check">✓</span>最新添加</button>
|
|
704
|
+
<button class="sort-opt" data-mode="recent" type="button"><span class="sort-check">✓</span>最近使用</button>
|
|
705
|
+
<button class="sort-opt" data-mode="freq" type="button"><span class="sort-check">✓</span>使用频率</button>
|
|
706
|
+
<button class="sort-opt" data-mode="manual" type="button"><span class="sort-check">✓</span>手动顺序</button>
|
|
707
|
+
</div>
|
|
646
708
|
</div>
|
|
647
709
|
<div class="sort-status" id="sortStatus">
|
|
648
710
|
<button class="btn" id="btnSortCmd" disabled>📋 复制变更指令</button>
|
|
@@ -757,6 +819,22 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
757
819
|
</div>
|
|
758
820
|
</div>
|
|
759
821
|
|
|
822
|
+
<!-- 统一确认弹框(确认 / 提示 / 输入,替代浏览器原生弹框) -->
|
|
823
|
+
<div class="modal-mask" id="cfMask">
|
|
824
|
+
<div class="cf-panel">
|
|
825
|
+
<div class="cf-title" id="cfTitle">确认</div>
|
|
826
|
+
<div class="cf-body" id="cfBody"></div>
|
|
827
|
+
<div class="cf-field" id="cfFieldWrap" style="display:none">
|
|
828
|
+
<label id="cfLabel"></label>
|
|
829
|
+
<input id="cfInput" />
|
|
830
|
+
</div>
|
|
831
|
+
<div class="cf-ops">
|
|
832
|
+
<button class="btn" id="cfCancel">取消</button>
|
|
833
|
+
<button class="btn primary" id="cfOk">确定</button>
|
|
834
|
+
</div>
|
|
835
|
+
</div>
|
|
836
|
+
</div>
|
|
837
|
+
|
|
760
838
|
<div class="toast" id="toast"></div>
|
|
761
839
|
|
|
762
840
|
<footer>
|
|
@@ -812,6 +890,9 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
812
890
|
var searchClear = document.getElementById('searchClear');
|
|
813
891
|
var sortStatus = document.getElementById('sortStatus');
|
|
814
892
|
var sortSel = document.getElementById('sortSel');
|
|
893
|
+
var sortMenu = document.getElementById('sortMenu');
|
|
894
|
+
var sortTxt = document.getElementById('sortTxt');
|
|
895
|
+
var SORT_LABELS = { added:'最新添加', recent:'最近使用', freq:'使用频率', manual:'手动顺序' };
|
|
815
896
|
var btnSortCmd = document.getElementById('btnSortCmd');
|
|
816
897
|
var btnMarkSaved = document.getElementById('btnMarkSaved');
|
|
817
898
|
var bkStatus = document.getElementById('bkStatus');
|
|
@@ -944,16 +1025,15 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
944
1025
|
var html = '';
|
|
945
1026
|
var all = state.data.prompts.length;
|
|
946
1027
|
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>';
|
|
1028
|
+
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
1029
|
html += '<div class="nav-scroll">';
|
|
949
1030
|
html += '<div class="nav-item' + (state.curGroup==='fav'?' on':'') + '" data-g="fav"><span class="icon">⭐</span>收藏<span class="cnt">' + favN + '</span></div>';
|
|
950
1031
|
state.data.groups.forEach(function(g){
|
|
951
1032
|
var n = state.data.prompts.filter(function(p){ return p.group === g.id; }).length;
|
|
952
1033
|
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
1034
|
});
|
|
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
1035
|
html += '</div>';
|
|
1036
|
+
html += '<div class="side-tip"><b>锦囊用法</b><br>· 页面直接增删改:+ 新建分组 / + 新增提示词 / ✎ 编辑 / 🗑 删除(授权目录即写即存)<br>· 也可对 AI 说「把 XX 加到写作输出分组」<br>· 顶栏切换排序;⇄ 移组、拖卡片排序自动保存;拖分组条调整分组顺序<br>· 点卡片标签按标签筛选(再点取消);标签悬停 ✕ 删 / + 加;⤓ 导出 / ⤒ 导入 JSON 备份<br>· ⭐ 收藏常用 · 复制次数在卡片右上角</div>';
|
|
957
1037
|
sideNav.innerHTML = html;
|
|
958
1038
|
sideNav.querySelectorAll('.nav-item').forEach(function(el){
|
|
959
1039
|
el.addEventListener('click', function(){
|
|
@@ -1001,7 +1081,7 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1001
1081
|
});
|
|
1002
1082
|
});
|
|
1003
1083
|
var addBtn = sideNav.querySelector('#navAdd');
|
|
1004
|
-
if (addBtn) addBtn.addEventListener('click', function(){ openGroupForm(null); });
|
|
1084
|
+
if (addBtn) addBtn.addEventListener('click', function(e){ e.stopPropagation(); openGroupForm(null); });
|
|
1005
1085
|
}
|
|
1006
1086
|
|
|
1007
1087
|
function render(){
|
|
@@ -1198,7 +1278,12 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1198
1278
|
persistUserState();
|
|
1199
1279
|
scheduleBackup();
|
|
1200
1280
|
}
|
|
1201
|
-
function syncSortUI(){
|
|
1281
|
+
function syncSortUI(){
|
|
1282
|
+
if (sortTxt) sortTxt.textContent = SORT_LABELS[state.sortMode] || SORT_LABELS.added;
|
|
1283
|
+
if (sortMenu) sortMenu.querySelectorAll('.sort-opt').forEach(function(o){
|
|
1284
|
+
o.classList.toggle('on', o.getAttribute('data-mode') === state.sortMode);
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1202
1287
|
function ensureManual(){
|
|
1203
1288
|
if (state.sortMode === 'manual') return;
|
|
1204
1289
|
state.sortMode = 'manual';
|
|
@@ -1279,10 +1364,30 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1279
1364
|
if (!gpopEl.contains(e.target) && !onAnchor){ closeGpop(); }
|
|
1280
1365
|
}
|
|
1281
1366
|
});
|
|
1282
|
-
|
|
1283
|
-
|
|
1367
|
+
function toggleSortMenu(force){
|
|
1368
|
+
if (!sortMenu) return;
|
|
1369
|
+
var willOpen = (force !== undefined) ? force : sortMenu.hidden;
|
|
1370
|
+
sortMenu.hidden = !willOpen;
|
|
1371
|
+
sortSel.classList.toggle('open', willOpen);
|
|
1372
|
+
}
|
|
1373
|
+
sortSel.addEventListener('click', function(e){
|
|
1374
|
+
e.stopPropagation();
|
|
1375
|
+
toggleSortMenu();
|
|
1376
|
+
});
|
|
1377
|
+
sortMenu.addEventListener('click', function(e){
|
|
1378
|
+
var opt = e.target.closest('.sort-opt');
|
|
1379
|
+
if (!opt) return;
|
|
1380
|
+
state.sortMode = opt.getAttribute('data-mode');
|
|
1284
1381
|
persistUserState();
|
|
1285
1382
|
render();
|
|
1383
|
+
syncSortUI();
|
|
1384
|
+
toggleSortMenu(false);
|
|
1385
|
+
});
|
|
1386
|
+
document.addEventListener('click', function(e){
|
|
1387
|
+
if (!sortMenu.hidden){
|
|
1388
|
+
var onSel = e.target.closest && e.target.closest('#sortSel');
|
|
1389
|
+
if (!sortMenu.contains(e.target) && !onSel){ toggleSortMenu(false); }
|
|
1390
|
+
}
|
|
1286
1391
|
});
|
|
1287
1392
|
|
|
1288
1393
|
/* ---------- 模板插值({{变量}} / {变量}) ---------- */
|
|
@@ -1417,6 +1522,71 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1417
1522
|
if (toastTimer) clearTimeout(toastTimer);
|
|
1418
1523
|
toastTimer = setTimeout(function(){ el.className = 'toast'; }, 2800);
|
|
1419
1524
|
}
|
|
1525
|
+
/* ---------- 统一确认弹框(替代原生 confirm/alert/prompt) ---------- */
|
|
1526
|
+
var cfMask = document.getElementById('cfMask');
|
|
1527
|
+
var cfTitle = document.getElementById('cfTitle');
|
|
1528
|
+
var cfBody = document.getElementById('cfBody');
|
|
1529
|
+
var cfFieldWrap = document.getElementById('cfFieldWrap');
|
|
1530
|
+
var cfLabel = document.getElementById('cfLabel');
|
|
1531
|
+
var cfInput = document.getElementById('cfInput');
|
|
1532
|
+
var cfOk = document.getElementById('cfOk');
|
|
1533
|
+
var cfCancel = document.getElementById('cfCancel');
|
|
1534
|
+
var cfPending = null; var cfMode = 'confirm';
|
|
1535
|
+
function cfClose(res){
|
|
1536
|
+
cfMask.classList.remove('open');
|
|
1537
|
+
if (cfPending){ cfPending(res); cfPending = null; }
|
|
1538
|
+
}
|
|
1539
|
+
function cfOpen(opts){
|
|
1540
|
+
cfMode = opts.mode || 'confirm';
|
|
1541
|
+
cfTitle.textContent = opts.title || '确认';
|
|
1542
|
+
cfBody.textContent = opts.body || '';
|
|
1543
|
+
if (cfMode === 'prompt'){
|
|
1544
|
+
cfFieldWrap.style.display = '';
|
|
1545
|
+
cfLabel.textContent = opts.label || '';
|
|
1546
|
+
cfInput.value = opts.def || '';
|
|
1547
|
+
cfInput.placeholder = opts.placeholder || '';
|
|
1548
|
+
} else {
|
|
1549
|
+
cfFieldWrap.style.display = 'none';
|
|
1550
|
+
}
|
|
1551
|
+
cfOk.textContent = opts.okText || (cfMode === 'alert' ? '知道了' : '确定');
|
|
1552
|
+
cfOk.classList.toggle('danger', !!opts.danger);
|
|
1553
|
+
cfCancel.textContent = opts.cancelText || '取消';
|
|
1554
|
+
cfCancel.style.display = (cfMode === 'alert') ? 'none' : '';
|
|
1555
|
+
cfMask.classList.add('open');
|
|
1556
|
+
setTimeout(function(){
|
|
1557
|
+
if (cfMode === 'prompt'){ cfInput.focus(); cfInput.select(); }
|
|
1558
|
+
else cfOk.focus();
|
|
1559
|
+
}, 60);
|
|
1560
|
+
return new Promise(function(res){ cfPending = res; });
|
|
1561
|
+
}
|
|
1562
|
+
cfOk.addEventListener('click', function(){
|
|
1563
|
+
cfClose({ ok: true, value: (cfMode === 'prompt') ? cfInput.value : null });
|
|
1564
|
+
});
|
|
1565
|
+
cfCancel.addEventListener('click', function(){ cfClose({ ok: false, canceled: true, value: null }); });
|
|
1566
|
+
cfMask.addEventListener('click', function(e){
|
|
1567
|
+
if (e.target === this) cfClose({ ok: false, canceled: true, value: null });
|
|
1568
|
+
});
|
|
1569
|
+
cfInput.addEventListener('keydown', function(e){
|
|
1570
|
+
if (e.key === 'Enter'){ e.preventDefault(); cfOk.click(); }
|
|
1571
|
+
});
|
|
1572
|
+
document.addEventListener('keydown', function(e){
|
|
1573
|
+
if (!cfMask.classList.contains('open')) return;
|
|
1574
|
+
if (e.key === 'Escape') cfClose({ ok: false, canceled: true, value: null });
|
|
1575
|
+
});
|
|
1576
|
+
function uiConfirm(title, body, opts){
|
|
1577
|
+
opts = opts || {};
|
|
1578
|
+
return cfOpen({
|
|
1579
|
+
mode: 'confirm', title: title, body: body,
|
|
1580
|
+
okText: opts.okText, cancelText: opts.cancelText, danger: opts.danger
|
|
1581
|
+
}).then(function(r){ return !!r.ok; });
|
|
1582
|
+
}
|
|
1583
|
+
function uiAlert(title, body){
|
|
1584
|
+
return cfOpen({ mode: 'alert', title: title, body: body });
|
|
1585
|
+
}
|
|
1586
|
+
function uiPrompt(title, label, def, placeholder){
|
|
1587
|
+
return cfOpen({ mode: 'prompt', title: title, label: label, def: def, placeholder: placeholder })
|
|
1588
|
+
.then(function(r){ return r.canceled ? null : r.value; });
|
|
1589
|
+
}
|
|
1420
1590
|
function ensureWriteDir(){
|
|
1421
1591
|
if (bkDirHandle) return Promise.resolve(true);
|
|
1422
1592
|
return maybeAutoAuth().then(function(){ return !!bkDirHandle; });
|
|
@@ -1448,6 +1618,7 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1448
1618
|
if (next === null) return false;
|
|
1449
1619
|
return bkWrite('prompts.json', 'data', JSON.stringify(next, null, 2)).then(function(){
|
|
1450
1620
|
state.data = next;
|
|
1621
|
+
syncMirror(next);
|
|
1451
1622
|
applyUserState();
|
|
1452
1623
|
state.dirty = false; state.moves = [];
|
|
1453
1624
|
persistUserState(); refreshDirtyUI();
|
|
@@ -1484,6 +1655,7 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1484
1655
|
});
|
|
1485
1656
|
return bkWrite('prompts.json', 'data', JSON.stringify(latest, null, 2)).then(function(){
|
|
1486
1657
|
state.data = latest;
|
|
1658
|
+
syncMirror(latest);
|
|
1487
1659
|
applyUserState();
|
|
1488
1660
|
state.dirty = false; state.moves = [];
|
|
1489
1661
|
persistUserState(); refreshDirtyUI();
|
|
@@ -1613,14 +1785,16 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1613
1785
|
}).then(function(ok){ if (ok) toast(editId ? '提示词已更新' : '提示词已入囊'); });
|
|
1614
1786
|
}
|
|
1615
1787
|
function delItem(p){
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1788
|
+
uiConfirm('删除提示词', '确定删除「' + p.title + '」?删除前会自动备份,可从 ↩ 恢复。', { danger: true, okText: '删除' }).then(function(ok){
|
|
1789
|
+
if (!ok) return;
|
|
1790
|
+
writePromptsJson({
|
|
1791
|
+
apply: function(data){
|
|
1792
|
+
data.prompts = data.prompts.filter(function(x){ return x.id !== p.id; });
|
|
1793
|
+
return data;
|
|
1794
|
+
},
|
|
1795
|
+
cmd: '请删除锦囊里「' + p.title + '」这条'
|
|
1796
|
+
}).then(function(ok2){ if (ok2) toast('已删除「' + p.title + '」'); });
|
|
1797
|
+
});
|
|
1624
1798
|
}
|
|
1625
1799
|
/* 标签快捷操作:卡片标签悬停 ✕ 删 / + 加(授权目录即写即存,未授权生成指令) */
|
|
1626
1800
|
function removeTag(p, t){
|
|
@@ -1642,21 +1816,22 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1642
1816
|
}
|
|
1643
1817
|
function quickAddTag(p){
|
|
1644
1818
|
var cur = (p.tags || []).slice(0, 3);
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1819
|
+
uiPrompt('添加标签', '为「' + p.title + '」添加标签(多个用逗号分隔,最多 3 个):', cur.join(', '), '如:日报,工作').then(function(input){
|
|
1820
|
+
if (input === null) return;
|
|
1821
|
+
var adds = input.split(/[,,]/).map(function(s){ return s.trim(); }).filter(Boolean);
|
|
1822
|
+
if (!adds.length) return;
|
|
1823
|
+
var merged = cur.concat(adds).slice(0, 3);
|
|
1824
|
+
writePromptsJson({
|
|
1825
|
+
apply: function(data){
|
|
1826
|
+
var item = data.prompts.find(function(x){ return x.id === p.id; });
|
|
1827
|
+
if (!item){ toast('该提示词已不存在,请刷新后重试', true); return null; }
|
|
1828
|
+
item.tags = merged;
|
|
1829
|
+
item.updatedAt = nowStamp();
|
|
1830
|
+
return data;
|
|
1831
|
+
},
|
|
1832
|
+
cmd: '请把锦囊里「' + p.title + '」的标签更新为:' + merged.join(', ') + '(同时把 updatedAt 更新为当前时间 YYYY-MM-DD HH:mm:ss)'
|
|
1833
|
+
}).then(function(ok){ if (ok) toast('标签已更新'); });
|
|
1834
|
+
});
|
|
1660
1835
|
}
|
|
1661
1836
|
document.getElementById('itemCancel').addEventListener('click', closeItemForm);
|
|
1662
1837
|
document.getElementById('itemSave').addEventListener('click', saveItem);
|
|
@@ -1726,15 +1901,17 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1726
1901
|
function delGroup(g){
|
|
1727
1902
|
var n = state.data.prompts.filter(function(p){ return p.group === g.id; }).length;
|
|
1728
1903
|
if (n > 0){ toast('「' + g.name + '」内还有 ' + n + ' 条提示词,请先移出或删除', true); return; }
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1904
|
+
uiConfirm('删除分组', '确定删除分组「' + g.name + '」?', { danger: true, okText: '删除' }).then(function(ok){
|
|
1905
|
+
if (!ok) return;
|
|
1906
|
+
if (state.curGroup === g.id) state.curGroup = 'all';
|
|
1907
|
+
writePromptsJson({
|
|
1908
|
+
apply: function(data){
|
|
1909
|
+
data.groups = data.groups.filter(function(x){ return x.id !== g.id; });
|
|
1910
|
+
return data;
|
|
1911
|
+
},
|
|
1912
|
+
cmd: '请删除锦囊分组「' + g.name + '」'
|
|
1913
|
+
}).then(function(ok2){ if (ok2) toast('分组「' + g.name + '」已删除'); });
|
|
1914
|
+
});
|
|
1738
1915
|
}
|
|
1739
1916
|
function reorderGroups(moveId, beforeId){
|
|
1740
1917
|
var ids = state.data.groups.map(function(g){ return g.id; });
|
|
@@ -1797,9 +1974,11 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1797
1974
|
}
|
|
1798
1975
|
} catch(err){ toast('JSON 解析失败,请确认文件内容', true); return; }
|
|
1799
1976
|
var cur = (state.data && state.data.prompts) ? state.data.prompts.length : 0;
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1977
|
+
uiConfirm('覆盖导入', '导入将覆盖现有 ' + cur + ' 条数据为 ' + d.prompts.length + ' 条(导入前自动备份,可从 ↩ 恢复)。确定继续?', { danger: true, okText: '覆盖导入' }).then(function(ok){
|
|
1978
|
+
if (!ok) return;
|
|
1979
|
+
writePromptsJson({ apply: function(){ return d; }, cmd: '' }).then(function(ok2){
|
|
1980
|
+
if (ok2) toast('已导入 ' + d.prompts.length + ' 条提示词');
|
|
1981
|
+
});
|
|
1803
1982
|
});
|
|
1804
1983
|
};
|
|
1805
1984
|
reader.readAsText(f, 'utf-8');
|
|
@@ -1868,6 +2047,7 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1868
2047
|
if (e.key === 'Escape'){
|
|
1869
2048
|
closeFillPanel();
|
|
1870
2049
|
closeGpop();
|
|
2050
|
+
toggleSortMenu(false);
|
|
1871
2051
|
closeItemForm();
|
|
1872
2052
|
closeGroupForm();
|
|
1873
2053
|
document.getElementById('bkMask').classList.remove('open');
|
|
@@ -2030,6 +2210,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
2210
|
});
|
|
2031
2211
|
});
|
|
2032
2212
|
}
|
|
2213
|
+
/* 数据镜像:localStorage 存全量数据快照(file:// 下 IndexedDB 偶发不可用时兜底恢复,键按 pouch-id 隔离) */
|
|
2214
|
+
function syncMirror(data){
|
|
2215
|
+
try{ localStorage.setItem(SF_NS + '-mirror', JSON.stringify(data)); }catch(e){}
|
|
2216
|
+
}
|
|
2217
|
+
function readMirror(){
|
|
2218
|
+
try{
|
|
2219
|
+
var raw = localStorage.getItem(SF_NS + '-mirror');
|
|
2220
|
+
if (!raw) return null;
|
|
2221
|
+
var d = JSON.parse(raw);
|
|
2222
|
+
if (d && d.groups && d.prompts) return d;
|
|
2223
|
+
}catch(e){}
|
|
2224
|
+
return null;
|
|
2225
|
+
}
|
|
2033
2226
|
/* 备份文件读:backup/ 优先,读不到回退根目录旧文件(旧目录结构兼容) */
|
|
2034
2227
|
function readBk(name){
|
|
2035
2228
|
return bkRead(name, 'backup').then(function(text){
|
|
@@ -2109,22 +2302,24 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
|
|
|
2109
2302
|
if (bkDirHandle) return Promise.resolve(true);
|
|
2110
2303
|
if (!bkSupported() || bkDeclined || pendingAuth) return Promise.resolve(false);
|
|
2111
2304
|
if (!state.data) return Promise.resolve(false);
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2305
|
+
return uiConfirm('锦囊自动备份', '数据一有变更就会自动备份,建议授权写入 prompt-pouch.html 所在目录(首次选一次,浏览器会记住)。\n\n点「授权目录」打开目录选择 → 选 prompt-pouch.html 所在文件夹即可;\n点「暂不」则改用浏览器本地快照(保留最近 5 份,清浏览器数据会丢)。', { okText: '授权目录', cancelText: '暂不' }).then(function(ok){
|
|
2306
|
+
if (!ok){
|
|
2307
|
+
bkDeclined = true;
|
|
2308
|
+
try{ localStorage.setItem(SF_NS + '-bk-declined', '1'); }catch(e){}
|
|
2309
|
+
return false;
|
|
2310
|
+
}
|
|
2311
|
+
pendingAuth = true; updateBkUI();
|
|
2312
|
+
return window.showDirectoryPicker({ id: 'pouch-backup', mode: 'readwrite' })
|
|
2313
|
+
.then(function(h){
|
|
2314
|
+
bkDirHandle = h; pendingAuth = false; bkDeclined = false; subDirCache = {};
|
|
2315
|
+
try{ localStorage.removeItem(SF_NS + '-bk-declined'); }catch(e){}
|
|
2316
|
+
idbSet('bkHandle', bkDirHandle);
|
|
2317
|
+
updateBkUI();
|
|
2318
|
+
reloadFromDirIfNeeded();
|
|
2319
|
+
return true;
|
|
2320
|
+
})
|
|
2321
|
+
.catch(function(){ pendingAuth = false; updateBkUI(); return false; });
|
|
2322
|
+
});
|
|
2128
2323
|
}
|
|
2129
2324
|
function updateBkUI(){
|
|
2130
2325
|
if (!bkSupported()){
|
|
@@ -2168,18 +2363,55 @@ 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
2363
|
pendingAuth = false;
|
|
2169
2364
|
}).catch(function(){});
|
|
2170
2365
|
}
|
|
2366
|
+
/* file:// 直开补洞:fetch 被浏览器拦截读不到数据文件时,授权目录后直读 data/prompts.json 替换示例/快照 */
|
|
2367
|
+
function reloadFromDirIfNeeded(){
|
|
2368
|
+
if (!bkDirHandle || bootMode === 'live') return;
|
|
2369
|
+
readPouch().then(function(text){
|
|
2370
|
+
if (text === null) return;
|
|
2371
|
+
var data = JSON.parse(text);
|
|
2372
|
+
state.data = data;
|
|
2373
|
+
idbSet('lastData', data);
|
|
2374
|
+
syncMirror(data);
|
|
2375
|
+
bootMode = 'live';
|
|
2376
|
+
cacheBadge.style.display = 'none';
|
|
2377
|
+
applyUserState();
|
|
2378
|
+
renderNav(); render();
|
|
2379
|
+
toast('✓ 已直读 data/prompts.json,你的分组与提示词已载入');
|
|
2380
|
+
}).catch(function(){});
|
|
2381
|
+
}
|
|
2382
|
+
/* 「🔓 恢复数据」:已授权过目录但刷新后权限需重新确认(file:// 权限不跨会话持久)→ 一键 requestPermission 后直读,免重新选目录 */
|
|
2383
|
+
function restoreAuth(){
|
|
2384
|
+
if (!bkDirHandle || typeof bkDirHandle.requestPermission !== 'function') return;
|
|
2385
|
+
bkDirHandle.requestPermission({ mode: 'readwrite' }).then(function(st){
|
|
2386
|
+
if (st === 'granted'){
|
|
2387
|
+
pendingAuth = false; updateBkUI();
|
|
2388
|
+
reloadFromDirIfNeeded();
|
|
2389
|
+
} else {
|
|
2390
|
+
toast('未获授权:点「💾 授权目录」重新选择目录', true);
|
|
2391
|
+
}
|
|
2392
|
+
}).catch(function(){ toast('权限恢复失败,请重新授权目录', true); });
|
|
2393
|
+
}
|
|
2394
|
+
function updateBootBadge(){
|
|
2395
|
+
if (bootMode === 'live' || !bkDirHandle || !bootRendered) return;
|
|
2396
|
+
var link = ' <a id="bootRestore" style="cursor:pointer;text-decoration:underline">🔓 恢复数据</a>';
|
|
2397
|
+
cacheBadge.innerHTML = (bootMode === 'cache'
|
|
2398
|
+
? '⚠️ 快照模式:当前显示上次快照,目录权限需确认后直读最新数据'
|
|
2399
|
+
: '📌 示例模式:检测到曾授权过目录,点击确认权限即可载入你的数据') + link;
|
|
2400
|
+
cacheBadge.style.display = '';
|
|
2401
|
+
document.getElementById('bootRestore').addEventListener('click', restoreAuth);
|
|
2402
|
+
}
|
|
2171
2403
|
btnBkEnable.addEventListener('click', function(){
|
|
2172
2404
|
if (bkDirHandle && !pendingAuth){
|
|
2173
2405
|
writeBackupNow().then(function(ok){ toast(ok ? '✓ 备份完成 · backup/pouch-backup.json' : '备份失败,请重新授权目录', !ok); });
|
|
2174
2406
|
return;
|
|
2175
2407
|
}
|
|
2176
2408
|
if (!bkSupported()){
|
|
2177
|
-
|
|
2409
|
+
uiAlert('目录授权不可用', '当前浏览器不支持「选择目录」授权(Chrome / Edge 可用)。\n本页已自动使用本地快照备份(保留最近 5 份,随浏览器数据走);\n也可随时对 AI 说「导出锦囊数据」生成备份文件备用。');
|
|
2178
2410
|
return;
|
|
2179
2411
|
}
|
|
2180
2412
|
if (bkDirHandle && pendingAuth){
|
|
2181
2413
|
bkDirHandle.requestPermission({ mode: 'readwrite' }).then(function(st){
|
|
2182
|
-
if (st === 'granted'){ pendingAuth = false; updateBkUI(); writeBackupNow(); }
|
|
2414
|
+
if (st === 'granted'){ pendingAuth = false; updateBkUI(); writeBackupNow(); reloadFromDirIfNeeded(); }
|
|
2183
2415
|
else bkStatus.textContent = '未获授权,备份暂停';
|
|
2184
2416
|
}).catch(function(){});
|
|
2185
2417
|
return;
|
|
@@ -2191,21 +2423,25 @@ 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
2423
|
idbSet('bkHandle', bkDirHandle);
|
|
2192
2424
|
updateBkUI();
|
|
2193
2425
|
writeBackupNow();
|
|
2426
|
+
reloadFromDirIfNeeded();
|
|
2194
2427
|
})
|
|
2195
2428
|
.catch(function(){});
|
|
2196
2429
|
});
|
|
2197
2430
|
btnBkChange.addEventListener('click', function(){
|
|
2198
2431
|
if (!bkSupported()){ updateBkUI(); return; }
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
.
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2432
|
+
uiConfirm('更换备份目录', '建议仍选 prompt-pouch.html 所在目录:页面新增/编辑的分组与提示词、自动备份都写在这个目录,选别处会造成页面与数据文件不一致。\n\n确定继续选择目录?', { okText: '选择目录' }).then(function(ok){
|
|
2433
|
+
if (!ok) return;
|
|
2434
|
+
window.showDirectoryPicker({ id: 'pouch-backup', mode: 'readwrite' })
|
|
2435
|
+
.then(function(h){
|
|
2436
|
+
bkDirHandle = h; pendingAuth = false; bkDeclined = false; subDirCache = {};
|
|
2437
|
+
try{ localStorage.removeItem(SF_NS + '-bk-declined'); }catch(e){}
|
|
2438
|
+
idbSet('bkHandle', bkDirHandle);
|
|
2439
|
+
updateBkUI();
|
|
2440
|
+
writeBackupNow();
|
|
2441
|
+
reloadFromDirIfNeeded();
|
|
2442
|
+
})
|
|
2443
|
+
.catch(function(){});
|
|
2444
|
+
});
|
|
2209
2445
|
});
|
|
2210
2446
|
btnBkRestore.addEventListener('click', function(){
|
|
2211
2447
|
document.getElementById('bkList').innerHTML = '<div class="bk-empty">读取备份清单…</div>';
|
|
@@ -2256,31 +2492,33 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
|
|
|
2256
2492
|
}
|
|
2257
2493
|
function doRestore(content){
|
|
2258
2494
|
var snap;
|
|
2259
|
-
try{ snap = JSON.parse(content); }catch(e){
|
|
2495
|
+
try{ snap = JSON.parse(content); }catch(e){ uiAlert('恢复失败', '备份文件损坏,无法恢复。'); return; }
|
|
2260
2496
|
if (!snap || !snap.data || !Array.isArray(snap.data.groups) || !Array.isArray(snap.data.prompts)){
|
|
2261
|
-
|
|
2497
|
+
uiAlert('恢复失败', '备份结构不完整,无法恢复。'); return;
|
|
2262
2498
|
}
|
|
2263
|
-
|
|
2499
|
+
uiConfirm('恢复备份', '将用这份备份覆盖当前数据:\n\n· 备份时间:' + fmtTime(snap.time)
|
|
2264
2500
|
+ '\n· 内容:' + snap.counts.groups + ' 分组 / ' + snap.counts.prompts + ' 条提示词\n\n'
|
|
2265
|
-
+ '恢复前会先把当前数据自动另存一份新备份,可随时再恢复回来。\n\n确定恢复?')
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2501
|
+
+ '恢复前会先把当前数据自动另存一份新备份,可随时再恢复回来。\n\n确定恢复?', { danger: true, okText: '恢复' }).then(function(ok){
|
|
2502
|
+
if (!ok) return;
|
|
2503
|
+
// 先把当前状态固化进备份(目录备份滚入 .prev.json / 快照环记一份),再应用所选备份
|
|
2504
|
+
(bkDirHandle ? writeBackupNow() : writeSnapNow()).then(function(){
|
|
2505
|
+
state.data = snap.data;
|
|
2506
|
+
syncMirror(snap.data);
|
|
2507
|
+
applyUserStateFrom(snap.userState || {});
|
|
2508
|
+
state.dirty = false; state.moves = [];
|
|
2509
|
+
persistUserState();
|
|
2510
|
+
renderNav(); render();
|
|
2511
|
+
document.getElementById('bkMask').classList.remove('open');
|
|
2512
|
+
if (bkDirHandle){
|
|
2513
|
+
return bkWrite('prompts.json', JSON.stringify(snap.data, null, 2)).then(function(){
|
|
2514
|
+
toast('✓ 已恢复并写入 prompts.json,刷新即见');
|
|
2515
|
+
}).catch(function(){
|
|
2516
|
+
toast('已恢复;写入 prompts.json 失败,请重新授权目录', true);
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
markDirty();
|
|
2520
|
+
toast('✓ 已恢复;未授权目录——点「📋 复制变更指令」交给 AI 落盘,或先「💾 授权目录备份」');
|
|
2521
|
+
});
|
|
2284
2522
|
});
|
|
2285
2523
|
}
|
|
2286
2524
|
document.getElementById('bkCancelBtn').addEventListener('click', function(){
|
|
@@ -2303,7 +2541,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
2541
|
render();
|
|
2304
2542
|
if (bootMode === 'cache') cacheBadge.style.display = '';
|
|
2305
2543
|
if (bootMode === 'demo'){
|
|
2306
|
-
cacheBadge.textContent = '📌 示例模式:未读到 data/prompts.json
|
|
2544
|
+
cacheBadge.textContent = '📌 示例模式:未读到 data/prompts.json,当前展示系统自带示例。若你有自己的数据文件:点顶部「💾 授权目录」选 prompt-pouch.html 所在文件夹即可直读载入;或经 HTTP 打开页面(file:// 直开会被浏览器拦截文件读取)。';
|
|
2307
2545
|
cacheBadge.style.display = '';
|
|
2308
2546
|
}
|
|
2309
2547
|
}
|
|
@@ -2319,26 +2557,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
2557
|
.then(function(data){
|
|
2320
2558
|
state.data = data;
|
|
2321
2559
|
idbSet('lastData', data);
|
|
2560
|
+
syncMirror(data);
|
|
2322
2561
|
bootMode = 'live';
|
|
2323
2562
|
firstRender();
|
|
2324
2563
|
})
|
|
2325
2564
|
.catch(function(){
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
}
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2565
|
+
// file:// 场景:fetch 被浏览器拦截 → 已授权目录则直读 data/prompts.json(File System Access API)
|
|
2566
|
+
return dirHandleReady.then(function(){
|
|
2567
|
+
return readPouch().then(function(text){
|
|
2568
|
+
if (text !== null) return JSON.parse(text);
|
|
2569
|
+
throw new Error('no-fs');
|
|
2570
|
+
});
|
|
2571
|
+
}).then(function(data){
|
|
2572
|
+
state.data = data;
|
|
2573
|
+
idbSet('lastData', data);
|
|
2574
|
+
syncMirror(data);
|
|
2575
|
+
bootMode = 'live';
|
|
2576
|
+
firstRender();
|
|
2577
|
+
}).catch(function(){
|
|
2578
|
+
idbGet('lastData').then(function(cached){
|
|
2579
|
+
if (cached){
|
|
2580
|
+
state.data = cached;
|
|
2581
|
+
bootMode = 'cache';
|
|
2582
|
+
firstRender();
|
|
2583
|
+
} else {
|
|
2584
|
+
var mirror = readMirror();
|
|
2585
|
+
if (mirror){
|
|
2586
|
+
state.data = mirror;
|
|
2587
|
+
bootMode = 'cache';
|
|
2588
|
+
firstRender();
|
|
2589
|
+
} else {
|
|
2590
|
+
state.data = DEFAULT_POUCH;
|
|
2591
|
+
bootMode = 'demo';
|
|
2592
|
+
firstRender();
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
});
|
|
2336
2596
|
});
|
|
2337
2597
|
});
|
|
2338
2598
|
// 本地态并行:可用则应用收藏与排序并刷新备份区(不阻塞数据展示)
|
|
2599
|
+
var dirHandleReady = idbOpen().then(function(){ return loadDirHandle(); }).catch(function(){});
|
|
2339
2600
|
idbOpen()
|
|
2340
2601
|
.then(loadLocalState)
|
|
2341
|
-
.then(
|
|
2602
|
+
.then(function(){ return dirHandleReady; })
|
|
2342
2603
|
.catch(function(){})
|
|
2343
2604
|
.then(function(){
|
|
2344
2605
|
applyUserState();
|
|
@@ -2346,6 +2607,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
2607
|
refreshDirtyUI();
|
|
2347
2608
|
if (bootRendered){ renderNav(); render(); }
|
|
2348
2609
|
updateBkUI();
|
|
2610
|
+
updateBootBadge();
|
|
2349
2611
|
});
|
|
2350
2612
|
})();
|
|
2351
2613
|
|