@nonamelego/dsh-catppuccin 0.4.3 → 0.5.0-beta.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/README.en.md +7 -5
- package/README.md +6 -5
- package/lib/client.js +118 -66
- package/lib/client.js.map +1 -1
- package/lib/index.js +108 -145
- package/lib/tui-themes.js +2 -2
- package/package.json +13 -8
package/README.en.md
CHANGED
|
@@ -271,11 +271,13 @@ Then add `@nonamelego/dsh-catppuccin` to the profile's `package.json`
|
|
|
271
271
|
A: The stock Appearance row only lists the built-in light / dark / follow-system
|
|
272
272
|
preferences. The four flavours live in the **Catppuccin** row right below it.
|
|
273
273
|
- **Q: "How is my theme choice remembered?"**
|
|
274
|
-
A: The choice persists in
|
|
275
|
-
every DSH instance on the machine), with
|
|
276
|
-
cross-tab sync. So plain `dsh web`,
|
|
277
|
-
(which boots on a fresh random loopback
|
|
278
|
-
automatically. The glass switch and every
|
|
274
|
+
A: The choice persists in DSH's official settings document (the `catppuccin` namespace,
|
|
275
|
+
stored under the DSH home, shared across every DSH instance on the machine), with
|
|
276
|
+
localStorage as an in-browser cache and cross-tab sync. So plain `dsh web`,
|
|
277
|
+
`dsh web --port <custom>`, and **DSH Desktop** (which boots on a fresh random loopback
|
|
278
|
+
port every launch) all restore the preference automatically. The glass switch and every
|
|
279
|
+
knob persist the same way. Since 0.5.0, a legacy `catppuccin-state.json` is migrated
|
|
280
|
+
into the settings document once on first launch (the file is kept as a rollback copy).
|
|
279
281
|
- **Q: "How do I know if this plugin has a new version?"**
|
|
280
282
|
A: Settings → General → **Check Catppuccin plugin updates** compares against npm in one
|
|
281
283
|
click and gives a copyable upgrade command; or run
|
package/README.md
CHANGED
|
@@ -253,11 +253,12 @@ pnpm --dir C:\Users\LeGo\.dsh\profiles\web add link:D:\Vibe-Coding\dsh-catppucci
|
|
|
253
253
|
A: 官方外观行只列出内置的浅色/深色/跟随系统偏好。四个主题在它正下方的
|
|
254
254
|
**Catppuccin** 行里。
|
|
255
255
|
- Q: **_"我的主题选择是怎么记住的?"_**
|
|
256
|
-
A: 选择持久保存在 DSH
|
|
257
|
-
DSH 共享这份偏好),浏览器 localStorage
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
256
|
+
A: 选择持久保存在 DSH 的官方 settings 文档(`catppuccin` namespace,存于
|
|
257
|
+
DSH home 下,同一台机器的 DSH 共享这份偏好),浏览器 localStorage 作为
|
|
258
|
+
即时缓存与多标签页同步。因此普通 `dsh web`、`dsh web --port` 自定义端口,
|
|
259
|
+
以及 **DSH Desktop**(每次启动用随机回环端口)都能跨重启自动恢复;玻璃质感
|
|
260
|
+
开关与各旋钮同样持久保存。0.5.0 起旧版 `catppuccin-state.json` 会在首次
|
|
261
|
+
启动时一次性迁移进 settings 文档(文件保留作回退)。
|
|
261
262
|
- Q: **_"怎么知道这个插件有没有新版本?"_**
|
|
262
263
|
A: 设置 → 常规 → **检查 Catppuccin 插件更新** 一键检测本插件在 npm 上的最新版本,
|
|
263
264
|
发现新版会给出可复制的升级命令;也可以随时手动执行
|
package/lib/client.js
CHANGED
|
@@ -1037,8 +1037,9 @@ window.__ModuleLoader__.load({
|
|
|
1037
1037
|
observer.disconnect();
|
|
1038
1038
|
};
|
|
1039
1039
|
}
|
|
1040
|
-
/**
|
|
1041
|
-
|
|
1040
|
+
/** Settings namespace both halves address: Host registers it, Client binds
|
|
1041
|
+
* it through `ctx.settingsScope` (see `src/settings-catppuccin.ts`). */
|
|
1042
|
+
const CATPPUCCIN_SETTINGS_NS = "catppuccin";
|
|
1042
1043
|
/** The four registered Catppuccin theme ids. MUST stay in sync with the
|
|
1043
1044
|
* `themeId` of `CATPPUCCIN_FLAVORS` in `src/client/palettes.ts` — guarded by
|
|
1044
1045
|
* `tests/state.spec.ts` — because the persisted flavour value IS the theme id. */
|
|
@@ -1098,6 +1099,23 @@ window.__ModuleLoader__.load({
|
|
|
1098
1099
|
const expected = defaultState();
|
|
1099
1100
|
return state.flavor === expected.flavor && state.glass.enabled === expected.glass.enabled && state.glass.mode === expected.glass.mode && state.glass.blur === expected.glass.blur && state.glass.frost === expected.glass.frost && state.glass.brightness === expected.glass.brightness;
|
|
1100
1101
|
}
|
|
1102
|
+
/** Drop the synthetic `version` field: full state → settings-document section. */
|
|
1103
|
+
function settingsSectionFromState(state) {
|
|
1104
|
+
return {
|
|
1105
|
+
flavor: state.flavor,
|
|
1106
|
+
glass: { ...state.glass }
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
/** Lift a settings-document section back into the full state-shaped contract
|
|
1110
|
+
* (re-sanitized so a hand-edited document can never poison the plugin). */
|
|
1111
|
+
function stateFromSettingsSection(section) {
|
|
1112
|
+
return sanitizeState(section);
|
|
1113
|
+
}
|
|
1114
|
+
/** Whether two settings sections are field-for-field equal (used to skip
|
|
1115
|
+
* redundant mirror writes and to recognize the Host echoing our own write). */
|
|
1116
|
+
function settingsSectionsEqual(a, b) {
|
|
1117
|
+
return a.flavor === b.flavor && a.glass.enabled === b.glass.enabled && a.glass.mode === b.glass.mode && a.glass.blur === b.glass.blur && a.glass.frost === b.glass.frost && a.glass.brightness === b.glass.brightness;
|
|
1118
|
+
}
|
|
1101
1119
|
//#endregion
|
|
1102
1120
|
//#region src/client/glass/glass-layer.ts
|
|
1103
1121
|
/** html attribute selecting the glass layer: CSS hooks and page effects. */
|
|
@@ -1927,75 +1945,98 @@ window.__ModuleLoader__.load({
|
|
|
1927
1945
|
//#endregion
|
|
1928
1946
|
//#region src/client/state-sync.ts
|
|
1929
1947
|
/**
|
|
1930
|
-
*
|
|
1931
|
-
*
|
|
1932
|
-
*
|
|
1933
|
-
* of truth; this module adds the fetch wrapper plus a debounced trailing
|
|
1934
|
-
* writer so rapid knob drags coalesce into a single PUT. Every failure is
|
|
1935
|
-
* non-fatal: the plugin keeps working from browser localStorage alone.
|
|
1948
|
+
* Bind the Catppuccin namespace on this context. The scope derives from the
|
|
1949
|
+
* shared document mirror (no wire read of its own) and its life is bound to
|
|
1950
|
+
* the calling fiber, so binding never blocks on the settings transport.
|
|
1936
1951
|
*/
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
try {
|
|
1940
|
-
const response = await fetch(STATE_ROUTE_PATH, { headers: { accept: "application/json" } });
|
|
1941
|
-
if (!response.ok) return {
|
|
1942
|
-
available: false,
|
|
1943
|
-
state: null
|
|
1944
|
-
};
|
|
1945
|
-
const payload = await response.json();
|
|
1946
|
-
if (payload.ok !== true) return {
|
|
1947
|
-
available: false,
|
|
1948
|
-
state: null
|
|
1949
|
-
};
|
|
1950
|
-
return {
|
|
1951
|
-
available: true,
|
|
1952
|
-
state: payload.state === null || payload.state === void 0 ? null : sanitizeState(payload.state)
|
|
1953
|
-
};
|
|
1954
|
-
} catch {
|
|
1955
|
-
return {
|
|
1956
|
-
available: false,
|
|
1957
|
-
state: null
|
|
1958
|
-
};
|
|
1959
|
-
}
|
|
1952
|
+
function bindCatppuccinScope(ctx) {
|
|
1953
|
+
return ctx.settingsScope.bind({ namespace: CATPPUCCIN_SETTINGS_NS });
|
|
1960
1954
|
}
|
|
1961
|
-
/**
|
|
1962
|
-
|
|
1955
|
+
/** Whether the scope is usable for durable persistence: the document is
|
|
1956
|
+
* resolved (`ready`) and the Host persistence is active (`host`). A `memory`
|
|
1957
|
+
* scope (non-loopback pages / absent transport) or a not-yet-ready one is
|
|
1958
|
+
* NOT usable — the caller keeps localStorage as the only store. */
|
|
1959
|
+
function isScopeUsable(snapshot) {
|
|
1960
|
+
return snapshot.status === "ready" && snapshot.mode === "host";
|
|
1961
|
+
}
|
|
1962
|
+
/** The local state contract (with the synthetic `version`) for a usable
|
|
1963
|
+
* scope snapshot; `null` while the section is not resolved. */
|
|
1964
|
+
function durableStateFromSnapshot(snapshot) {
|
|
1965
|
+
if (!isScopeUsable(snapshot) || snapshot.value === void 0) return null;
|
|
1966
|
+
return stateFromSettingsSection(snapshot.value);
|
|
1967
|
+
}
|
|
1968
|
+
/** One atomic mutation covering the whole local state (revision-fenced).
|
|
1969
|
+
* Writing every field keeps the section self-contained — a partial write
|
|
1970
|
+
* could otherwise leave a knob stuck on a stale override after a document
|
|
1971
|
+
* edit elsewhere. */
|
|
1972
|
+
function stateToMutateOps(state) {
|
|
1973
|
+
const section = settingsSectionFromState(state);
|
|
1974
|
+
return [
|
|
1975
|
+
{
|
|
1976
|
+
op: "set",
|
|
1977
|
+
path: ["flavor"],
|
|
1978
|
+
value: section.flavor
|
|
1979
|
+
},
|
|
1980
|
+
{
|
|
1981
|
+
op: "set",
|
|
1982
|
+
path: ["glass", "enabled"],
|
|
1983
|
+
value: section.glass.enabled
|
|
1984
|
+
},
|
|
1985
|
+
{
|
|
1986
|
+
op: "set",
|
|
1987
|
+
path: ["glass", "mode"],
|
|
1988
|
+
value: section.glass.mode
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
op: "set",
|
|
1992
|
+
path: ["glass", "blur"],
|
|
1993
|
+
value: section.glass.blur
|
|
1994
|
+
},
|
|
1995
|
+
{
|
|
1996
|
+
op: "set",
|
|
1997
|
+
path: ["glass", "frost"],
|
|
1998
|
+
value: section.glass.frost
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
op: "set",
|
|
2002
|
+
path: ["glass", "brightness"],
|
|
2003
|
+
value: section.glass.brightness
|
|
2004
|
+
}
|
|
2005
|
+
];
|
|
2006
|
+
}
|
|
2007
|
+
/** Persist the local state through the scope as one atomic mutation.
|
|
2008
|
+
* Resolves `true` when the write was issued. */
|
|
2009
|
+
async function persistStateToScope(scope, state) {
|
|
1963
2010
|
try {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
headers: {
|
|
1967
|
-
accept: "application/json",
|
|
1968
|
-
"content-type": "application/json"
|
|
1969
|
-
},
|
|
1970
|
-
body: JSON.stringify(sanitizeState(state))
|
|
1971
|
-
})).ok;
|
|
2011
|
+
await scope.mutate(stateToMutateOps(state));
|
|
2012
|
+
return true;
|
|
1972
2013
|
} catch {
|
|
1973
2014
|
return false;
|
|
1974
2015
|
}
|
|
1975
2016
|
}
|
|
1976
2017
|
let pendingTimer;
|
|
1977
|
-
let
|
|
2018
|
+
let pendingWrite;
|
|
1978
2019
|
/**
|
|
1979
2020
|
* Debounced trailing persist: after a burst of changes (a slider drag emits a
|
|
1980
|
-
* stream of snapshot updates) the
|
|
1981
|
-
*
|
|
1982
|
-
*
|
|
2021
|
+
* stream of snapshot updates) the write happens once, a short beat after the
|
|
2022
|
+
* last change. Pass an accessor so the state snapshot is taken at flush time,
|
|
2023
|
+
* never stale.
|
|
1983
2024
|
*/
|
|
1984
|
-
function scheduleDurablePersist(
|
|
2025
|
+
function scheduleDurablePersist(write, delayMs = 300) {
|
|
1985
2026
|
if (pendingTimer !== void 0) window.clearTimeout(pendingTimer);
|
|
1986
|
-
|
|
2027
|
+
pendingWrite = write;
|
|
1987
2028
|
pendingTimer = window.setTimeout(() => {
|
|
1988
2029
|
pendingTimer = void 0;
|
|
1989
|
-
const
|
|
1990
|
-
|
|
1991
|
-
if (
|
|
2030
|
+
const run = pendingWrite;
|
|
2031
|
+
pendingWrite = void 0;
|
|
2032
|
+
if (run !== void 0) Promise.resolve().then(run).catch(() => {});
|
|
1992
2033
|
}, delayMs);
|
|
1993
2034
|
}
|
|
1994
2035
|
/** Drop any queued persist (plugin unload / page teardown). */
|
|
1995
2036
|
function cancelDurablePersist() {
|
|
1996
2037
|
if (pendingTimer !== void 0) window.clearTimeout(pendingTimer);
|
|
1997
2038
|
pendingTimer = void 0;
|
|
1998
|
-
|
|
2039
|
+
pendingWrite = void 0;
|
|
1999
2040
|
}
|
|
2000
2041
|
//#endregion
|
|
2001
2042
|
//#region \0dsh-css:/home/runner/work/dsh-catppuccin-theme/dsh-catppuccin-theme/src/client/glass/glass.module.css.mjs
|
|
@@ -2086,11 +2127,13 @@ window.__ModuleLoader__.load({
|
|
|
2086
2127
|
if (preference !== "light" && preference !== "dark") return false;
|
|
2087
2128
|
return preference === livePick;
|
|
2088
2129
|
}
|
|
2089
|
-
/** Required services: slots + locale (settings rows)
|
|
2130
|
+
/** Required services: slots + locale (settings rows), theme (register +
|
|
2131
|
+
* switch), and the settings scope (durable persistence). */
|
|
2090
2132
|
const inject = [
|
|
2091
2133
|
"slots",
|
|
2092
2134
|
"locale",
|
|
2093
|
-
"theme"
|
|
2135
|
+
"theme",
|
|
2136
|
+
"settingsScope"
|
|
2094
2137
|
];
|
|
2095
2138
|
/**
|
|
2096
2139
|
* Register the Catppuccin dictionaries, the four flavour themes, and the
|
|
@@ -2121,11 +2164,16 @@ window.__ModuleLoader__.load({
|
|
|
2121
2164
|
};
|
|
2122
2165
|
}, "catppuccin: flavour themes");
|
|
2123
2166
|
const glass = new GlassLayer(ctx);
|
|
2167
|
+
const scope = bindCatppuccinScope(ctx);
|
|
2124
2168
|
const buildLocalState = () => ({
|
|
2125
2169
|
version: 1,
|
|
2126
2170
|
flavor: readFlavor(),
|
|
2127
2171
|
glass: glass.getRemoteState()
|
|
2128
2172
|
});
|
|
2173
|
+
const persistLocal = () => {
|
|
2174
|
+
if (!isScopeUsable(scope.getSnapshot())) return;
|
|
2175
|
+
persistStateToScope(scope, buildLocalState());
|
|
2176
|
+
};
|
|
2129
2177
|
ctx.effect(() => {
|
|
2130
2178
|
let liveBuiltinPick = null;
|
|
2131
2179
|
const originalSetTheme = theme.setTheme;
|
|
@@ -2155,22 +2203,26 @@ window.__ModuleLoader__.load({
|
|
|
2155
2203
|
};
|
|
2156
2204
|
window.addEventListener("storage", onStorage);
|
|
2157
2205
|
const offGlass = glass.subscribe(() => {
|
|
2158
|
-
scheduleDurablePersist(
|
|
2206
|
+
scheduleDurablePersist(persistLocal);
|
|
2159
2207
|
});
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
if (
|
|
2164
|
-
|
|
2165
|
-
|
|
2208
|
+
const applyScopeSnapshot = () => {
|
|
2209
|
+
const snapshot = scope.getSnapshot();
|
|
2210
|
+
const state = durableStateFromSnapshot(snapshot);
|
|
2211
|
+
if (state === null) return;
|
|
2212
|
+
const local = buildLocalState();
|
|
2213
|
+
if (snapshot.user === void 0) {
|
|
2214
|
+
if (!isDefaultState(local)) scheduleDurablePersist(persistLocal);
|
|
2166
2215
|
return;
|
|
2167
2216
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2217
|
+
if (settingsSectionsEqual(settingsSectionFromState(local), settingsSectionFromState(state))) return;
|
|
2218
|
+
writeFlavor(state.flavor);
|
|
2219
|
+
glass.applyRemote(state.glass);
|
|
2170
2220
|
applyDesired();
|
|
2171
|
-
}
|
|
2221
|
+
};
|
|
2222
|
+
applyScopeSnapshot();
|
|
2223
|
+
const offScope = scope.subscribe(applyScopeSnapshot);
|
|
2172
2224
|
return () => {
|
|
2173
|
-
|
|
2225
|
+
offScope();
|
|
2174
2226
|
offGlass();
|
|
2175
2227
|
disposer();
|
|
2176
2228
|
window.removeEventListener("storage", onStorage);
|
|
@@ -2193,14 +2245,14 @@ window.__ModuleLoader__.load({
|
|
|
2193
2245
|
if (choice === "off") {
|
|
2194
2246
|
writeFlavor("off");
|
|
2195
2247
|
theme.setTheme(readRestoredPreference());
|
|
2196
|
-
scheduleDurablePersist(
|
|
2248
|
+
scheduleDurablePersist(persistLocal);
|
|
2197
2249
|
return;
|
|
2198
2250
|
}
|
|
2199
2251
|
const flavor = flavorInfo(choice);
|
|
2200
2252
|
if (!flavor) return;
|
|
2201
2253
|
writeFlavor(flavor.themeId);
|
|
2202
2254
|
theme.setTheme(flavor.themeId);
|
|
2203
|
-
scheduleDurablePersist(
|
|
2255
|
+
scheduleDurablePersist(persistLocal);
|
|
2204
2256
|
}
|
|
2205
2257
|
});
|
|
2206
2258
|
ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|