@lengduan/dsh-client-ui-skin-815 0.1.21 → 0.1.22

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.
@@ -1,300 +1,300 @@
1
- /**
2
- * 1945 终战皮肤。客户端入口只做展示层:南京受降油画背景、token 覆盖。
3
- * effect 销毁器还原全部 CSS/DOM 写入。
4
- */
5
- import type { Context } from '@deepseek-ai/cordis'
6
- import { SURRENDER_PHOTO_2K, SURRENDER_PHOTO_4K } from './art.generated.ts'
7
- import { MAID_ATELIER_TITLEBAR_BRAND } from './titlebar-brand.ts'
8
- import './vj815.module.css'
9
-
10
- const SKIN_TITLE = '一九四五年八月十五日 · DeepSeek Harness'
11
- const SKIN_OWNER = '815'
12
- const SKIN_SYSTEM_CHROME_COLOR = '#080a06'
13
- const SIDEBAR_COLUMN_SELECTOR = ":is([data-pane='sidebar'], [class*='sidebarCol'])"
14
-
15
- const BACKDROP_PROPERTIES = [
16
- 'background-image',
17
- 'background-position',
18
- 'background-size',
19
- 'background-attachment',
20
- 'background-repeat',
21
- 'background-color',
22
- '--vj-photo',
23
- '--vj-sidebar-width',
24
- '--vj-titlebar-height',
25
- ] as const
26
-
27
- function decorateTitlebarBrand(ownedNodes: Set<Element>): void {
28
- const titlebar = document.querySelector<HTMLElement>("[class*='titlebar']")
29
- if (!titlebar) return
30
- if (titlebar.querySelector("[data-skin-chrome='titlebar-brand']")) return
31
- const brand = document.createElement('span')
32
- brand.dataset.skinChrome = 'titlebar-brand'
33
- brand.dataset.skinOwner = SKIN_OWNER
34
- brand.setAttribute('aria-hidden', 'true')
35
- brand.innerHTML = MAID_ATELIER_TITLEBAR_BRAND
36
- ownedNodes.add(brand)
37
- titlebar.prepend(brand)
38
- }
39
-
40
- /**
41
- * 从女仆皮 copy:给侧栏 treeitem 打会话标记,选中行才能挂青天白日小旗。
42
- * @param decoratedElements - 卸载时清除 data-* 的节点集合
43
- */
44
- function decorateWorkspaceTree(decoratedElements: Set<HTMLElement>): void {
45
- const sidebar = document.querySelector<HTMLElement>(SIDEBAR_COLUMN_SELECTOR)
46
- if (!sidebar) return
47
-
48
- sidebar.querySelectorAll<HTMLElement>(
49
- '[data-vj-session], [data-vj-session-flat]',
50
- ).forEach((element) => {
51
- delete element.dataset.vjSession
52
- delete element.dataset.vjSessionFlat
53
- })
54
-
55
- sidebar.querySelectorAll<HTMLElement>("[role='tree']").forEach((tree) => {
56
- const rows = [...tree.querySelectorAll<HTMLElement>("[role='treeitem']")]
57
- if (tree.matches("[class*='flatList']") && !rows.some(row => row.hasAttribute('aria-expanded'))) {
58
- rows.filter(row => row.hasAttribute('aria-selected')).forEach((sessionRow) => {
59
- sessionRow.dataset.vjSession = ''
60
- sessionRow.dataset.vjSessionFlat = ''
61
- decoratedElements.add(sessionRow)
62
- })
63
- return
64
- }
65
-
66
- let workspaceRow: HTMLElement | undefined
67
- let sessionRows: HTMLElement[] = []
68
- const decorateGroup = (): void => {
69
- if (!workspaceRow) return
70
- sessionRows.forEach((sessionRow) => {
71
- sessionRow.dataset.vjSession = ''
72
- decoratedElements.add(sessionRow)
73
- })
74
- }
75
-
76
- rows.forEach((row) => {
77
- if (row.hasAttribute('aria-expanded')) {
78
- decorateGroup()
79
- workspaceRow = row
80
- sessionRows = []
81
- } else if (workspaceRow && row.hasAttribute('aria-selected')) {
82
- sessionRows.push(row)
83
- }
84
- })
85
- decorateGroup()
86
- })
87
- }
88
-
89
- /**
90
- * 写入皮肤背景,并由 effect 在卸载时整份撤回。
91
- * @param ctx - 拥有效果生命周期的 Cordis 上下文
92
- */
93
- export function apply(ctx: Context): void {
94
- const body = document.body
95
- const originalTitle = document.title
96
- const previous = new Map<string, string>()
97
- for (const property of BACKDROP_PROPERTIES) {
98
- previous.set(property, body.style.getPropertyValue(property))
99
- }
100
-
101
- const ownedNodes = new Set<Element>()
102
- const decoratedElements = new Set<HTMLElement>()
103
- let themeColorMeta: HTMLMetaElement | null = null
104
- let previousThemeColor: string | undefined
105
- let themeColorObserver: MutationObserver | undefined
106
- let observer: MutationObserver | undefined
107
- let titlebarOverlay: WindowControlsOverlay | undefined
108
- let syncTitlebarHeight: (() => void) | undefined
109
- let observedSidebar: HTMLElement | undefined
110
- let resizeObserver: ResizeObserver | undefined
111
- let syncBackdrop: (() => void) | undefined
112
-
113
- ctx.effect(() => () => {
114
- body.removeAttribute('data-dsh-815')
115
- observer?.disconnect()
116
- themeColorObserver?.disconnect()
117
- resizeObserver?.disconnect()
118
- if (syncBackdrop !== undefined) {
119
- window.removeEventListener('resize', syncBackdrop)
120
- }
121
- if (titlebarOverlay !== undefined && syncTitlebarHeight !== undefined) {
122
- titlebarOverlay.removeEventListener('geometrychange', syncTitlebarHeight)
123
- }
124
- for (const [property, value] of previous) {
125
- body.style.setProperty(property, value)
126
- }
127
- ownedNodes.forEach(element => element.remove())
128
- decoratedElements.forEach((element) => {
129
- delete element.dataset.vjSession
130
- delete element.dataset.vjSessionFlat
131
- })
132
- if (themeColorMeta?.isConnected && themeColorMeta.content === SKIN_SYSTEM_CHROME_COLOR) {
133
- themeColorMeta.content = previousThemeColor ?? ''
134
- }
135
- if (document.title === SKIN_TITLE) document.title = originalTitle
136
- }, 'ui-skin-815: surrender photograph backdrop')
137
-
138
- const syncSystemChrome = (): void => {
139
- const meta = document.head.querySelector<HTMLMetaElement>('meta[name="theme-color"]')
140
- if (meta === null) return
141
- if (meta !== themeColorMeta) {
142
- themeColorMeta = meta
143
- previousThemeColor = meta.content
144
- }
145
- if (meta.content !== SKIN_SYSTEM_CHROME_COLOR) meta.content = SKIN_SYSTEM_CHROME_COLOR
146
- }
147
- themeColorObserver = new MutationObserver(syncSystemChrome)
148
- themeColorObserver.observe(document.head, {
149
- attributes: true,
150
- attributeFilter: ['content'],
151
- childList: true,
152
- subtree: true,
153
- })
154
- syncSystemChrome()
155
-
156
- body.setAttribute('data-dsh-815', '')
157
- body.style.setProperty('background-position', 'center 42%')
158
- body.style.setProperty('background-size', 'cover')
159
- body.style.setProperty('background-attachment', 'fixed')
160
- body.style.setProperty('background-repeat', 'no-repeat')
161
- body.style.setProperty('background-color', '#080a06')
162
-
163
- /** 设备像素宽 >= 2560 用 4K,否则 2K。 */
164
- syncBackdrop = (): void => {
165
- const deviceWidth = window.innerWidth * (window.devicePixelRatio || 1)
166
- const photo = deviceWidth >= 2560 ? SURRENDER_PHOTO_4K : SURRENDER_PHOTO_2K
167
- const url = `url(${photo})`
168
- body.style.setProperty('--vj-photo', url)
169
- body.style.setProperty('background-image', url)
170
- }
171
- window.addEventListener('resize', syncBackdrop)
172
- syncBackdrop()
173
-
174
- const widthSheet = document.createElement('style')
175
- widthSheet.dataset.skinChrome = 'sidebar-width-rule'
176
- widthSheet.dataset.skinOwner = SKIN_OWNER
177
- ownedNodes.add(widthSheet)
178
- document.head.append(widthSheet)
179
- widthSheet.sheet!.insertRule('html, body { background-color: #080a06; --vj-sidebar-width: 280px; --vj-titlebar-height: 0px; }')
180
- const appendRule = (rule: string): void => {
181
- widthSheet.sheet!.insertRule(rule, widthSheet.sheet!.cssRules.length)
182
- }
183
- appendRule('body[data-dsh-815] [class*="frame"][data-wco] { grid-template-rows: env(titlebar-area-height, 40px) 1fr; }')
184
- appendRule('body[data-dsh-815] [class*="frame"][data-desktop] { grid-template-rows: 32px 1fr; }')
185
- const widthRule = widthSheet.sheet!.cssRules[0] as CSSStyleRule
186
-
187
- syncTitlebarHeight = (): void => {
188
- const columns = document.querySelector<HTMLElement>(SIDEBAR_COLUMN_SELECTOR)
189
- if (columns !== null) {
190
- const top = columns.getBoundingClientRect().top
191
- if (top > 0) {
192
- widthRule.style.setProperty('--vj-titlebar-height', `${top}px`)
193
- return
194
- }
195
- }
196
- if (document.querySelector("[class*='frame'][data-desktop]") !== null) {
197
- widthRule.style.setProperty('--vj-titlebar-height', '32px')
198
- return
199
- }
200
- widthRule.style.setProperty('--vj-titlebar-height', '0px')
201
- }
202
- titlebarOverlay = navigator.windowControlsOverlay
203
- titlebarOverlay?.addEventListener('geometrychange', syncTitlebarHeight)
204
- syncTitlebarHeight()
205
-
206
- const applySidebarWidth = (width: number): void => {
207
- if (width <= 0) return
208
- widthRule.style.setProperty('--vj-sidebar-width', `${Math.round(width * 100) / 100}px`)
209
- }
210
-
211
- if (typeof ResizeObserver !== 'undefined') {
212
- resizeObserver = new ResizeObserver((entries) => {
213
- const entry = entries.at(-1)
214
- if (!entry) return
215
- applySidebarWidth(entry.contentRect.width)
216
- })
217
- }
218
-
219
- const ensureSidebarObserved = (): void => {
220
- const sidebar = document.querySelector<HTMLElement>(SIDEBAR_COLUMN_SELECTOR)
221
- if (!resizeObserver || sidebar === observedSidebar) return
222
- if (!sidebar) {
223
- if (observedSidebar) resizeObserver.unobserve(observedSidebar)
224
- observedSidebar = undefined
225
- return
226
- }
227
- if (observedSidebar) resizeObserver.unobserve(observedSidebar)
228
- observedSidebar = sidebar
229
- resizeObserver.observe(sidebar)
230
- }
231
-
232
- decorateTitlebarBrand(ownedNodes)
233
- decorateWorkspaceTree(decoratedElements)
234
- ensureSidebarObserved()
235
- const initialSidebar = document.querySelector<HTMLElement>(SIDEBAR_COLUMN_SELECTOR)
236
- if (initialSidebar) applySidebarWidth(initialSidebar.getBoundingClientRect().width)
237
-
238
- observer = new MutationObserver((records) => {
239
- let sidebarStructureChanged = false
240
- let workspaceStateChanged = false
241
- for (const record of records) {
242
- if (record.type === 'attributes') {
243
- const target = record.target instanceof Element ? record.target : undefined
244
- if ((record.attributeName === 'aria-expanded' || record.attributeName === 'aria-selected')
245
- && target !== undefined && target.closest(SIDEBAR_COLUMN_SELECTOR) !== null) {
246
- workspaceStateChanged = true
247
- }
248
- continue
249
- }
250
- const appNodes = [...record.addedNodes].filter(node => node instanceof Element)
251
- if (appNodes.some(node =>
252
- node instanceof Element
253
- && (node.matches(SIDEBAR_COLUMN_SELECTOR)
254
- || node.querySelector(SIDEBAR_COLUMN_SELECTOR) !== null
255
- || node.matches("[class*='titlebar']")
256
- || node.querySelector("[class*='titlebar']") !== null))) {
257
- sidebarStructureChanged = true
258
- }
259
- }
260
- if (sidebarStructureChanged) {
261
- decorateTitlebarBrand(ownedNodes)
262
- decorateWorkspaceTree(decoratedElements)
263
- ensureSidebarObserved()
264
- syncTitlebarHeight?.()
265
- } else if (workspaceStateChanged) {
266
- decorateWorkspaceTree(decoratedElements)
267
- }
268
- })
269
- observer.observe(body, {
270
- childList: true,
271
- subtree: true,
272
- attributes: true,
273
- attributeFilter: ['aria-expanded', 'aria-selected'],
274
- })
275
-
276
- const caption = document.createElement('div')
277
- caption.dataset.skinChrome = 'caption'
278
- caption.dataset.skinOwner = SKIN_OWNER
279
- /* 展签两行:主行画名,副行时间地点事件。 */
280
- const captionTitle = document.createElement('span')
281
- captionTitle.dataset.skinChrome = 'caption-title'
282
- captionTitle.textContent = '陈坚《公元一九四五年九月九日九时》'
283
- const captionSub = document.createElement('span')
284
- captionSub.dataset.skinChrome = 'caption-sub'
285
- captionSub.textContent = '1945.9.9 九时 · 南京 中国战区日军投降签字'
286
- caption.append(captionTitle, captionSub)
287
- ownedNodes.add(caption)
288
- body.append(caption)
289
-
290
- const favicon = document.createElement('link')
291
- favicon.rel = 'icon'
292
- favicon.type = 'image/jpeg'
293
- favicon.href = SURRENDER_PHOTO_2K
294
- favicon.dataset.skinChrome = 'favicon'
295
- favicon.dataset.skinOwner = SKIN_OWNER
296
- ownedNodes.add(favicon)
297
- document.head.append(favicon)
298
-
299
- document.title = SKIN_TITLE
300
- }
1
+ /**
2
+ * 1945 终战皮肤。客户端入口只做展示层:南京受降油画背景、token 覆盖。
3
+ * effect 销毁器还原全部 CSS/DOM 写入。
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis'
6
+ import { SURRENDER_PHOTO_2K, SURRENDER_PHOTO_4K } from './art.generated.ts'
7
+ import { MAID_ATELIER_TITLEBAR_BRAND } from './titlebar-brand.ts'
8
+ import './vj815.module.css'
9
+
10
+ const SKIN_TITLE = '一九四五年八月十五日 · DeepSeek Harness'
11
+ const SKIN_OWNER = '815'
12
+ const SKIN_SYSTEM_CHROME_COLOR = '#080a06'
13
+ const SIDEBAR_COLUMN_SELECTOR = ":is([data-pane='sidebar'], [class*='sidebarCol'])"
14
+
15
+ const BACKDROP_PROPERTIES = [
16
+ 'background-image',
17
+ 'background-position',
18
+ 'background-size',
19
+ 'background-attachment',
20
+ 'background-repeat',
21
+ 'background-color',
22
+ '--vj-photo',
23
+ '--vj-sidebar-width',
24
+ '--vj-titlebar-height',
25
+ ] as const
26
+
27
+ function decorateTitlebarBrand(ownedNodes: Set<Element>): void {
28
+ const titlebar = document.querySelector<HTMLElement>("[class*='titlebar']")
29
+ if (!titlebar) return
30
+ if (titlebar.querySelector("[data-skin-chrome='titlebar-brand']")) return
31
+ const brand = document.createElement('span')
32
+ brand.dataset.skinChrome = 'titlebar-brand'
33
+ brand.dataset.skinOwner = SKIN_OWNER
34
+ brand.setAttribute('aria-hidden', 'true')
35
+ brand.innerHTML = MAID_ATELIER_TITLEBAR_BRAND
36
+ ownedNodes.add(brand)
37
+ titlebar.prepend(brand)
38
+ }
39
+
40
+ /**
41
+ * 从女仆皮 copy:给侧栏 treeitem 打会话标记,选中行才能挂青天白日小旗。
42
+ * @param decoratedElements - 卸载时清除 data-* 的节点集合
43
+ */
44
+ function decorateWorkspaceTree(decoratedElements: Set<HTMLElement>): void {
45
+ const sidebar = document.querySelector<HTMLElement>(SIDEBAR_COLUMN_SELECTOR)
46
+ if (!sidebar) return
47
+
48
+ sidebar.querySelectorAll<HTMLElement>(
49
+ '[data-vj-session], [data-vj-session-flat]',
50
+ ).forEach((element) => {
51
+ delete element.dataset.vjSession
52
+ delete element.dataset.vjSessionFlat
53
+ })
54
+
55
+ sidebar.querySelectorAll<HTMLElement>("[role='tree']").forEach((tree) => {
56
+ const rows = [...tree.querySelectorAll<HTMLElement>("[role='treeitem']")]
57
+ if (tree.matches("[class*='flatList']") && !rows.some(row => row.hasAttribute('aria-expanded'))) {
58
+ rows.filter(row => row.hasAttribute('aria-selected')).forEach((sessionRow) => {
59
+ sessionRow.dataset.vjSession = ''
60
+ sessionRow.dataset.vjSessionFlat = ''
61
+ decoratedElements.add(sessionRow)
62
+ })
63
+ return
64
+ }
65
+
66
+ let workspaceRow: HTMLElement | undefined
67
+ let sessionRows: HTMLElement[] = []
68
+ const decorateGroup = (): void => {
69
+ if (!workspaceRow) return
70
+ sessionRows.forEach((sessionRow) => {
71
+ sessionRow.dataset.vjSession = ''
72
+ decoratedElements.add(sessionRow)
73
+ })
74
+ }
75
+
76
+ rows.forEach((row) => {
77
+ if (row.hasAttribute('aria-expanded')) {
78
+ decorateGroup()
79
+ workspaceRow = row
80
+ sessionRows = []
81
+ } else if (workspaceRow && row.hasAttribute('aria-selected')) {
82
+ sessionRows.push(row)
83
+ }
84
+ })
85
+ decorateGroup()
86
+ })
87
+ }
88
+
89
+ /**
90
+ * 写入皮肤背景,并由 effect 在卸载时整份撤回。
91
+ * @param ctx - 拥有效果生命周期的 Cordis 上下文
92
+ */
93
+ export function apply(ctx: Context): void {
94
+ const body = document.body
95
+ const originalTitle = document.title
96
+ const previous = new Map<string, string>()
97
+ for (const property of BACKDROP_PROPERTIES) {
98
+ previous.set(property, body.style.getPropertyValue(property))
99
+ }
100
+
101
+ const ownedNodes = new Set<Element>()
102
+ const decoratedElements = new Set<HTMLElement>()
103
+ let themeColorMeta: HTMLMetaElement | null = null
104
+ let previousThemeColor: string | undefined
105
+ let themeColorObserver: MutationObserver | undefined
106
+ let observer: MutationObserver | undefined
107
+ let titlebarOverlay: WindowControlsOverlay | undefined
108
+ let syncTitlebarHeight: (() => void) | undefined
109
+ let observedSidebar: HTMLElement | undefined
110
+ let resizeObserver: ResizeObserver | undefined
111
+ let syncBackdrop: (() => void) | undefined
112
+
113
+ ctx.effect(() => () => {
114
+ body.removeAttribute('data-dsh-815')
115
+ observer?.disconnect()
116
+ themeColorObserver?.disconnect()
117
+ resizeObserver?.disconnect()
118
+ if (syncBackdrop !== undefined) {
119
+ window.removeEventListener('resize', syncBackdrop)
120
+ }
121
+ if (titlebarOverlay !== undefined && syncTitlebarHeight !== undefined) {
122
+ titlebarOverlay.removeEventListener('geometrychange', syncTitlebarHeight)
123
+ }
124
+ for (const [property, value] of previous) {
125
+ body.style.setProperty(property, value)
126
+ }
127
+ ownedNodes.forEach(element => element.remove())
128
+ decoratedElements.forEach((element) => {
129
+ delete element.dataset.vjSession
130
+ delete element.dataset.vjSessionFlat
131
+ })
132
+ if (themeColorMeta?.isConnected && themeColorMeta.content === SKIN_SYSTEM_CHROME_COLOR) {
133
+ themeColorMeta.content = previousThemeColor ?? ''
134
+ }
135
+ if (document.title === SKIN_TITLE) document.title = originalTitle
136
+ }, 'ui-skin-815: surrender photograph backdrop')
137
+
138
+ const syncSystemChrome = (): void => {
139
+ const meta = document.head.querySelector<HTMLMetaElement>('meta[name="theme-color"]')
140
+ if (meta === null) return
141
+ if (meta !== themeColorMeta) {
142
+ themeColorMeta = meta
143
+ previousThemeColor = meta.content
144
+ }
145
+ if (meta.content !== SKIN_SYSTEM_CHROME_COLOR) meta.content = SKIN_SYSTEM_CHROME_COLOR
146
+ }
147
+ themeColorObserver = new MutationObserver(syncSystemChrome)
148
+ themeColorObserver.observe(document.head, {
149
+ attributes: true,
150
+ attributeFilter: ['content'],
151
+ childList: true,
152
+ subtree: true,
153
+ })
154
+ syncSystemChrome()
155
+
156
+ body.setAttribute('data-dsh-815', '')
157
+ body.style.setProperty('background-position', 'center 42%')
158
+ body.style.setProperty('background-size', 'cover')
159
+ body.style.setProperty('background-attachment', 'fixed')
160
+ body.style.setProperty('background-repeat', 'no-repeat')
161
+ body.style.setProperty('background-color', '#080a06')
162
+
163
+ /** 设备像素宽 >= 2560 用 4K,否则 2K。 */
164
+ syncBackdrop = (): void => {
165
+ const deviceWidth = window.innerWidth * (window.devicePixelRatio || 1)
166
+ const photo = deviceWidth >= 2560 ? SURRENDER_PHOTO_4K : SURRENDER_PHOTO_2K
167
+ const url = `url(${photo})`
168
+ body.style.setProperty('--vj-photo', url)
169
+ body.style.setProperty('background-image', url)
170
+ }
171
+ window.addEventListener('resize', syncBackdrop)
172
+ syncBackdrop()
173
+
174
+ const widthSheet = document.createElement('style')
175
+ widthSheet.dataset.skinChrome = 'sidebar-width-rule'
176
+ widthSheet.dataset.skinOwner = SKIN_OWNER
177
+ ownedNodes.add(widthSheet)
178
+ document.head.append(widthSheet)
179
+ widthSheet.sheet!.insertRule('html, body { background-color: #080a06; --vj-sidebar-width: 280px; --vj-titlebar-height: 0px; }')
180
+ const appendRule = (rule: string): void => {
181
+ widthSheet.sheet!.insertRule(rule, widthSheet.sheet!.cssRules.length)
182
+ }
183
+ appendRule('body[data-dsh-815] [class*="frame"][data-wco] { grid-template-rows: env(titlebar-area-height, 40px) 1fr; }')
184
+ appendRule('body[data-dsh-815] [class*="frame"][data-desktop] { grid-template-rows: 32px 1fr; }')
185
+ const widthRule = widthSheet.sheet!.cssRules[0] as CSSStyleRule
186
+
187
+ syncTitlebarHeight = (): void => {
188
+ const columns = document.querySelector<HTMLElement>(SIDEBAR_COLUMN_SELECTOR)
189
+ if (columns !== null) {
190
+ const top = columns.getBoundingClientRect().top
191
+ if (top > 0) {
192
+ widthRule.style.setProperty('--vj-titlebar-height', `${top}px`)
193
+ return
194
+ }
195
+ }
196
+ if (document.querySelector("[class*='frame'][data-desktop]") !== null) {
197
+ widthRule.style.setProperty('--vj-titlebar-height', '32px')
198
+ return
199
+ }
200
+ widthRule.style.setProperty('--vj-titlebar-height', '0px')
201
+ }
202
+ titlebarOverlay = navigator.windowControlsOverlay
203
+ titlebarOverlay?.addEventListener('geometrychange', syncTitlebarHeight)
204
+ syncTitlebarHeight()
205
+
206
+ const applySidebarWidth = (width: number): void => {
207
+ if (width <= 0) return
208
+ widthRule.style.setProperty('--vj-sidebar-width', `${Math.round(width * 100) / 100}px`)
209
+ }
210
+
211
+ if (typeof ResizeObserver !== 'undefined') {
212
+ resizeObserver = new ResizeObserver((entries) => {
213
+ const entry = entries.at(-1)
214
+ if (!entry) return
215
+ applySidebarWidth(entry.contentRect.width)
216
+ })
217
+ }
218
+
219
+ const ensureSidebarObserved = (): void => {
220
+ const sidebar = document.querySelector<HTMLElement>(SIDEBAR_COLUMN_SELECTOR)
221
+ if (!resizeObserver || sidebar === observedSidebar) return
222
+ if (!sidebar) {
223
+ if (observedSidebar) resizeObserver.unobserve(observedSidebar)
224
+ observedSidebar = undefined
225
+ return
226
+ }
227
+ if (observedSidebar) resizeObserver.unobserve(observedSidebar)
228
+ observedSidebar = sidebar
229
+ resizeObserver.observe(sidebar)
230
+ }
231
+
232
+ decorateTitlebarBrand(ownedNodes)
233
+ decorateWorkspaceTree(decoratedElements)
234
+ ensureSidebarObserved()
235
+ const initialSidebar = document.querySelector<HTMLElement>(SIDEBAR_COLUMN_SELECTOR)
236
+ if (initialSidebar) applySidebarWidth(initialSidebar.getBoundingClientRect().width)
237
+
238
+ observer = new MutationObserver((records) => {
239
+ let sidebarStructureChanged = false
240
+ let workspaceStateChanged = false
241
+ for (const record of records) {
242
+ if (record.type === 'attributes') {
243
+ const target = record.target instanceof Element ? record.target : undefined
244
+ if ((record.attributeName === 'aria-expanded' || record.attributeName === 'aria-selected')
245
+ && target !== undefined && target.closest(SIDEBAR_COLUMN_SELECTOR) !== null) {
246
+ workspaceStateChanged = true
247
+ }
248
+ continue
249
+ }
250
+ const appNodes = [...record.addedNodes].filter(node => node instanceof Element)
251
+ if (appNodes.some(node =>
252
+ node instanceof Element
253
+ && (node.matches(SIDEBAR_COLUMN_SELECTOR)
254
+ || node.querySelector(SIDEBAR_COLUMN_SELECTOR) !== null
255
+ || node.matches("[class*='titlebar']")
256
+ || node.querySelector("[class*='titlebar']") !== null))) {
257
+ sidebarStructureChanged = true
258
+ }
259
+ }
260
+ if (sidebarStructureChanged) {
261
+ decorateTitlebarBrand(ownedNodes)
262
+ decorateWorkspaceTree(decoratedElements)
263
+ ensureSidebarObserved()
264
+ syncTitlebarHeight?.()
265
+ } else if (workspaceStateChanged) {
266
+ decorateWorkspaceTree(decoratedElements)
267
+ }
268
+ })
269
+ observer.observe(body, {
270
+ childList: true,
271
+ subtree: true,
272
+ attributes: true,
273
+ attributeFilter: ['aria-expanded', 'aria-selected'],
274
+ })
275
+
276
+ const caption = document.createElement('div')
277
+ caption.dataset.skinChrome = 'caption'
278
+ caption.dataset.skinOwner = SKIN_OWNER
279
+ /* 展签两行:主行画名,副行时间地点事件。 */
280
+ const captionTitle = document.createElement('span')
281
+ captionTitle.dataset.skinChrome = 'caption-title'
282
+ captionTitle.textContent = '陈坚《公元一九四五年九月九日九时》'
283
+ const captionSub = document.createElement('span')
284
+ captionSub.dataset.skinChrome = 'caption-sub'
285
+ captionSub.textContent = '1945.9.9 九时 · 南京 中国战区日军投降签字'
286
+ caption.append(captionTitle, captionSub)
287
+ ownedNodes.add(caption)
288
+ body.append(caption)
289
+
290
+ const favicon = document.createElement('link')
291
+ favicon.rel = 'icon'
292
+ favicon.type = 'image/jpeg'
293
+ favicon.href = SURRENDER_PHOTO_2K
294
+ favicon.dataset.skinChrome = 'favicon'
295
+ favicon.dataset.skinOwner = SKIN_OWNER
296
+ ownedNodes.add(favicon)
297
+ document.head.append(favicon)
298
+
299
+ document.title = SKIN_TITLE
300
+ }