♻️ refactor(client): 浏览器半拆成 src 模块,client.js 改由构建产出

This commit is contained in:
pyh
2026-10-04 19:01:22 +08:00
parent 3986aac863
commit 68ab60273e
30 changed files with 3506 additions and 1394 deletions
+80
View File
@@ -0,0 +1,80 @@
/**
* The light in-app popup stack: its lifetime, its ordering, and its bound.
*
* @module @dsh-plugin/session-notify/client/core/toasts
*/
import { TOAST_DURATION_MS, TOAST_LIMIT } from '../constants.js'
/**
* Build the popup stack over one store.
* @param deps - the plugin store, and the copy table a candidate is rendered with.
* @returns the stack's operations, including the three the popup element calls back into.
*/
export function createToastStack({ store, copy }) {
const timers = new Map()
let seq = 0
/** Drop one popup and cancel its lifetime timer. */
const closeToast = (id) => {
const timer = timers.get(id)
if (timer !== undefined) {
clearTimeout(timer)
timers.delete(id)
}
const snapshot = store.getSnapshot()
if (!snapshot.toasts.some((toast) => toast.id === id)) return
store.set({ ...snapshot, toasts: snapshot.toasts.filter((toast) => toast.id !== id) })
}
/** Arm one popup's lifetime; hovering calls `hold` first, then this again. */
const armToast = (id) => {
const timer = timers.get(id)
if (timer !== undefined) clearTimeout(timer)
timers.set(id, setTimeout(() => closeToast(id), TOAST_DURATION_MS))
}
/** Pause one popup's lifetime while the pointer rests on it. */
const holdToast = (id) => {
const timer = timers.get(id)
if (timer === undefined) return
clearTimeout(timer)
timers.delete(id)
}
/**
* Show one light in-app popup: the trigger as the title, the conversation
* and its detail as the description, so the heading says what happened and
* the line under it says where.
*/
const showToast = (candidate) => {
const notice = copy.copyFor(candidate)
seq += 1
const toast = {
id: `dsn-${String(seq)}`,
kind: notice.kind,
title: notice.title,
body: notice.body,
sessionId: candidate.sessionId,
}
const snapshot = store.getSnapshot()
const next = [toast, ...snapshot.toasts]
for (const dropped of next.slice(TOAST_LIMIT)) {
const timer = timers.get(dropped.id)
if (timer !== undefined) {
clearTimeout(timer)
timers.delete(dropped.id)
}
}
store.set({ ...snapshot, toasts: next.slice(0, TOAST_LIMIT) })
armToast(toast.id)
}
/** Cancel every lifetime timer: the plugin is being disposed, so nothing may fire later. */
const dispose = () => {
for (const timer of timers.values()) clearTimeout(timer)
timers.clear()
}
return { showToast, closeToast, armToast, holdToast, dispose }
}