dsh-composer-glass 0.1.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/LICENSE +21 -0
- package/README.en.md +57 -0
- package/README.md +53 -0
- package/cordis.patch.yml +12 -0
- package/images/01-start-session.png +0 -0
- package/images/02-in-conversation.png +0 -0
- package/images/03-settings.png +0 -0
- package/lib/client.js +292 -0
- package/lib/index.js +35 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-composer-glass contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# dsh-composer-glass
|
|
4
|
+
|
|
5
|
+
**Turns the DSH composer into a single uniformly translucent frosted-glass pane**
|
|
6
|
+
|
|
7
|
+
Toggle lives in Settings → General
|
|
8
|
+
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](#install)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
***
|
|
15
|
+
|
|
16
|
+
## Preview
|
|
17
|
+
|
|
18
|
+
| Start a session | In conversation |
|
|
19
|
+
| :---------------------------------------------: | :-----------------------------------------------: |
|
|
20
|
+
|  |  |
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- The composer is rendered as **one uniformly translucent frosted-glass material**, not partial blur
|
|
25
|
+
- A single on/off switch under **Settings → General**
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
| Action | Command |
|
|
32
|
+
| :-------- | :------------------------------------------------------- |
|
|
33
|
+
| Install | `dsh plugin --profile web add dsh-composer-glass` |
|
|
34
|
+
| Upgrade | `dsh plugin --profile web add dsh-composer-glass@latest` |
|
|
35
|
+
| Uninstall | `dsh plugin --profile web remove dsh-composer-glass` |
|
|
36
|
+
|
|
37
|
+
Restart DSH, then enable it under **Settings → General**.
|
|
38
|
+
|
|
39
|
+
> \[!NOTE]
|
|
40
|
+
> On Windows, if you clean up leftovers by hand, just delete
|
|
41
|
+
> `node_modules\dsh-composer-glass`.
|
|
42
|
+
|
|
43
|
+
## Notes
|
|
44
|
+
|
|
45
|
+
- **The switch does not persist**: it resets on page reload — persisting it
|
|
46
|
+
needs a Host-side settings namespace, which is left as a follow-up change.
|
|
47
|
+
- **A material, not a refractor**: the composer sits over flat colour, so
|
|
48
|
+
refracting it produces no visible change. The reasoning and the measurements
|
|
49
|
+
are in [docs/WHY-NOT-REFRACTION.md](docs/WHY-NOT-REFRACTION.md).
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
<div align="center">
|
|
54
|
+
|
|
55
|
+
[MIT](LICENSE)
|
|
56
|
+
|
|
57
|
+
</div>
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# dsh-composer-glass
|
|
4
|
+
|
|
5
|
+
**把 DSH 的输入框变成一整块均匀半透明的毛玻璃面板**
|
|
6
|
+
|
|
7
|
+
开关位于 设置 → 通用
|
|
8
|
+
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](#安装)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
***
|
|
15
|
+
|
|
16
|
+
## 效果预览
|
|
17
|
+
|
|
18
|
+
| 开始会话 | 会话效果 |
|
|
19
|
+
| :----------------------------------: | :------------------------------------: |
|
|
20
|
+
|  |  |
|
|
21
|
+
|
|
22
|
+
## 特性
|
|
23
|
+
|
|
24
|
+
- 输入框整体呈现**均匀半透明的毛玻璃材质**,而非局部磨砂
|
|
25
|
+
- 在 **设置 → 通用** 中提供一键开关
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
## 快速开始
|
|
30
|
+
|
|
31
|
+
| 操作 | 命令 |
|
|
32
|
+
| :- | :------------------------------------------------------- |
|
|
33
|
+
| 安装 | `dsh plugin --profile web add dsh-composer-glass` |
|
|
34
|
+
| 升级 | `dsh plugin --profile web add dsh-composer-glass@latest` |
|
|
35
|
+
| 卸载 | `dsh plugin --profile web remove dsh-composer-glass` |
|
|
36
|
+
|
|
37
|
+
安装完成后重启 DSH,在 **设置 → 通用** 里打开开关。
|
|
38
|
+
|
|
39
|
+
> \[!NOTE]
|
|
40
|
+
> Windows 上如果手动清理残留目录,删掉 `node_modules\dsh-composer-glass` 即可。
|
|
41
|
+
|
|
42
|
+
## 说明
|
|
43
|
+
|
|
44
|
+
- **开关不持久化**:开关状态刷新页面后会重置 —— 持久化需要 Host 侧 settings 命名空间,留作后续改动。
|
|
45
|
+
- **材质,而非折射**:输入框背后是平坦纯色,折射它不会有任何可见变化。原因与实测记录见 [docs/WHY-NOT-REFRACTION.md](docs/WHY-NOT-REFRACTION.md)。
|
|
46
|
+
|
|
47
|
+
## 许可
|
|
48
|
+
|
|
49
|
+
<div align="center">
|
|
50
|
+
|
|
51
|
+
[MIT](LICENSE)
|
|
52
|
+
|
|
53
|
+
</div>
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# dsh-composer-glass — bundle patch
|
|
2
|
+
#
|
|
3
|
+
# Declared through package.json:
|
|
4
|
+
# "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
|
|
5
|
+
#
|
|
6
|
+
# Loading this package as a profile layer therefore registers its plugin row
|
|
7
|
+
# automatically. Do not also hand-write an `- insert:` row for this package in a
|
|
8
|
+
# profile's own cordis.patch.yml — the id would be inserted twice.
|
|
9
|
+
|
|
10
|
+
- insert:
|
|
11
|
+
- id: composer-glass
|
|
12
|
+
name: 'dsh-composer-glass'
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-composer-glass — browser half.
|
|
3
|
+
*
|
|
4
|
+
* Bundle format: `window.__ModuleLoader__.load({ id, factory(require) })`. The
|
|
5
|
+
* shell supplies the CJS shim and pre-registered modules (react, ...), so this
|
|
6
|
+
* file needs no bundler and no build step.
|
|
7
|
+
*
|
|
8
|
+
* WHAT IT DOES
|
|
9
|
+
* Turns the composer card into a single uniformly translucent pane. It is a
|
|
10
|
+
* MATERIAL, not a refractor: measured behaviour of the target engine made real
|
|
11
|
+
* backdrop refraction unworkable here, and the reasoning is recorded in the
|
|
12
|
+
* repository notes rather than silently dropped.
|
|
13
|
+
*
|
|
14
|
+
* The one control is a preference row under Settings -> General. That slot's
|
|
15
|
+
* runtime contract states the owner passes NO props, so the row draws its own
|
|
16
|
+
* label, value and write path — the internals below mirror DSH's own General
|
|
17
|
+
* rows (see `EnterBehaviorRow` in dsh-client-ui-conversation) field for field.
|
|
18
|
+
*
|
|
19
|
+
* Assets: none. No images, no fonts, no network. The whole effect is CSS.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
window.__ModuleLoader__.load({
|
|
23
|
+
id: 'dsh-composer-glass',
|
|
24
|
+
factory: (require) => {
|
|
25
|
+
var module = { exports: {} }
|
|
26
|
+
var exports = module.exports
|
|
27
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
28
|
+
const React = require('react')
|
|
29
|
+
|
|
30
|
+
/** Root attribute whose value ("on" / "off") gates every style rule. */
|
|
31
|
+
const ROOT_ATTR = 'data-dsh-glass'
|
|
32
|
+
/** Class-name prefix, so this plugin's rules can never collide. */
|
|
33
|
+
const PREFIX = 'dsh-glass'
|
|
34
|
+
/** Locale namespace owned by this plugin. */
|
|
35
|
+
const NS = 'dsh-composer-glass'
|
|
36
|
+
/** Cell key for the Settings -> General row. */
|
|
37
|
+
const ROW_ID = 'dsh-glass-pane'
|
|
38
|
+
/** Row order: after the shipped rows (… transcript-view 12, composer-enter 20). */
|
|
39
|
+
const ROW_ORDER = 30
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The tuned material. These are the deliverable: frozen constants rather
|
|
43
|
+
* than runtime variables, so the look cannot drift on its own.
|
|
44
|
+
*/
|
|
45
|
+
const MATERIAL = {
|
|
46
|
+
blur: 10,
|
|
47
|
+
saturate: 165,
|
|
48
|
+
brightness: 0.91,
|
|
49
|
+
tint: 0.17,
|
|
50
|
+
radius: 22,
|
|
51
|
+
ring: 0.34
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------- locale
|
|
55
|
+
|
|
56
|
+
/** Bilingual dictionaries. Every shipped locale id must be present. */
|
|
57
|
+
const MESSAGES = {
|
|
58
|
+
zh: {
|
|
59
|
+
'row.title': '输入框毛玻璃',
|
|
60
|
+
'row.desc': '让输入框变成整块均匀半透明的毛玻璃面板',
|
|
61
|
+
'opt.on': '开启',
|
|
62
|
+
'opt.off': '关闭'
|
|
63
|
+
},
|
|
64
|
+
en: {
|
|
65
|
+
'row.title': 'Composer frosted glass',
|
|
66
|
+
'row.desc': 'Turn the composer into a uniformly translucent frosted-glass pane',
|
|
67
|
+
'opt.on': 'On',
|
|
68
|
+
'opt.off': 'Off'
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ----------------------------------------------------------------- style
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Three preconditions make the pane read as glass. All three are required,
|
|
76
|
+
* and each was learned by breaking it:
|
|
77
|
+
*
|
|
78
|
+
* 1. Clear the opaque chrome. The shipped rule is
|
|
79
|
+
* .wSkVaW_root{background:var(--dsw-alias-bg-base)}
|
|
80
|
+
* A `background` SHORTHAND also sets a background-image layer, so
|
|
81
|
+
* overriding background-color alone leaves that layer behind. Hence
|
|
82
|
+
* `background: none`.
|
|
83
|
+
* 2. Remove the composer seat's downward fade,
|
|
84
|
+
* linear-gradient(180deg, transparent 0px, <base> 36px)
|
|
85
|
+
* which reads transparent -> SOLID and so makes the LOWER part opaque.
|
|
86
|
+
* It is the direct cause of "only the top of the card looks transparent".
|
|
87
|
+
* 3. The pane's own low alpha. `tint` is the pane background-color's alpha
|
|
88
|
+
* and is the pane's own property; it does not depend on what sits behind.
|
|
89
|
+
*
|
|
90
|
+
* FRAGILITY: the `.wSkVaW_*` selectors are build-hashed class names read out
|
|
91
|
+
* of the shipped bundle. They hold for the version this was written against
|
|
92
|
+
* and can change on a DSH upgrade. If the effect silently reverts, check
|
|
93
|
+
* these first. `[data-composer-card]` is a stable data attribute and is
|
|
94
|
+
* preferred wherever one is available.
|
|
95
|
+
*/
|
|
96
|
+
const CSS = [
|
|
97
|
+
'.wSkVaW_root, .wSkVaW_scrollBody, .wSkVaW_body, .wSkVaW_viewArea { background: none !important; }',
|
|
98
|
+
'.wSkVaW_composerSeat { background: none !important; }',
|
|
99
|
+
|
|
100
|
+
':root {',
|
|
101
|
+
' --dsh-glass-ring: rgba(255,255,255,0.34);',
|
|
102
|
+
' --dsh-glass-inner: rgba(255,255,255,0.05);',
|
|
103
|
+
' --dsh-glass-shade: rgba(0,0,0,0.12);',
|
|
104
|
+
' --dsh-glass-halo: 0 12px 36px rgba(0,0,0,0.28);',
|
|
105
|
+
'}',
|
|
106
|
+
// DSH carries its dark palette on body[data-ds-dark-theme]; a `.dark`
|
|
107
|
+
// class is never used for tokens, so `.dark` selectors would never match.
|
|
108
|
+
'body[data-ds-dark-theme] {',
|
|
109
|
+
' --dsh-glass-ring: rgba(255,255,255,0.22);',
|
|
110
|
+
' --dsh-glass-inner: rgba(255,255,255,0.04);',
|
|
111
|
+
' --dsh-glass-shade: rgba(0,0,0,0.18);',
|
|
112
|
+
' --dsh-glass-halo: 0 14px 40px rgba(0,0,0,0.45);',
|
|
113
|
+
'}',
|
|
114
|
+
|
|
115
|
+
':root[' + ROOT_ATTR + '="on"] [data-composer-card] {',
|
|
116
|
+
' background-color: color-mix(in srgb, var(--dsw-specific-input-major) calc(' + MATERIAL.tint + ' * 100%), transparent) !important;',
|
|
117
|
+
' backdrop-filter: blur(' + MATERIAL.blur + 'px) saturate(' + MATERIAL.saturate + '%) brightness(' + MATERIAL.brightness + ');',
|
|
118
|
+
' -webkit-backdrop-filter: blur(' + MATERIAL.blur + 'px) saturate(' + MATERIAL.saturate + '%) brightness(' + MATERIAL.brightness + ');',
|
|
119
|
+
' box-shadow:',
|
|
120
|
+
' inset 0 0 0 1px var(--dsh-glass-ring),',
|
|
121
|
+
' inset 0 1px 0 0 var(--dsh-glass-inner),',
|
|
122
|
+
' inset 0 -12px 24px -18px var(--dsh-glass-shade),',
|
|
123
|
+
' var(--dsh-glass-halo) !important;',
|
|
124
|
+
' border-radius: ' + MATERIAL.radius + 'px;',
|
|
125
|
+
' transition: background-color .18s ease, backdrop-filter .18s ease;',
|
|
126
|
+
'}',
|
|
127
|
+
// Exactly one decorative layer. Every extra layer erodes the uniformity
|
|
128
|
+
// that makes the pane read as a single piece of glass.
|
|
129
|
+
':root[' + ROOT_ATTR + '="on"] [data-composer-card]::after {',
|
|
130
|
+
" content: '';",
|
|
131
|
+
' position: absolute;',
|
|
132
|
+
' inset: 0;',
|
|
133
|
+
' border-radius: inherit;',
|
|
134
|
+
' pointer-events: none;',
|
|
135
|
+
' background: linear-gradient(170deg, rgba(255,255,255,.09) 0%, rgba(255,255,255,0) 36%);',
|
|
136
|
+
' z-index: 0;',
|
|
137
|
+
'}',
|
|
138
|
+
|
|
139
|
+
// DSH ships no global `* { box-sizing: border-box }` reset, so a row with
|
|
140
|
+
// width/padding would overflow without its own.
|
|
141
|
+
'.' + PREFIX + '-row, .' + PREFIX + '-row * { box-sizing: border-box; }',
|
|
142
|
+
'.' + PREFIX + '-row {',
|
|
143
|
+
' border-bottom: .5px solid var(--dsw-alias-border-l2, rgba(128,128,128,.25));',
|
|
144
|
+
' align-items: center; gap: 8px; padding: 16px 0; display: flex;',
|
|
145
|
+
'}',
|
|
146
|
+
'.' + PREFIX + '-rowText { flex-direction: column; flex: 1; gap: 4px; min-width: 0; padding-right: 48px; display: flex; }',
|
|
147
|
+
// Key colours are hardcoded with a token fallback: an alias token can be
|
|
148
|
+
// absent or resolve to something unreadable in one of the themes, and a
|
|
149
|
+
// failed var() fails silently.
|
|
150
|
+
'.' + PREFIX + '-title { color: var(--dsw-alias-label-primary, #e8e8ea); font-size: 14px; font-weight: 400; line-height: 22px; }',
|
|
151
|
+
'.' + PREFIX + '-desc { color: var(--dsw-alias-label-tertiary, #9a9aa0); font-size: 12px; font-weight: 400; line-height: 18px; }',
|
|
152
|
+
'.' + PREFIX + '-seg {',
|
|
153
|
+
' background: var(--dsw-alias-bg-module-platform, rgba(128,128,128,.16));',
|
|
154
|
+
' border-radius: 18px; height: 36px; padding: 3px; display: inline-flex; gap: 2px; flex: none;',
|
|
155
|
+
'}',
|
|
156
|
+
'.' + PREFIX + '-seg button {',
|
|
157
|
+
' appearance: none; border: none; cursor: pointer; font: inherit;',
|
|
158
|
+
' font-size: 14px; line-height: 22px;',
|
|
159
|
+
' color: var(--dsw-alias-label-secondary, #b9b9c0);',
|
|
160
|
+
' background: transparent; border-radius: 15px; padding: 0 14px; height: 30px;',
|
|
161
|
+
'}',
|
|
162
|
+
'.' + PREFIX + '-seg button[data-active="true"] {',
|
|
163
|
+
' background: var(--dsw-alias-bg-overlay, rgba(90,90,100,.55));',
|
|
164
|
+
' color: var(--dsw-alias-label-primary, #fff);',
|
|
165
|
+
'}'
|
|
166
|
+
].join('\n')
|
|
167
|
+
|
|
168
|
+
// ------------------------------------------------------------------ state
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Live enable state, owned by `apply`.
|
|
172
|
+
*
|
|
173
|
+
* A settings row unmounts when the user navigates away from the section, so
|
|
174
|
+
* the state cannot live inside the component. It is deliberately module
|
|
175
|
+
* scope rather than a settings namespace: a plugin's preference normally
|
|
176
|
+
* belongs in `settingsScope`, which is the durable, cross-restart home — but
|
|
177
|
+
* that requires a Host-side `settings.register()`, and that is the intended
|
|
178
|
+
* next step for this package rather than something to fake here.
|
|
179
|
+
*/
|
|
180
|
+
let enabled = true
|
|
181
|
+
const listeners = []
|
|
182
|
+
|
|
183
|
+
function setEnabled(next) {
|
|
184
|
+
enabled = next
|
|
185
|
+
document.documentElement.setAttribute(ROOT_ATTR, next ? 'on' : 'off')
|
|
186
|
+
for (const notify of listeners) notify(next)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// -------------------------------------------------------------------- row
|
|
190
|
+
|
|
191
|
+
function GlassRow(props) {
|
|
192
|
+
// Prefer the renderer's injected `t` seat, and fall back to a locally
|
|
193
|
+
// bound translate so the row still works if the seat is absent.
|
|
194
|
+
const t = props && props.t ? props.t : fallbackT
|
|
195
|
+
const [on, setOn] = React.useState(enabled)
|
|
196
|
+
|
|
197
|
+
React.useEffect(() => {
|
|
198
|
+
const update = (value) => setOn(value)
|
|
199
|
+
listeners.push(update)
|
|
200
|
+
setOn(enabled)
|
|
201
|
+
return () => {
|
|
202
|
+
const at = listeners.indexOf(update)
|
|
203
|
+
if (at >= 0) listeners.splice(at, 1)
|
|
204
|
+
}
|
|
205
|
+
}, [])
|
|
206
|
+
|
|
207
|
+
const option = (label, value) =>
|
|
208
|
+
React.createElement(
|
|
209
|
+
'button',
|
|
210
|
+
{
|
|
211
|
+
key: label,
|
|
212
|
+
type: 'button',
|
|
213
|
+
'data-active': String(on === value),
|
|
214
|
+
'aria-pressed': on === value,
|
|
215
|
+
onClick: () => setEnabled(value)
|
|
216
|
+
},
|
|
217
|
+
label
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return React.createElement(
|
|
221
|
+
'div',
|
|
222
|
+
{ className: PREFIX + '-row' },
|
|
223
|
+
React.createElement(
|
|
224
|
+
'div',
|
|
225
|
+
{ className: PREFIX + '-rowText' },
|
|
226
|
+
React.createElement('div', { className: PREFIX + '-title' }, t('row.title')),
|
|
227
|
+
React.createElement('div', { className: PREFIX + '-desc' }, t('row.desc'))
|
|
228
|
+
),
|
|
229
|
+
React.createElement(
|
|
230
|
+
'div',
|
|
231
|
+
{ className: PREFIX + '-seg', role: 'radiogroup', 'aria-label': t('row.title') },
|
|
232
|
+
option(t('opt.on'), true),
|
|
233
|
+
option(t('opt.off'), false)
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Bound translate, replaced in `apply` once the locale service is in hand. */
|
|
239
|
+
let fallbackT = (key) => key
|
|
240
|
+
|
|
241
|
+
// ----------------------------------------------------------------- plugin
|
|
242
|
+
|
|
243
|
+
exports.inject = ['slots', 'locale']
|
|
244
|
+
|
|
245
|
+
exports.apply = function apply(ctx) {
|
|
246
|
+
// A formal package has no style service (the dynamic-plugin `styles`
|
|
247
|
+
// symbol does not exist here), so the stylesheet is a plain tag this
|
|
248
|
+
// plugin owns and removes with its fiber.
|
|
249
|
+
ctx.effect(() => {
|
|
250
|
+
const tag = document.createElement('style')
|
|
251
|
+
tag.dataset.plugin = 'dsh-composer-glass'
|
|
252
|
+
tag.dataset.pluginCss = 'dsh-composer-glass'
|
|
253
|
+
tag.textContent = CSS
|
|
254
|
+
document.head.appendChild(tag)
|
|
255
|
+
return () => tag.remove()
|
|
256
|
+
}, 'composer-glass: styles')
|
|
257
|
+
|
|
258
|
+
// Register one dictionary per shipped locale id. Registration bumps the
|
|
259
|
+
// locale revision, so already-mounted outlets pick the texts up at once.
|
|
260
|
+
ctx.effect(() => {
|
|
261
|
+
const disposers = Object.keys(MESSAGES).map((id) => ctx.locale.register(NS, id, MESSAGES[id]))
|
|
262
|
+
return () => disposers.forEach((dispose) => dispose())
|
|
263
|
+
}, 'composer-glass: dictionaries')
|
|
264
|
+
|
|
265
|
+
fallbackT = ctx.locale.bind(NS)
|
|
266
|
+
|
|
267
|
+
// The pane must be correct on first paint, before Settings is ever opened.
|
|
268
|
+
setEnabled(true)
|
|
269
|
+
|
|
270
|
+
ctx.effect(
|
|
271
|
+
() =>
|
|
272
|
+
ctx.slots.inject('settings.general.item', () =>
|
|
273
|
+
ctx.slots.register(
|
|
274
|
+
{ name: 'settings.general.item', id: ROW_ID, order: ROW_ORDER, locale: NS },
|
|
275
|
+
GlassRow
|
|
276
|
+
)
|
|
277
|
+
),
|
|
278
|
+
'composer-glass: settings row'
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
ctx.effect(
|
|
282
|
+
() => () => {
|
|
283
|
+
listeners.length = 0
|
|
284
|
+
document.documentElement.removeAttribute(ROOT_ATTR)
|
|
285
|
+
},
|
|
286
|
+
'composer-glass: teardown'
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return module.exports
|
|
291
|
+
}
|
|
292
|
+
})
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-composer-glass — Host half.
|
|
3
|
+
*
|
|
4
|
+
* This plugin is purely presentational: every byte of its behaviour is CSS and
|
|
5
|
+
* one React row in the browser. It therefore has nothing to do here, and says so
|
|
6
|
+
* explicitly rather than shipping an empty file that looks unfinished.
|
|
7
|
+
*
|
|
8
|
+
* Why the file exists at all: a bundle declares one plugin row, and the row's
|
|
9
|
+
* package is loaded on the Host as well as served to the browser. Providing a
|
|
10
|
+
* named, inert Host half keeps the composition symmetrical with every other DSH
|
|
11
|
+
* plugin and keeps the door open for a settings namespace later.
|
|
12
|
+
*
|
|
13
|
+
* THE NEXT STEP, IF THE PREFERENCE SHOULD SURVIVE A RESTART
|
|
14
|
+
* The on/off state currently lives in browser module scope, so it resets when
|
|
15
|
+
* the page reloads. Persisting it needs a settings namespace:
|
|
16
|
+
*
|
|
17
|
+
* export const inject = ['settings']
|
|
18
|
+
* export function apply(ctx) {
|
|
19
|
+
* const scope = ctx.settings.register('composer-glass', schema)
|
|
20
|
+
* // …and on the client: ctx.settingsScope.bind({ namespace: 'composer-glass' })
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* That is deliberately NOT stubbed out here: an unused namespace would show up
|
|
24
|
+
* as a configuration surface in Settings with nothing behind it, and the client
|
|
25
|
+
* half would still need its own binding. It is a real, separable change.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** Stable plugin name, matching `cordis.patch.yml`. */
|
|
29
|
+
export const name = 'composer-glass'
|
|
30
|
+
|
|
31
|
+
/** No Host services are consumed. */
|
|
32
|
+
export const inject = []
|
|
33
|
+
|
|
34
|
+
/** Intentionally a no-op; see the file header. */
|
|
35
|
+
export function apply() {}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-composer-glass",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DSH plugin: turn the composer into a uniformly translucent frosted-glass pane, with an on/off preference row in Settings → General.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib",
|
|
14
|
+
"images",
|
|
15
|
+
"cordis.patch.yml",
|
|
16
|
+
"README.md",
|
|
17
|
+
"README.en.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"dsh",
|
|
25
|
+
"cordis",
|
|
26
|
+
"glass",
|
|
27
|
+
"frosted-glass",
|
|
28
|
+
"glassmorphism",
|
|
29
|
+
"composer",
|
|
30
|
+
"theme",
|
|
31
|
+
"ui"
|
|
32
|
+
],
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/Lbunc/dsh-composer-glass.git"
|
|
36
|
+
},
|
|
37
|
+
"dsh": {
|
|
38
|
+
"bundle": {
|
|
39
|
+
"patch": "./cordis.patch.yml"
|
|
40
|
+
},
|
|
41
|
+
"client": {
|
|
42
|
+
"inject": [
|
|
43
|
+
"@deepseek-ai/dsh-client-locale"
|
|
44
|
+
],
|
|
45
|
+
"platform": "web"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": "^18.2.0",
|
|
50
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1"
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT"
|
|
53
|
+
}
|