Skip to content

Commit cbef99f

Browse files
committed
Rework CSS snippets
Introduce a new CSS snippets system: add state.cssSnippets and STORAGE_KEYS.cssSnippets, normalize stored snippets (with built-in presets) and persist changes. applyCustomCSS now composes active snippets with raw custom CSS and listens for bds:cssSnippetsChanged for live updates. Add Settings UI to manage snippets (save/edit/toggle/restore/delete) including a new SnippetList.svelte component, modal, toolbar and styles, plus mount/app/drawer refresh hooks to update the UI. Update localization strings for snippet-related labels and adjust an e2e test selector to be more robust.
1 parent 8a75a20 commit cbef99f

14 files changed

Lines changed: 742 additions & 26 deletions

File tree

src/content/index.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ async function init() {
4646
await waitForBody();
4747
await loadStateFromStorage();
4848

49-
applyCustomCSS(state.settings.customCSS);
49+
applyCustomCSS(state.settings.customCSS, state.cssSnippets);
5050

5151
// Initialize localization locale
5252
i18n.init(state.settings.syncLocale ? null : state.settings.locale);
@@ -79,7 +79,12 @@ async function init() {
7979

8080
// Live-update custom CSS when settings change
8181
window.addEventListener("bds:settingsChanged", () => {
82-
applyCustomCSS(state.settings.customCSS);
82+
applyCustomCSS(state.settings.customCSS, state.cssSnippets);
83+
});
84+
85+
// Live-update custom CSS when snippets change
86+
window.addEventListener("bds:cssSnippetsChanged", () => {
87+
applyCustomCSS(state.settings.customCSS, state.cssSnippets);
8388
});
8489

8590
window.addEventListener("bds:deep-research-config-changed", () => {
@@ -248,13 +253,25 @@ async function waitForBody() {
248253
});
249254
}
250255

251-
function applyCustomCSS(css) {
256+
function applyCustomCSS(customCSS, snippets) {
252257
const id = "bds-custom-css";
253258
let style = document.getElementById(id);
254259
if (!style) {
255260
style = document.createElement("style");
256261
style.id = id;
257262
document.head.appendChild(style);
258263
}
259-
style.textContent = css || "";
264+
265+
let compiled = customCSS || "";
266+
if (Array.isArray(snippets)) {
267+
const activeSnippetsCss = snippets
268+
.filter((s) => s.active)
269+
.map((s) => `/* Snippet: ${s.name} */\n${s.css}`)
270+
.join("\n\n");
271+
if (activeSnippetsCss) {
272+
compiled = `${activeSnippetsCss}\n\n/* Raw Custom CSS */\n${compiled}`;
273+
}
274+
}
275+
276+
style.textContent = compiled;
260277
}

src/content/state.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const state = {
6565
whatsNewPending: false,
6666
/** @type {Object<string, string[]>} session ID -> tag names */
6767
chatTags: {},
68+
cssSnippets: [],
6869
savedItems: [],
6970
/** @type {{indicator: 'none'|'minor'|'major'|'critical', description: string, lastChecked: number}} */
7071
serverStatus: {

src/content/storage.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
SYSTEM_PROMPT_TEMPLATE_VERSION,
1212
DOWNLOAD_BEHAVIOR_VERSION,
1313
DEFAULT_REMOTE_CONFIG,
14+
CSS_PRESETS,
1415
} from "../lib/constants.js";
1516
import { makeId } from "../lib/utils/helpers.js";
1617
import { setHtmlToMarkdownMaxDepth } from "./dom/message-text.js";
@@ -32,6 +33,7 @@ export async function loadStateFromStorage() {
3233
STORAGE_KEYS.savedItems,
3334
STORAGE_KEYS.remoteAnnouncement,
3435
STORAGE_KEYS.dismissedAnnouncements,
36+
STORAGE_KEYS.cssSnippets,
3537
"bds_locale_updates",
3638
]);
3739

@@ -107,6 +109,7 @@ export async function loadStateFromStorage() {
107109
}
108110

109111
state.skills = normalizeSkills(values[STORAGE_KEYS.skills]);
112+
state.cssSnippets = normalizeCssSnippets(values[STORAGE_KEYS.cssSnippets]);
110113
state.memories = normalizeMemories(values[STORAGE_KEYS.memories]);
111114
state.characters = normalizeCharacters(values[STORAGE_KEYS.characters]);
112115
state.projects = normalizeProjects(values[STORAGE_KEYS.projects]);
@@ -337,6 +340,70 @@ export function normalizeSavedItems(raw) {
337340
}));
338341
}
339342

343+
export function normalizeCssSnippets(raw) {
344+
const defaultPresets = [
345+
{
346+
id: "preset-reducePadding",
347+
name: "presetReducePadding",
348+
css: CSS_PRESETS.reducePadding.css,
349+
active: false,
350+
isPreset: true,
351+
},
352+
{
353+
id: "preset-widerMessages",
354+
name: "presetWiderMessages",
355+
css: CSS_PRESETS.widerMessages.css,
356+
active: false,
357+
isPreset: true,
358+
},
359+
{
360+
id: "preset-compact",
361+
name: "presetCompact",
362+
css: CSS_PRESETS.compact.css,
363+
active: false,
364+
isPreset: true,
365+
},
366+
{
367+
id: "preset-betterCodeFont",
368+
name: "presetBetterCodeFont",
369+
css: CSS_PRESETS.betterCodeFont.css,
370+
active: false,
371+
isPreset: true,
372+
},
373+
];
374+
375+
if (!Array.isArray(raw)) {
376+
return defaultPresets;
377+
}
378+
379+
const map = new Map(raw.map((item) => [item.id, item]));
380+
381+
const finalPresets = defaultPresets.map((preset) => {
382+
const existing = map.get(preset.id);
383+
if (existing) {
384+
map.delete(preset.id);
385+
return {
386+
...preset,
387+
css: existing.css !== undefined ? existing.css : preset.css,
388+
active: !!existing.active,
389+
};
390+
}
391+
return preset;
392+
});
393+
394+
const customSnippets = Array.from(map.values())
395+
.map((item) => ({
396+
id: String(item.id || makeId()),
397+
name: String(item.name || "Snippet"),
398+
css: String(item.css || ""),
399+
active: !!item.active,
400+
isPreset: false,
401+
}))
402+
.filter((item) => item.css.trim().length > 0);
403+
404+
return [...finalPresets, ...customSnippets];
405+
}
406+
340407
// ── Storage change listener ──
341408

342409
export function bindStorageChangeListener() {
@@ -423,6 +490,14 @@ export function bindStorageChangeListener() {
423490
if (state.ui) state.ui.refreshSavedItems();
424491
}
425492

493+
if (changes[STORAGE_KEYS.cssSnippets]) {
494+
state.cssSnippets = normalizeCssSnippets(changes[STORAGE_KEYS.cssSnippets].newValue);
495+
if (state.ui && typeof state.ui.refreshCssSnippets === 'function') {
496+
state.ui.refreshCssSnippets();
497+
}
498+
window.dispatchEvent(new CustomEvent("bds:cssSnippetsChanged"));
499+
}
500+
426501
if (changes[STORAGE_KEYS.remoteAnnouncement]) {
427502
state.remoteAnnouncements = Array.isArray(changes[STORAGE_KEYS.remoteAnnouncement].newValue) ? changes[STORAGE_KEYS.remoteAnnouncement].newValue : [];
428503
window.dispatchEvent(new CustomEvent("bds:remote-announcement-updated", { detail: state.remoteAnnouncements }));

src/content/ui/App.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
export function refreshSavedItems() {
5959
if (drawerRef) drawerRef.refreshSavedItems();
6060
}
61+
export function refreshCssSnippets() {
62+
if (drawerRef) drawerRef.refreshCssSnippets();
63+
}
6164
6265
export function refreshWhatsNew() {
6366
whatsNewPending = appState.whatsNewPending;

src/content/ui/Drawer.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
export function refreshSavedItems() {
4545
if (savedItemsRef) savedItemsRef.refresh();
4646
}
47+
export function refreshCssSnippets() {
48+
if (settingsRef) settingsRef.refreshCssSnippets();
49+
}
4750
4851
function openProjectsManager() {
4952
showProjectsManager = true;

0 commit comments

Comments
 (0)