sdocs-dev 1.0.0 → 1.1.1

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.
@@ -49,6 +49,7 @@ function populateFontSelect(sel, includeInherit) {
49
49
  // ── Dark mode ──────────────────────────────────
50
50
 
51
51
  const LIGHT_DEFAULTS = {
52
+ bgColor: '#ffffff',
52
53
  colorDefault: '#1c1917',
53
54
  codeBg: '#f4f1ed',
54
55
  codeColor: '#6b21a8',
@@ -58,6 +59,7 @@ const LIGHT_DEFAULTS = {
58
59
  };
59
60
 
60
61
  const DARK_DEFAULTS = {
62
+ bgColor: '#1c1a17',
61
63
  colorDefault: '#e7e5e2',
62
64
  codeBg: '#1a1816',
63
65
  codeColor: '#b8a99a',
@@ -70,6 +72,10 @@ function getThemeDefaults() {
70
72
  return document.documentElement.dataset.theme === 'dark' ? DARK_DEFAULTS : LIGHT_DEFAULTS;
71
73
  }
72
74
 
75
+ function getThemeDefaultsFor(theme) {
76
+ return theme === 'dark' ? DARK_DEFAULTS : LIGHT_DEFAULTS;
77
+ }
78
+
73
79
  function getColorDefault() {
74
80
  return getThemeDefaults().colorDefault;
75
81
  }
@@ -90,14 +96,87 @@ function updateThemeIcon(theme) {
90
96
 
91
97
  function applyTheme(theme) {
92
98
  document.documentElement.dataset.theme = theme;
99
+ S.activeTheme = theme;
93
100
  localStorage.setItem('sdocs-theme', theme);
94
101
  updateThemeIcon(theme);
95
102
  }
96
103
 
104
+ // ── Per-theme color save/load ──────────────────────
105
+
106
+ function saveCurrentThemeColors() {
107
+ var theme = S.activeTheme;
108
+ var colors = S.themeColors[theme];
109
+ // Save all 13 color control values
110
+ SDocStyles.ALL_COLOR_IDS.forEach(function(ctrlId) {
111
+ var el = document.getElementById(ctrlId);
112
+ if (el) colors[ctrlId] = el.value;
113
+ });
114
+ }
115
+
116
+ function loadThemeColors(theme) {
117
+ var colors = S.themeColors[theme];
118
+ var overridden = S.themeOverridden[theme];
119
+ var defaults = getThemeDefaultsFor(theme);
120
+
121
+ // Cascade root first
122
+ if (overridden.has('ctrl-color')) {
123
+ S.setColorValue('ctrl-color', colors['ctrl-color'], true);
124
+ } else {
125
+ S.setColorValue('ctrl-color', defaults.colorDefault, false);
126
+ }
127
+
128
+ // Then cascade children that are overridden
129
+ var cascadeIds = Object.keys(SDocStyles.COLOR_VAR_MAP);
130
+ cascadeIds.forEach(function(ctrlId) {
131
+ if (ctrlId === 'ctrl-color') return; // already handled
132
+ if (overridden.has(ctrlId)) {
133
+ S.setColorValue(ctrlId, colors[ctrlId], true);
134
+ }
135
+ // Non-overridden cascade children are already set by cascade from parent
136
+ });
137
+
138
+ // Standalone colors
139
+ var standaloneMap = {
140
+ 'ctrl-bg-color': defaults.bgColor,
141
+ 'ctrl-link-color': defaults.linkColor,
142
+ 'ctrl-code-bg': defaults.codeBg,
143
+ 'ctrl-code-color': defaults.codeColor,
144
+ 'ctrl-bq-border-color': defaults.bqBorderColor,
145
+ 'ctrl-bq-color': defaults.bqColor,
146
+ };
147
+ for (var ctrlId in standaloneMap) {
148
+ var el = document.getElementById(ctrlId);
149
+ if (!el) continue;
150
+ if (overridden.has(ctrlId)) {
151
+ el.value = colors[ctrlId];
152
+ } else {
153
+ el.value = standaloneMap[ctrlId];
154
+ }
155
+ var allVals = S.readAllControlValues();
156
+ SDocStyles.controlToCssVars(ctrlId, el.value, allVals)
157
+ .forEach(function(a) { S.setStyleVar(a.cssVar, a.value); });
158
+ }
159
+ }
160
+
161
+ function updateThemeTabs(theme) {
162
+ var lightTab = document.getElementById('theme-tab-light');
163
+ var darkTab = document.getElementById('theme-tab-dark');
164
+ if (lightTab) lightTab.classList.toggle('active', theme === 'light');
165
+ if (darkTab) darkTab.classList.toggle('active', theme === 'dark');
166
+ }
167
+
168
+ function switchThemeAndUpdate(theme) {
169
+ if (theme === S.activeTheme) return;
170
+ saveCurrentThemeColors();
171
+ applyTheme(theme);
172
+ loadThemeColors(theme);
173
+ updateThemeTabs(theme);
174
+ S.syncAll('controls');
175
+ }
176
+
97
177
  function toggleTheme() {
98
- const current = document.documentElement.dataset.theme;
99
- applyTheme(current === 'dark' ? 'light' : 'dark');
100
- updateDefaultColors();
178
+ var current = S.activeTheme;
179
+ switchThemeAndUpdate(current === 'dark' ? 'light' : 'dark');
101
180
  }
102
181
 
103
182
  function updateDefaultColors() {
@@ -108,6 +187,7 @@ function updateDefaultColors() {
108
187
  }
109
188
  // Update standalone color defaults for reset buttons
110
189
  const standaloneMap = {
190
+ 'ctrl-bg-color': defaults.bgColor,
111
191
  'ctrl-link-color': defaults.linkColor,
112
192
  'ctrl-code-bg': defaults.codeBg,
113
193
  'ctrl-code-color': defaults.codeColor,
@@ -131,6 +211,7 @@ function updateDefaultColors() {
131
211
  function getStandaloneDefault(ctrlId) {
132
212
  const d = getThemeDefaults();
133
213
  const map = {
214
+ 'ctrl-bg-color': d.bgColor,
134
215
  'ctrl-link-color': d.linkColor,
135
216
  'ctrl-code-bg': d.codeBg,
136
217
  'ctrl-code-color': d.codeColor,
@@ -142,12 +223,12 @@ function getStandaloneDefault(ctrlId) {
142
223
 
143
224
  // ── Init at load time ──────────────────────────────────
144
225
 
145
- applyTheme(getPreferredTheme());
226
+ var initTheme = getPreferredTheme();
227
+ applyTheme(initTheme);
146
228
 
147
229
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
148
230
  if (!localStorage.getItem('sdocs-theme')) {
149
- applyTheme(e.matches ? 'dark' : 'light');
150
- updateDefaultColors();
231
+ switchThemeAndUpdate(e.matches ? 'dark' : 'light');
151
232
  }
152
233
  });
153
234
 
@@ -160,9 +241,14 @@ populateFontSelect(document.getElementById('ctrl-h-font-family'), true);
160
241
  S.GOOGLE_FONTS = GOOGLE_FONTS;
161
242
  S.loadGoogleFont = loadGoogleFont;
162
243
  S.getThemeDefaults = getThemeDefaults;
244
+ S.getThemeDefaultsFor = getThemeDefaultsFor;
163
245
  S.getColorDefault = getColorDefault;
164
246
  S.getStandaloneDefault = getStandaloneDefault;
165
247
  S.updateDefaultColors = updateDefaultColors;
166
248
  S.toggleTheme = toggleTheme;
249
+ S.switchThemeAndUpdate = switchThemeAndUpdate;
250
+ S.saveCurrentThemeColors = saveCurrentThemeColors;
251
+ S.loadThemeColors = loadThemeColors;
252
+ S.updateThemeTabs = updateThemeTabs;
167
253
 
168
254
  })();
@@ -19,30 +19,33 @@ function parseInlineObject(str) {
19
19
  return obj;
20
20
  }
21
21
 
22
- function parseSimpleYaml(str) {
22
+ function parseBlock(lines, startIdx, indent) {
23
23
  const result = {};
24
- const lines = str.split('\n');
25
- let i = 0;
26
- while (i < lines.length) {
27
- const line = lines[i];
28
- const km = line.match(/^(\w[\w-]*):\s*(.*)/);
29
- if (!km) { i++; continue; }
30
- const key = km[1], rest = km[2].trim();
24
+ let i = startIdx;
25
+ const prefix = new RegExp('^' + ' '.repeat(indent));
26
+ const deeper = new RegExp('^' + ' '.repeat(indent + 2));
27
+ while (i < lines.length && prefix.test(lines[i])) {
28
+ const nl = lines[i].substring(indent);
29
+ const nm = nl.match(/^(\w[\w-]*):\s*(.*)/);
30
+ if (!nm) { i++; continue; }
31
+ const key = nm[1], rest = nm[2].trim();
31
32
  if (rest.startsWith('{')) {
32
33
  result[key] = parseInlineObject(rest); i++;
33
- } else if (rest === '') {
34
- const nested = {}; i++;
35
- while (i < lines.length && /^ /.test(lines[i])) {
36
- const nl = lines[i].trim();
37
- const nm = nl.match(/^(\w[\w-]*):\s*(.*)/);
38
- if (nm) nested[nm[1]] = nm[2].trim().startsWith('{')
39
- ? parseInlineObject(nm[2].trim()) : parseScalar(nm[2].trim());
40
- i++;
41
- }
42
- result[key] = nested;
43
- } else { result[key] = parseScalar(rest); i++; }
34
+ } else if (rest === '' && i + 1 < lines.length && deeper.test(lines[i + 1])) {
35
+ i++;
36
+ var sub = parseBlock(lines, i, indent + 2);
37
+ result[key] = sub.obj;
38
+ i = sub.nextIdx;
39
+ } else {
40
+ result[key] = parseScalar(rest); i++;
41
+ }
44
42
  }
45
- return result;
43
+ return { obj: result, nextIdx: i };
44
+ }
45
+
46
+ function parseSimpleYaml(str) {
47
+ const lines = str.split('\n');
48
+ return parseBlock(lines, 0, 0).obj;
46
49
  }
47
50
 
48
51
  function parseFrontMatter(text) {
@@ -52,6 +55,13 @@ function parseFrontMatter(text) {
52
55
  return { meta: parseSimpleYaml(m[1]), body: text.slice(m[0].length) };
53
56
  }
54
57
 
58
+ function hasNestedObjects(obj) {
59
+ for (const v of Object.values(obj)) {
60
+ if (typeof v === 'object' && v !== null) return true;
61
+ }
62
+ return false;
63
+ }
64
+
55
65
  function serializeFrontMatter(meta) {
56
66
  const lines = ['---'];
57
67
  for (const [k, v] of Object.entries(meta)) {
@@ -59,8 +69,21 @@ function serializeFrontMatter(meta) {
59
69
  lines.push(`${k}:`);
60
70
  for (const [sk, sv] of Object.entries(v)) {
61
71
  if (typeof sv === 'object' && sv !== null) {
62
- const inner = Object.entries(sv).map(([a,b]) => `${a}: ${JSON.stringify(b)}`).join(', ');
63
- lines.push(` ${sk}: { ${inner} }`);
72
+ // If sub-object contains nested objects (3 levels), serialize as block
73
+ if (hasNestedObjects(sv)) {
74
+ lines.push(` ${sk}:`);
75
+ for (const [a, b] of Object.entries(sv)) {
76
+ if (typeof b === 'object' && b !== null) {
77
+ const inner = Object.entries(b).map(([c,d]) => `${c}: ${JSON.stringify(d)}`).join(', ');
78
+ lines.push(` ${a}: { ${inner} }`);
79
+ } else {
80
+ lines.push(` ${a}: ${JSON.stringify(b)}`);
81
+ }
82
+ }
83
+ } else {
84
+ const inner = Object.entries(sv).map(([a,b]) => `${a}: ${JSON.stringify(b)}`).join(', ');
85
+ lines.push(` ${sk}: { ${inner} }`);
86
+ }
64
87
  } else { lines.push(` ${sk}: ${JSON.stringify(sv)}`); }
65
88
  }
66
89
  } else { lines.push(`${k}: ${JSON.stringify(v)}`); }