sdocs-dev 1.0.0 → 1.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/bin/sdocs-dev.js +69 -13
- package/package.json +1 -1
- package/public/css/layout.css +1 -0
- package/public/css/panel.css +45 -0
- package/public/css/rendered.css +7 -1
- package/public/css/tokens.css +1 -0
- package/public/default.md +100 -3
- package/public/index.html +103 -50
- package/public/sdocs-app.js +10 -0
- package/public/sdocs-controls.js +72 -8
- package/public/sdocs-export.js +26 -8
- package/public/sdocs-state.js +19 -3
- package/public/sdocs-styles.js +271 -5
- package/public/sdocs-theme.js +92 -6
- package/public/sdocs-yaml.js +45 -22
package/public/sdocs-theme.js
CHANGED
|
@@ -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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
})();
|
package/public/sdocs-yaml.js
CHANGED
|
@@ -19,30 +19,33 @@ function parseInlineObject(str) {
|
|
|
19
19
|
return obj;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function
|
|
22
|
+
function parseBlock(lines, startIdx, indent) {
|
|
23
23
|
const result = {};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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)}`); }
|