@lemoncat7/dsh-knowledge 2.3.3 → 2.3.4

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.
Files changed (70) hide show
  1. package/README.md +1 -1
  2. package/docs/ui-maintenance.md +97 -0
  3. package/lib/client.d.ts.map +1 -1
  4. package/lib/client.js +356 -205
  5. package/lib/client.js.map +4 -4
  6. package/lib/design-tokens.d.ts +12 -0
  7. package/lib/design-tokens.d.ts.map +1 -0
  8. package/lib/design-tokens.js +58 -0
  9. package/lib/design-tokens.js.map +1 -0
  10. package/lib/knowledge-activity-controller.d.ts +3 -8
  11. package/lib/knowledge-activity-controller.d.ts.map +1 -1
  12. package/lib/knowledge-activity-controller.js +19 -7
  13. package/lib/knowledge-activity-controller.js.map +1 -1
  14. package/lib/knowledge-activity-notes.d.ts.map +1 -1
  15. package/lib/knowledge-activity-notes.js +19 -10
  16. package/lib/knowledge-activity-notes.js.map +1 -1
  17. package/lib/knowledge-activity-panel.d.ts.map +1 -1
  18. package/lib/knowledge-activity-panel.js +32 -20
  19. package/lib/knowledge-activity-panel.js.map +1 -1
  20. package/lib/knowledge-activity-presentation.d.ts +14 -0
  21. package/lib/knowledge-activity-presentation.d.ts.map +1 -0
  22. package/lib/knowledge-activity-presentation.js +77 -0
  23. package/lib/knowledge-activity-presentation.js.map +1 -0
  24. package/lib/knowledge-activity-state.d.ts +17 -0
  25. package/lib/knowledge-activity-state.d.ts.map +1 -0
  26. package/lib/knowledge-activity-state.js +14 -0
  27. package/lib/knowledge-activity-state.js.map +1 -0
  28. package/lib/latest-request.d.ts +7 -0
  29. package/lib/latest-request.d.ts.map +1 -0
  30. package/lib/latest-request.js +14 -0
  31. package/lib/latest-request.js.map +1 -0
  32. package/lib/local-provider.d.ts +0 -2
  33. package/lib/local-provider.d.ts.map +1 -1
  34. package/lib/local-provider.js +2 -323
  35. package/lib/local-provider.js.map +1 -1
  36. package/lib/notes/share-page-styles.d.ts +2 -0
  37. package/lib/notes/share-page-styles.d.ts.map +1 -0
  38. package/lib/notes/share-page-styles.js +11 -0
  39. package/lib/notes/share-page-styles.js.map +1 -0
  40. package/lib/notes/share-page.d.ts.map +1 -1
  41. package/lib/notes/share-page.js +1 -8
  42. package/lib/notes/share-page.js.map +1 -1
  43. package/lib/storage/migrations.d.ts +5 -0
  44. package/lib/storage/migrations.d.ts.map +1 -0
  45. package/lib/storage/migrations.js +325 -0
  46. package/lib/storage/migrations.js.map +1 -0
  47. package/lib/theme-bridge.d.ts +9 -7
  48. package/lib/theme-bridge.d.ts.map +1 -1
  49. package/lib/theme-bridge.js +3 -23
  50. package/lib/theme-bridge.js.map +1 -1
  51. package/lib/web-note-editor-selection.d.ts.map +1 -1
  52. package/lib/web-note-editor-selection.js +50 -15
  53. package/lib/web-note-editor-selection.js.map +1 -1
  54. package/lib/web-note-editor.d.ts.map +1 -1
  55. package/lib/web-note-editor.js +15 -1
  56. package/lib/web-note-editor.js.map +1 -1
  57. package/lib/web.d.ts.map +1 -1
  58. package/lib/web.js +4 -0
  59. package/lib/web.js.map +1 -1
  60. package/package.json +1 -1
  61. package/web/app.js +78 -176
  62. package/web/design-tokens.css +1 -0
  63. package/web/dialogs.js +94 -0
  64. package/web/document-actions.js +68 -0
  65. package/web/index.html +1 -0
  66. package/web/model-catalog.js +22 -0
  67. package/web/note-editor.js +57 -57
  68. package/web/select-control.js +193 -0
  69. package/web/styles.css +93 -185
  70. package/web/ui-primitives.js +5 -0
@@ -0,0 +1,193 @@
1
+ /** Plugin-owned select chrome. The hidden select retains form/value/event semantics. */
2
+ export function installSelectControls(root = document.body) {
3
+ const controls = new Map()
4
+ let active
5
+ let sequence = 0
6
+ const make = (tag, className, text) => {
7
+ const node = document.createElement(tag)
8
+ node.className = className
9
+ if (text !== undefined) node.textContent = text
10
+ return node
11
+ }
12
+ const close = (restore = false) => {
13
+ if (!active) return
14
+ const { button, menu } = active
15
+ active = undefined
16
+ menu.remove()
17
+ button.setAttribute('aria-expanded', 'false')
18
+ if (restore && button.isConnected) button.focus()
19
+ }
20
+ const enhance = select => {
21
+ if (controls.has(select) || select.multiple || select.size > 1) return
22
+ const wrapper = make('span', 'knowledge-select')
23
+ const originalTabIndex = select.getAttribute('tabindex')
24
+ const originalAriaHidden = select.getAttribute('aria-hidden')
25
+ const button = make('button', `${select.className} knowledge-select-trigger`)
26
+ button.type = 'button'
27
+ button.setAttribute('role', 'combobox')
28
+ button.setAttribute('aria-haspopup', 'listbox')
29
+ button.setAttribute('aria-expanded', 'false')
30
+ const menuId = `knowledge-select-${++sequence}`
31
+ button.setAttribute('aria-controls', menuId)
32
+ select.before(wrapper)
33
+ wrapper.append(select, button)
34
+ select.classList.add('knowledge-select-source')
35
+ select.tabIndex = -1
36
+ select.setAttribute('aria-hidden', 'true')
37
+ const sync = () => {
38
+ button.textContent = select.selectedOptions[0]?.label || '请选择'
39
+ button.disabled = select.disabled
40
+ button.setAttribute('aria-required', String(select.required))
41
+ if (select.validity.valid) button.removeAttribute('aria-invalid')
42
+ const label = select.getAttribute('aria-label') || select.labels?.[0]?.textContent || select.closest('.field, label')?.querySelector('label, span')?.textContent || '选择选项'
43
+ button.setAttribute('aria-label', `${label}:${button.textContent}`)
44
+ if (select.getAttribute('aria-labelledby')) button.setAttribute('aria-labelledby', select.getAttribute('aria-labelledby'))
45
+ button.title = button.textContent
46
+ if (active?.select === select) close()
47
+ }
48
+ const valueDescriptor = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, 'value')
49
+ Object.defineProperty(select, 'value', {
50
+ configurable: true,
51
+ get() { return valueDescriptor.get.call(this) },
52
+ set(value) { valueDescriptor.set.call(this, value); sync() },
53
+ })
54
+ const place = () => {
55
+ if (active?.select !== select) return
56
+ const rect = button.getBoundingClientRect()
57
+ if (!button.isConnected || !rect.width) return close()
58
+ const menu = active.menu
59
+ const width = Math.min(Math.max(rect.width, 180), window.innerWidth - 16)
60
+ const below = window.innerHeight - rect.bottom - 12
61
+ const above = rect.top - 12
62
+ const useAbove = below < 180 && above > below
63
+ menu.style.width = `${width}px`
64
+ menu.style.maxHeight = `${Math.max(60, Math.min(300, useAbove ? above : below))}px`
65
+ menu.style.left = `${Math.max(8, Math.min(rect.left, window.innerWidth - width - 8))}px`
66
+ menu.style.top = useAbove ? 'auto' : `${rect.bottom + 5}px`
67
+ menu.style.bottom = useAbove ? `${window.innerHeight - rect.top + 5}px` : 'auto'
68
+ }
69
+ const open = () => {
70
+ if (select.disabled) return
71
+ if (active?.select === select) return close(true)
72
+ close()
73
+ const menu = make('div', 'knowledge-select-menu')
74
+ menu.id = menuId
75
+ menu.setAttribute('role', 'listbox')
76
+ menu.setAttribute('aria-label', button.getAttribute('aria-label'))
77
+ const items = []
78
+ for (const option of select.options) {
79
+ if (option.hidden) continue
80
+ const item = make('button', 'knowledge-select-option', option.label)
81
+ item.type = 'button'
82
+ item.setAttribute('role', 'option')
83
+ item.setAttribute('aria-selected', String(option.selected))
84
+ item.disabled = option.disabled || option.parentElement?.disabled === true
85
+ item.tabIndex = -1
86
+ item.addEventListener('click', () => {
87
+ select.value = option.value
88
+ select.dispatchEvent(new Event('input', { bubbles: true }))
89
+ select.dispatchEvent(new Event('change', { bubbles: true }))
90
+ close()
91
+ if (button.isConnected) button.focus()
92
+ })
93
+ menu.append(item)
94
+ if (!item.disabled) items.push(item)
95
+ }
96
+ if (!menu.childElementCount) menu.append(make('p', 'knowledge-select-empty', '暂无可选项'))
97
+ active = { select, button, menu, place }
98
+ root.append(menu)
99
+ button.setAttribute('aria-expanded', 'true')
100
+ place()
101
+ const selected = items.find(item => item.getAttribute('aria-selected') === 'true') || items[0]
102
+ selected?.focus({ preventScroll: true })
103
+ if (selected) menu.scrollTop = Math.max(0, selected.offsetTop - menu.clientHeight / 2)
104
+ let search = ''
105
+ let searchTime = 0
106
+ menu.addEventListener('keydown', event => {
107
+ const index = items.indexOf(document.activeElement)
108
+ let next
109
+ if (event.key === 'ArrowDown') next = items[(index + 1) % items.length]
110
+ else if (event.key === 'ArrowUp') next = items[(index - 1 + items.length) % items.length]
111
+ else if (event.key === 'Home') next = items[0]
112
+ else if (event.key === 'End') next = items.at(-1)
113
+ else if (event.key === 'Tab') { close(true); return }
114
+ else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && event.key !== ' ') {
115
+ search = Date.now() - searchTime > 600 ? event.key : search + event.key
116
+ searchTime = Date.now()
117
+ next = items.find(item => item.textContent.toLocaleLowerCase().startsWith(search.toLocaleLowerCase()))
118
+ }
119
+ if (next) { event.preventDefault(); next.focus() }
120
+ })
121
+ }
122
+ button.addEventListener('click', open)
123
+ button.addEventListener('keydown', event => {
124
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { event.preventDefault(); open() }
125
+ })
126
+ select.addEventListener('change', sync)
127
+ const invalid = event => {
128
+ event.preventDefault()
129
+ button.setAttribute('aria-invalid', 'true')
130
+ button.title = select.validationMessage
131
+ button.focus()
132
+ }
133
+ select.addEventListener('invalid', invalid)
134
+ const observer = new MutationObserver(sync)
135
+ observer.observe(select, { attributes: true, childList: true, subtree: true, characterData: true })
136
+ const dispose = () => {
137
+ observer.disconnect()
138
+ select.removeEventListener('change', sync)
139
+ select.removeEventListener('invalid', invalid)
140
+ delete select.value
141
+ select.classList.remove('knowledge-select-source')
142
+ for (const [name, value] of [['tabindex', originalTabIndex], ['aria-hidden', originalAriaHidden]]) {
143
+ if (value === null) select.removeAttribute(name)
144
+ else select.setAttribute(name, value)
145
+ }
146
+ if (select.parentElement === wrapper) wrapper.replaceWith(select)
147
+ }
148
+ controls.set(select, { dispose, sync })
149
+ sync()
150
+ }
151
+ const scan = node => {
152
+ if (!(node instanceof Element)) return
153
+ if (node.matches('select')) enhance(node)
154
+ node.querySelectorAll('select').forEach(enhance)
155
+ }
156
+ const observer = new MutationObserver(records => {
157
+ for (const record of records) for (const node of record.addedNodes) scan(node)
158
+ for (const [select, control] of controls) {
159
+ if (select.isConnected) continue
160
+ control.dispose()
161
+ controls.delete(select)
162
+ if (active?.select === select) close()
163
+ }
164
+ })
165
+ observer.observe(root, { childList: true, subtree: true })
166
+ scan(root)
167
+ const outside = event => {
168
+ if (active && !active.menu.contains(event.target) && !active.button.contains(event.target)) close()
169
+ }
170
+ const escape = event => {
171
+ if (active && event.key === 'Escape') { event.preventDefault(); event.stopImmediatePropagation(); close(true) }
172
+ }
173
+ const reposition = event => { if (active && !active.menu.contains(event.target)) active.place() }
174
+ document.addEventListener('pointerdown', outside, true)
175
+ document.addEventListener('keydown', escape, true)
176
+ window.addEventListener('resize', reposition)
177
+ window.addEventListener('scroll', reposition, true)
178
+ const reset = () => queueMicrotask(() => { for (const value of controls.values()) value.sync() })
179
+ root.addEventListener('reset', reset)
180
+ return () => {
181
+ close()
182
+ observer.disconnect()
183
+ for (const [select, control] of controls) {
184
+ control.dispose()
185
+ }
186
+ controls.clear()
187
+ document.removeEventListener('pointerdown', outside, true)
188
+ document.removeEventListener('keydown', escape, true)
189
+ window.removeEventListener('resize', reposition)
190
+ window.removeEventListener('scroll', reposition, true)
191
+ root.removeEventListener('reset', reset)
192
+ }
193
+ }