tmux-weblink 2.0.13 → 2.0.15
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/README.md +3 -5
- package/dist/agent.js +1 -1
- package/dist/frontend.js +1 -1
- package/dist/index.js +3 -3
- package/dist/lib/build-app.js +6 -6
- package/dist/lib/cli.js +7 -11
- package/dist/lib/db.js +1 -1
- package/dist/lib/pages/federation.js +1 -1
- package/dist/lib/pages/files-index.js +20 -22
- package/dist/lib/pages/history-index.js +22 -24
- package/dist/lib/pages/notes-index.js +17 -19
- package/dist/lib/pages/notes-page.js +10 -13
- package/dist/lib/pages/quick-commands.js +25 -27
- package/dist/lib/pages/settings.js +17 -58
- package/dist/lib/pages/shell.js +17 -29
- package/dist/lib/setup-features.js +1 -1
- package/dist/lib/shared-layout.js +39 -41
- package/dist/lib/state-paths.js +1 -1
- package/package.json +1 -1
- package/dist/lib/commandbar.js +0 -555
- package/dist/lib/ext-loader.js +0 -1
- package/dist/lib/pages/schedule-index.js +0 -392
- package/dist/lib/plugins.js +0 -3
- package/dist/lib/schedule-delay.js +0 -9
- package/dist/lib/scheduler-drawer.js +0 -379
- package/dist/lib/scheduler.js +0 -1
|
@@ -1,392 +0,0 @@
|
|
|
1
|
-
import{cssVarsStyle as y}from"../theme.js";import{commandbarCSS as S,commandbarHTML as k,commandbarScript as $}from"../commandbar.js";import{notesDrawerCSS as E,notesDrawerHTML as L,notesDrawerScript as A}from"../notes-drawer.js";import{sharedLayoutCSS as B,sharedHeader as C,sharedSidebar as M,newSessionModalHTML as q,newSessionModalScript as I}from"../shared-layout.js";import{DELAY_INVALID_MESSAGE as D,DELAY_MAX_MESSAGE as R,MAX_SCHEDULE_MS as T,scheduleDelayParseScript as z}from"../schedule-delay.js";function t(s){return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function p(s){const n=new Date(s);return n.toLocaleDateString()+" "+n.toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"})}function N(s,n){return s.length?`<div class="task-list">${s.map(e=>`<div class="task">
|
|
2
|
-
<div class="task-row1">
|
|
3
|
-
<span class="cmd">${t(e.text)}</span>
|
|
4
|
-
<span class="status-pill status-${e.status}">${e.status}</span>
|
|
5
|
-
</div>
|
|
6
|
-
<div class="task-row2">
|
|
7
|
-
<a class="trig-target" href="/s/${encodeURIComponent(e.sessionName)}?window=${e.windowIndex}">${t(e.sessionName)} · win:${e.windowIndex}</a>
|
|
8
|
-
<span class="meta">${t(p(e.triggeredAt))}</span>
|
|
9
|
-
</div>${e.status==="error"&&e.error?`
|
|
10
|
-
<div class="trig-error">${t(e.error)}</div>`:""}
|
|
11
|
-
</div>`).join(`
|
|
12
|
-
`)}</div>`:`<p class="empty">No tasks triggered in the last ${n} day${n===1?"":"s"}.</p>`}function G(s,n,c,e=7,i=!1,u=[]){const d=[...s].sort((r,o)=>r.fireAt-o.fireAt),l=new Map;for(const r of d){const o=l.get(r.sessionName);o?o.push(r):l.set(r.sessionName,[r])}const m=[...l.entries()].map(([r,o])=>{const w=o.map(a=>`<div class="task" data-id="${t(a.id)}">
|
|
13
|
-
<div class="task-row1">
|
|
14
|
-
<span class="cmd">${t(a.text)}</span>
|
|
15
|
-
<span class="countdown" data-fire-at="${a.fireAt}">\u2026</span>
|
|
16
|
-
</div>
|
|
17
|
-
<div class="task-row2">
|
|
18
|
-
<span class="meta"><a class="win-link" href="/s/${encodeURIComponent(a.sessionName)}?window=${a.windowIndex}">win:${a.windowIndex}</a> · fires ${t(p(a.fireAt))}</span>
|
|
19
|
-
<div style="display:flex;gap:4px;align-items:center;">
|
|
20
|
-
<button class="reschedule-btn" data-id="${t(a.id)}">reschedule</button>
|
|
21
|
-
<button class="cancel-btn" data-id="${t(a.id)}">cancel</button>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
<div class="reschedule-row" data-id="${t(a.id)}">
|
|
25
|
-
<div class="reschedule-presets">
|
|
26
|
-
<button class="reschedule-preset-btn" data-delay="1m">1m</button>
|
|
27
|
-
<button class="reschedule-preset-btn" data-delay="5m">5m</button>
|
|
28
|
-
<button class="reschedule-preset-btn" data-delay="15m">15m</button>
|
|
29
|
-
<button class="reschedule-preset-btn" data-delay="1h">1h</button>
|
|
30
|
-
</div>
|
|
31
|
-
<div class="reschedule-input-row">
|
|
32
|
-
<input class="reschedule-input" type="text" placeholder="1h, 5m, 70h, 30d" autocomplete="off" />
|
|
33
|
-
<button class="reschedule-confirm-btn">Set</button>
|
|
34
|
-
<span class="reschedule-error"></span>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</div>`).join(`
|
|
38
|
-
`);return`<div class="session-group" data-session="${t(r)}">
|
|
39
|
-
<a class="session-head" href="/s/${encodeURIComponent(r)}?tab=scheduler">
|
|
40
|
-
<span class="sname">${t(r)}</span>
|
|
41
|
-
<span class="scount">${o.length}</span>
|
|
42
|
-
</a>
|
|
43
|
-
<div class="task-list">${w}</div>
|
|
44
|
-
</div>`}).join(`
|
|
45
|
-
`),h=d.length?m:'<p class="empty" id="empty-msg">No scheduled tasks.</p>',f=N(n,e),g=T,b=D,x=R,v=`
|
|
46
|
-
.session-head {
|
|
47
|
-
display: flex; align-items: center; gap: 8px; text-decoration: none;
|
|
48
|
-
font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
|
|
49
|
-
color: var(--panel-accent); margin-bottom: 8px;
|
|
50
|
-
}
|
|
51
|
-
.session-head:hover .sname { text-decoration: underline; }
|
|
52
|
-
.session-head .scount {
|
|
53
|
-
font-size: var(--text-xs); color: var(--panel-success); border: 1px solid var(--panel-border);
|
|
54
|
-
border-radius: 10px; padding: 2px 9px; text-transform: none; letter-spacing: 0;
|
|
55
|
-
}
|
|
56
|
-
.session-group { margin-bottom: 18px; }
|
|
57
|
-
.task {
|
|
58
|
-
padding: 14px 16px; border: 1px solid var(--panel-border); border-radius: 10px;
|
|
59
|
-
margin-bottom: 8px; background: var(--panel-bg);
|
|
60
|
-
display: flex; flex-direction: column; gap: 6px;
|
|
61
|
-
}
|
|
62
|
-
.task-row1 { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
|
|
63
|
-
.task .cmd {
|
|
64
|
-
font-size: var(--text-sm); color: var(--page-fg);
|
|
65
|
-
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
66
|
-
}
|
|
67
|
-
.task .countdown {
|
|
68
|
-
font-size: var(--text-sm); font-weight: 600; color: var(--panel-success);
|
|
69
|
-
flex-shrink: 0; min-width: 56px; text-align: right;
|
|
70
|
-
}
|
|
71
|
-
.task .countdown.urgent { color: #f0c674; }
|
|
72
|
-
.task .countdown.imminent { color: #cc6666; }
|
|
73
|
-
.task-row2 { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
|
|
74
|
-
.task .meta { font-size: var(--text-xs); color: var(--panel-muted); }
|
|
75
|
-
.win-link { color: var(--panel-accent); text-decoration: none; }
|
|
76
|
-
.win-link:hover { text-decoration: underline; }
|
|
77
|
-
.cancel-btn {
|
|
78
|
-
font-size: var(--text-xs); color: var(--panel-muted); background: none;
|
|
79
|
-
border: 1px solid var(--panel-border); padding: 6px 12px; border-radius: 6px;
|
|
80
|
-
cursor: pointer; font-family: inherit; transition: all 0.15s;
|
|
81
|
-
min-height: 44px;
|
|
82
|
-
}
|
|
83
|
-
.cancel-btn:hover { border-color: #cc6666; color: #cc6666; background: color-mix(in srgb, #cc6666 8%, transparent); }
|
|
84
|
-
.cancel-btn:focus-visible { box-shadow: 0 0 0 2px #cc6666; outline: none; }
|
|
85
|
-
.reschedule-btn {
|
|
86
|
-
font-size: var(--text-xs); color: var(--panel-muted); background: none;
|
|
87
|
-
border: 1px solid var(--panel-border); padding: 6px 12px; border-radius: 6px;
|
|
88
|
-
cursor: pointer; font-family: inherit; transition: all 0.15s; margin-right: 4px;
|
|
89
|
-
min-height: 44px;
|
|
90
|
-
}
|
|
91
|
-
.reschedule-btn:hover { border-color: var(--panel-accent); color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
92
|
-
.reschedule-btn:focus-visible { box-shadow: 0 0 0 2px var(--panel-accent); outline: none; }
|
|
93
|
-
.reschedule-row {
|
|
94
|
-
display: none; flex-direction: column; gap: 6px;
|
|
95
|
-
padding-top: 8px; border-top: 1px solid var(--panel-border);
|
|
96
|
-
}
|
|
97
|
-
.reschedule-row.active { display: flex; }
|
|
98
|
-
.reschedule-presets { display: flex; gap: 6px; }
|
|
99
|
-
.reschedule-preset-btn {
|
|
100
|
-
font-size: var(--text-xs); color: var(--panel-muted); background: none;
|
|
101
|
-
border: 1px solid var(--panel-border); padding: 6px 12px; border-radius: 6px;
|
|
102
|
-
cursor: pointer; font-family: inherit; transition: all 0.15s;
|
|
103
|
-
min-height: 44px;
|
|
104
|
-
}
|
|
105
|
-
.reschedule-preset-btn:hover { border-color: var(--panel-accent); color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
106
|
-
.reschedule-preset-btn:focus-visible { box-shadow: 0 0 0 2px var(--panel-accent); outline: none; }
|
|
107
|
-
.reschedule-input-row { display: flex; gap: 8px; align-items: center; }
|
|
108
|
-
.reschedule-input {
|
|
109
|
-
flex: 1; background: color-mix(in srgb, var(--page-bg) 70%, transparent); border: 1px solid var(--panel-border);
|
|
110
|
-
color: var(--page-fg); font-family: inherit; font-size: var(--text-xs);
|
|
111
|
-
padding: 6px 10px; border-radius: 6px; outline: none; transition: border-color 0.15s;
|
|
112
|
-
}
|
|
113
|
-
.reschedule-input:focus { border-color: var(--panel-accent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
114
|
-
.reschedule-input.error { border-color: #cc6666; }
|
|
115
|
-
.reschedule-confirm-btn {
|
|
116
|
-
font-size: var(--text-xs); background: color-mix(in srgb, var(--panel-success) 12%, transparent);
|
|
117
|
-
border: 1px solid var(--panel-success); color: var(--panel-success);
|
|
118
|
-
padding: 8px 16px; border-radius: 6px; cursor: pointer; font-family: inherit;
|
|
119
|
-
min-height: 44px;
|
|
120
|
-
}
|
|
121
|
-
.reschedule-confirm-btn:hover { background: color-mix(in srgb, var(--panel-success) 22%, transparent); }
|
|
122
|
-
.reschedule-confirm-btn:focus-visible { box-shadow: 0 0 0 2px var(--panel-success); outline: none; }
|
|
123
|
-
.reschedule-confirm-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
124
|
-
.reschedule-error { font-size: var(--text-xs); color: #cc6666; }
|
|
125
|
-
.page-tab-bar { display: flex; gap: 4px; border-bottom: 1px solid var(--panel-border); margin-bottom: 20px; }
|
|
126
|
-
.page-tab {
|
|
127
|
-
font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
|
|
128
|
-
color: var(--panel-muted); background: none; border: none; cursor: pointer;
|
|
129
|
-
padding: 10px 16px; font-family: inherit; border-bottom: 2px solid transparent;
|
|
130
|
-
margin-bottom: -1px; transition: color 0.15s, border-color 0.15s;
|
|
131
|
-
min-height: 44px;
|
|
132
|
-
}
|
|
133
|
-
.page-tab:hover { color: var(--panel-accent); }
|
|
134
|
-
.page-tab.active { color: var(--panel-accent); border-bottom-color: var(--panel-accent); }
|
|
135
|
-
.page-tab:focus-visible { outline: none; box-shadow: inset 0 -2px 0 0 var(--panel-accent); }
|
|
136
|
-
.tab-panel { display: none; }
|
|
137
|
-
.tab-panel.active { display: block; }
|
|
138
|
-
.status-pill {
|
|
139
|
-
font-size: var(--text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
|
|
140
|
-
border: 1px solid var(--panel-border); border-radius: 10px; padding: 2px 10px; flex-shrink: 0;
|
|
141
|
-
}
|
|
142
|
-
.status-pill.status-ok { color: var(--panel-success); border-color: var(--panel-success); }
|
|
143
|
-
.status-pill.status-error { color: #cc6666; border-color: #cc6666; }
|
|
144
|
-
.status-pill.status-missed { color: var(--panel-muted); }
|
|
145
|
-
.trig-target { font-size: var(--text-xs); color: var(--panel-accent); text-decoration: none; }
|
|
146
|
-
.trig-target:hover { text-decoration: underline; }
|
|
147
|
-
.trig-error {
|
|
148
|
-
font-size: var(--text-xs); color: #cc6666; white-space: pre-wrap; word-break: break-word;
|
|
149
|
-
margin-top: 2px; padding-top: 6px; border-top: 1px solid var(--panel-border);
|
|
150
|
-
}
|
|
151
|
-
.empty { font-size: var(--text-sm); color: var(--panel-muted); line-height: 1.6; margin-top: 20px; }
|
|
152
|
-
@media (max-width: 560px) {
|
|
153
|
-
.task-row2 { flex-direction: column; align-items: flex-start; gap: 8px; }
|
|
154
|
-
.reschedule-input-row { flex-direction: column; align-items: stretch; }
|
|
155
|
-
.reschedule-input-row button { align-self: flex-start; }
|
|
156
|
-
}
|
|
157
|
-
${i?S():""}
|
|
158
|
-
${E()}`;return`<!DOCTYPE html>
|
|
159
|
-
<html lang="en">
|
|
160
|
-
<head>
|
|
161
|
-
<meta charset="UTF-8" />
|
|
162
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
163
|
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
164
|
-
<title>Scheduled tasks - tmux-web</title>
|
|
165
|
-
<style>
|
|
166
|
-
${y(c.shell)}
|
|
167
|
-
${B(v)}
|
|
168
|
-
</style>
|
|
169
|
-
</head>
|
|
170
|
-
<body>
|
|
171
|
-
|
|
172
|
-
${C({commandbarEnabled:i,title:"Scheduled",themeTemplate:c.template})}
|
|
173
|
-
|
|
174
|
-
<div class="page-wrap">
|
|
175
|
-
<div class="page-layout">
|
|
176
|
-
${M({activePage:"schedule",refreshHref:"/schedule"})}
|
|
177
|
-
<main class="main-panel">
|
|
178
|
-
<div class="page-tab-bar">
|
|
179
|
-
<button class="page-tab active" data-tab="upcoming">Upcoming</button>
|
|
180
|
-
<button class="page-tab" data-tab="triggered">Recently Triggered</button>
|
|
181
|
-
</div>
|
|
182
|
-
<div class="tab-panel active" data-panel="upcoming">
|
|
183
|
-
<div id="schedule-list">${h}</div>
|
|
184
|
-
</div>
|
|
185
|
-
<div class="tab-panel" data-panel="triggered">
|
|
186
|
-
${f}
|
|
187
|
-
</div>
|
|
188
|
-
</main>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
${q()}
|
|
193
|
-
${i?k():""}
|
|
194
|
-
${L("Notes - Global")}
|
|
195
|
-
|
|
196
|
-
<script type="module">
|
|
197
|
-
${A("__global__")}
|
|
198
|
-
${i?$(u,[]):""}
|
|
199
|
-
${I()}
|
|
200
|
-
</script>
|
|
201
|
-
|
|
202
|
-
<script>
|
|
203
|
-
const MAX_SCHEDULE_MS = ${g};
|
|
204
|
-
${z()}
|
|
205
|
-
|
|
206
|
-
// \u2500\u2500 Tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
207
|
-
function activateTab(name) {
|
|
208
|
-
document.querySelectorAll('.page-tab').forEach((t) => t.classList.toggle('active', t.dataset.tab === name));
|
|
209
|
-
document.querySelectorAll('.tab-panel').forEach((p) => p.classList.toggle('active', p.dataset.panel === name));
|
|
210
|
-
}
|
|
211
|
-
document.querySelectorAll('.page-tab').forEach((t) => {
|
|
212
|
-
t.addEventListener('click', () => {
|
|
213
|
-
const name = t.dataset.tab;
|
|
214
|
-
activateTab(name);
|
|
215
|
-
const url = new URL(location.href);
|
|
216
|
-
if (name === 'upcoming') url.searchParams.delete('tab');
|
|
217
|
-
else url.searchParams.set('tab', name);
|
|
218
|
-
history.replaceState({}, '', url);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
if (new URLSearchParams(location.search).get('tab') === 'triggered') activateTab('triggered');
|
|
222
|
-
|
|
223
|
-
function formatCountdown(ms) {
|
|
224
|
-
if (ms <= 0) return 'FIRING';
|
|
225
|
-
const s = Math.ceil(ms / 1000);
|
|
226
|
-
const h = Math.floor(s / 3600);
|
|
227
|
-
const m = Math.floor((s % 3600) / 60);
|
|
228
|
-
const sec = s % 60;
|
|
229
|
-
if (h > 0) return h + ':' + String(m).padStart(2, '0') + ':' + String(sec).padStart(2, '0');
|
|
230
|
-
return String(m).padStart(2, '0') + ':' + String(sec).padStart(2, '0');
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function tick() {
|
|
234
|
-
document.querySelectorAll('.countdown').forEach((el) => {
|
|
235
|
-
const fireAt = parseInt(el.dataset.fireAt, 10);
|
|
236
|
-
const remaining = fireAt - Date.now();
|
|
237
|
-
el.textContent = formatCountdown(remaining);
|
|
238
|
-
el.classList.remove('urgent', 'imminent');
|
|
239
|
-
if (remaining <= 10000) el.classList.add('imminent');
|
|
240
|
-
else if (remaining <= 60000) el.classList.add('urgent');
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function refreshEmptyState() {
|
|
245
|
-
document.querySelectorAll('.session-group').forEach((g) => {
|
|
246
|
-
if (!g.querySelector('.task')) g.remove();
|
|
247
|
-
});
|
|
248
|
-
const list = document.getElementById('schedule-list');
|
|
249
|
-
if (!list.querySelector('.task') && !document.getElementById('empty-msg')) {
|
|
250
|
-
const p = document.createElement('p');
|
|
251
|
-
p.className = 'empty';
|
|
252
|
-
p.id = 'empty-msg';
|
|
253
|
-
p.textContent = 'No scheduled tasks.';
|
|
254
|
-
list.appendChild(p);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function openReschedule(id) {
|
|
259
|
-
document.querySelectorAll('.reschedule-row.active').forEach((r) => {
|
|
260
|
-
if (r.dataset.id !== id) closeReschedule(r.dataset.id);
|
|
261
|
-
});
|
|
262
|
-
const row = document.querySelector('.reschedule-row[data-id="' + id + '"]');
|
|
263
|
-
if (!row) return;
|
|
264
|
-
row.classList.add('active');
|
|
265
|
-
const input = row.querySelector('.reschedule-input');
|
|
266
|
-
input.value = ''; input.classList.remove('error');
|
|
267
|
-
row.querySelector('.reschedule-error').textContent = '';
|
|
268
|
-
input.focus();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function closeReschedule(id) {
|
|
272
|
-
const row = document.querySelector('.reschedule-row[data-id="' + id + '"]');
|
|
273
|
-
if (!row) return;
|
|
274
|
-
row.classList.remove('active');
|
|
275
|
-
const input = row.querySelector('.reschedule-input');
|
|
276
|
-
input.value = ''; input.classList.remove('error');
|
|
277
|
-
row.querySelector('.reschedule-error').textContent = '';
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
async function submitReschedule(id, delayStr) {
|
|
281
|
-
const row = document.querySelector('.reschedule-row[data-id="' + id + '"]');
|
|
282
|
-
if (!row) return;
|
|
283
|
-
const input = row.querySelector('.reschedule-input');
|
|
284
|
-
const errorEl = row.querySelector('.reschedule-error');
|
|
285
|
-
const confirmBtn = row.querySelector('.reschedule-confirm-btn');
|
|
286
|
-
const delayMs = parseDelay(delayStr !== undefined ? delayStr : input.value);
|
|
287
|
-
if (!delayMs) {
|
|
288
|
-
input.classList.add('error');
|
|
289
|
-
errorEl.textContent = ${JSON.stringify(b)};
|
|
290
|
-
input.focus();
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
if (delayMs > MAX_SCHEDULE_MS) {
|
|
294
|
-
input.classList.add('error');
|
|
295
|
-
errorEl.textContent = ${JSON.stringify(x)};
|
|
296
|
-
input.focus();
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
input.classList.remove('error'); errorEl.textContent = '';
|
|
300
|
-
confirmBtn.disabled = true;
|
|
301
|
-
try {
|
|
302
|
-
const res = await fetch('/api/schedule/' + id, {
|
|
303
|
-
method: 'PATCH',
|
|
304
|
-
headers: { 'Content-Type': 'application/json' },
|
|
305
|
-
body: JSON.stringify({ delayMs }),
|
|
306
|
-
});
|
|
307
|
-
if (!res.ok) {
|
|
308
|
-
const err = await res.json().catch(() => ({}));
|
|
309
|
-
errorEl.textContent = err.error || 'Server error.';
|
|
310
|
-
confirmBtn.disabled = false;
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
const data = await res.json();
|
|
314
|
-
const task = document.querySelector('.task[data-id="' + id + '"]');
|
|
315
|
-
if (task) {
|
|
316
|
-
const countdown = task.querySelector('.countdown');
|
|
317
|
-
if (countdown) countdown.dataset.fireAt = String(data.fireAt);
|
|
318
|
-
const meta = task.querySelector('.meta');
|
|
319
|
-
if (meta) {
|
|
320
|
-
const d = new Date(data.fireAt);
|
|
321
|
-
const win = (meta.textContent.match(/win:(d+)/) || ['','?'])[1];
|
|
322
|
-
const time = d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
323
|
-
meta.textContent = 'win:' + win + ' \xB7 fires ' + time;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
closeReschedule(id);
|
|
327
|
-
} catch {
|
|
328
|
-
errorEl.textContent = 'Network error.';
|
|
329
|
-
confirmBtn.disabled = false;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
document.getElementById('schedule-list').addEventListener('click', async (e) => {
|
|
334
|
-
const cancelBtn = e.target.closest('.cancel-btn');
|
|
335
|
-
if (cancelBtn) {
|
|
336
|
-
const id = cancelBtn.dataset.id;
|
|
337
|
-
cancelBtn.disabled = true;
|
|
338
|
-
try {
|
|
339
|
-
const res = await fetch('/api/schedule/' + id, { method: 'DELETE' });
|
|
340
|
-
if (res.ok) {
|
|
341
|
-
const task = document.querySelector('.task[data-id="' + id + '"]');
|
|
342
|
-
if (task) task.remove();
|
|
343
|
-
refreshEmptyState();
|
|
344
|
-
} else { cancelBtn.disabled = false; }
|
|
345
|
-
} catch { cancelBtn.disabled = false; }
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const rescheduleBtn = e.target.closest('.reschedule-btn');
|
|
350
|
-
if (rescheduleBtn) {
|
|
351
|
-
const id = rescheduleBtn.dataset.id;
|
|
352
|
-
const row = document.querySelector('.reschedule-row[data-id="' + id + '"]');
|
|
353
|
-
if (row && row.classList.contains('active')) closeReschedule(id);
|
|
354
|
-
else openReschedule(id);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const confirmBtn = e.target.closest('.reschedule-confirm-btn');
|
|
359
|
-
if (confirmBtn) {
|
|
360
|
-
const row = confirmBtn.closest('.reschedule-row');
|
|
361
|
-
if (row) await submitReschedule(row.dataset.id);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const presetBtn = e.target.closest('.reschedule-preset-btn');
|
|
366
|
-
if (presetBtn) {
|
|
367
|
-
const row = presetBtn.closest('.reschedule-row');
|
|
368
|
-
if (row) await submitReschedule(row.dataset.id, presetBtn.dataset.delay);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
document.getElementById('schedule-list').addEventListener('keydown', (e) => {
|
|
374
|
-
const input = e.target.closest('.reschedule-input');
|
|
375
|
-
if (!input) return;
|
|
376
|
-
const row = input.closest('.reschedule-row');
|
|
377
|
-
if (!row) return;
|
|
378
|
-
if (e.key === 'Escape') { e.preventDefault(); closeReschedule(row.dataset.id); }
|
|
379
|
-
else if (e.key === 'Enter') { e.preventDefault(); void submitReschedule(row.dataset.id); }
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
document.getElementById('schedule-list').addEventListener('focusout', (e) => {
|
|
383
|
-
const row = e.target.closest('.reschedule-row');
|
|
384
|
-
if (!row || row.contains(e.relatedTarget)) return;
|
|
385
|
-
closeReschedule(row.dataset.id);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
tick();
|
|
389
|
-
setInterval(tick, 1000);
|
|
390
|
-
</script>
|
|
391
|
-
</body>
|
|
392
|
-
</html>`}export{G as renderScheduleIndex};
|
package/dist/lib/plugins.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{execFile as d,execFileSync as p}from"node:child_process";import{writeFile as w,mkdir as g}from"node:fs/promises";import{existsSync as l}from"node:fs";import u from"node:path";import{promisify as y}from"node:util";import{getDataRoot as x}from"./state-paths.js";import{readSettings as s,writeSettings as a}from"./settings.js";const $=y(d),e=x();async function r(){await g(e,{recursive:!0});const t=u.join(e,"package.json");l(t)||await w(t,JSON.stringify({name:"tmux-web-plugins",private:!0,version:"1.0.0",description:"tmux-web plugin install dir \u2014 managed by `tmux-web add/remove`"},null,2)+`
|
|
2
|
-
`)}function c(t){p("npm",t,{cwd:e,stdio:"inherit"})}async function A(t){await r(),console.log(`\u25B8 installing ${t}`),c(["install",t]);const n=await s();n.plugins??=[],n.plugins.includes(t)?console.log(`\u2713 ${t} already enabled`):(n.plugins.push(t),await a(n),console.log(`\u2713 enabled ${t}`))}async function F(t){l(u.join(e,"node_modules",t))&&(console.log(`\u25B8 uninstalling ${t}`),c(["uninstall",t]));const n=await s();n.plugins?.includes(t)?(n.plugins=n.plugins.filter(i=>i!==t),await a(n),console.log(`\u2713 disabled ${t}`)):console.log(`(${t} was not enabled)`)}function I(){return e}const b=/^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*(@[\w.^~*>=<.-]+)?$/i;function f(t){return typeof t=="string"&&t.length>0&&t.length<=214&&b.test(t)}async function m(t){try{const{stdout:n,stderr:i}=await $("npm",t,{cwd:e});return{ok:!0,output:(n+i).trim()}}catch(n){return{ok:!1,output:[n?.stdout,n?.stderr,n?.message].filter(Boolean).join(`
|
|
3
|
-
`).trim()||"npm command failed"}}}async function _(t){if(!f(t))return{ok:!1,output:`invalid package name: ${t}`};await r();const n=await m(["install",t]);if(!n.ok)return n;const i=await s();return i.plugins??=[],i.plugins.includes(t)||(i.plugins.push(t),await a(i)),n}async function R(t){if(!f(t))return{ok:!1,output:`invalid package name: ${t}`};let n="";if(l(u.join(e,"node_modules",t))){const o=await m(["uninstall",t]);if(!o.ok)return o;n=o.output}const i=await s();return i.plugins?.includes(t)&&(i.plugins=i.plugins.filter(o=>o!==t),await a(i)),{ok:!0,output:n}}export{A as cmdAdd,F as cmdRemove,I as getPluginDir,_ as installPlugin,f as isValidPackageName,R as uninstallPlugin};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
const o=864e5,u=31536e6,m=36e5,l="Use: 1h, 5m, 30s, 1m30s, 70h, 30d",a="Maximum delay is 1 year (e.g. 365d, 8760h)",s="delay must be between 1ms and 1 year";function i(t){const r=(t||"").trim().toLowerCase();if(!r)return null;const e=r.match(/^(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/);if(!e||!e[1]&&!e[2]&&!e[3]&&!e[4])return null;const n=(parseInt(e[1]||"0",10)*86400+parseInt(e[2]||"0",10)*3600+parseInt(e[3]||"0",10)*60+parseInt(e[4]||"0",10))*1e3;return n>0?n:null}function c(t){return Number.isFinite(t)&&t>=1&&t<=31536e6}function p(t){if(!t||typeof t!="object")return null;const r=t.delayMs;return typeof r!="number"||!Number.isFinite(r)?null:r<1||r>31536e6?s:null}function M(){return`
|
|
2
|
-
function parseDelay(str) {
|
|
3
|
-
str = (str || '').trim().toLowerCase();
|
|
4
|
-
if (!str) return null;
|
|
5
|
-
const m = str.match(/^(?:(\\d+)d)?(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$/);
|
|
6
|
-
if (!m || (!m[1] && !m[2] && !m[3] && !m[4])) return null;
|
|
7
|
-
const ms = ((parseInt(m[1] || '0', 10) * 86400) + (parseInt(m[2] || '0', 10) * 3600) + (parseInt(m[3] || '0', 10) * 60) + parseInt(m[4] || '0', 10)) * 1000;
|
|
8
|
-
return ms > 0 ? ms : null;
|
|
9
|
-
}`.trim()}export{m as ARM_SCAN_INTERVAL_MS,l as DELAY_INVALID_MESSAGE,a as DELAY_MAX_MESSAGE,s as DELAY_RANGE_MESSAGE,u as MAX_SCHEDULE_MS,o as MAX_TIMER_MS,p as getScheduleDelayError,c as isValidDelayMs,i as parseDelayMs,M as scheduleDelayParseScript};
|