tmux-weblink 0.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/README.md +70 -0
- package/dist/assets/addon-fit-D5N335P2.js +16 -0
- package/dist/assets/terminal-client.js +4 -0
- package/dist/assets/xterm-NI46XXQY.js +51 -0
- package/dist/assets/xterm.css +285 -0
- package/dist/browser/terminal-client.js +4 -0
- package/dist/frontend.js +1 -0
- package/dist/index.js +12 -0
- package/dist/lib/agent-detect.js +3 -0
- package/dist/lib/agents-watch.js +2 -0
- package/dist/lib/atomicWrite.js +1 -0
- package/dist/lib/auditLog.js +1 -0
- package/dist/lib/auth.js +2 -0
- package/dist/lib/cli.js +32 -0
- package/dist/lib/commandbar.js +555 -0
- package/dist/lib/db.js +1 -0
- package/dist/lib/drawer-resize.js +166 -0
- package/dist/lib/drawer-script.js +8 -0
- package/dist/lib/ext-loader.js +1 -0
- package/dist/lib/html.js +1 -0
- package/dist/lib/icons.js +1 -0
- package/dist/lib/image-upload.js +1 -0
- package/dist/lib/load-env.js +6 -0
- package/dist/lib/mobile-toolbar.js +313 -0
- package/dist/lib/notes-db.js +18 -0
- package/dist/lib/notes-drawer.js +210 -0
- package/dist/lib/notes-utils.js +15 -0
- package/dist/lib/pages/agents-index.js +105 -0
- package/dist/lib/pages/history-index.js +85 -0
- package/dist/lib/pages/landing.js +78 -0
- package/dist/lib/pages/login.js +191 -0
- package/dist/lib/pages/notes-index.js +65 -0
- package/dist/lib/pages/notes-page.js +158 -0
- package/dist/lib/pages/quick-commands.js +361 -0
- package/dist/lib/pages/schedule-index.js +392 -0
- package/dist/lib/pages/settings.js +206 -0
- package/dist/lib/pages/terminal.js +364 -0
- package/dist/lib/pinned-views.js +1 -0
- package/dist/lib/plugins.js +3 -0
- package/dist/lib/quick-commands.js +1 -0
- package/dist/lib/rateLimiter.js +2 -0
- package/dist/lib/schedule-delay.js +9 -0
- package/dist/lib/scheduler-drawer.js +379 -0
- package/dist/lib/scheduler.js +1 -0
- package/dist/lib/security-config.js +2 -0
- package/dist/lib/session-access.js +1 -0
- package/dist/lib/session-windows.js +1 -0
- package/dist/lib/sessions-drawer.js +634 -0
- package/dist/lib/sessions-sidebar.js +1 -0
- package/dist/lib/settings.js +2 -0
- package/dist/lib/setup-features.js +1 -0
- package/dist/lib/setup-prompts.js +3 -0
- package/dist/lib/shared-layout.js +412 -0
- package/dist/lib/state-paths.js +1 -0
- package/dist/lib/terminal-config.js +1 -0
- package/dist/lib/theme-store.js +2 -0
- package/dist/lib/theme.js +28 -0
- package/dist/lib/themes/dark-cove.js +1 -0
- package/dist/lib/themes/ghostty.js +1 -0
- package/dist/lib/themes/index.js +1 -0
- package/dist/lib/themes/types.js +0 -0
- package/dist/lib/themes/vscode.js +1 -0
- package/dist/lib/themes/warm-clay.js +1 -0
- package/dist/lib/tmux-capture.js +5 -0
- package/dist/lib/tmux-control.js +2 -0
- package/dist/lib/tmux-panes.js +2 -0
- package/dist/lib/tmux-windows.js +4 -0
- package/dist/lib/watched-panes.js +1 -0
- package/dist/lib/window-history.js +1 -0
- package/dist/lib/window-labels.js +1 -0
- package/dist/lib/windows-drawer.js +364 -0
- package/dist/lib/ws-message.js +1 -0
- package/dist/sessions.js +2 -0
- package/package.json +62 -0
- package/scripts/fix-pty-perms.mjs +16 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import{cssVarsStyle as S}from"../theme.js";import{commandbarCSS as k,commandbarHTML as $,commandbarScript as E}from"../commandbar.js";import{notesDrawerCSS as L,notesDrawerHTML as A,notesDrawerScript as B}from"../notes-drawer.js";import{sharedLayoutCSS as C,sharedHeader as M,sharedSidebar as q,newSessionModalHTML as I,newSessionModalScript as D}from"../shared-layout.js";import{DELAY_INVALID_MESSAGE as R,DELAY_MAX_MESSAGE as T,MAX_SCHEDULE_MS as z,scheduleDelayParseScript as N}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 _(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 O(s,n,c,e=7,i=!1,u=[],m=!1){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 h=[...l.entries()].map(([r,o])=>{const y=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">${y}</div>
|
|
44
|
+
</div>`}).join(`
|
|
45
|
+
`),f=d.length?h:'<p class="empty" id="empty-msg">No scheduled tasks.</p>',g=_(n,e),b=z,x=R,v=T,w=`
|
|
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?k():""}
|
|
158
|
+
${L()}`;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
|
+
${S(c.shell)}
|
|
167
|
+
${C(w)}
|
|
168
|
+
</style>
|
|
169
|
+
</head>
|
|
170
|
+
<body>
|
|
171
|
+
|
|
172
|
+
${M({commandbarEnabled:i,title:"Scheduled",themeTemplate:c.template})}
|
|
173
|
+
|
|
174
|
+
<div class="page-wrap">
|
|
175
|
+
<div class="page-layout">
|
|
176
|
+
${q({activePage:"schedule",agentsEnabled:m,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">${f}</div>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="tab-panel" data-panel="triggered">
|
|
186
|
+
${g}
|
|
187
|
+
</div>
|
|
188
|
+
</main>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
${I()}
|
|
193
|
+
${i?$():""}
|
|
194
|
+
${A("Notes - Global")}
|
|
195
|
+
|
|
196
|
+
<script type="module">
|
|
197
|
+
${B("__global__")}
|
|
198
|
+
${i?E(u,[]):""}
|
|
199
|
+
${D()}
|
|
200
|
+
</script>
|
|
201
|
+
|
|
202
|
+
<script>
|
|
203
|
+
const MAX_SCHEDULE_MS = ${b};
|
|
204
|
+
${N()}
|
|
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(x)};
|
|
290
|
+
input.focus();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (delayMs > MAX_SCHEDULE_MS) {
|
|
294
|
+
input.classList.add('error');
|
|
295
|
+
errorEl.textContent = ${JSON.stringify(v)};
|
|
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{O as renderScheduleIndex};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import{cssVarsStyle as x}from"../theme.js";import{getThemeTemplates as f,THEME_TEMPLATE_IDS as y}from"../themes/index.js";function n(a){return a.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function u(a,e){return`<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
|
+
<title>${n(a)} - tmux-web</title>
|
|
8
|
+
<style>
|
|
9
|
+
${x(e.shell)}
|
|
10
|
+
html, body { background: var(--page-bg); color: var(--page-fg); min-height: 100%; font-family: var(--font-sans); }
|
|
11
|
+
.container { max-width: 680px; margin: 80px auto; padding: 0 24px; }
|
|
12
|
+
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 28px; }
|
|
13
|
+
h1 { font-size: var(--text-xl); font-weight: 600; letter-spacing: -0.02em; color: var(--page-fg); }
|
|
14
|
+
.back-link {
|
|
15
|
+
font-size: var(--text-sm); color: var(--panel-muted); text-decoration: none;
|
|
16
|
+
border: 1px solid var(--panel-border); padding: 8px 16px; border-radius: 10px; transition: all 0.15s;
|
|
17
|
+
}
|
|
18
|
+
.back-link:hover { border-color: var(--panel-accent); color: var(--panel-accent); background: color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
19
|
+
.restart-note {
|
|
20
|
+
font-size: var(--text-sm); line-height: 1.6; color: var(--panel-muted);
|
|
21
|
+
border: 1px dashed var(--panel-border); border-radius: 12px;
|
|
22
|
+
padding: 12px 16px; margin-bottom: 24px;
|
|
23
|
+
}
|
|
24
|
+
.restart-note strong { color: var(--panel-accent); }
|
|
25
|
+
.saved-flash {
|
|
26
|
+
font-size: var(--text-sm); color: var(--panel-success);
|
|
27
|
+
border: 1px solid var(--panel-success); border-radius: 12px;
|
|
28
|
+
padding: 12px 16px; margin-bottom: 16px;
|
|
29
|
+
}
|
|
30
|
+
.error-flash {
|
|
31
|
+
font-size: var(--text-sm); color: var(--page-fg); white-space: pre-wrap; word-break: break-word;
|
|
32
|
+
border: 1px solid #e06c75; border-radius: 12px;
|
|
33
|
+
padding: 12px 16px; margin-bottom: 16px; background: color-mix(in srgb, #e06c75 8%, var(--panel-bg));
|
|
34
|
+
}
|
|
35
|
+
.section { border: 1px solid var(--panel-border); border-radius: 16px; background: var(--panel-bg); padding: 20px; margin-bottom: 16px; }
|
|
36
|
+
.section h2 { font-size: var(--text-sm); font-weight: 600; color: var(--page-fg); margin-bottom: 4px; }
|
|
37
|
+
.section .desc { font-size: var(--text-sm); color: var(--panel-muted); line-height: 1.6; margin-bottom: 16px; }
|
|
38
|
+
label.row { display: flex; align-items: center; gap: 10px; font-size: var(--text-sm); cursor: pointer; padding: 5px 0; }
|
|
39
|
+
label.row input { accent-color: var(--panel-success); }
|
|
40
|
+
.radios { display: flex; flex-direction: column; gap: 6px; }
|
|
41
|
+
.override-note { font-size: var(--text-xs); color: var(--panel-muted); margin-top: 10px; }
|
|
42
|
+
.btn {
|
|
43
|
+
font-size: var(--text-sm); color: var(--page-fg); background: var(--panel-bg);
|
|
44
|
+
border: 1px solid var(--panel-border); padding: 9px 18px; border-radius: 12px;
|
|
45
|
+
cursor: pointer; transition: all 0.15s; font-family: inherit;
|
|
46
|
+
}
|
|
47
|
+
.btn:hover { border-color: var(--panel-accent); color: var(--panel-accent); }
|
|
48
|
+
.btn.primary { border-color: var(--panel-success); color: var(--panel-success); }
|
|
49
|
+
.btn.danger:hover { border-color: #e06c75; color: #e06c75; }
|
|
50
|
+
.plugin-row { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 8px 0; font-size: var(--text-sm); border-top: 1px solid var(--panel-border); }
|
|
51
|
+
.plugin-row:first-of-type { border-top: none; }
|
|
52
|
+
.plugin-row .pkg { word-break: break-all; }
|
|
53
|
+
.plugin-add { display: flex; gap: 10px; margin-top: 14px; }
|
|
54
|
+
.plugin-add input[type=text] {
|
|
55
|
+
flex: 1; font-size: var(--text-sm); font-family: inherit; color: var(--page-fg);
|
|
56
|
+
background: var(--page-bg); border: 1px solid var(--panel-border);
|
|
57
|
+
border-radius: 12px; padding: 9px 12px; outline: none; transition: border-color 0.15s, box-shadow 0.15s;
|
|
58
|
+
}
|
|
59
|
+
.plugin-add input[type=text]:focus { border-color: var(--panel-accent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
60
|
+
.num-input {
|
|
61
|
+
width: 100px; font-size: var(--text-sm); font-family: inherit; color: var(--page-fg);
|
|
62
|
+
background: var(--page-bg); border: 1px solid var(--panel-border);
|
|
63
|
+
border-radius: 12px; padding: 9px 12px; outline: none; transition: border-color 0.15s, box-shadow 0.15s;
|
|
64
|
+
}
|
|
65
|
+
.num-input:focus { border-color: var(--panel-accent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
66
|
+
.suggest { font-size: var(--text-sm); color: var(--panel-muted); margin-top: 10px; }
|
|
67
|
+
.suggest button { background: none; border: none; color: var(--panel-accent); cursor: pointer; font: inherit; font-size: var(--text-sm); padding: 0; text-decoration: underline; }
|
|
68
|
+
.theme-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
69
|
+
.theme-card {
|
|
70
|
+
border: 1px solid var(--panel-border); border-radius: 14px; padding: 16px;
|
|
71
|
+
cursor: pointer; transition: border-color 0.15s, transform 0.1s, box-shadow 0.15s; background: var(--panel-bg);
|
|
72
|
+
}
|
|
73
|
+
.theme-card:hover { border-color: var(--panel-accent); transform: translateY(-1px); box-shadow: 0 4px 20px color-mix(in srgb, var(--panel-accent) 8%, transparent); }
|
|
74
|
+
.theme-card.active { border-color: var(--panel-success); }
|
|
75
|
+
.theme-card .tname { font-size: var(--text-sm); font-weight: 600; color: var(--page-fg); display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
|
|
76
|
+
.theme-card .tname input { accent-color: var(--panel-success); }
|
|
77
|
+
.swatches { display: flex; gap: 4px; flex-wrap: wrap; }
|
|
78
|
+
.swatch { width: 18px; height: 18px; border-radius: 5px; border: 1px solid rgba(128,128,128,0.18); }
|
|
79
|
+
.form-actions { margin-top: 12px; }
|
|
80
|
+
.config-paths { font-size: var(--text-xs); color: var(--panel-muted); line-height: 1.6; margin-top: 28px; }
|
|
81
|
+
.config-paths code { background: color-mix(in srgb, var(--panel-accent) 8%, transparent); padding: 3px 7px; border-radius: 6px; }
|
|
82
|
+
.btn { min-height: 44px; }
|
|
83
|
+
@media (max-width: 640px) {
|
|
84
|
+
.container { margin: 24px auto; padding: 0 16px; }
|
|
85
|
+
.page-header { flex-direction: column; align-items: flex-start; gap: 12px; }
|
|
86
|
+
.theme-grid { grid-template-columns: 1fr; }
|
|
87
|
+
.num-input { width: 100%; }
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
90
|
+
</head>`}function w(a,e){let r="";return a&&(r+='<div class="saved-flash">\u2713 Saved. Restart tmux-web to apply.</div>'),e&&(r+=`<div class="error-flash">${n(e)}</div>`),r}function z(a){const{settings:e,renderer:r,rendererOverridden:i,theme:l,plugins:t,saved:d=!1,error:o}=a,c=e.commandbar===!0,s=e.agents===!0,h=e.agentsBackgroundWatch===!0,m=e.terminalRenderer??"xterm",g=e.defaultView??"default",b=e.scheduleHistoryDays??7,v=t.length?t.map(p=>`<div class="plugin-row">
|
|
91
|
+
<span class="pkg">${n(p)}</span>
|
|
92
|
+
<form method="POST" action="/settings/plugins" onsubmit="return confirm('Remove ${n(p)}?');">
|
|
93
|
+
<input type="hidden" name="action" value="remove" />
|
|
94
|
+
<input type="hidden" name="pkg" value="${n(p)}" />
|
|
95
|
+
<button type="submit" class="btn danger">Remove</button>
|
|
96
|
+
</form>
|
|
97
|
+
</div>`).join(`
|
|
98
|
+
`):'<p class="desc" style="margin:0">No plugins enabled.</p>';return`${u("Settings",l)}
|
|
99
|
+
<body>
|
|
100
|
+
<div class="container">
|
|
101
|
+
<div class="page-header">
|
|
102
|
+
<h1>Settings</h1>
|
|
103
|
+
<a href="/" class="back-link">Back</a>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="restart-note">
|
|
107
|
+
<strong>Note:</strong> these settings are read once at startup. You may need to
|
|
108
|
+
<strong>restart the tmux-web process</strong> after making changes for them to take effect.
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
${w(d,o)}
|
|
112
|
+
|
|
113
|
+
<form method="POST" action="/settings">
|
|
114
|
+
<div class="section">
|
|
115
|
+
<h2>Command bar</h2>
|
|
116
|
+
<p class="desc">\u2318K session search + quick actions.</p>
|
|
117
|
+
<label class="row"><input type="checkbox" name="commandbar" ${c?"checked":""} /> Enable command bar</label>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="section">
|
|
121
|
+
<h2>Agents page</h2>
|
|
122
|
+
<p class="desc">Watch AI agents (Claude, Codex, OpenCode, Cursor) running in the last 10 panes you viewed, at <code>/agents</code>. Off by default.</p>
|
|
123
|
+
<label class="row"><input type="checkbox" name="agents" ${s?"checked":""} /> Enable agents page</label>
|
|
124
|
+
<label class="row"><input type="checkbox" name="agentsBackgroundWatch" ${h?"checked":""} /> Watch in the background (poll even when the page is closed)</label>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="section">
|
|
128
|
+
<h2>Terminal library</h2>
|
|
129
|
+
<p class="desc">Rendering engine for the terminal view.</p>
|
|
130
|
+
<div class="radios">
|
|
131
|
+
<label class="row"><input type="radio" name="terminalRenderer" value="xterm" ${m==="xterm"?"checked":""} /> xterm.js (default)</label>
|
|
132
|
+
<label class="row"><input type="radio" name="terminalRenderer" value="ghostty" ${m==="ghostty"?"checked":""} /> ghostty-web</label>
|
|
133
|
+
</div>
|
|
134
|
+
${i?`<p class="override-note">\u26A0 A CLI flag or <code>TMUX_WEB_TERMINAL_RENDERER</code> env var is currently forcing <strong>${r}</strong>, overriding this setting for the running process.</p>`:""}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div class="section">
|
|
138
|
+
<h2>Default home tab</h2>
|
|
139
|
+
<p class="desc">Which tab the home page opens on.</p>
|
|
140
|
+
<div class="radios">
|
|
141
|
+
<label class="row"><input type="radio" name="defaultView" value="default" ${g==="default"?"checked":""} /> Default</label>
|
|
142
|
+
<label class="row"><input type="radio" name="defaultView" value="recent" ${g==="recent"?"checked":""} /> Last Updated</label>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div class="section">
|
|
147
|
+
<h2>Schedule history</h2>
|
|
148
|
+
<p class="desc">Days to keep the <code>/schedule</code> "Recently Triggered" history (fired & missed tasks). 1\u2013365, default 7.</p>
|
|
149
|
+
<label class="row"><input type="number" class="num-input" name="scheduleHistoryDays" min="1" max="365" value="${b}" /> days</label>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div class="form-actions">
|
|
153
|
+
<button type="submit" class="btn primary">Save settings</button>
|
|
154
|
+
</div>
|
|
155
|
+
</form>
|
|
156
|
+
|
|
157
|
+
<div class="section" style="margin-top:16px">
|
|
158
|
+
<h2>Plugins</h2>
|
|
159
|
+
<p class="desc">Installed via npm into the tmux-web data dir. Add/remove runs npm and may take a few seconds.</p>
|
|
160
|
+
${v}
|
|
161
|
+
<form method="POST" action="/settings/plugins" class="plugin-add">
|
|
162
|
+
<input type="hidden" name="action" value="add" />
|
|
163
|
+
<input type="text" id="pkg-input" name="pkg" placeholder="npm package name" autocomplete="off" />
|
|
164
|
+
<button type="submit" class="btn">Add</button>
|
|
165
|
+
</form>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div class="section">
|
|
169
|
+
<h2>Theme</h2>
|
|
170
|
+
<p class="desc">Shell chrome + terminal colors.</p>
|
|
171
|
+
<a href="/settings/theme" class="btn" style="display:inline-block;text-decoration:none">Customize theme \u2192</a>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</body>
|
|
175
|
+
</html>`}const k={vscode:"VS Code",ghostty:"Ghostty","warm-clay":"Warm Clay","dark-cove":"Dark Cove"},$=["background","foreground","red","green","yellow","blue","magenta","cyan"];function E(a){const{theme:e,saved:r=!1}=a,i=f(),l=y.map(t=>{const d=i[t],o=e.template===t,c=$.map(s=>`<span class="swatch" style="background:${n(d.terminal[s])}" title="${s}"></span>`).join("");return`<label class="theme-card${o?" active":""}">
|
|
176
|
+
<div class="tname">
|
|
177
|
+
<input type="radio" name="template" value="${t}" ${o?"checked":""} style="accent-color:var(--panel-success)" />
|
|
178
|
+
${n(k[t]??t)}
|
|
179
|
+
</div>
|
|
180
|
+
<div class="swatches">${c}</div>
|
|
181
|
+
</label>`}).join(`
|
|
182
|
+
`);return`${u("Theme",e)}
|
|
183
|
+
<body>
|
|
184
|
+
<div class="container">
|
|
185
|
+
<div class="page-header">
|
|
186
|
+
<h1>Theme</h1>
|
|
187
|
+
<a href="/settings" class="back-link">Back</a>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div class="restart-note">
|
|
191
|
+
<strong>Note:</strong> the active theme is applied immediately to new page renders.
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
${r?'<div class="saved-flash">\u2713 Theme saved.</div>':""}
|
|
195
|
+
|
|
196
|
+
<form method="POST" action="/settings/theme">
|
|
197
|
+
<div class="theme-grid">
|
|
198
|
+
${l}
|
|
199
|
+
</div>
|
|
200
|
+
<div class="form-actions" style="margin-top:16px">
|
|
201
|
+
<button type="submit" class="btn primary">Save theme</button>
|
|
202
|
+
</div>
|
|
203
|
+
</form>
|
|
204
|
+
</div>
|
|
205
|
+
</body>
|
|
206
|
+
</html>`}export{z as renderSettings,E as renderThemeSettings};
|