@wendongfly/myhi 1.3.110 → 1.3.112
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/grid.html +179 -35
- package/package.json +1 -1
package/dist/grid.html
CHANGED
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
.pane { display:flex; flex-direction:column; min-height:260px; min-width:0; background:var(--surface); border:1px solid var(--border); border-radius:10px; overflow:hidden; }
|
|
46
46
|
.pane-head { flex-shrink:0; display:flex; align-items:center; gap:0.4rem; padding:0.3rem 0.4rem; background:var(--surface2); border-bottom:1px solid var(--border); }
|
|
47
47
|
.pane-head select { flex:1; min-width:0; background:var(--bg); color:var(--text); border:1px solid var(--border); border-radius:6px; padding:0.28rem 0.4rem; font-size:0.78rem; }
|
|
48
|
+
.pane-head .pane-picker { flex:1; min-width:0; display:flex; align-items:center; justify-content:space-between; gap:0.3rem; text-align:left; }
|
|
49
|
+
#tp-list [data-id]:hover { background:var(--surface2) !important; }
|
|
48
50
|
.pane-head button { background:var(--bg); border:1px solid var(--border); color:var(--muted); border-radius:6px; padding:0.2rem 0.45rem; font-size:0.8rem; cursor:pointer; line-height:1; }
|
|
49
51
|
.pane-head button:hover { color:var(--text); border-color:var(--blue2); }
|
|
50
52
|
.pane-body { flex:1; min-height:0; position:relative; }
|
|
@@ -67,10 +69,48 @@
|
|
|
67
69
|
<span class="spacer"></span>
|
|
68
70
|
<button class="bar-btn" onclick="refreshSessions(true)" title="刷新任务列表">↻</button>
|
|
69
71
|
<button class="bar-btn" onclick="addPane('')" title="加一个空窗格,从下拉选已有任务">+ 窗格</button>
|
|
70
|
-
<button class="bar-btn" style="border-color:var(--green);color:var(--green)" onclick="
|
|
72
|
+
<button class="bar-btn" style="border-color:var(--green);color:var(--green)" onclick="openNewTask()" title="新建任务(和首页一致:预设/名称/命令/目录)">🆕 新建任务</button>
|
|
71
73
|
</div>
|
|
72
74
|
<div id="grid"></div>
|
|
73
|
-
<div id="empty-hint" style="display:none">点「+
|
|
75
|
+
<div id="empty-hint" style="display:none">点「+ 窗格」或「🆕 新建任务」把多个任务并排看。<br>拉宽窗口自动多几格,或用上方按钮锁定布局。</div>
|
|
76
|
+
|
|
77
|
+
<!-- 任务选择弹窗(搜索 + 分组 + 标签)-->
|
|
78
|
+
<div id="task-picker" style="display:none;position:fixed;inset:0;z-index:100">
|
|
79
|
+
<div style="position:absolute;inset:0;background:rgba(0,0,0,0.5)" onclick="closeTaskPicker()"></div>
|
|
80
|
+
<div style="position:relative;max-width:540px;width:92%;margin:6vh auto 0;background:var(--surface);border:1px solid var(--border);border-radius:12px;max-height:82vh;display:flex;flex-direction:column;box-shadow:0 16px 48px rgba(0,0,0,0.45)">
|
|
81
|
+
<div style="padding:0.6rem 0.8rem;border-bottom:1px solid var(--border);display:flex;gap:0.5rem;align-items:center">
|
|
82
|
+
<input id="tp-search" placeholder="搜索任务(名称 / 目录 / 标签)…" autocomplete="off" style="flex:1;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:7px;padding:0.42rem 0.6rem;font-size:0.82rem">
|
|
83
|
+
<button onclick="closeTaskPicker()" style="background:var(--bg);border:1px solid var(--border);color:var(--muted);border-radius:7px;padding:0.38rem 0.6rem;cursor:pointer">✕</button>
|
|
84
|
+
</div>
|
|
85
|
+
<div style="padding:0.45rem 0.8rem;display:flex;gap:0.5rem;align-items:center;flex-wrap:wrap;border-bottom:1px solid var(--border)">
|
|
86
|
+
<div class="seg" id="tp-group">
|
|
87
|
+
<button data-g="time">最近</button><button data-g="project">项目</button><button data-g="tag">标签</button>
|
|
88
|
+
</div>
|
|
89
|
+
<span id="tp-tagbar" style="display:none;gap:0.3rem;flex-wrap:wrap;align-items:center"></span>
|
|
90
|
+
</div>
|
|
91
|
+
<div id="tp-list" style="flex:1;overflow-y:auto;padding:0.4rem 0.6rem"></div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- 新建任务面板(和首页「新建终端会话」一致)-->
|
|
96
|
+
<div id="new-task" style="display:none;position:fixed;inset:0;z-index:100">
|
|
97
|
+
<div style="position:absolute;inset:0;background:rgba(0,0,0,0.5)" onclick="closeNewTask()"></div>
|
|
98
|
+
<div style="position:relative;max-width:460px;width:92%;margin:8vh auto 0;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:1rem;box-shadow:0 16px 48px rgba(0,0,0,0.45)">
|
|
99
|
+
<div style="font-weight:600;margin-bottom:0.7rem">新建任务</div>
|
|
100
|
+
<div id="nt-presets" style="display:flex;gap:0.4rem;margin-bottom:0.7rem;flex-wrap:wrap"></div>
|
|
101
|
+
<label style="font-size:0.72rem;color:var(--muted)">名称 <span style="color:var(--red)">必填</span></label>
|
|
102
|
+
<input id="nt-title" placeholder="请输入任务名称" autocomplete="off" style="width:100%;box-sizing:border-box;margin:0.2rem 0 0.6rem;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:7px;padding:0.45rem 0.6rem;font-size:0.85rem">
|
|
103
|
+
<label style="font-size:0.72rem;color:var(--muted)">启动命令(可选,终端类型用)</label>
|
|
104
|
+
<input id="nt-cmd" placeholder="留空仅打开 shell" autocomplete="off" style="width:100%;box-sizing:border-box;margin:0.2rem 0 0.6rem;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:7px;padding:0.45rem 0.6rem;font-size:0.82rem;font-family:monospace">
|
|
105
|
+
<label style="font-size:0.72rem;color:var(--muted)">工作目录(可选)</label>
|
|
106
|
+
<input id="nt-cwd" placeholder="留空使用默认目录" autocomplete="off" style="width:100%;box-sizing:border-box;margin:0.2rem 0 0.4rem;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:7px;padding:0.45rem 0.6rem;font-size:0.82rem">
|
|
107
|
+
<div id="nt-recent" style="display:flex;flex-wrap:wrap;gap:0.3rem;margin-bottom:0.7rem"></div>
|
|
108
|
+
<div style="display:flex;gap:0.5rem">
|
|
109
|
+
<button onclick="closeNewTask()" style="flex:1;background:var(--bg);border:1px solid var(--border);color:var(--muted);border-radius:8px;padding:0.5rem;cursor:pointer">取消</button>
|
|
110
|
+
<button onclick="submitNewTask()" style="flex:2;background:var(--green);border:none;color:#fff;border-radius:8px;padding:0.5rem;cursor:pointer;font-weight:600">创建</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
74
114
|
|
|
75
115
|
<script>
|
|
76
116
|
const LS_KEY = 'myhi_grid_v1';
|
|
@@ -125,39 +165,39 @@
|
|
|
125
165
|
}));
|
|
126
166
|
|
|
127
167
|
// ── 窗格 ──
|
|
128
|
-
function
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return sel;
|
|
168
|
+
function updatePickerLabel(pane) {
|
|
169
|
+
const s = pane._sid ? sessById(pane._sid) : null;
|
|
170
|
+
pane._picker.innerHTML = s
|
|
171
|
+
? `<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(sName(s))}</span><span style="color:var(--dim);flex-shrink:0">▾</span>`
|
|
172
|
+
: `<span style="color:var(--muted)">+ 选择任务</span><span style="color:var(--dim);flex-shrink:0">▾</span>`;
|
|
134
173
|
}
|
|
135
174
|
function addPane(sid, skipSave) {
|
|
136
175
|
const pane = document.createElement('div'); pane.className = 'pane'; pane._sid = sid || '';
|
|
137
176
|
const head = document.createElement('div'); head.className = 'pane-head';
|
|
138
|
-
const
|
|
177
|
+
const picker = document.createElement('button'); picker.className = 'pane-picker';
|
|
139
178
|
const full = document.createElement('button'); full.textContent='↗'; full.title='在新标签页打开';
|
|
140
179
|
const close = document.createElement('button'); close.textContent='✕'; close.title='关闭此格'; close.style.color='var(--red)';
|
|
141
|
-
head.appendChild(
|
|
180
|
+
head.appendChild(picker); head.appendChild(full); head.appendChild(close);
|
|
142
181
|
const body = document.createElement('div'); body.className = 'pane-body';
|
|
143
|
-
const empty = document.createElement('div'); empty.className='pane-empty'; empty.textContent='
|
|
182
|
+
const empty = document.createElement('div'); empty.className='pane-empty'; empty.textContent='点上方「选择任务」挑一个(可搜索/分组/按标签)'; body.appendChild(empty);
|
|
144
183
|
pane.appendChild(head); pane.appendChild(body);
|
|
145
|
-
pane.
|
|
184
|
+
pane._picker = picker; pane._body = body; pane._empty = empty; pane._iframe = null;
|
|
146
185
|
|
|
147
|
-
|
|
186
|
+
picker.addEventListener('click', () => openTaskPicker(pane));
|
|
148
187
|
full.addEventListener('click', () => { if (pane._sid) window.open('/terminal/' + pane._sid, '_blank'); });
|
|
149
188
|
close.addEventListener('click', () => removePane(pane));
|
|
150
189
|
|
|
151
190
|
gridEl.appendChild(pane);
|
|
152
191
|
paneEls.push(pane);
|
|
153
|
-
|
|
192
|
+
updatePickerLabel(pane);
|
|
193
|
+
if (sid) setPaneSession(pane, sid);
|
|
154
194
|
if (!skipSave) { saveState(); }
|
|
155
195
|
applyLayout();
|
|
156
196
|
}
|
|
157
|
-
function setPaneSession(pane, sid
|
|
197
|
+
function setPaneSession(pane, sid) {
|
|
158
198
|
pane._sid = sid || '';
|
|
159
199
|
pane._key = sid ? keyOf(sessById(sid)) : null; // 记住稳定标识,供重启后找回
|
|
160
|
-
|
|
200
|
+
updatePickerLabel(pane);
|
|
161
201
|
// 只重建这一格的 iframe,其它格不受影响
|
|
162
202
|
if (pane._iframe) { pane._iframe.remove(); pane._iframe = null; }
|
|
163
203
|
if (sid) {
|
|
@@ -176,35 +216,139 @@
|
|
|
176
216
|
}
|
|
177
217
|
|
|
178
218
|
// ── 任务列表 ──
|
|
179
|
-
async function refreshSessions(
|
|
219
|
+
async function refreshSessions(rebuild) {
|
|
180
220
|
try {
|
|
181
221
|
const r = await fetch('/api/sessions'); if (!r.ok) return;
|
|
182
222
|
const data = await r.json();
|
|
183
223
|
sessions = (Array.isArray(data) ? data : (data.sessions || [])).filter(s => sId(s));
|
|
184
224
|
} catch {}
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
225
|
+
if (rebuild) for (const pane of paneEls) updatePickerLabel(pane);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── 任务选择弹窗(搜索 + 最近/项目/标签分组 + 标签过滤,借鉴首页)──
|
|
229
|
+
let _pickTarget = null;
|
|
230
|
+
let _tpGroup = localStorage.getItem('myhi_grid_group') || 'time';
|
|
231
|
+
let _tpTag = null;
|
|
232
|
+
function sessionTime(s){ return s.lastActivityAt || s.createdAt || 0; }
|
|
233
|
+
window.openTaskPicker = function(pane) {
|
|
234
|
+
_pickTarget = pane;
|
|
235
|
+
document.getElementById('task-picker').style.display = '';
|
|
236
|
+
document.getElementById('tp-search').value = '';
|
|
237
|
+
refreshSessions(false).then(renderPicker);
|
|
238
|
+
renderPicker();
|
|
239
|
+
setTimeout(() => document.getElementById('tp-search').focus(), 30);
|
|
240
|
+
};
|
|
241
|
+
window.closeTaskPicker = function(){ document.getElementById('task-picker').style.display='none'; _pickTarget=null; };
|
|
242
|
+
function pushMap(m,k,v){ if(!m.has(k)) m.set(k,[]); m.get(k).push(v); }
|
|
243
|
+
function renderPicker() {
|
|
244
|
+
document.querySelectorAll('#tp-group button').forEach(b => b.classList.toggle('active', b.dataset.g === _tpGroup));
|
|
245
|
+
const tagbar = document.getElementById('tp-tagbar');
|
|
246
|
+
if (_tpGroup === 'tag') {
|
|
247
|
+
const allTags = [...new Set(sessions.flatMap(s => s.tags || []))].sort();
|
|
248
|
+
const chip = (label,val,on) => `<button data-tag="${val==null?'':esc(val)}" style="border:1px solid ${on?'var(--blue2)':'var(--border)'};background:${on?'var(--blue2)':'var(--bg)'};color:${on?'#fff':'var(--muted)'};border-radius:999px;font-size:0.7rem;padding:0.12rem 0.5rem;cursor:pointer">${esc(label)}</button>`;
|
|
249
|
+
tagbar.innerHTML = chip('全部',null,!_tpTag) + allTags.map(t=>chip(t,t,_tpTag===t)).join('');
|
|
250
|
+
tagbar.style.display = 'flex';
|
|
251
|
+
} else { tagbar.style.display='none'; }
|
|
252
|
+
|
|
253
|
+
const kw = (document.getElementById('tp-search').value||'').trim().toLowerCase();
|
|
254
|
+
let items = sessions.filter(s => !kw || sName(s).toLowerCase().includes(kw) || (s.cwd||'').toLowerCase().includes(kw) || (s.tags||[]).join(' ').toLowerCase().includes(kw));
|
|
255
|
+
items.sort((a,b)=>sessionTime(b)-sessionTime(a));
|
|
256
|
+
const itemHtml = (s) => {
|
|
257
|
+
const cur = _pickTarget && _pickTarget._sid === sId(s);
|
|
258
|
+
const tags = (s.tags||[]).map(t=>`<span style="background:var(--surface2);color:var(--muted);border-radius:4px;font-size:0.62rem;padding:0.02rem 0.3rem">${esc(t)}</span>`).join(' ');
|
|
259
|
+
return `<div data-id="${esc(sId(s))}" style="padding:0.45rem 0.5rem;border-radius:7px;cursor:pointer;border:1px solid ${cur?'var(--blue2)':'transparent'};background:${cur?'var(--surface2)':'transparent'}">
|
|
260
|
+
<div style="font-size:0.82rem;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(sName(s))} ${tags}</div>
|
|
261
|
+
<div style="font-size:0.66rem;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(s.cwd||'(无目录)')}</div>
|
|
262
|
+
</div>`;
|
|
263
|
+
};
|
|
264
|
+
const header = (t) => `<div style="color:var(--muted);font-size:0.7rem;font-weight:600;margin:0.6rem 0.2rem 0.25rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(t)}</div>`;
|
|
265
|
+
let html = '';
|
|
266
|
+
if (_tpGroup === 'project') {
|
|
267
|
+
const groups = new Map();
|
|
268
|
+
for (const s of items) pushMap(groups, s.cwd || '(无目录)', s);
|
|
269
|
+
const ordered = [...groups.entries()].sort((a,b)=>sessionTime(b[1][0])-sessionTime(a[1][0]));
|
|
270
|
+
for (const [k,arr] of ordered) html += header(k) + arr.map(itemHtml).join('');
|
|
271
|
+
} else if (_tpGroup === 'tag') {
|
|
272
|
+
if (_tpTag) { html += items.filter(s => (s.tags||[]).includes(_tpTag)).map(itemHtml).join(''); }
|
|
273
|
+
else {
|
|
274
|
+
const tagged = new Map(); const untagged = [];
|
|
275
|
+
for (const s of items) { const ts=s.tags||[]; if(!ts.length) untagged.push(s); for(const t of ts) pushMap(tagged,t,s); }
|
|
276
|
+
const ordered=[...tagged.entries()].sort((a,b)=>sessionTime(b[1][0])-sessionTime(a[1][0]));
|
|
277
|
+
for (const [t,arr] of ordered) html += header('# '+t) + arr.map(itemHtml).join('');
|
|
278
|
+
if (untagged.length) html += header('无标签') + untagged.map(itemHtml).join('');
|
|
191
279
|
}
|
|
192
|
-
}
|
|
280
|
+
} else { html = items.map(itemHtml).join(''); }
|
|
281
|
+
document.getElementById('tp-list').innerHTML = html || '<div style="color:var(--muted);text-align:center;padding:1.5rem 0;font-size:0.8rem">没有匹配的任务</div>';
|
|
193
282
|
}
|
|
283
|
+
// 事件委托:选任务 / 选标签 / 切分组 / 搜索
|
|
284
|
+
document.getElementById('tp-list').addEventListener('click', (e) => {
|
|
285
|
+
const el = e.target.closest('[data-id]'); if (!el) return;
|
|
286
|
+
if (_pickTarget) setPaneSession(_pickTarget, el.dataset.id);
|
|
287
|
+
closeTaskPicker();
|
|
288
|
+
});
|
|
289
|
+
document.getElementById('tp-tagbar').addEventListener('click', (e) => {
|
|
290
|
+
const el = e.target.closest('[data-tag]'); if (!el) return;
|
|
291
|
+
_tpTag = el.dataset.tag || null; renderPicker();
|
|
292
|
+
});
|
|
293
|
+
document.querySelectorAll('#tp-group button').forEach(b => b.addEventListener('click', () => {
|
|
294
|
+
_tpGroup = b.dataset.g; _tpTag = null; localStorage.setItem('myhi_grid_group', _tpGroup); renderPicker();
|
|
295
|
+
}));
|
|
296
|
+
document.getElementById('tp-search').addEventListener('input', renderPicker);
|
|
194
297
|
|
|
195
|
-
// ──
|
|
298
|
+
// ── 新建任务(和首页「新建终端会话」一致:预设 + 名称 + 命令 + 目录 + 最近目录)──
|
|
196
299
|
const socket = io();
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
300
|
+
const NT_PRESETS = [
|
|
301
|
+
{ icon:'🤖', label:'Claude', title:'claude', type:'agent', cmd:'' },
|
|
302
|
+
{ icon:'✨', label:'Gemini', title:'gemini', type:'pty', cmd:'gemini' },
|
|
303
|
+
{ icon:'🐚', label:'Shell', title:'shell', type:'pty', cmd:'' },
|
|
304
|
+
];
|
|
305
|
+
let _ntPreset = 0;
|
|
306
|
+
const RECENT_DIRS_KEY = 'myhi_recent_dirs'; // 和首页共用
|
|
307
|
+
function ntRecentDirs(){ try { return JSON.parse(localStorage.getItem(RECENT_DIRS_KEY)) || {}; } catch { return {}; } }
|
|
308
|
+
function ntRecordDir(dir){ if(!dir) return; const d=ntRecentDirs(); d[dir]=(d[dir]||0)+1; const s=Object.entries(d).sort((a,b)=>b[1]-a[1]).slice(0,5); localStorage.setItem(RECENT_DIRS_KEY, JSON.stringify(Object.fromEntries(s))); }
|
|
309
|
+
function ntRenderPresets(){
|
|
310
|
+
document.getElementById('nt-presets').innerHTML = NT_PRESETS.map((p,i)=>`<button data-i="${i}" style="flex:1;min-width:5rem;display:flex;align-items:center;justify-content:center;gap:0.3rem;border:1px solid ${i===_ntPreset?'var(--blue2)':'var(--border)'};background:${i===_ntPreset?'var(--blue2)':'var(--bg)'};color:${i===_ntPreset?'#fff':'var(--text2)'};border-radius:8px;padding:0.4rem;font-size:0.8rem;cursor:pointer">${p.icon} ${esc(p.label)}</button>`).join('');
|
|
311
|
+
}
|
|
312
|
+
function ntRenderRecent(){
|
|
313
|
+
const dirs = Object.entries(ntRecentDirs()).sort((a,b)=>b[1]-a[1]);
|
|
314
|
+
document.getElementById('nt-recent').innerHTML = dirs.map(([p])=>`<button data-dir="${esc(p)}" style="background:var(--surface2);border:1px solid var(--border);color:var(--muted);border-radius:6px;font-size:0.68rem;padding:0.12rem 0.4rem;cursor:pointer;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(p)}</button>`).join('');
|
|
315
|
+
}
|
|
316
|
+
window.openNewTask = function(){
|
|
317
|
+
_ntPreset = 0;
|
|
318
|
+
document.getElementById('new-task').style.display = '';
|
|
319
|
+
document.getElementById('nt-title').value = '';
|
|
320
|
+
document.getElementById('nt-cmd').value = '';
|
|
321
|
+
document.getElementById('nt-cwd').value = '';
|
|
322
|
+
ntRenderPresets(); ntRenderRecent();
|
|
323
|
+
setTimeout(()=>document.getElementById('nt-title').focus(), 30);
|
|
324
|
+
};
|
|
325
|
+
window.closeNewTask = function(){ document.getElementById('new-task').style.display='none'; };
|
|
326
|
+
window.submitNewTask = function(){
|
|
327
|
+
const title = document.getElementById('nt-title').value.trim();
|
|
328
|
+
if (!title) { document.getElementById('nt-title').focus(); return; }
|
|
329
|
+
const cmd = document.getElementById('nt-cmd').value.trim() || undefined;
|
|
330
|
+
const cwd = document.getElementById('nt-cwd').value.trim() || undefined;
|
|
331
|
+
if (cwd) ntRecordDir(cwd);
|
|
332
|
+
const preset = NT_PRESETS[_ntPreset];
|
|
333
|
+
const done = (res) => {
|
|
334
|
+
if (res && res.ok && res.session) { closeNewTask(); refreshSessions(true).then(()=>addPane(res.session.id)); }
|
|
335
|
+
else alert('创建失败:' + ((res && res.error) || '未知错误'));
|
|
336
|
+
};
|
|
337
|
+
if (preset.type === 'agent') socket.emit('create-agent', { title, cwd }, done);
|
|
338
|
+
else socket.emit('create', { title, initCmd: cmd, cwd }, done);
|
|
207
339
|
};
|
|
340
|
+
document.getElementById('nt-presets').addEventListener('click', (e) => {
|
|
341
|
+
const b = e.target.closest('[data-i]'); if (!b) return;
|
|
342
|
+
_ntPreset = +b.dataset.i; const p = NT_PRESETS[_ntPreset];
|
|
343
|
+
const t = document.getElementById('nt-title');
|
|
344
|
+
if (!t.value.trim() || NT_PRESETS.some(x => x.title === t.value.trim())) t.value = p.title;
|
|
345
|
+
document.getElementById('nt-cmd').value = p.cmd || '';
|
|
346
|
+
ntRenderPresets();
|
|
347
|
+
});
|
|
348
|
+
document.getElementById('nt-recent').addEventListener('click', (e) => {
|
|
349
|
+
const b = e.target.closest('[data-dir]'); if (!b) return;
|
|
350
|
+
document.getElementById('nt-cwd').value = b.dataset.dir;
|
|
351
|
+
});
|
|
208
352
|
|
|
209
353
|
// ── 初始化 ──
|
|
210
354
|
(async function init() {
|
package/package.json
CHANGED