sdocs-dev 1.2.0 → 1.3.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.
- package/README.md +149 -0
- package/bin/sdocs-dev.js +282 -38
- package/package.json +13 -11
- package/public/sdocs-slugify.js +12 -0
- package/public/sdocs-styles.js +279 -353
- package/public/brotli-wasm.js +0 -519
- package/public/brotli_wasm_bg.wasm +0 -0
- package/public/css/layout.css +0 -241
- package/public/css/mobile.css +0 -189
- package/public/css/panel.css +0 -393
- package/public/css/rendered.css +0 -295
- package/public/css/tokens.css +0 -116
- package/public/css/write.css +0 -128
- package/public/default.md +0 -255
- package/public/fonts/inter-400.woff2 +0 -0
- package/public/fonts/inter-500.woff2 +0 -0
- package/public/fonts/inter-600.woff2 +0 -0
- package/public/images/examples.png +0 -0
- package/public/index.html +0 -600
- package/public/sdocs-app.js +0 -653
- package/public/sdocs-controls.js +0 -272
- package/public/sdocs-export.js +0 -212
- package/public/sdocs-state.js +0 -59
- package/public/sdocs-theme.js +0 -260
- package/public/sdocs-write.js +0 -737
- package/public/sw.js +0 -108
- package/public/vendor/marked.min.js +0 -6
- package/server.js +0 -128
package/public/sdocs-controls.js
DELETED
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
// sdocs-controls.js — CSS variable management and control wiring
|
|
2
|
-
(function () {
|
|
3
|
-
'use strict';
|
|
4
|
-
|
|
5
|
-
var S = SDocs;
|
|
6
|
-
|
|
7
|
-
// ── CSS var updates ──────────────────────────────────
|
|
8
|
-
|
|
9
|
-
function applyCtrl(id) {
|
|
10
|
-
var el = document.getElementById(id);
|
|
11
|
-
if (!el) return;
|
|
12
|
-
var v = el.value;
|
|
13
|
-
|
|
14
|
-
if (id === 'ctrl-font-family' || id === 'ctrl-h-font-family') {
|
|
15
|
-
var name = v.replace(/['"]/g,'').split(',')[0].trim();
|
|
16
|
-
if (S.GOOGLE_FONTS.includes(name)) S.loadGoogleFont(name);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
var allVals = readAllControlValues();
|
|
20
|
-
SDocStyles.controlToCssVars(id, v, allVals)
|
|
21
|
-
.forEach(function(a) { S.setStyleVar(a.cssVar, a.value); });
|
|
22
|
-
S.syncAll('controls');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function readAllControlValues() {
|
|
26
|
-
var vals = {};
|
|
27
|
-
Object.keys(SDocStyles.CTRL_CSS_MAP).forEach(function(id) {
|
|
28
|
-
var el = document.getElementById(id);
|
|
29
|
-
if (el) vals[id] = el.value;
|
|
30
|
-
});
|
|
31
|
-
Object.keys(SDocStyles.COLOR_VAR_MAP).forEach(function(id) {
|
|
32
|
-
var el = document.getElementById(id);
|
|
33
|
-
if (el) vals[id] = el.value;
|
|
34
|
-
});
|
|
35
|
-
return vals;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function linkRangeNum(rangeId, numId) {
|
|
39
|
-
var r = document.getElementById(rangeId);
|
|
40
|
-
var n = document.getElementById(numId);
|
|
41
|
-
r.addEventListener('input', function() { n.value = r.value; applyCtrl(numId); });
|
|
42
|
-
n.addEventListener('input', function() { r.value = n.value; applyCtrl(numId); });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
SDocStyles.RANGE_NUM_PAIRS.forEach(function(pair) { linkRangeNum(pair[0], pair[1]); });
|
|
46
|
-
|
|
47
|
-
var STANDALONE_COLOR_IDS = new Set(SDocStyles.STANDALONE_COLOR_IDS);
|
|
48
|
-
|
|
49
|
-
[
|
|
50
|
-
'ctrl-font-family','ctrl-h-font-family',
|
|
51
|
-
'ctrl-h1-weight','ctrl-h2-weight','ctrl-h3-weight','ctrl-h4-weight',
|
|
52
|
-
'ctrl-bg-color','ctrl-link-color','ctrl-link-decoration',
|
|
53
|
-
'ctrl-code-font','ctrl-code-bg','ctrl-code-color',
|
|
54
|
-
'ctrl-bq-border-color','ctrl-bq-bg','ctrl-bq-color',
|
|
55
|
-
].forEach(function(id) {
|
|
56
|
-
var handler = function() { if (STANDALONE_COLOR_IDS.has(id)) S.overriddenColors.add(id); applyCtrl(id); };
|
|
57
|
-
document.getElementById(id).addEventListener('input', handler);
|
|
58
|
-
document.getElementById(id).addEventListener('change', handler);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// ── Color cascade ──────────────────────────────────
|
|
62
|
-
|
|
63
|
-
var COLOR_VAR = SDocStyles.COLOR_VAR_MAP;
|
|
64
|
-
var COLOR_CHILDREN = SDocStyles.COLOR_CASCADE;
|
|
65
|
-
|
|
66
|
-
function setColorValue(ctrlId, value, userAction) {
|
|
67
|
-
if (userAction) S.overriddenColors.add(ctrlId);
|
|
68
|
-
S.setStyleVar(COLOR_VAR[ctrlId], value);
|
|
69
|
-
var ctrl = document.getElementById(ctrlId);
|
|
70
|
-
if (ctrl) ctrl.value = value;
|
|
71
|
-
var children = COLOR_CHILDREN[ctrlId] || [];
|
|
72
|
-
for (var i = 0; i < children.length; i++) {
|
|
73
|
-
if (!S.overriddenColors.has(children[i])) {
|
|
74
|
-
setColorValue(children[i], value, false);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function resetColorValue(ctrlId) {
|
|
80
|
-
S.overriddenColors.delete(ctrlId);
|
|
81
|
-
setColorValue(ctrlId, S.getColorDefault(), false);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
Object.keys(COLOR_VAR).forEach(function(ctrlId) {
|
|
85
|
-
var el = document.getElementById(ctrlId);
|
|
86
|
-
if (!el) return;
|
|
87
|
-
el.addEventListener('input', function() { setColorValue(ctrlId, el.value, true); S.syncAll('controls'); });
|
|
88
|
-
el.addEventListener('change', function() { setColorValue(ctrlId, el.value, true); S.syncAll('controls'); });
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
document.getElementById('reset-color').addEventListener('click', function() { S.overriddenColors.delete('ctrl-color'); setColorValue('ctrl-color', S.getColorDefault(), false); S.syncAll('controls'); });
|
|
92
|
-
document.getElementById('reset-h-color').addEventListener('click', function() { resetColorValue('ctrl-h-color'); S.syncAll('controls'); });
|
|
93
|
-
document.getElementById('reset-h1-color').addEventListener('click', function() { resetColorValue('ctrl-h1-color'); S.syncAll('controls'); });
|
|
94
|
-
document.getElementById('reset-h2-color').addEventListener('click', function() { resetColorValue('ctrl-h2-color'); S.syncAll('controls'); });
|
|
95
|
-
document.getElementById('reset-h3-color').addEventListener('click', function() { resetColorValue('ctrl-h3-color'); S.syncAll('controls'); });
|
|
96
|
-
document.getElementById('reset-h4-color').addEventListener('click', function() { resetColorValue('ctrl-h4-color'); S.syncAll('controls'); });
|
|
97
|
-
document.getElementById('reset-p-color').addEventListener('click', function() { resetColorValue('ctrl-p-color'); S.syncAll('controls'); });
|
|
98
|
-
document.getElementById('reset-list-color').addEventListener('click', function() { resetColorValue('ctrl-list-color'); S.syncAll('controls'); });
|
|
99
|
-
|
|
100
|
-
['ctrl-bg-color','ctrl-link-color','ctrl-code-bg','ctrl-code-color','ctrl-bq-border-color','ctrl-bq-bg','ctrl-bq-color'].forEach(function(ctrlId) {
|
|
101
|
-
var btnId = 'reset-' + ctrlId.replace('ctrl-', '');
|
|
102
|
-
document.getElementById(btnId).addEventListener('click', function() {
|
|
103
|
-
var defaultVal = S.getStandaloneDefault(ctrlId);
|
|
104
|
-
var el = document.getElementById(ctrlId);
|
|
105
|
-
S.overriddenColors.delete(ctrlId);
|
|
106
|
-
el.value = defaultVal;
|
|
107
|
-
var assignments = SDocStyles.controlToCssVars(ctrlId, defaultVal, readAllControlValues());
|
|
108
|
-
assignments.forEach(function(a) { S.setStyleVar(a.cssVar, a.value); });
|
|
109
|
-
S.syncAll('controls');
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// ── Apply styles from meta → controls ──────────────────
|
|
114
|
-
|
|
115
|
-
function setCtrl(id, val) {
|
|
116
|
-
if (val === undefined || val === null) return;
|
|
117
|
-
var el = document.getElementById(id);
|
|
118
|
-
if (!el) return;
|
|
119
|
-
el.value = val;
|
|
120
|
-
var rangeId = id.replace(/-num$/, '-range');
|
|
121
|
-
var rng = document.getElementById(rangeId);
|
|
122
|
-
if (rng) rng.value = val;
|
|
123
|
-
applyCtrl(id);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function applyStylesFromMeta(s) {
|
|
127
|
-
if (!s) return;
|
|
128
|
-
// Suppress syncAll during batch updates to avoid capturing partial state
|
|
129
|
-
var wasSyncing = S._syncing;
|
|
130
|
-
S._syncing = true;
|
|
131
|
-
|
|
132
|
-
var result = SDocStyles.stylesToControls(s);
|
|
133
|
-
var controls = result.controls;
|
|
134
|
-
|
|
135
|
-
// Font family selects need special handling to match bare names against <option> values
|
|
136
|
-
[['fontFamily', 'ctrl-font-family'], ['headers.fontFamily', 'ctrl-h-font-family']].forEach(function(pair) {
|
|
137
|
-
var styleKey = pair[0], ctrlId = pair[1];
|
|
138
|
-
var fontName = styleKey === 'fontFamily' ? s.fontFamily : (s.headers || {}).fontFamily;
|
|
139
|
-
if (fontName) {
|
|
140
|
-
var sel = document.getElementById(ctrlId);
|
|
141
|
-
var match = [].slice.call(sel.options).find(function(o) {
|
|
142
|
-
return o.value.replace(/['"]/g,'').split(',')[0].trim() === fontName ||
|
|
143
|
-
o.textContent === fontName;
|
|
144
|
-
});
|
|
145
|
-
if (match) { sel.value = match.value; applyCtrl(ctrlId); }
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
Object.keys(controls).forEach(function(id) {
|
|
150
|
-
if (id === 'ctrl-font-family' || id === 'ctrl-h-font-family') return;
|
|
151
|
-
if (SDocStyles.COLOR_VAR_MAP[id]) return;
|
|
152
|
-
setCtrl(id, controls[id]);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
if (result.hasThemeColors) {
|
|
156
|
-
// Per-theme color mode: populate both theme states
|
|
157
|
-
S.themeColors.light = result.lightColors || {};
|
|
158
|
-
S.themeColors.dark = result.darkColors || {};
|
|
159
|
-
S.themeOverridden.light = result.lightOverridden || new Set();
|
|
160
|
-
S.themeOverridden.dark = result.darkOverridden || new Set();
|
|
161
|
-
|
|
162
|
-
// Load the active theme's colors into controls
|
|
163
|
-
S._syncing = wasSyncing;
|
|
164
|
-
S.loadThemeColors(S.activeTheme);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Legacy single-theme path: top-level colors go to light theme only,
|
|
169
|
-
// then loadThemeColors applies the correct theme's colors.
|
|
170
|
-
var lightOverridden = S.themeOverridden.light;
|
|
171
|
-
var lightColors = S.themeColors.light;
|
|
172
|
-
var newOverridden = result.overriddenColors;
|
|
173
|
-
|
|
174
|
-
lightOverridden.clear();
|
|
175
|
-
S.themeOverridden.dark.clear();
|
|
176
|
-
|
|
177
|
-
newOverridden.forEach(function(id) {
|
|
178
|
-
lightOverridden.add(id);
|
|
179
|
-
lightColors[id] = controls[id];
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// Also store standalone colors from legacy format into light theme
|
|
183
|
-
var legacyStandalone = [];
|
|
184
|
-
if (s.background) legacyStandalone.push(['ctrl-bg-color', s.background]);
|
|
185
|
-
if (s.link && s.link.color) legacyStandalone.push(['ctrl-link-color', s.link.color]);
|
|
186
|
-
if (s.code) {
|
|
187
|
-
if (s.code.background) legacyStandalone.push(['ctrl-code-bg', s.code.background]);
|
|
188
|
-
if (s.code.color) legacyStandalone.push(['ctrl-code-color', s.code.color]);
|
|
189
|
-
}
|
|
190
|
-
if (s.blockquote) {
|
|
191
|
-
if (s.blockquote.borderColor) legacyStandalone.push(['ctrl-bq-border-color', s.blockquote.borderColor]);
|
|
192
|
-
if (s.blockquote.background) legacyStandalone.push(['ctrl-bq-bg', s.blockquote.background]);
|
|
193
|
-
if (s.blockquote.color) legacyStandalone.push(['ctrl-bq-color', s.blockquote.color]);
|
|
194
|
-
}
|
|
195
|
-
legacyStandalone.forEach(function(pair) {
|
|
196
|
-
lightOverridden.add(pair[0]);
|
|
197
|
-
lightColors[pair[0]] = pair[1];
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// Now apply the active theme's colors (light overrides or dark defaults)
|
|
201
|
-
S._syncing = wasSyncing;
|
|
202
|
-
S.loadThemeColors(S.activeTheme);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function collectStyles() {
|
|
206
|
-
S.saveCurrentThemeColors();
|
|
207
|
-
var lightHas = S.themeOverridden.light.size > 0;
|
|
208
|
-
var darkHas = S.themeOverridden.dark.size > 0;
|
|
209
|
-
if (lightHas || darkHas) {
|
|
210
|
-
return SDocStyles.collectStylesDual(
|
|
211
|
-
readAllControlValues(),
|
|
212
|
-
S.themeOverridden.light, S.themeOverridden.dark,
|
|
213
|
-
S.themeColors.light, S.themeColors.dark
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
return SDocStyles.collectStyles(readAllControlValues(), S.overriddenColors);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ── Reset all styles ──────────────────────────────────
|
|
220
|
-
|
|
221
|
-
function resetAllStyles() {
|
|
222
|
-
// Clear both theme states
|
|
223
|
-
S.themeOverridden.light.clear();
|
|
224
|
-
S.themeOverridden.dark.clear();
|
|
225
|
-
S.themeColors.light = {};
|
|
226
|
-
S.themeColors.dark = {};
|
|
227
|
-
|
|
228
|
-
setColorValue('ctrl-color', S.getColorDefault(), false);
|
|
229
|
-
// Set standalone color controls to theme-appropriate defaults
|
|
230
|
-
STANDALONE_COLOR_IDS.forEach(function(ctrlId) {
|
|
231
|
-
var el = document.getElementById(ctrlId);
|
|
232
|
-
if (el) el.value = S.getStandaloneDefault(ctrlId);
|
|
233
|
-
});
|
|
234
|
-
document.querySelectorAll('#right input, #right select').forEach(function(el) {
|
|
235
|
-
if (STANDALONE_COLOR_IDS.has(el.id)) return; // already set above
|
|
236
|
-
if (el.type === 'range' || el.type === 'number') el.value = el.defaultValue;
|
|
237
|
-
else if (el.tagName === 'SELECT') el.selectedIndex = [].slice.call(el.options).findIndex(function(o) { return o.defaultSelected; });
|
|
238
|
-
else if (el.type === 'color') el.value = el.defaultValue;
|
|
239
|
-
});
|
|
240
|
-
['ctrl-bg-color','ctrl-font-family','ctrl-base-size-num','ctrl-line-height-num',
|
|
241
|
-
'ctrl-h-font-family','ctrl-h-scale-num','ctrl-h-mb-num',
|
|
242
|
-
'ctrl-h1-size-num','ctrl-h1-weight','ctrl-h2-size-num','ctrl-h2-weight',
|
|
243
|
-
'ctrl-h3-size-num','ctrl-h3-weight','ctrl-h4-size-num','ctrl-h4-weight',
|
|
244
|
-
'ctrl-p-lh-num','ctrl-p-mb-num',
|
|
245
|
-
'ctrl-link-color','ctrl-link-decoration',
|
|
246
|
-
'ctrl-code-font','ctrl-code-bg','ctrl-code-color',
|
|
247
|
-
'ctrl-bq-border-color','ctrl-bq-bg','ctrl-bq-bw-num','ctrl-bq-size-num','ctrl-bq-color',
|
|
248
|
-
'ctrl-list-spacing-num','ctrl-list-indent-num',
|
|
249
|
-
].forEach(function(id) { applyCtrl(id); });
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// ── Init: set colors to theme-appropriate defaults ──────
|
|
253
|
-
|
|
254
|
-
if (document.documentElement.dataset.theme === 'dark') {
|
|
255
|
-
STANDALONE_COLOR_IDS.forEach(function(ctrlId) {
|
|
256
|
-
var el = document.getElementById(ctrlId);
|
|
257
|
-
if (el) el.value = S.getStandaloneDefault(ctrlId);
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
setColorValue('ctrl-color', S.getColorDefault(), false);
|
|
262
|
-
|
|
263
|
-
// ── Register on SDocs for cross-module access ──────────
|
|
264
|
-
|
|
265
|
-
S.setColorValue = setColorValue;
|
|
266
|
-
S.readAllControlValues = readAllControlValues;
|
|
267
|
-
S.collectStyles = collectStyles;
|
|
268
|
-
S.applyStylesFromMeta = applyStylesFromMeta;
|
|
269
|
-
S.resetAllStyles = resetAllStyles;
|
|
270
|
-
S.STANDALONE_COLOR_IDS = STANDALONE_COLOR_IDS;
|
|
271
|
-
|
|
272
|
-
})();
|
package/public/sdocs-export.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
// sdocs-export.js — Export pipeline: CSS, HTML, PDF, Word, save-default
|
|
2
|
-
(function () {
|
|
3
|
-
'use strict';
|
|
4
|
-
|
|
5
|
-
var S = SDocs;
|
|
6
|
-
|
|
7
|
-
function cv(name) {
|
|
8
|
-
return (S.renderedEl.style.getPropertyValue(name) ||
|
|
9
|
-
getComputedStyle(S.renderedEl).getPropertyValue(name)).trim();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function buildExportCSS() {
|
|
13
|
-
var bgColor = cv('--md-bg') || '#ffffff';
|
|
14
|
-
var fontFamily = cv('--md-font-family') || "'Inter', sans-serif";
|
|
15
|
-
var baseSize = cv('--md-base-size') || '16px';
|
|
16
|
-
var lineHeight = cv('--md-line-height') || '1.75';
|
|
17
|
-
var color = cv('--md-color') || '#1c1917';
|
|
18
|
-
|
|
19
|
-
var hFontFamily = cv('--md-h-font-family') || 'inherit';
|
|
20
|
-
var hScale = parseFloat(cv('--md-h-scale') || '1');
|
|
21
|
-
var hMB = cv('--md-h-margin-bottom')|| '0.4em';
|
|
22
|
-
var hColor = cv('--md-h-color') || '#0f0d0c';
|
|
23
|
-
|
|
24
|
-
var h1Size = 'calc(' + (cv('--md-h1-size') || '2.1em') + ' * ' + hScale + ')';
|
|
25
|
-
var h1Color = cv('--md-h1-color') || hColor;
|
|
26
|
-
var h1Weight = cv('--md-h1-weight') || '700';
|
|
27
|
-
var h2Size = 'calc(' + (cv('--md-h2-size') || '1.55em') + ' * ' + hScale + ')';
|
|
28
|
-
var h2Color = cv('--md-h2-color') || hColor;
|
|
29
|
-
var h2Weight = cv('--md-h2-weight') || '600';
|
|
30
|
-
var h3Size = 'calc(' + (cv('--md-h3-size') || '1.2em') + ' * ' + hScale + ')';
|
|
31
|
-
var h3Color = cv('--md-h3-color') || hColor;
|
|
32
|
-
var h3Weight = cv('--md-h3-weight') || '600';
|
|
33
|
-
var h4Size = 'calc(' + (cv('--md-h4-size') || '1.0em') + ' * ' + hScale + ')';
|
|
34
|
-
var h4Color = cv('--md-h4-color') || hColor;
|
|
35
|
-
var h4Weight = cv('--md-h4-weight') || '600';
|
|
36
|
-
|
|
37
|
-
var pColor = cv('--md-p-color') || '#3c3733';
|
|
38
|
-
var pLH = cv('--md-p-line-height')|| lineHeight;
|
|
39
|
-
var pMargin = cv('--md-p-margin') || '0 0 1.1em';
|
|
40
|
-
|
|
41
|
-
var linkColor = cv('--md-link-color') || '#2563eb';
|
|
42
|
-
var linkDec = cv('--md-link-decoration') || 'underline';
|
|
43
|
-
|
|
44
|
-
var codeBG = cv('--md-code-bg') || '#f4f1ed';
|
|
45
|
-
var codeColor = cv('--md-code-color') || '#6b21a8';
|
|
46
|
-
var codeFont = cv('--md-code-font') || "'JetBrains Mono', monospace";
|
|
47
|
-
var preBG = cv('--md-pre-bg') || codeBG;
|
|
48
|
-
|
|
49
|
-
var bqBorder = cv('--md-bq-border') || '3px solid #2563eb';
|
|
50
|
-
var bqBg = cv('--md-bq-bg') || '#f7f5f2';
|
|
51
|
-
var bqColor = cv('--md-bq-color') || '#6b6560';
|
|
52
|
-
var bqPad = cv('--md-bq-padding') || '0.5em 1em';
|
|
53
|
-
var bqMargin = cv('--md-bq-margin') || '1.2em 0';
|
|
54
|
-
|
|
55
|
-
return '\nbody {\n font-family: ' + fontFamily + ';\n font-size: ' + baseSize + ';\n color: ' + color + ';\n line-height: ' + lineHeight + ';\n background-color: ' + bgColor + ';\n max-width: 720px;\n margin: 0 auto;\n padding: 40px 48px 60px;\n -webkit-font-smoothing: antialiased;\n}\nh1 { font-family: ' + hFontFamily + '; font-size: ' + h1Size + '; color: ' + h1Color + '; font-weight: ' + h1Weight + '; letter-spacing: -0.02em; line-height: 1.2; margin: 0 0 ' + hMB + '; }\nh2 { font-family: ' + hFontFamily + '; font-size: ' + h2Size + '; color: ' + h2Color + '; font-weight: ' + h2Weight + '; letter-spacing: -0.015em; line-height: 1.3; margin: 1.4em 0 ' + hMB + '; padding-bottom: 0.3em; border-bottom: 1px solid #ede8e2; }\nh3 { font-family: ' + hFontFamily + '; font-size: ' + h3Size + '; color: ' + h3Color + '; font-weight: ' + h3Weight + '; letter-spacing: -0.01em; line-height: 1.4; margin: 1.2em 0 ' + hMB + '; }\nh4 { font-family: ' + hFontFamily + '; font-size: ' + h4Size + '; color: ' + h4Color + '; font-weight: ' + h4Weight + '; line-height: 1.5; margin: 1em 0 ' + hMB + '; }\np { color: ' + pColor + '; line-height: ' + pLH + '; margin: ' + pMargin + '; }\na { color: ' + linkColor + '; text-decoration: ' + linkDec + '; text-underline-offset: 2px; }\ncode { background: ' + codeBG + '; color: ' + codeColor + '; padding: 0.15em 0.45em; border-radius: 4px; font-family: ' + codeFont + '; font-size: 0.85em; }\npre { background: ' + preBG + '; padding: 1.1em 1.25em; border-radius: 8px; overflow-x: auto; margin: 1.2em 0; border: 1px solid #e7e2db; }\npre code { background: none; padding: 0; color: #3c3733; font-size: 0.88em; }\nblockquote { border-left: ' + bqBorder + '; color: ' + bqColor + '; padding: ' + bqPad + '; margin: ' + bqMargin + '; background: ' + bqBg + '; border-radius: 0 6px 6px 0; }\nblockquote p { margin: 0; color: inherit; }\nul, ol { padding-left: 1.6em; margin: 0.5em 0 1.1em; }\nli { margin-bottom: 0.3em; }\nhr { border: none; border-top: 1px solid #ede8e2; margin: 2em 0; }\ntable { border-collapse: collapse; width: 100%; margin: 1.2em 0; font-size: 0.92em; }\nth, td { border: 1px solid #e2ddd6; padding: 7px 12px; text-align: left; }\nth { background: #f4f1ed; font-weight: 600; }\ntr:nth-child(even) td { background: #fafaf8; }\nimg { max-width: 100%; border-radius: 8px; }\n';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function buildExportHTML() {
|
|
59
|
-
var fontName = document.getElementById('ctrl-font-family').value.replace(/['"]/g,'').split(',')[0].trim();
|
|
60
|
-
var fontLink = S.GOOGLE_FONTS.includes(fontName)
|
|
61
|
-
? '<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=' + encodeURIComponent(fontName) + ':wght@400;500;600;700&display=swap">'
|
|
62
|
-
: '';
|
|
63
|
-
var title = (S.currentMeta.title || 'Document').replace(/</g,'<');
|
|
64
|
-
var clone = S.renderedEl.cloneNode(true);
|
|
65
|
-
clone.querySelectorAll('.section-toggle').forEach(function(el) { el.remove(); });
|
|
66
|
-
clone.querySelectorAll('.md-section-body').forEach(function(el) {
|
|
67
|
-
while (el.firstChild) el.parentNode.insertBefore(el.firstChild, el);
|
|
68
|
-
el.remove();
|
|
69
|
-
});
|
|
70
|
-
clone.querySelectorAll('.md-section').forEach(function(el) {
|
|
71
|
-
while (el.firstChild) el.parentNode.insertBefore(el.firstChild, el);
|
|
72
|
-
el.remove();
|
|
73
|
-
});
|
|
74
|
-
return '<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>' + title + '</title>\n' + fontLink + '\n<style>' + buildExportCSS() + '</style>\n</head>\n<body>\n' +
|
|
75
|
-
clone.innerHTML
|
|
76
|
-
.replace(/<button class="copy-btn"[^]*?<\/button>/g, '')
|
|
77
|
-
.replace(/<button class="header-copy-btn"[^]*?<\/button>/g, '')
|
|
78
|
-
.replace(/<div class="pre-wrapper">([\s\S]*?)<\/div>/g, '$1')
|
|
79
|
-
.replace(/<a class="header-anchor"[^]*?<\/a>/g, '') +
|
|
80
|
-
'\n</body>\n</html>';
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function exportPDF() {
|
|
84
|
-
S.setStatus('Opening print dialog\u2026');
|
|
85
|
-
var html = buildExportHTML();
|
|
86
|
-
var win = window.open('', '_blank', 'width=900,height=700');
|
|
87
|
-
win.document.open();
|
|
88
|
-
win.document.write(html + '<script>\n document.fonts.ready.then(() => {\n window.focus();\n window.print();\n });\n <\/script>');
|
|
89
|
-
win.document.close();
|
|
90
|
-
S.setStatus('PDF print dialog opened');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
var htmlDocxLoaded = false;
|
|
94
|
-
|
|
95
|
-
function loadHtmlDocx() {
|
|
96
|
-
return new Promise(function(resolve, reject) {
|
|
97
|
-
if (htmlDocxLoaded) { resolve(); return; }
|
|
98
|
-
var s = document.createElement('script');
|
|
99
|
-
s.src = 'https://cdn.jsdelivr.net/npm/html-docx-js@0.3.1/dist/html-docx.js';
|
|
100
|
-
s.onload = function() { htmlDocxLoaded = true; resolve(); };
|
|
101
|
-
s.onerror = function() { reject(new Error('Could not load html-docx-js')); };
|
|
102
|
-
document.head.appendChild(s);
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function exportWord() {
|
|
107
|
-
S.setStatus('Generating Word document\u2026');
|
|
108
|
-
try {
|
|
109
|
-
await loadHtmlDocx();
|
|
110
|
-
var html = buildExportHTML();
|
|
111
|
-
var blob = window.htmlDocx.asBlob(html, {
|
|
112
|
-
orientation: 'portrait',
|
|
113
|
-
margins: { top: 720, right: 900, bottom: 720, left: 900 },
|
|
114
|
-
});
|
|
115
|
-
var a = document.createElement('a');
|
|
116
|
-
a.href = URL.createObjectURL(blob);
|
|
117
|
-
a.download = (S.currentMeta.title || 'document').replace(/[^a-z0-9_-]/gi,'_') + '.docx';
|
|
118
|
-
a.click();
|
|
119
|
-
URL.revokeObjectURL(a.href);
|
|
120
|
-
S.setStatus('Exported Word .docx');
|
|
121
|
-
} catch (e) {
|
|
122
|
-
S.setStatus('Word export failed: ' + e.message);
|
|
123
|
-
console.error(e);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function download(filename, content) {
|
|
128
|
-
var a = document.createElement('a');
|
|
129
|
-
a.href = URL.createObjectURL(new Blob([content], { type: 'text/plain' }));
|
|
130
|
-
a.download = filename;
|
|
131
|
-
a.click();
|
|
132
|
-
URL.revokeObjectURL(a.href);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ── Export panel handlers ──────────────────────────────
|
|
136
|
-
|
|
137
|
-
document.getElementById('exp-pdf').addEventListener('click', exportPDF);
|
|
138
|
-
document.getElementById('exp-word').addEventListener('click', exportWord);
|
|
139
|
-
|
|
140
|
-
document.getElementById('exp-raw').addEventListener('click', function() {
|
|
141
|
-
download('document.md', S.currentBody);
|
|
142
|
-
S.setStatus('Exported raw .md');
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
document.getElementById('exp-styled').addEventListener('click', function() {
|
|
146
|
-
var meta = Object.assign({}, S.currentMeta, { styles: S.collectStyles() });
|
|
147
|
-
var fm = SDocYaml.serializeFrontMatter(meta);
|
|
148
|
-
download('document.md', fm + '\n' + S.currentBody);
|
|
149
|
-
S.setStatus('Exported styled .md with YAML front matter');
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// ── Save as default styles ──────────────────────────────
|
|
153
|
-
|
|
154
|
-
function formatStyleValue(k, v) {
|
|
155
|
-
if (typeof v !== 'object' || v === null) {
|
|
156
|
-
return k + ': ' + JSON.stringify(v);
|
|
157
|
-
}
|
|
158
|
-
// light/dark theme blocks: serialize as nested block (not inline)
|
|
159
|
-
if (k === 'light' || k === 'dark') {
|
|
160
|
-
var blockLines = [k + ':'];
|
|
161
|
-
Object.keys(v).forEach(function(sk) {
|
|
162
|
-
var sv = v[sk];
|
|
163
|
-
if (typeof sv === 'object' && sv !== null) {
|
|
164
|
-
var inner = Object.keys(sv).map(function(a) { return a + ': ' + JSON.stringify(sv[a]); }).join(', ');
|
|
165
|
-
blockLines.push(' ' + sk + ': { ' + inner + ' }');
|
|
166
|
-
} else {
|
|
167
|
-
blockLines.push(' ' + sk + ': ' + JSON.stringify(sv));
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
return blockLines.join('\n');
|
|
171
|
-
}
|
|
172
|
-
// Default: inline object
|
|
173
|
-
var inner = Object.keys(v).map(function(a) { return a + ': ' + JSON.stringify(v[a]); }).join(', ');
|
|
174
|
-
return k + ': { ' + inner + ' }';
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function buildStylesYaml() {
|
|
178
|
-
var styles = S.collectStyles();
|
|
179
|
-
var lines = [];
|
|
180
|
-
Object.keys(styles).forEach(function(k) {
|
|
181
|
-
lines.push(formatStyleValue(k, styles[k]));
|
|
182
|
-
});
|
|
183
|
-
return lines.join('\n');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
var saveDefaultCmd = '';
|
|
187
|
-
|
|
188
|
-
function refreshSaveDefaultPreview() {
|
|
189
|
-
var yaml = buildStylesYaml();
|
|
190
|
-
saveDefaultCmd = "mkdir -p ~/.sdocs && cat > ~/.sdocs/styles.yaml << 'SDOCS'\n" + yaml + '\nSDOCS';
|
|
191
|
-
document.getElementById('save-default-display').textContent = saveDefaultCmd;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
document.querySelector('[data-target="body-save-default"]').addEventListener('click', function() {
|
|
195
|
-
// Delay so the panel-body open class is toggled first
|
|
196
|
-
setTimeout(refreshSaveDefaultPreview, 0);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
document.getElementById('btn-copy-default').addEventListener('click', async function() {
|
|
200
|
-
refreshSaveDefaultPreview();
|
|
201
|
-
try {
|
|
202
|
-
await navigator.clipboard.writeText(saveDefaultCmd);
|
|
203
|
-
var msg = document.getElementById('save-default-msg');
|
|
204
|
-
msg.textContent = 'Copied! Paste in your terminal to save defaults.';
|
|
205
|
-
msg.style.display = 'block';
|
|
206
|
-
setTimeout(function() { msg.style.display = 'none'; }, 4000);
|
|
207
|
-
} catch (e) {
|
|
208
|
-
S.setStatus('Could not copy to clipboard');
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
})();
|
package/public/sdocs-state.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// sdocs-state.js — Shared mutable state namespace
|
|
2
|
-
// All modules read/write through window.SDocs
|
|
3
|
-
(function () {
|
|
4
|
-
'use strict';
|
|
5
|
-
|
|
6
|
-
window.SDocs = {
|
|
7
|
-
// Document state
|
|
8
|
-
currentBody: '',
|
|
9
|
-
currentMeta: {},
|
|
10
|
-
|
|
11
|
-
// Per-theme color state
|
|
12
|
-
activeTheme: 'light',
|
|
13
|
-
themeColors: { light: {}, dark: {} },
|
|
14
|
-
themeOverridden: { light: new Set(), dark: new Set() },
|
|
15
|
-
|
|
16
|
-
// DOM references
|
|
17
|
-
renderedEl: document.getElementById('rendered'),
|
|
18
|
-
rawEl: document.getElementById('raw'),
|
|
19
|
-
writeEl: null,
|
|
20
|
-
|
|
21
|
-
// Mode tracking
|
|
22
|
-
currentMode: 'read',
|
|
23
|
-
|
|
24
|
-
// Sync flags
|
|
25
|
-
_syncing: false,
|
|
26
|
-
_isDefaultState: true,
|
|
27
|
-
_hashTimer: null,
|
|
28
|
-
_rawSyncTimer: null,
|
|
29
|
-
_writeSyncTimer: null,
|
|
30
|
-
|
|
31
|
-
// Cross-module functions (registered by defining module)
|
|
32
|
-
// Theme: toggleTheme, getThemeDefaults, getColorDefault, getStandaloneDefault,
|
|
33
|
-
// loadGoogleFont, updateDefaultColors, GOOGLE_FONTS,
|
|
34
|
-
// switchThemeAndUpdate, saveCurrentThemeColors, loadThemeColors
|
|
35
|
-
// Controls: setColorValue, readAllControlValues, collectStyles,
|
|
36
|
-
// applyStylesFromMeta, resetAllStyles, STANDALONE_COLOR_IDS
|
|
37
|
-
// App: syncAll, setStatus, setMode, render, loadText
|
|
38
|
-
// Write: enterWriteMode, exitWriteMode
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// Backwards compat: S.overriddenColors delegates to active theme's set
|
|
42
|
-
Object.defineProperty(SDocs, 'overriddenColors', {
|
|
43
|
-
get: function() { return SDocs.themeOverridden[SDocs.activeTheme]; },
|
|
44
|
-
set: function(v) { SDocs.themeOverridden[SDocs.activeTheme] = v; },
|
|
45
|
-
enumerable: true,
|
|
46
|
-
configurable: true,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
SDocs.contentAreaEl = document.getElementById('content-area');
|
|
50
|
-
|
|
51
|
-
SDocs.setStyleVar = function(cssVar, value) {
|
|
52
|
-
SDocs.renderedEl.style.setProperty(cssVar, value);
|
|
53
|
-
if (SDocs.writeEl) SDocs.writeEl.style.setProperty(cssVar, value);
|
|
54
|
-
if (cssVar === '--md-bg') {
|
|
55
|
-
SDocs.contentAreaEl.style.setProperty('background-color', value);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
})();
|