sdocs-dev 1.0.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/bin/sdocs-dev.js +443 -0
- package/package.json +40 -0
- package/public/css/layout.css +237 -0
- package/public/css/mobile.css +189 -0
- package/public/css/panel.css +348 -0
- package/public/css/rendered.css +278 -0
- package/public/css/tokens.css +112 -0
- package/public/css/write.css +128 -0
- package/public/default.md +129 -0
- package/public/index.html +533 -0
- package/public/sdocs-app.js +595 -0
- package/public/sdocs-controls.js +221 -0
- package/public/sdocs-export.js +193 -0
- package/public/sdocs-state.js +43 -0
- package/public/sdocs-styles.js +285 -0
- package/public/sdocs-theme.js +168 -0
- package/public/sdocs-write.js +737 -0
- package/public/sdocs-yaml.js +78 -0
- package/server.js +65 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
// sdocs-styles.js — Pure data tables + logic for SDocs style system
|
|
2
|
+
// Shared by app.js (browser) and test/run.js (Node)
|
|
3
|
+
(function (exports) {
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
// ═══════════════════════════════════════════════════════
|
|
7
|
+
// DATA TABLES
|
|
8
|
+
// ═══════════════════════════════════════════════════════
|
|
9
|
+
|
|
10
|
+
const COLOR_DEFAULT = '#1c1917';
|
|
11
|
+
|
|
12
|
+
// Control ID → CSS variable name (color controls only)
|
|
13
|
+
const COLOR_VAR_MAP = {
|
|
14
|
+
'ctrl-color': '--md-color',
|
|
15
|
+
'ctrl-h-color': '--md-h-color',
|
|
16
|
+
'ctrl-h1-color': '--md-h1-color',
|
|
17
|
+
'ctrl-h2-color': '--md-h2-color',
|
|
18
|
+
'ctrl-h3-color': '--md-h3-color',
|
|
19
|
+
'ctrl-h4-color': '--md-h4-color',
|
|
20
|
+
'ctrl-p-color': '--md-p-color',
|
|
21
|
+
'ctrl-list-color': '--md-list-color',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Cascade tree: parent → direct children
|
|
25
|
+
const COLOR_CASCADE = {
|
|
26
|
+
'ctrl-color': ['ctrl-h-color', 'ctrl-p-color'],
|
|
27
|
+
'ctrl-h-color': ['ctrl-h1-color', 'ctrl-h2-color', 'ctrl-h3-color', 'ctrl-h4-color'],
|
|
28
|
+
'ctrl-p-color': ['ctrl-list-color'],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Control ID → { cssVar, suffix?, compound? }
|
|
32
|
+
// Maps every non-color control to its CSS variable and optional unit suffix
|
|
33
|
+
const CTRL_CSS_MAP = {
|
|
34
|
+
'ctrl-font-family': { cssVar: '--md-font-family' },
|
|
35
|
+
'ctrl-base-size-num': { cssVar: '--md-base-size', suffix: 'px' },
|
|
36
|
+
'ctrl-line-height-num': { cssVar: '--md-line-height' },
|
|
37
|
+
'ctrl-h-font-family': { cssVar: '--md-h-font-family' },
|
|
38
|
+
'ctrl-h-scale-num': { cssVar: '--md-h-scale' },
|
|
39
|
+
'ctrl-h-mb-num': { cssVar: '--md-h-margin-bottom', suffix: 'em' },
|
|
40
|
+
'ctrl-h1-size-num': { cssVar: '--md-h1-size', suffix: 'em' },
|
|
41
|
+
'ctrl-h1-weight': { cssVar: '--md-h1-weight' },
|
|
42
|
+
'ctrl-h2-size-num': { cssVar: '--md-h2-size', suffix: 'em' },
|
|
43
|
+
'ctrl-h2-weight': { cssVar: '--md-h2-weight' },
|
|
44
|
+
'ctrl-h3-size-num': { cssVar: '--md-h3-size', suffix: 'em' },
|
|
45
|
+
'ctrl-h3-weight': { cssVar: '--md-h3-weight' },
|
|
46
|
+
'ctrl-h4-size-num': { cssVar: '--md-h4-size', suffix: 'em' },
|
|
47
|
+
'ctrl-h4-weight': { cssVar: '--md-h4-weight' },
|
|
48
|
+
'ctrl-p-lh-num': { cssVar: '--md-p-line-height' },
|
|
49
|
+
'ctrl-p-mb-num': { cssVar: '--md-p-margin', template: '0 0 {v}em' },
|
|
50
|
+
'ctrl-link-color': { cssVar: '--md-link-color' },
|
|
51
|
+
'ctrl-link-decoration': { cssVar: '--md-link-decoration' },
|
|
52
|
+
'ctrl-code-font': { cssVar: '--md-code-font' },
|
|
53
|
+
'ctrl-code-bg': { cssVar: ['--md-code-bg', '--md-pre-bg'] },
|
|
54
|
+
'ctrl-code-color': { cssVar: '--md-code-color' },
|
|
55
|
+
'ctrl-bq-border-color': { cssVar: '--md-bq-border', compound: 'bq-border' },
|
|
56
|
+
'ctrl-bq-bw-num': { cssVar: '--md-bq-border', compound: 'bq-border' },
|
|
57
|
+
'ctrl-bq-size-num': { cssVar: '--md-bq-size', suffix: 'em' },
|
|
58
|
+
'ctrl-bq-color': { cssVar: '--md-bq-color' },
|
|
59
|
+
'ctrl-list-spacing-num': { cssVar: '--md-list-spacing', suffix: 'em' },
|
|
60
|
+
'ctrl-list-indent-num': { cssVar: '--md-list-indent', suffix: 'em' },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Range ↔ Number input pairs
|
|
64
|
+
const RANGE_NUM_PAIRS = [
|
|
65
|
+
['ctrl-base-size-range', 'ctrl-base-size-num'],
|
|
66
|
+
['ctrl-line-height-range', 'ctrl-line-height-num'],
|
|
67
|
+
['ctrl-h-scale-range', 'ctrl-h-scale-num'],
|
|
68
|
+
['ctrl-h-mb-range', 'ctrl-h-mb-num'],
|
|
69
|
+
['ctrl-h1-size-range', 'ctrl-h1-size-num'],
|
|
70
|
+
['ctrl-h2-size-range', 'ctrl-h2-size-num'],
|
|
71
|
+
['ctrl-h3-size-range', 'ctrl-h3-size-num'],
|
|
72
|
+
['ctrl-h4-size-range', 'ctrl-h4-size-num'],
|
|
73
|
+
['ctrl-p-lh-range', 'ctrl-p-lh-num'],
|
|
74
|
+
['ctrl-p-mb-range', 'ctrl-p-mb-num'],
|
|
75
|
+
['ctrl-bq-bw-range', 'ctrl-bq-bw-num'],
|
|
76
|
+
['ctrl-bq-size-range', 'ctrl-bq-size-num'],
|
|
77
|
+
['ctrl-list-spacing-range', 'ctrl-list-spacing-num'],
|
|
78
|
+
['ctrl-list-indent-range', 'ctrl-list-indent-num'],
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
// ═══════════════════════════════════════════════════════
|
|
82
|
+
// PURE FUNCTIONS
|
|
83
|
+
// ═══════════════════════════════════════════════════════
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* controlToCssVars(ctrlId, value, allValues)
|
|
87
|
+
* Returns [{ cssVar, value }] — the CSS property assignments for a control change.
|
|
88
|
+
*/
|
|
89
|
+
function controlToCssVars(ctrlId, value, allValues) {
|
|
90
|
+
// Color controls go through the cascade system, not CTRL_CSS_MAP
|
|
91
|
+
if (COLOR_VAR_MAP[ctrlId]) {
|
|
92
|
+
return [{ cssVar: COLOR_VAR_MAP[ctrlId], value: value }];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const entry = CTRL_CSS_MAP[ctrlId];
|
|
96
|
+
if (!entry) return [];
|
|
97
|
+
|
|
98
|
+
// Compound: bq-border combines color + width
|
|
99
|
+
if (entry.compound === 'bq-border') {
|
|
100
|
+
const col = allValues['ctrl-bq-border-color'] || '#2563EB';
|
|
101
|
+
const w = allValues['ctrl-bq-bw-num'] || '3';
|
|
102
|
+
return [{ cssVar: '--md-bq-border', value: `${w}px solid ${col}` }];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Template: p-margin uses "0 0 {v}em"
|
|
106
|
+
if (entry.template) {
|
|
107
|
+
const formatted = entry.template.replace('{v}', value);
|
|
108
|
+
return [{ cssVar: entry.cssVar, value: formatted }];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Array of CSS vars (e.g. code-bg sets both --md-code-bg and --md-pre-bg)
|
|
112
|
+
if (Array.isArray(entry.cssVar)) {
|
|
113
|
+
return entry.cssVar.map(cv => ({ cssVar: cv, value: value + (entry.suffix || '') }));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Simple: value + optional suffix
|
|
117
|
+
return [{ cssVar: entry.cssVar, value: value + (entry.suffix || '') }];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* cascadeColor(ctrlId, value, overridden)
|
|
122
|
+
* Returns { ctrlId: value } — all controls that should be updated.
|
|
123
|
+
* Propagates to non-overridden children recursively.
|
|
124
|
+
*/
|
|
125
|
+
function cascadeColor(ctrlId, value, overridden) {
|
|
126
|
+
const updates = {};
|
|
127
|
+
updates[ctrlId] = value;
|
|
128
|
+
for (const childId of (COLOR_CASCADE[ctrlId] || [])) {
|
|
129
|
+
if (!overridden.has(childId)) {
|
|
130
|
+
const childUpdates = cascadeColor(childId, value, overridden);
|
|
131
|
+
Object.assign(updates, childUpdates);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return updates;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* collectStyles(values, overriddenColors)
|
|
139
|
+
* Takes a plain { controlId: value } object + the overridden set.
|
|
140
|
+
* Returns the styles object for YAML serialization.
|
|
141
|
+
*/
|
|
142
|
+
function collectStyles(values, overriddenColors) {
|
|
143
|
+
const gv = id => values[id] || '';
|
|
144
|
+
const gn = id => parseFloat(values[id]) || 0;
|
|
145
|
+
|
|
146
|
+
const styles = {
|
|
147
|
+
fontFamily: gv('ctrl-font-family').replace(/['"]/g, '').split(',')[0].trim(),
|
|
148
|
+
baseFontSize: gn('ctrl-base-size-num'),
|
|
149
|
+
lineHeight: gn('ctrl-line-height-num'),
|
|
150
|
+
headers: {
|
|
151
|
+
fontFamily: gv('ctrl-h-font-family').replace(/['"]/g, '').split(',')[0].trim(),
|
|
152
|
+
scale: gn('ctrl-h-scale-num'),
|
|
153
|
+
marginBottom: gn('ctrl-h-mb-num'),
|
|
154
|
+
},
|
|
155
|
+
h1: { fontSize: gn('ctrl-h1-size-num'), fontWeight: parseInt(gv('ctrl-h1-weight')) || 0 },
|
|
156
|
+
h2: { fontSize: gn('ctrl-h2-size-num'), fontWeight: parseInt(gv('ctrl-h2-weight')) || 0 },
|
|
157
|
+
h3: { fontSize: gn('ctrl-h3-size-num'), fontWeight: parseInt(gv('ctrl-h3-weight')) || 0 },
|
|
158
|
+
h4: { fontSize: gn('ctrl-h4-size-num'), fontWeight: parseInt(gv('ctrl-h4-weight')) || 0 },
|
|
159
|
+
p: {
|
|
160
|
+
lineHeight: gn('ctrl-p-lh-num'),
|
|
161
|
+
marginBottom: gn('ctrl-p-mb-num'),
|
|
162
|
+
},
|
|
163
|
+
link: { color: gv('ctrl-link-color'), decoration: gv('ctrl-link-decoration') },
|
|
164
|
+
code: {
|
|
165
|
+
font: gv('ctrl-code-font').replace(/['"]/g, '').split(',')[0].trim(),
|
|
166
|
+
background: gv('ctrl-code-bg'),
|
|
167
|
+
color: gv('ctrl-code-color'),
|
|
168
|
+
},
|
|
169
|
+
blockquote: {
|
|
170
|
+
borderColor: gv('ctrl-bq-border-color'),
|
|
171
|
+
borderWidth: gn('ctrl-bq-bw-num'),
|
|
172
|
+
fontSize: gn('ctrl-bq-size-num'),
|
|
173
|
+
color: gv('ctrl-bq-color'),
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
styles.list = {
|
|
178
|
+
spacing: gn('ctrl-list-spacing-num'),
|
|
179
|
+
indent: gn('ctrl-list-indent-num'),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Only emit cascade colors that were explicitly overridden
|
|
183
|
+
if (overriddenColors.has('ctrl-color')) styles.color = gv('ctrl-color');
|
|
184
|
+
if (overriddenColors.has('ctrl-h-color')) styles.headers.color = gv('ctrl-h-color');
|
|
185
|
+
if (overriddenColors.has('ctrl-h1-color')) styles.h1.color = gv('ctrl-h1-color');
|
|
186
|
+
if (overriddenColors.has('ctrl-h2-color')) styles.h2.color = gv('ctrl-h2-color');
|
|
187
|
+
if (overriddenColors.has('ctrl-h3-color')) styles.h3.color = gv('ctrl-h3-color');
|
|
188
|
+
if (overriddenColors.has('ctrl-h4-color')) styles.h4.color = gv('ctrl-h4-color');
|
|
189
|
+
if (overriddenColors.has('ctrl-p-color')) styles.p.color = gv('ctrl-p-color');
|
|
190
|
+
if (overriddenColors.has('ctrl-list-color')) styles.list.color = gv('ctrl-list-color');
|
|
191
|
+
|
|
192
|
+
return styles;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* stylesToControls(styles)
|
|
197
|
+
* Inverse of collectStyles. Takes a styles object (from YAML front matter).
|
|
198
|
+
* Returns { controls: { controlId: value }, overriddenColors: Set }.
|
|
199
|
+
*/
|
|
200
|
+
function stylesToControls(styles) {
|
|
201
|
+
if (!styles) return { controls: {}, overriddenColors: new Set() };
|
|
202
|
+
|
|
203
|
+
const controls = {};
|
|
204
|
+
const overridden = new Set();
|
|
205
|
+
|
|
206
|
+
if (styles.fontFamily) controls['ctrl-font-family'] = styles.fontFamily;
|
|
207
|
+
if (styles.baseFontSize) controls['ctrl-base-size-num'] = styles.baseFontSize;
|
|
208
|
+
if (styles.lineHeight) controls['ctrl-line-height-num'] = styles.lineHeight;
|
|
209
|
+
|
|
210
|
+
if (styles.color) {
|
|
211
|
+
controls['ctrl-color'] = styles.color;
|
|
212
|
+
overridden.add('ctrl-color');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const h = styles.headers || {};
|
|
216
|
+
if (h.fontFamily) controls['ctrl-h-font-family'] = h.fontFamily;
|
|
217
|
+
if (h.scale) controls['ctrl-h-scale-num'] = h.scale;
|
|
218
|
+
if (h.marginBottom) controls['ctrl-h-mb-num'] = h.marginBottom;
|
|
219
|
+
if (h.color) {
|
|
220
|
+
controls['ctrl-h-color'] = h.color;
|
|
221
|
+
overridden.add('ctrl-h-color');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
['h1', 'h2', 'h3', 'h4'].forEach(t => {
|
|
225
|
+
const hs = styles[t] || {};
|
|
226
|
+
if (hs.fontSize) controls[`ctrl-${t}-size-num`] = hs.fontSize;
|
|
227
|
+
if (hs.fontWeight) controls[`ctrl-${t}-weight`] = String(hs.fontWeight);
|
|
228
|
+
if (hs.color) {
|
|
229
|
+
controls[`ctrl-${t}-color`] = hs.color;
|
|
230
|
+
overridden.add(`ctrl-${t}-color`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const p = styles.p || {};
|
|
235
|
+
if (p.lineHeight) controls['ctrl-p-lh-num'] = p.lineHeight;
|
|
236
|
+
if (p.marginBottom) controls['ctrl-p-mb-num'] = p.marginBottom;
|
|
237
|
+
if (p.color) {
|
|
238
|
+
controls['ctrl-p-color'] = p.color;
|
|
239
|
+
overridden.add('ctrl-p-color');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const lk = styles.link || {};
|
|
243
|
+
if (lk.color) controls['ctrl-link-color'] = lk.color;
|
|
244
|
+
if (lk.decoration) controls['ctrl-link-decoration'] = lk.decoration;
|
|
245
|
+
|
|
246
|
+
const cd = styles.code || {};
|
|
247
|
+
if (cd.font) controls['ctrl-code-font'] = cd.font;
|
|
248
|
+
if (cd.background) controls['ctrl-code-bg'] = cd.background;
|
|
249
|
+
if (cd.color) controls['ctrl-code-color'] = cd.color;
|
|
250
|
+
|
|
251
|
+
const bq = styles.blockquote || {};
|
|
252
|
+
if (bq.borderColor) controls['ctrl-bq-border-color'] = bq.borderColor;
|
|
253
|
+
if (bq.borderWidth) controls['ctrl-bq-bw-num'] = bq.borderWidth;
|
|
254
|
+
if (bq.fontSize) controls['ctrl-bq-size-num'] = bq.fontSize;
|
|
255
|
+
if (bq.color) controls['ctrl-bq-color'] = bq.color;
|
|
256
|
+
|
|
257
|
+
const ll = styles.list || {};
|
|
258
|
+
if (ll.spacing) controls['ctrl-list-spacing-num'] = ll.spacing;
|
|
259
|
+
if (ll.indent) controls['ctrl-list-indent-num'] = ll.indent;
|
|
260
|
+
if (ll.color) {
|
|
261
|
+
controls['ctrl-list-color'] = ll.color;
|
|
262
|
+
overridden.add('ctrl-list-color');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return { controls, overriddenColors: overridden };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ═══════════════════════════════════════════════════════
|
|
269
|
+
// EXPORTS
|
|
270
|
+
// ═══════════════════════════════════════════════════════
|
|
271
|
+
exports.COLOR_DEFAULT = COLOR_DEFAULT;
|
|
272
|
+
exports.COLOR_VAR_MAP = COLOR_VAR_MAP;
|
|
273
|
+
exports.COLOR_CASCADE = COLOR_CASCADE;
|
|
274
|
+
exports.CTRL_CSS_MAP = CTRL_CSS_MAP;
|
|
275
|
+
exports.RANGE_NUM_PAIRS = RANGE_NUM_PAIRS;
|
|
276
|
+
|
|
277
|
+
exports.controlToCssVars = controlToCssVars;
|
|
278
|
+
exports.cascadeColor = cascadeColor;
|
|
279
|
+
exports.collectStyles = collectStyles;
|
|
280
|
+
exports.stylesToControls = stylesToControls;
|
|
281
|
+
|
|
282
|
+
// UMD tail: in Node (tests) this writes to module.exports; in the browser
|
|
283
|
+
// it creates window.SDocStyles. We use this pattern instead of ES modules
|
|
284
|
+
// because index.html is a single-file inline-script app with no build step.
|
|
285
|
+
})(typeof module !== 'undefined' && module.exports ? module.exports : (window.SDocStyles = {}));
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// sdocs-theme.js — Theme, fonts, and dark mode
|
|
2
|
+
(function () {
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
var S = SDocs;
|
|
6
|
+
|
|
7
|
+
// ── Google Fonts ──────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const GOOGLE_FONTS = [
|
|
10
|
+
'Inter', 'Roboto', 'Open Sans', 'Lato', 'Montserrat', 'Source Sans 3',
|
|
11
|
+
'Oswald', 'Raleway', 'Poppins', 'Merriweather', 'Ubuntu',
|
|
12
|
+
'Nunito', 'Playfair Display', 'Roboto Slab', 'PT Sans', 'Lora',
|
|
13
|
+
'Mulish', 'Noto Sans', 'Rubik', 'Dosis',
|
|
14
|
+
'Josefin Sans', 'PT Serif', 'Libre Franklin', 'Crimson Text'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const loadedFonts = new Set(['Inter']);
|
|
18
|
+
|
|
19
|
+
function loadGoogleFont(family) {
|
|
20
|
+
if (loadedFonts.has(family)) return;
|
|
21
|
+
loadedFonts.add(family);
|
|
22
|
+
const link = document.createElement('link');
|
|
23
|
+
link.rel = 'stylesheet';
|
|
24
|
+
link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@400;600;700&display=swap`;
|
|
25
|
+
document.head.appendChild(link);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function populateFontSelect(sel, includeInherit) {
|
|
29
|
+
if (includeInherit) {
|
|
30
|
+
const opt = document.createElement('option');
|
|
31
|
+
opt.value = 'inherit';
|
|
32
|
+
opt.textContent = '— Same as body —';
|
|
33
|
+
sel.appendChild(opt);
|
|
34
|
+
}
|
|
35
|
+
GOOGLE_FONTS.forEach(f => {
|
|
36
|
+
const opt = document.createElement('option');
|
|
37
|
+
opt.value = `'${f}', sans-serif`;
|
|
38
|
+
opt.textContent = f;
|
|
39
|
+
sel.appendChild(opt);
|
|
40
|
+
});
|
|
41
|
+
[['Georgia, serif','Georgia'],['Times New Roman, serif','Times New Roman'],
|
|
42
|
+
['system-ui, sans-serif','System UI']].forEach(([v,l]) => {
|
|
43
|
+
const opt = document.createElement('option');
|
|
44
|
+
opt.value = v; opt.textContent = l;
|
|
45
|
+
sel.appendChild(opt);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Dark mode ──────────────────────────────────
|
|
50
|
+
|
|
51
|
+
const LIGHT_DEFAULTS = {
|
|
52
|
+
colorDefault: '#1c1917',
|
|
53
|
+
codeBg: '#f4f1ed',
|
|
54
|
+
codeColor: '#6b21a8',
|
|
55
|
+
linkColor: '#2563eb',
|
|
56
|
+
bqBorderColor: '#2563eb',
|
|
57
|
+
bqColor: '#6b6560',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const DARK_DEFAULTS = {
|
|
61
|
+
colorDefault: '#e7e5e2',
|
|
62
|
+
codeBg: '#1a1816',
|
|
63
|
+
codeColor: '#b8a99a',
|
|
64
|
+
linkColor: '#60a5fa',
|
|
65
|
+
bqBorderColor: '#60a5fa',
|
|
66
|
+
bqColor: '#a8a29e',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function getThemeDefaults() {
|
|
70
|
+
return document.documentElement.dataset.theme === 'dark' ? DARK_DEFAULTS : LIGHT_DEFAULTS;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getColorDefault() {
|
|
74
|
+
return getThemeDefaults().colorDefault;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getPreferredTheme() {
|
|
78
|
+
const stored = localStorage.getItem('sdocs-theme');
|
|
79
|
+
if (stored === 'dark' || stored === 'light') return stored;
|
|
80
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const SUN_ICON = '<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>';
|
|
84
|
+
const MOON_ICON = '<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>';
|
|
85
|
+
|
|
86
|
+
function updateThemeIcon(theme) {
|
|
87
|
+
const icon = document.getElementById('icon-theme');
|
|
88
|
+
if (icon) icon.innerHTML = theme === 'dark' ? SUN_ICON : MOON_ICON;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function applyTheme(theme) {
|
|
92
|
+
document.documentElement.dataset.theme = theme;
|
|
93
|
+
localStorage.setItem('sdocs-theme', theme);
|
|
94
|
+
updateThemeIcon(theme);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function toggleTheme() {
|
|
98
|
+
const current = document.documentElement.dataset.theme;
|
|
99
|
+
applyTheme(current === 'dark' ? 'light' : 'dark');
|
|
100
|
+
updateDefaultColors();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function updateDefaultColors() {
|
|
104
|
+
const defaults = getThemeDefaults();
|
|
105
|
+
if (!S.overriddenColors) return;
|
|
106
|
+
if (!S.overriddenColors.has('ctrl-color')) {
|
|
107
|
+
S.setColorValue('ctrl-color', defaults.colorDefault, false);
|
|
108
|
+
}
|
|
109
|
+
// Update standalone color defaults for reset buttons
|
|
110
|
+
const standaloneMap = {
|
|
111
|
+
'ctrl-link-color': defaults.linkColor,
|
|
112
|
+
'ctrl-code-bg': defaults.codeBg,
|
|
113
|
+
'ctrl-code-color': defaults.codeColor,
|
|
114
|
+
'ctrl-bq-border-color': defaults.bqBorderColor,
|
|
115
|
+
'ctrl-bq-color': defaults.bqColor,
|
|
116
|
+
};
|
|
117
|
+
for (const [ctrlId, val] of Object.entries(standaloneMap)) {
|
|
118
|
+
if (!S.overriddenColors.has(ctrlId)) {
|
|
119
|
+
const el = document.getElementById(ctrlId);
|
|
120
|
+
if (el) {
|
|
121
|
+
el.value = val;
|
|
122
|
+
const allVals = S.readAllControlValues();
|
|
123
|
+
SDocStyles.controlToCssVars(ctrlId, val, allVals)
|
|
124
|
+
.forEach(a => S.setStyleVar(a.cssVar, a.value));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
S.syncAll('controls');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getStandaloneDefault(ctrlId) {
|
|
132
|
+
const d = getThemeDefaults();
|
|
133
|
+
const map = {
|
|
134
|
+
'ctrl-link-color': d.linkColor,
|
|
135
|
+
'ctrl-code-bg': d.codeBg,
|
|
136
|
+
'ctrl-code-color': d.codeColor,
|
|
137
|
+
'ctrl-bq-border-color': d.bqBorderColor,
|
|
138
|
+
'ctrl-bq-color': d.bqColor,
|
|
139
|
+
};
|
|
140
|
+
return map[ctrlId];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Init at load time ──────────────────────────────────
|
|
144
|
+
|
|
145
|
+
applyTheme(getPreferredTheme());
|
|
146
|
+
|
|
147
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
|
148
|
+
if (!localStorage.getItem('sdocs-theme')) {
|
|
149
|
+
applyTheme(e.matches ? 'dark' : 'light');
|
|
150
|
+
updateDefaultColors();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
populateFontSelect(document.getElementById('ctrl-font-family'), false);
|
|
155
|
+
document.getElementById('ctrl-font-family').value = "'Inter', sans-serif";
|
|
156
|
+
populateFontSelect(document.getElementById('ctrl-h-font-family'), true);
|
|
157
|
+
|
|
158
|
+
// ── Register on SDocs for cross-module access ──────────
|
|
159
|
+
|
|
160
|
+
S.GOOGLE_FONTS = GOOGLE_FONTS;
|
|
161
|
+
S.loadGoogleFont = loadGoogleFont;
|
|
162
|
+
S.getThemeDefaults = getThemeDefaults;
|
|
163
|
+
S.getColorDefault = getColorDefault;
|
|
164
|
+
S.getStandaloneDefault = getStandaloneDefault;
|
|
165
|
+
S.updateDefaultColors = updateDefaultColors;
|
|
166
|
+
S.toggleTheme = toggleTheme;
|
|
167
|
+
|
|
168
|
+
})();
|