dsh-remote-plugin 0.5.9 → 0.6.1
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/apk/dsh-remote.apk +0 -0
- package/gateway.cjs +133 -0
- package/index.mjs +68 -3
- package/package.json +1 -1
- package/public/admin.html +17 -0
- package/public/admin.js +9 -0
- package/public/app.js +459 -17
- package/public/desktop/desktop.html +160 -15
- package/public/desktop/desktop.js +275 -4
- package/public/donate.png +0 -0
- package/public/index.html +271 -85
- package/public/update.json +4 -3
- package/public/version.json +1 -1
|
@@ -57,10 +57,14 @@ const state = {
|
|
|
57
57
|
questionModal: null,
|
|
58
58
|
streamsOk: { mux: false, host: false },
|
|
59
59
|
errCount: 0,
|
|
60
|
+
streamMode: 'ws', // 'ws' | 'poll'
|
|
61
|
+
pollSeq: { mux: 0, host: 0 },
|
|
60
62
|
fs: { path: null, initial: null, loaded: false },
|
|
61
63
|
view: 'sessions'
|
|
62
64
|
}
|
|
63
65
|
const streams = {}
|
|
66
|
+
let pollTimer = null
|
|
67
|
+
let wsRetryTimer = null
|
|
64
68
|
|
|
65
69
|
function esc(s) { return String(s ?? '').replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])) }
|
|
66
70
|
function short(id) { return '…' + String(id).slice(-8) }
|
|
@@ -92,6 +96,35 @@ function toast(text, kind = '') {
|
|
|
92
96
|
toast._t = setTimeout(() => el.classList.add('hidden'), 2600)
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
/* ---------------- 预设提示词 ---------------- */
|
|
100
|
+
const PRESETS_KEY = 'dshPromptPresets'
|
|
101
|
+
function readPresets() {
|
|
102
|
+
try {
|
|
103
|
+
const v = JSON.parse(LS.get(PRESETS_KEY, '[]') || '[]')
|
|
104
|
+
return Array.isArray(v) ? v.filter(p => p && typeof p.id === 'string' && typeof p.name === 'string' && typeof p.text === 'string') : []
|
|
105
|
+
} catch { return [] }
|
|
106
|
+
}
|
|
107
|
+
function renderPresetMenuDesktop() {
|
|
108
|
+
const btn = $('btn-preset')
|
|
109
|
+
const menu = $('preset-menu')
|
|
110
|
+
if (!btn || !menu) return
|
|
111
|
+
const list = readPresets()
|
|
112
|
+
btn.style.display = list.length ? '' : 'none'
|
|
113
|
+
menu.classList.add('hidden')
|
|
114
|
+
menu.innerHTML = list.map(p => `<button class="ds-feedback-item" data-ds-preset="${esc(p.id)}">${esc(p.name)}</button>`).join('')
|
|
115
|
+
}
|
|
116
|
+
function togglePresetMenuDesktop() {
|
|
117
|
+
const menu = $('preset-menu')
|
|
118
|
+
if (!menu) return
|
|
119
|
+
if (!readPresets().length) return
|
|
120
|
+
menu.classList.toggle('hidden')
|
|
121
|
+
}
|
|
122
|
+
function toggleCmdMenuDesktop() {
|
|
123
|
+
const menu = $('cmd-menu')
|
|
124
|
+
if (!menu) return
|
|
125
|
+
menu.classList.toggle('hidden')
|
|
126
|
+
}
|
|
127
|
+
|
|
95
128
|
/* ---------------- 反馈 ---------------- */
|
|
96
129
|
const FEEDBACK_LINKS = {
|
|
97
130
|
githubIssues: 'https://github.com/Blank-not-black/dsh-Remote/issues',
|
|
@@ -130,6 +163,64 @@ function openFeedbackModal() {
|
|
|
130
163
|
setTimeout(() => $('fb-msg').focus(), 50)
|
|
131
164
|
}
|
|
132
165
|
function closeFeedbackModal() { $('modal-feedback').classList.add('hidden') }
|
|
166
|
+
function openDonateModal() {
|
|
167
|
+
const m = $('modal-donate')
|
|
168
|
+
if (m) m.classList.remove('hidden')
|
|
169
|
+
}
|
|
170
|
+
/* ---------------- 更新内容弹窗 ---------------- */
|
|
171
|
+
const NOTES_KEY = 'seenNotesVersion'
|
|
172
|
+
let notesVersion = ''
|
|
173
|
+
let notesPages = []
|
|
174
|
+
let notesPage = 0
|
|
175
|
+
function splitNotes(notes) {
|
|
176
|
+
return String(notes || '').split(/[;;]/).map(s => s.trim()).filter(Boolean)
|
|
177
|
+
}
|
|
178
|
+
function renderNotesPages(items) {
|
|
179
|
+
const box = $('notes-pages')
|
|
180
|
+
if (!box) return
|
|
181
|
+
const pages = []
|
|
182
|
+
for (let i = 0; i < items.length; i += 3) pages.push(items.slice(i, i + 3))
|
|
183
|
+
notesPages = pages
|
|
184
|
+
notesPage = 0
|
|
185
|
+
box.innerHTML = pages.map(page => `<div class="notes-page" style="flex:0 0 100%;scroll-snap-align:start;box-sizing:border-box;min-width:0;">${page.map(item => `<div class="notes-item" style="padding:6px 0;line-height:1.5;">${esc(item)}</div>`).join('')}</div>`).join('')
|
|
186
|
+
box.scrollLeft = 0
|
|
187
|
+
updateNotesPage()
|
|
188
|
+
}
|
|
189
|
+
function updateNotesPage() {
|
|
190
|
+
const box = $('notes-pages')
|
|
191
|
+
const pageEl = $('notes-page')
|
|
192
|
+
if (!box || !pageEl) return
|
|
193
|
+
const total = notesPages.length || 1
|
|
194
|
+
const idx = Math.min(Math.max(0, Math.round(box.scrollLeft / Math.max(1, box.clientWidth))), total - 1)
|
|
195
|
+
notesPage = idx
|
|
196
|
+
pageEl.textContent = t('ds.notesPage', { current: idx + 1, total })
|
|
197
|
+
}
|
|
198
|
+
function scrollNotes(dir) {
|
|
199
|
+
const box = $('notes-pages')
|
|
200
|
+
if (box) box.scrollBy({ left: dir * box.clientWidth, behavior: 'smooth' })
|
|
201
|
+
}
|
|
202
|
+
function openNotesModal(info) {
|
|
203
|
+
if (!info?.version || String(info.version).includes('-rc')) return
|
|
204
|
+
if (LS.get(NOTES_KEY) === info.version) return
|
|
205
|
+
const items = splitNotes(info.notes)
|
|
206
|
+
if (!items.length) return
|
|
207
|
+
notesVersion = info.version
|
|
208
|
+
const vEl = $('notes-version')
|
|
209
|
+
if (vEl) vEl.textContent = 'v' + info.version
|
|
210
|
+
renderNotesPages(items)
|
|
211
|
+
$('modal-notes').classList.remove('hidden')
|
|
212
|
+
}
|
|
213
|
+
function closeNotesModal() {
|
|
214
|
+
$('modal-notes').classList.add('hidden')
|
|
215
|
+
if (notesVersion) { LS.set(NOTES_KEY, notesVersion); notesVersion = '' }
|
|
216
|
+
}
|
|
217
|
+
async function checkNotesOnStart() {
|
|
218
|
+
try {
|
|
219
|
+
const res = await fetch('../update.json?t=' + Date.now())
|
|
220
|
+
if (!res.ok) return
|
|
221
|
+
openNotesModal(await res.json())
|
|
222
|
+
} catch {}
|
|
223
|
+
}
|
|
133
224
|
async function submitFeedback() {
|
|
134
225
|
const type = document.querySelector('#fb-chips .ds-fb-chip.current')?.dataset.fbType || 'bug'
|
|
135
226
|
const message = $('fb-msg').value.trim()
|
|
@@ -483,9 +574,11 @@ function deleteGroup(name) {
|
|
|
483
574
|
if (state.token) selectFastestServer({ silent: true })
|
|
484
575
|
}
|
|
485
576
|
|
|
486
|
-
/* ---------------- 事件流 ---------------- */
|
|
577
|
+
/* ---------------- 事件流 (WebSocket + 轮询降级) ---------------- */
|
|
487
578
|
function openStreams() {
|
|
488
579
|
if (!state.token) return
|
|
580
|
+
if (state.streamMode === 'poll') stopPolling()
|
|
581
|
+
state.streamMode = 'ws'
|
|
489
582
|
openStream('mux', onMuxFrame, true)
|
|
490
583
|
openStream('host', onHostFrame, false)
|
|
491
584
|
}
|
|
@@ -500,12 +593,14 @@ function openStream(kind, handler, refreshOnOpen) {
|
|
|
500
593
|
ws.onopen = () => {
|
|
501
594
|
state.streamsOk[kind] = true
|
|
502
595
|
state.errCount = 0
|
|
596
|
+
if (state.streamMode === 'poll') { stopPolling(); state.streamMode = 'ws' }
|
|
503
597
|
updateConn()
|
|
504
598
|
if (kind === 'mux') { state.approvals = []; state.questions = []; renderNotifStack() }
|
|
505
599
|
if (refreshOnOpen) refreshSessions()
|
|
506
600
|
}
|
|
507
601
|
ws.onmessage = (msg) => {
|
|
508
602
|
state.streamsOk[kind] = true
|
|
603
|
+
state.errCount = 0
|
|
509
604
|
updateConn()
|
|
510
605
|
try { handler(JSON.parse(msg.data)) } catch {}
|
|
511
606
|
}
|
|
@@ -513,11 +608,84 @@ function openStream(kind, handler, refreshOnOpen) {
|
|
|
513
608
|
state.streamsOk[kind] = false
|
|
514
609
|
state.errCount++
|
|
515
610
|
updateConn()
|
|
611
|
+
if (state.streamMode === 'poll') return
|
|
612
|
+
if (state.errCount >= 3) { enterPollMode(); return }
|
|
516
613
|
if (state.servers.length && state.errCount % 5 === 0) setTimeout(() => selectFastestServer({ silent: true }), 300)
|
|
517
614
|
if (streams[kind] === ws) setTimeout(() => openStream(kind, handler, refreshOnOpen), 1200)
|
|
518
615
|
}
|
|
519
616
|
ws.onerror = () => { try { ws.close() } catch {} }
|
|
520
617
|
}
|
|
618
|
+
|
|
619
|
+
/* ---------------- 轮询降级模式 ---------------- */
|
|
620
|
+
function enterPollMode() {
|
|
621
|
+
if (state.streamMode === 'poll') return
|
|
622
|
+
state.streamMode = 'poll'
|
|
623
|
+
state.pollSeq = { mux: 0, host: 0 }
|
|
624
|
+
state.streamsOk = { mux: false, host: false }
|
|
625
|
+
try { streams.mux?.close() } catch {}
|
|
626
|
+
try { streams.host?.close() } catch {}
|
|
627
|
+
streams.mux = null
|
|
628
|
+
streams.host = null
|
|
629
|
+
refreshSessions()
|
|
630
|
+
startPolling()
|
|
631
|
+
updateConn()
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function stopPolling() {
|
|
635
|
+
clearInterval(pollTimer)
|
|
636
|
+
pollTimer = null
|
|
637
|
+
clearTimeout(wsRetryTimer)
|
|
638
|
+
wsRetryTimer = null
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function startPolling() {
|
|
642
|
+
stopPolling()
|
|
643
|
+
pollTimer = setInterval(pollOnce, 4000)
|
|
644
|
+
wsRetryTimer = setInterval(tryRestoreWs, 30000)
|
|
645
|
+
pollOnce()
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
let pollInFlight = false
|
|
649
|
+
async function pollOnce() {
|
|
650
|
+
if (state.streamMode !== 'poll' || pollInFlight) return
|
|
651
|
+
pollInFlight = true
|
|
652
|
+
try {
|
|
653
|
+
await Promise.all([pollKind('mux'), pollKind('host')])
|
|
654
|
+
} finally {
|
|
655
|
+
pollInFlight = false
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function pollKind(kind) {
|
|
660
|
+
if (state.streamMode !== 'poll') return
|
|
661
|
+
const since = state.pollSeq[kind] || 0
|
|
662
|
+
let res
|
|
663
|
+
try {
|
|
664
|
+
const signal = typeof AbortSignal?.timeout === 'function' ? AbortSignal.timeout(5000) : undefined
|
|
665
|
+
const headers = { authorization: 'Bearer ' + state.token, 'x-dsh-remote-client': 'web' }
|
|
666
|
+
res = signal ? await fetch(apiUrl(`/api/events.poll?kind=${kind}&since=${since}`), { signal, headers }) : await fetch(apiUrl(`/api/events.poll?kind=${kind}&since=${since}`), { headers })
|
|
667
|
+
} catch { return }
|
|
668
|
+
if (res.status === 401) { toast(t('ds.toastAuth'), 'err'); return }
|
|
669
|
+
if (!res.ok) return
|
|
670
|
+
let data
|
|
671
|
+
try { data = await res.json() } catch { return }
|
|
672
|
+
if (!data || !Array.isArray(data.events)) return
|
|
673
|
+
if (typeof data.latestSeq === 'number' && data.latestSeq < since) state.pollSeq[kind] = 0
|
|
674
|
+
for (const item of data.events) {
|
|
675
|
+
if (item.seq > (state.pollSeq[kind] || 0)) {
|
|
676
|
+
state.pollSeq[kind] = item.seq
|
|
677
|
+
if (kind === 'mux') onMuxFrame(item.event)
|
|
678
|
+
else onHostFrame(item.event)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function tryRestoreWs() {
|
|
684
|
+
if (state.streamMode !== 'poll' || !state.token) return
|
|
685
|
+
// 轮询继续跑,等 WS 真正 onopen 后再切回,避免重连窗口丢事件
|
|
686
|
+
openStream('mux', onMuxFrame, true)
|
|
687
|
+
openStream('host', onHostFrame, false)
|
|
688
|
+
}
|
|
521
689
|
function onMuxFrame(full) {
|
|
522
690
|
const f = full.payload
|
|
523
691
|
if (!f) return
|
|
@@ -654,6 +822,13 @@ function blockHtml(b) {
|
|
|
654
822
|
if (b.type === 'image') return `<div>🖼</div>`
|
|
655
823
|
return ''
|
|
656
824
|
}
|
|
825
|
+
function systemReminderText(blocks) {
|
|
826
|
+
if (!Array.isArray(blocks)) return ''
|
|
827
|
+
return blocks
|
|
828
|
+
.filter(b => b && typeof b === 'object' && b.type === 'text' && String(b.text ?? '').trimStart().startsWith('<system-reminder>'))
|
|
829
|
+
.map(b => String(b.text ?? ''))
|
|
830
|
+
.join('\n')
|
|
831
|
+
}
|
|
657
832
|
function eventHtml(entry) {
|
|
658
833
|
const ev = entry.event || {}
|
|
659
834
|
const data = ev.data || {}
|
|
@@ -663,6 +838,11 @@ function eventHtml(entry) {
|
|
|
663
838
|
const msg = data.message || {}
|
|
664
839
|
const role = data.role || msg.role || (type.startsWith('user') ? 'user' : 'assistant')
|
|
665
840
|
const blocks = msg.content || data.content || []
|
|
841
|
+
const sysText = type === 'user/message' ? systemReminderText(blocks) : ''
|
|
842
|
+
if (sysText) {
|
|
843
|
+
const shown = sysText.length > 400 ? sysText.slice(0, 400) + '…' : sysText
|
|
844
|
+
return `<details class="event ds-tool"><summary>${esc(t('ds.eventSystemReminder'))}</summary><pre>${esc(shown)}</pre></details>`
|
|
845
|
+
}
|
|
666
846
|
const text = blocks.map(blockHtml).join('')
|
|
667
847
|
return `<div class="ds-msg ${esc(role)}"><div class="role">${esc(role === 'user' ? t('ds.role.me') : t('ds.role.dsh'))}</div>${text || '<span style="opacity:.6">…</span>'}</div>`
|
|
668
848
|
}
|
|
@@ -686,10 +866,35 @@ function renderHistory() {
|
|
|
686
866
|
box.innerHTML = items.map(eventHtml).join('') || `<div class="ds-empty">${t('ds.historyEmpty')}</div>`
|
|
687
867
|
box.scrollTop = box.scrollHeight
|
|
688
868
|
}
|
|
869
|
+
async function runSlashCommand(text) {
|
|
870
|
+
const clean = String(text || '').trim()
|
|
871
|
+
if (!clean.startsWith('/') || !state.current) return false
|
|
872
|
+
try {
|
|
873
|
+
const signal = typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function'
|
|
874
|
+
? AbortSignal.timeout(20000)
|
|
875
|
+
: undefined
|
|
876
|
+
const res = await fetch(apiUrl('/remote/api/command'), {
|
|
877
|
+
method: 'POST',
|
|
878
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer ' + state.token, 'x-dsh-remote-client': 'web' },
|
|
879
|
+
body: JSON.stringify({ sessionId: state.current, line: clean }),
|
|
880
|
+
...(signal ? { signal } : {})
|
|
881
|
+
})
|
|
882
|
+
if (res.status === 401) { toast(t('ds.toastAuth'), 'err'); return true }
|
|
883
|
+
if (!res.ok) return false
|
|
884
|
+
const data = await res.json().catch(() => null)
|
|
885
|
+
if (data?.ok === false) return true
|
|
886
|
+
return data?.ok === true && data.executed === true
|
|
887
|
+
} catch (e) {
|
|
888
|
+
console.error('slash command bridge failed', e)
|
|
889
|
+
}
|
|
890
|
+
return false
|
|
891
|
+
}
|
|
892
|
+
|
|
689
893
|
async function sendMessage() {
|
|
690
894
|
const input = $('composer')
|
|
691
895
|
const text = input.value.trim()
|
|
692
896
|
if (!text || !state.current) return
|
|
897
|
+
if (await runSlashCommand(text)) { input.value = ''; return }
|
|
693
898
|
input.value = ''
|
|
694
899
|
const v = await safeRpc('session.prompt', { sessionId: state.current, text }, '')
|
|
695
900
|
if (v) toast(t('ds.toastSent'), 'ok')
|
|
@@ -912,17 +1117,39 @@ function showView(id) {
|
|
|
912
1117
|
if (id === 'view-chat') { const s = state.byId.get(state.current); $('ds-title').textContent = s ? titleOf(s) : t('ds.sessions') }
|
|
913
1118
|
else $('ds-title').textContent = t(titles[id])
|
|
914
1119
|
if (id === 'view-files' && !state.fs.loaded) loadFs(null, true)
|
|
1120
|
+
if (id === 'view-settings') showSettingsHome()
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const SETTINGS_GROUPS = ['general', 'servers', 'notify', 'theme', 'about']
|
|
1124
|
+
function showSettingsHome() {
|
|
1125
|
+
const home = $('settings-home')
|
|
1126
|
+
if (!home) return
|
|
1127
|
+
home.classList.remove('hidden')
|
|
1128
|
+
for (const name of SETTINGS_GROUPS) $('settings-page-' + name)?.classList.add('hidden')
|
|
1129
|
+
}
|
|
1130
|
+
function showSettingsPage(name) {
|
|
1131
|
+
const home = $('settings-home')
|
|
1132
|
+
if (!home || !SETTINGS_GROUPS.includes(name)) return
|
|
1133
|
+
home.classList.add('hidden')
|
|
1134
|
+
for (const g of SETTINGS_GROUPS) $('settings-page-' + g)?.classList.toggle('hidden', g !== name)
|
|
915
1135
|
}
|
|
916
1136
|
function updateConn() {
|
|
917
1137
|
const el = $('conn-badge')
|
|
1138
|
+
const cur = state.servers.find(s => s.url === state.server)
|
|
1139
|
+
const group = cur ? cur.group : state.activeGroup
|
|
1140
|
+
const label = cur ? (cur.note || cur.url) : (state.server || t('ds.origin'))
|
|
1141
|
+
if (state.streamMode === 'poll') {
|
|
1142
|
+
el.textContent = '●'
|
|
1143
|
+
el.className = 'ds-conn off'
|
|
1144
|
+
el.title = t('ds.connPollTitle')
|
|
1145
|
+
$('server-badge').textContent = t('ds.currentServer', { group, url: label })
|
|
1146
|
+
return
|
|
1147
|
+
}
|
|
918
1148
|
const any = Object.values(state.streamsOk).some(Boolean)
|
|
919
1149
|
const all = state.streamsOk.mux && state.streamsOk.host
|
|
920
1150
|
el.textContent = '●'
|
|
921
1151
|
el.className = 'ds-conn ' + (all ? 'on' : any ? 'ing' : '')
|
|
922
1152
|
el.title = all ? t('ds.connOn') : any ? t('ds.connIng') : t('ds.connOff')
|
|
923
|
-
const cur = state.servers.find(s => s.url === state.server)
|
|
924
|
-
const group = cur ? cur.group : state.activeGroup
|
|
925
|
-
const label = cur ? (cur.note || cur.url) : (state.server || t('ds.origin'))
|
|
926
1153
|
$('server-badge').textContent = t('ds.currentServer', { group, url: label })
|
|
927
1154
|
}
|
|
928
1155
|
|
|
@@ -945,8 +1172,46 @@ function bindUi() {
|
|
|
945
1172
|
$('composer').addEventListener('keydown', (e) => {
|
|
946
1173
|
if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) { e.preventDefault(); sendMessage() }
|
|
947
1174
|
})
|
|
1175
|
+
renderPresetMenuDesktop()
|
|
1176
|
+
$('btn-cmd').addEventListener('click', (e) => { e.stopPropagation(); toggleCmdMenuDesktop() })
|
|
1177
|
+
$('cmd-menu').addEventListener('click', (e) => {
|
|
1178
|
+
const item = e.target.closest('[data-ds-cmd]')
|
|
1179
|
+
if (item) {
|
|
1180
|
+
const input = $('composer')
|
|
1181
|
+
input.value = item.dataset.dsCmd + ' '
|
|
1182
|
+
input.focus()
|
|
1183
|
+
$('cmd-menu').classList.add('hidden')
|
|
1184
|
+
}
|
|
1185
|
+
})
|
|
1186
|
+
$('btn-preset').addEventListener('click', (e) => { e.stopPropagation(); togglePresetMenuDesktop() })
|
|
1187
|
+
$('preset-menu').addEventListener('click', (e) => {
|
|
1188
|
+
const item = e.target.closest('[data-ds-preset]')
|
|
1189
|
+
if (item) {
|
|
1190
|
+
const found = readPresets().find(x => x.id === item.dataset.dsPreset)
|
|
1191
|
+
if (found) {
|
|
1192
|
+
const input = $('composer')
|
|
1193
|
+
input.value = found.text
|
|
1194
|
+
input.focus()
|
|
1195
|
+
}
|
|
1196
|
+
$('preset-menu').classList.add('hidden')
|
|
1197
|
+
}
|
|
1198
|
+
})
|
|
1199
|
+
document.addEventListener('click', (e) => {
|
|
1200
|
+
if (!e.target.closest('#preset-menu') && !e.target.closest('#btn-preset')) $('preset-menu')?.classList.add('hidden')
|
|
1201
|
+
if (!e.target.closest('#cmd-menu') && !e.target.closest('#btn-cmd')) $('cmd-menu')?.classList.add('hidden')
|
|
1202
|
+
})
|
|
948
1203
|
$('btn-stats-top').addEventListener('click', toggleStatsDrawer)
|
|
949
1204
|
$('stats-drawer-close').addEventListener('click', toggleStatsDrawer)
|
|
1205
|
+
// 赞赏支持
|
|
1206
|
+
$('btn-donate').addEventListener('click', openDonateModal)
|
|
1207
|
+
$('btn-donate-about').addEventListener('click', openDonateModal)
|
|
1208
|
+
$('btn-donate-close').addEventListener('click', () => $('modal-donate').classList.add('hidden'))
|
|
1209
|
+
// 更新内容弹窗
|
|
1210
|
+
$('notes-close').addEventListener('click', closeNotesModal)
|
|
1211
|
+
$('notes-prev').addEventListener('click', () => scrollNotes(-1))
|
|
1212
|
+
$('notes-next').addEventListener('click', () => scrollNotes(1))
|
|
1213
|
+
$('notes-pages').addEventListener('scroll', updateNotesPage)
|
|
1214
|
+
$('modal-notes').addEventListener('click', (e) => { if (e.target === $('modal-notes')) closeNotesModal() })
|
|
950
1215
|
// 反馈
|
|
951
1216
|
$('btn-feedback').addEventListener('click', (e) => { e.stopPropagation(); toggleFeedbackMenu() })
|
|
952
1217
|
$('feedback-menu').addEventListener('click', (e) => {
|
|
@@ -977,6 +1242,11 @@ function bindUi() {
|
|
|
977
1242
|
})
|
|
978
1243
|
$('stats-chart').addEventListener('mouseleave', hideTip)
|
|
979
1244
|
|
|
1245
|
+
$('view-settings').addEventListener('click', (e) => {
|
|
1246
|
+
const group = e.target.closest('[data-settings-group]')
|
|
1247
|
+
if (group) { showSettingsPage(group.dataset.settingsGroup); return }
|
|
1248
|
+
if (e.target.closest('[data-settings-back]')) { showSettingsHome(); return }
|
|
1249
|
+
})
|
|
980
1250
|
$('btn-server-speed').addEventListener('click', () => selectFastestServer({ silent: false }))
|
|
981
1251
|
$('btn-server-add').addEventListener('click', addServer)
|
|
982
1252
|
$('btn-group-add').addEventListener('click', addGroup)
|
|
@@ -1017,6 +1287,7 @@ async function start() {
|
|
|
1017
1287
|
$('token-desc').textContent = state.token ? '● ' + state.token.slice(0, 12) + '…' : t('ds.toastAuth')
|
|
1018
1288
|
bindUi()
|
|
1019
1289
|
updateConn()
|
|
1290
|
+
checkNotesOnStart()
|
|
1020
1291
|
if (state.token) {
|
|
1021
1292
|
if (state.servers.length) await selectFastestServer({ silent: true, reconnect: false })
|
|
1022
1293
|
openStreams()
|
|
Binary file
|