sdocs-dev 1.3.0 → 1.4.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 +192 -25
- package/package.json +9 -10
- package/public/sdocs-styles.js +162 -162
- package/public/brotli-wasm-v1.js +0 -518
- package/public/brotli_wasm_bg.wasm +0 -0
- package/public/css/layout.css +0 -240
- package/public/css/mobile.css +0 -196
- package/public/css/panel.css +0 -393
- package/public/css/rendered.css +0 -478
- package/public/css/tokens.css +0 -116
- package/public/css/write.css +0 -128
- 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 -694
- package/public/sdoc.md +0 -542
- package/public/sdocs-app.js +0 -812
- package/public/sdocs-charts.js +0 -905
- package/public/sdocs-controls.js +0 -354
- package/public/sdocs-export.js +0 -1090
- package/public/sdocs-state.js +0 -73
- package/public/sdocs-theme.js +0 -262
- package/public/sdocs-write.js +0 -740
- package/public/sw.js +0 -110
- package/public/vendor/marked.min.js +0 -6
- package/public/vendor/purify.min.js +0 -3
- package/server.js +0 -176
package/public/sdocs-app.js
DELETED
|
@@ -1,812 +0,0 @@
|
|
|
1
|
-
// sdocs-app.js — Core app: render, sync, modes, drag/drop, init
|
|
2
|
-
(function () {
|
|
3
|
-
'use strict';
|
|
4
|
-
|
|
5
|
-
var S = SDocs;
|
|
6
|
-
|
|
7
|
-
// ── SVG icons ──────────────────────────────────
|
|
8
|
-
|
|
9
|
-
var LINK_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>';
|
|
10
|
-
var COPY_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
11
|
-
var CHECK_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
|
|
12
|
-
var CHEVRON_SVG = '<span class="section-toggle"><svg width="10" height="10" viewBox="0 0 10 10" fill="none"><path d="M3 2l4 3-4 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>';
|
|
13
|
-
var COPY_FEEDBACK_MS = 1500;
|
|
14
|
-
|
|
15
|
-
// ── Slugify + section helpers ──────────────────────
|
|
16
|
-
|
|
17
|
-
var slugify = SDocSlugify.slugify;
|
|
18
|
-
|
|
19
|
-
function buildSectionUrl(slug) {
|
|
20
|
-
var base = window.location.origin + window.location.pathname;
|
|
21
|
-
var hash = window.location.hash.slice(1);
|
|
22
|
-
var params = hash ? new URLSearchParams(hash) : new URLSearchParams();
|
|
23
|
-
params.delete('sec');
|
|
24
|
-
params.set('sec', slug);
|
|
25
|
-
if (S.currentMode !== 'read' && S.currentMode !== 'raw') {
|
|
26
|
-
params.set('mode', S.currentMode);
|
|
27
|
-
}
|
|
28
|
-
return base + '#' + params.toString();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function getSectionMarkdown(headingIndex) {
|
|
32
|
-
var lines = S.currentBody.split('\n');
|
|
33
|
-
var headings = [];
|
|
34
|
-
var inFence = false;
|
|
35
|
-
for (var i = 0; i < lines.length; i++) {
|
|
36
|
-
if (/^(`{3,}|~{3,})/.test(lines[i])) { inFence = !inFence; continue; }
|
|
37
|
-
if (inFence) continue;
|
|
38
|
-
var m = lines[i].match(/^(#{1,4})\s/);
|
|
39
|
-
if (m) headings.push({ line: i, level: m[1].length });
|
|
40
|
-
}
|
|
41
|
-
if (headingIndex < 0 || headingIndex >= headings.length) return '';
|
|
42
|
-
var target = headings[headingIndex];
|
|
43
|
-
var endLine = lines.length;
|
|
44
|
-
for (var j = headingIndex + 1; j < headings.length; j++) {
|
|
45
|
-
if (headings[j].level <= target.level) {
|
|
46
|
-
endLine = headings[j].line;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return lines.slice(target.line, endLine).join('\n').trimEnd();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ── Render sub-functions ──────────────────────────────────
|
|
54
|
-
|
|
55
|
-
function attachHeadingAnchors(container) {
|
|
56
|
-
var slugCounts = {};
|
|
57
|
-
var allHeadings = [].slice.call(container.querySelectorAll('h1, h2, h3, h4'));
|
|
58
|
-
allHeadings.forEach(function(h, idx) {
|
|
59
|
-
var slug = slugify(h.textContent);
|
|
60
|
-
if (!slug) slug = 'section';
|
|
61
|
-
if (slugCounts[slug] != null) {
|
|
62
|
-
slugCounts[slug]++;
|
|
63
|
-
slug = slug + '-' + slugCounts[slug];
|
|
64
|
-
} else {
|
|
65
|
-
slugCounts[slug] = 0;
|
|
66
|
-
}
|
|
67
|
-
h.id = slug;
|
|
68
|
-
|
|
69
|
-
var anchor = document.createElement('a');
|
|
70
|
-
anchor.className = 'header-anchor';
|
|
71
|
-
anchor.innerHTML = LINK_SVG;
|
|
72
|
-
anchor.title = 'Copy link to section';
|
|
73
|
-
anchor.addEventListener('click', function(e) {
|
|
74
|
-
e.preventDefault();
|
|
75
|
-
navigator.clipboard.writeText(buildSectionUrl(slug)).then(function() {
|
|
76
|
-
anchor.innerHTML = CHECK_SVG;
|
|
77
|
-
setTimeout(function() { anchor.innerHTML = LINK_SVG; }, COPY_FEEDBACK_MS);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
h.appendChild(anchor);
|
|
81
|
-
|
|
82
|
-
var copyBtn = document.createElement('button');
|
|
83
|
-
copyBtn.className = 'header-copy-btn';
|
|
84
|
-
copyBtn.innerHTML = COPY_SVG;
|
|
85
|
-
copyBtn.title = 'Copy section';
|
|
86
|
-
var hIdx = idx;
|
|
87
|
-
copyBtn.addEventListener('click', function(e) {
|
|
88
|
-
e.stopPropagation();
|
|
89
|
-
e.preventDefault();
|
|
90
|
-
var md = getSectionMarkdown(hIdx);
|
|
91
|
-
navigator.clipboard.writeText(md).then(function() {
|
|
92
|
-
copyBtn.innerHTML = CHECK_SVG;
|
|
93
|
-
setTimeout(function() { copyBtn.innerHTML = COPY_SVG; }, COPY_FEEDBACK_MS);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
h.appendChild(copyBtn);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function attachCodeCopyButtons(container) {
|
|
101
|
-
container.querySelectorAll('pre').forEach(function(pre) {
|
|
102
|
-
var wrapper = document.createElement('div');
|
|
103
|
-
wrapper.className = 'pre-wrapper';
|
|
104
|
-
pre.parentNode.insertBefore(wrapper, pre);
|
|
105
|
-
wrapper.appendChild(pre);
|
|
106
|
-
var btn = document.createElement('button');
|
|
107
|
-
btn.className = 'copy-btn';
|
|
108
|
-
btn.innerHTML = COPY_SVG;
|
|
109
|
-
btn.title = 'Copy code';
|
|
110
|
-
btn.addEventListener('click', function() {
|
|
111
|
-
var code = pre.querySelector('code');
|
|
112
|
-
navigator.clipboard.writeText(code ? code.textContent : pre.textContent).then(function() {
|
|
113
|
-
btn.innerHTML = CHECK_SVG;
|
|
114
|
-
setTimeout(function() { btn.innerHTML = COPY_SVG; }, COPY_FEEDBACK_MS);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
wrapper.appendChild(btn);
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
var SECTION_LEVELS = { H2: 2, H3: 3, H4: 4 };
|
|
122
|
-
|
|
123
|
-
function buildCollapsibleSections(container) {
|
|
124
|
-
// H1 expand/collapse toggle (controls all sections below)
|
|
125
|
-
container.querySelectorAll('h1').forEach(function(h1) {
|
|
126
|
-
h1.insertAdjacentHTML('afterbegin', CHEVRON_SVG);
|
|
127
|
-
h1.style.cursor = 'pointer';
|
|
128
|
-
h1.addEventListener('click', function(e) {
|
|
129
|
-
if (e.target.closest('.header-anchor') || e.target.closest('.header-copy-btn')) return;
|
|
130
|
-
var toggle = h1.querySelector('.section-toggle');
|
|
131
|
-
var isOpen = toggle.classList.toggle('open');
|
|
132
|
-
container.querySelectorAll('.md-section-body').forEach(function(b) { b.classList.toggle('open', isOpen); });
|
|
133
|
-
container.querySelectorAll('.md-section > h2 > .section-toggle, .md-section > h3 > .section-toggle, .md-section > h4 > .section-toggle').forEach(function(t) { t.classList.toggle('open', isOpen); });
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Nest H2/H3/H4 into collapsible section wrappers.
|
|
138
|
-
// Each stack frame tracks its heading level so siblings are siblings
|
|
139
|
-
// even when intermediate levels are skipped (e.g. h1 → h4 directly).
|
|
140
|
-
var children = [].slice.call(container.children);
|
|
141
|
-
var stack = [{ body: container, level: 0 }];
|
|
142
|
-
children.forEach(function(child) {
|
|
143
|
-
var level = SECTION_LEVELS[child.tagName];
|
|
144
|
-
if (level) {
|
|
145
|
-
while (stack[stack.length - 1].level >= level) stack.pop();
|
|
146
|
-
var sectionDiv = document.createElement('div');
|
|
147
|
-
sectionDiv.className = 'md-section';
|
|
148
|
-
var sectionBody = document.createElement('div');
|
|
149
|
-
sectionBody.className = 'md-section-body';
|
|
150
|
-
child.insertAdjacentHTML('afterbegin', CHEVRON_SVG);
|
|
151
|
-
stack[stack.length - 1].body.appendChild(sectionDiv);
|
|
152
|
-
sectionDiv.appendChild(child);
|
|
153
|
-
sectionDiv.appendChild(sectionBody);
|
|
154
|
-
stack.push({ body: sectionBody, level: level });
|
|
155
|
-
} else {
|
|
156
|
-
stack[stack.length - 1].body.appendChild(child);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// Attach click handlers for section heading toggles
|
|
161
|
-
container.querySelectorAll('.md-section > h2, .md-section > h3, .md-section > h4').forEach(function(heading) {
|
|
162
|
-
heading.addEventListener('click', function(e) {
|
|
163
|
-
if (e.target.closest('.header-anchor') || e.target.closest('.header-copy-btn')) return;
|
|
164
|
-
var yBefore = heading.getBoundingClientRect().top;
|
|
165
|
-
var section = heading.closest('.md-section');
|
|
166
|
-
var body = section.querySelector('.md-section-body');
|
|
167
|
-
var toggle = section.querySelector('.section-toggle');
|
|
168
|
-
var isOpen = body.classList.toggle('open');
|
|
169
|
-
toggle.classList.toggle('open', isOpen);
|
|
170
|
-
body.querySelectorAll('.md-section-body').forEach(function(b) { b.classList.toggle('open', isOpen); });
|
|
171
|
-
body.querySelectorAll('.section-toggle').forEach(function(t) { t.classList.toggle('open', isOpen); });
|
|
172
|
-
var yAfter = heading.getBoundingClientRect().top;
|
|
173
|
-
if (yAfter !== yBefore) {
|
|
174
|
-
contentArea.scrollTop += yAfter - yBefore;
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ── Render (orchestrator) ──────────────────────────────────
|
|
181
|
-
|
|
182
|
-
function render() {
|
|
183
|
-
S.destroyCharts();
|
|
184
|
-
var oldSpacer = S.renderedEl.querySelector('.sec-scroll-spacer');
|
|
185
|
-
if (oldSpacer) oldSpacer.remove();
|
|
186
|
-
S.renderedEl.innerHTML = DOMPurify.sanitize(marked.parse(S.currentBody));
|
|
187
|
-
|
|
188
|
-
attachHeadingAnchors(S.renderedEl);
|
|
189
|
-
attachCodeCopyButtons(S.renderedEl);
|
|
190
|
-
buildCollapsibleSections(S.renderedEl);
|
|
191
|
-
S.processCharts(S.renderedEl);
|
|
192
|
-
renderFileInfoCard();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// ── File-info card ─────────────────────────────────────────
|
|
196
|
-
|
|
197
|
-
function renderFileInfoCard() {
|
|
198
|
-
var card = document.getElementById('file-info-card');
|
|
199
|
-
if (!card) return;
|
|
200
|
-
var meta = S.currentMeta || {};
|
|
201
|
-
var local = S.localMeta || {};
|
|
202
|
-
var rowsEl = card.querySelector('.fic-rows');
|
|
203
|
-
|
|
204
|
-
var rows = [];
|
|
205
|
-
if (meta.file) rows.push({ key: 'file', label: 'File', value: meta.file, local: false });
|
|
206
|
-
if (local.path) rows.push({ key: 'path', label: 'Path', value: local.path, local: true });
|
|
207
|
-
if (local.fullPath) rows.push({ key: 'fullPath', label: 'Full', value: local.fullPath, local: true });
|
|
208
|
-
|
|
209
|
-
if (rows.length === 0) {
|
|
210
|
-
card.hidden = true;
|
|
211
|
-
rowsEl.innerHTML = '';
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
card.hidden = false;
|
|
216
|
-
var note = card.querySelector('.fic-privacy-note');
|
|
217
|
-
if (note) note.hidden = !rows.some(function(r) { return r.local; });
|
|
218
|
-
rowsEl.innerHTML = rows.map(function(r) {
|
|
219
|
-
var pill = r.local ? '<span class="fic-local-pill" title="Only visible on this device — not included in shared sdocs">local only</span>' : '';
|
|
220
|
-
return '<div class="fic-row" data-key="' + r.key + '">'
|
|
221
|
-
+ '<span class="fic-label">' + r.label + '</span>'
|
|
222
|
-
+ '<span class="fic-value">' + escapeHtml(r.value) + '</span>'
|
|
223
|
-
+ pill
|
|
224
|
-
+ '<button class="fic-copy" title="Copy ' + r.label.toLowerCase() + '">' + COPY_SVG + '</button>'
|
|
225
|
-
+ '</div>';
|
|
226
|
-
}).join('');
|
|
227
|
-
|
|
228
|
-
rowsEl.querySelectorAll('.fic-copy').forEach(function(btn) {
|
|
229
|
-
btn.addEventListener('click', function() {
|
|
230
|
-
var row = btn.closest('.fic-row');
|
|
231
|
-
var val = row.querySelector('.fic-value').textContent;
|
|
232
|
-
copyWithIconFeedback(val, btn);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function escapeHtml(s) {
|
|
238
|
-
return String(s).replace(/[&<>"']/g, function(c) {
|
|
239
|
-
return ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' })[c];
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Swap the button's <svg> to a check, restore after COPY_FEEDBACK_MS.
|
|
244
|
-
// Works for both icon-only and icon+label buttons.
|
|
245
|
-
function copyWithIconFeedback(text, btn) {
|
|
246
|
-
navigator.clipboard.writeText(text).then(function() {
|
|
247
|
-
if (!btn) return;
|
|
248
|
-
var svg = btn.querySelector('svg');
|
|
249
|
-
if (svg) {
|
|
250
|
-
svg.outerHTML = CHECK_SVG;
|
|
251
|
-
setTimeout(function() {
|
|
252
|
-
var current = btn.querySelector('svg');
|
|
253
|
-
if (current) current.outerHTML = COPY_SVG;
|
|
254
|
-
}, COPY_FEEDBACK_MS);
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
260
|
-
var copyAll = document.getElementById('fic-copy-all');
|
|
261
|
-
if (copyAll) {
|
|
262
|
-
copyAll.addEventListener('click', function() {
|
|
263
|
-
var meta = Object.assign({}, S.currentMeta || {}, { styles: S.collectStyles() });
|
|
264
|
-
var full = SDocYaml.serializeFrontMatter(meta) + '\n' + (S.currentBody || '');
|
|
265
|
-
copyWithIconFeedback(full, copyAll);
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
// ── Status ──────────────────────────────────
|
|
271
|
-
|
|
272
|
-
function setStatus(msg) {
|
|
273
|
-
document.getElementById('status-text').textContent = msg;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// ── Load content ──────────────────────────────────
|
|
277
|
-
|
|
278
|
-
function loadText(text, filename) {
|
|
279
|
-
var parsed = SDocYaml.parseFrontMatter(text);
|
|
280
|
-
S.currentMeta = parsed.meta;
|
|
281
|
-
S.currentBody = parsed.body;
|
|
282
|
-
S.chartStyles = (parsed.meta.styles && parsed.meta.styles.chart) || null;
|
|
283
|
-
render();
|
|
284
|
-
if (parsed.meta.styles) S.applyStylesFromMeta(parsed.meta.styles);
|
|
285
|
-
// Re-apply theme defaults for standalone colors (front matter may have
|
|
286
|
-
// theme-specific values that don't match the viewer's current theme)
|
|
287
|
-
S.STANDALONE_COLOR_IDS.forEach(function(ctrlId) {
|
|
288
|
-
if (!S.overriddenColors.has(ctrlId)) {
|
|
289
|
-
var val = S.getStandaloneDefault(ctrlId);
|
|
290
|
-
var el = document.getElementById(ctrlId);
|
|
291
|
-
if (el) {
|
|
292
|
-
el.value = val;
|
|
293
|
-
var allVals = S.readAllControlValues();
|
|
294
|
-
SDocStyles.controlToCssVars(ctrlId, val, allVals)
|
|
295
|
-
.forEach(function(a) { S.setStyleVar(a.cssVar, a.value); });
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
S.currentMeta = Object.assign({}, S.currentMeta, { styles: S.collectStyles() });
|
|
300
|
-
S.rawEl.value = SDocYaml.serializeFrontMatter(S.currentMeta) + '\n' + S.currentBody;
|
|
301
|
-
setStatus(filename ? 'Loaded: ' + filename : '');
|
|
302
|
-
syncAll('load');
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// ── Compression helpers (brotli + base64url) ──
|
|
306
|
-
|
|
307
|
-
function concatChunks(chunks) {
|
|
308
|
-
var total = chunks.reduce(function(n, c) { return n + c.length; }, 0);
|
|
309
|
-
var buf = new Uint8Array(total);
|
|
310
|
-
var offset = 0;
|
|
311
|
-
for (var i = 0; i < chunks.length; i++) { buf.set(chunks[i], offset); offset += chunks[i].length; }
|
|
312
|
-
return buf;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async function readAllChunks(readable) {
|
|
316
|
-
var chunks = [];
|
|
317
|
-
var reader = readable.getReader();
|
|
318
|
-
while (true) {
|
|
319
|
-
var result = await reader.read();
|
|
320
|
-
if (result.done) break;
|
|
321
|
-
chunks.push(result.value);
|
|
322
|
-
}
|
|
323
|
-
return concatChunks(chunks);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function toBase64Url(bytes) {
|
|
327
|
-
var bin = Array.from(new Uint8Array(bytes), function(b) { return String.fromCharCode(b); }).join('');
|
|
328
|
-
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function fromBase64Url(str) {
|
|
332
|
-
var b64 = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
333
|
-
var bin = atob(b64);
|
|
334
|
-
var bytes = new Uint8Array(bin.length);
|
|
335
|
-
for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
336
|
-
return bytes;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
async function compressDeflate(text) {
|
|
340
|
-
var encoded = new TextEncoder().encode(text);
|
|
341
|
-
var cs = new CompressionStream('deflate-raw');
|
|
342
|
-
var writer = cs.writable.getWriter();
|
|
343
|
-
writer.write(encoded);
|
|
344
|
-
writer.close();
|
|
345
|
-
return toBase64Url(await readAllChunks(cs.readable));
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async function compressText(text) {
|
|
349
|
-
if (typeof BrotliWasm === 'undefined') return compressDeflate(text);
|
|
350
|
-
try {
|
|
351
|
-
await BrotliWasm.ready;
|
|
352
|
-
var encoded = new TextEncoder().encode(text);
|
|
353
|
-
var compressed = BrotliWasm.compress(encoded, { quality: 11 });
|
|
354
|
-
return toBase64Url(compressed);
|
|
355
|
-
} catch (_) {
|
|
356
|
-
return compressDeflate(text);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
async function decompressDeflate(bytes) {
|
|
361
|
-
var ds = new DecompressionStream('deflate-raw');
|
|
362
|
-
var writer = ds.writable.getWriter();
|
|
363
|
-
writer.write(bytes);
|
|
364
|
-
writer.close();
|
|
365
|
-
return new TextDecoder().decode(await readAllChunks(ds.readable));
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async function decompressText(b64url) {
|
|
369
|
-
var bytes = fromBase64Url(b64url);
|
|
370
|
-
// Try brotli first, fall back to deflate for old URLs or missing WASM
|
|
371
|
-
if (typeof BrotliWasm !== 'undefined') {
|
|
372
|
-
try {
|
|
373
|
-
await BrotliWasm.ready;
|
|
374
|
-
var decompressed = BrotliWasm.decompress(bytes);
|
|
375
|
-
return new TextDecoder().decode(decompressed);
|
|
376
|
-
} catch (_) {}
|
|
377
|
-
}
|
|
378
|
-
return decompressDeflate(bytes);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// ── Auto-save to URL hash ──────────────────────────
|
|
382
|
-
|
|
383
|
-
function updateHash() {
|
|
384
|
-
clearTimeout(S._hashTimer);
|
|
385
|
-
S._hashTimer = setTimeout(async function() {
|
|
386
|
-
if (S._isDefaultState && S.currentMode === 'read') {
|
|
387
|
-
history.replaceState(null, '', window.location.pathname === '/new' ? '/' : window.location.pathname);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
var params = new URLSearchParams();
|
|
391
|
-
if (!S._isDefaultState) {
|
|
392
|
-
var styles = SDocStyles.stripStyleDefaults(S.collectStyles());
|
|
393
|
-
var meta = Object.assign({}, S.currentMeta);
|
|
394
|
-
if (Object.keys(styles).length > 0) meta.styles = styles;
|
|
395
|
-
else delete meta.styles;
|
|
396
|
-
var full = SDocYaml.serializeFrontMatter(meta) + '\n' + S.currentBody;
|
|
397
|
-
var compressed = await compressText(full);
|
|
398
|
-
params.set('md', compressed);
|
|
399
|
-
}
|
|
400
|
-
if (S.currentMode !== 'read') {
|
|
401
|
-
params.set('mode', S.currentMode);
|
|
402
|
-
}
|
|
403
|
-
var basePath = window.location.pathname === '/new' ? '/' : window.location.pathname;
|
|
404
|
-
history.replaceState(null, '', basePath + '#' + params.toString());
|
|
405
|
-
}, 400);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// ── State sync ──────────────────────────────────
|
|
409
|
-
|
|
410
|
-
function syncAll(source) {
|
|
411
|
-
if (S._syncing) return;
|
|
412
|
-
S._syncing = true;
|
|
413
|
-
try {
|
|
414
|
-
if (source === 'controls') {
|
|
415
|
-
S._isDefaultState = false;
|
|
416
|
-
S.invalidateLocalMeta();
|
|
417
|
-
S.currentMeta = Object.assign({}, S.currentMeta, { styles: S.collectStyles() });
|
|
418
|
-
S.rawEl.value = SDocYaml.serializeFrontMatter(S.currentMeta) + '\n' + S.currentBody;
|
|
419
|
-
updateHash();
|
|
420
|
-
} else if (source === 'raw') {
|
|
421
|
-
S._isDefaultState = false;
|
|
422
|
-
var parsed = SDocYaml.parseFrontMatter(S.rawEl.value);
|
|
423
|
-
S.currentMeta = parsed.meta;
|
|
424
|
-
S.currentBody = parsed.body;
|
|
425
|
-
render();
|
|
426
|
-
if (parsed.meta.styles) S.applyStylesFromMeta(parsed.meta.styles);
|
|
427
|
-
updateHash();
|
|
428
|
-
} else if (source === 'write') {
|
|
429
|
-
S._isDefaultState = false;
|
|
430
|
-
S.currentMeta = Object.assign({}, S.currentMeta, { styles: S.collectStyles() });
|
|
431
|
-
S.rawEl.value = SDocYaml.serializeFrontMatter(S.currentMeta) + '\n' + S.currentBody;
|
|
432
|
-
updateHash();
|
|
433
|
-
} else if (source === 'load') {
|
|
434
|
-
updateHash();
|
|
435
|
-
}
|
|
436
|
-
} finally {
|
|
437
|
-
S._syncing = false;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// ── Drag & drop ──────────────────────────────────
|
|
442
|
-
|
|
443
|
-
var contentArea = document.getElementById('content-area');
|
|
444
|
-
|
|
445
|
-
contentArea.addEventListener('dragover', function(e) {
|
|
446
|
-
e.preventDefault();
|
|
447
|
-
contentArea.classList.add('drag-over');
|
|
448
|
-
});
|
|
449
|
-
['dragleave','dragend'].forEach(function(ev) {
|
|
450
|
-
contentArea.addEventListener(ev, function() { contentArea.classList.remove('drag-over'); });
|
|
451
|
-
});
|
|
452
|
-
contentArea.addEventListener('drop', function(e) {
|
|
453
|
-
e.preventDefault();
|
|
454
|
-
contentArea.classList.remove('drag-over');
|
|
455
|
-
var file = e.dataTransfer.files[0];
|
|
456
|
-
if (!file) return;
|
|
457
|
-
var reader = new FileReader();
|
|
458
|
-
reader.onload = function(ev) { S._isDefaultState = false; loadText(ev.target.result, file.name); };
|
|
459
|
-
reader.readAsText(file);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
S.rawEl.addEventListener('input', function() {
|
|
463
|
-
// Content has diverged from the on-disk file — drop local paths.
|
|
464
|
-
S.invalidateLocalMeta();
|
|
465
|
-
|
|
466
|
-
clearTimeout(S._rawSyncTimer);
|
|
467
|
-
S._rawSyncTimer = setTimeout(function() { syncAll('raw'); }, 300);
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
// ── Mode toggle (read / style / raw / export) ──────────────────
|
|
471
|
-
|
|
472
|
-
function setMode(mode, skipHash) {
|
|
473
|
-
var prev = S.currentMode;
|
|
474
|
-
S.currentMode = mode;
|
|
475
|
-
|
|
476
|
-
// Exit write mode — extract markdown back
|
|
477
|
-
if (prev === 'write' && mode !== 'write') {
|
|
478
|
-
S.exitWriteMode();
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
S.renderedEl.style.display = (mode === 'raw' || mode === 'write') ? 'none' : '';
|
|
482
|
-
S.rawEl.style.display = mode === 'raw' ? 'block' : 'none';
|
|
483
|
-
|
|
484
|
-
document.getElementById('btn-read').classList.toggle('active', mode === 'read');
|
|
485
|
-
document.getElementById('btn-style').classList.toggle('active', mode === 'style');
|
|
486
|
-
document.getElementById('btn-write').classList.toggle('active', mode === 'write');
|
|
487
|
-
document.getElementById('btn-raw').classList.toggle('active', mode === 'raw');
|
|
488
|
-
document.getElementById('btn-export').classList.toggle('active', mode === 'export');
|
|
489
|
-
|
|
490
|
-
document.body.classList.toggle('style-mode', mode === 'style');
|
|
491
|
-
document.body.classList.toggle('read-mode', mode === 'read');
|
|
492
|
-
document.body.classList.toggle('write-mode', mode === 'write');
|
|
493
|
-
document.body.classList.toggle('raw-mode', mode === 'raw');
|
|
494
|
-
document.body.classList.toggle('export-mode', mode === 'export');
|
|
495
|
-
document.body.classList.remove('mobile-sheet-open');
|
|
496
|
-
document.body.classList.remove('mobile-export-open');
|
|
497
|
-
|
|
498
|
-
// Enter write mode — populate contentEditable
|
|
499
|
-
if (mode === 'write') {
|
|
500
|
-
S.enterWriteMode();
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (mode === 'read') {
|
|
504
|
-
document.getElementById('content-area').focus();
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
if (!skipHash) updateHash();
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
document.getElementById('btn-theme').addEventListener('click', function() { S.toggleTheme(); });
|
|
511
|
-
document.getElementById('theme-tab-light').addEventListener('click', function() { S.switchThemeAndUpdate('light'); });
|
|
512
|
-
document.getElementById('theme-tab-dark').addEventListener('click', function() { S.switchThemeAndUpdate('dark'); });
|
|
513
|
-
document.getElementById('btn-read').addEventListener('click', function() { setMode('read'); });
|
|
514
|
-
document.getElementById('btn-style').addEventListener('click', function() { setMode('style'); });
|
|
515
|
-
document.getElementById('btn-write').addEventListener('click', function() { setMode('write'); });
|
|
516
|
-
document.getElementById('btn-raw').addEventListener('click', function() { setMode('raw'); });
|
|
517
|
-
document.getElementById('btn-export').addEventListener('click', function() { setMode('export'); });
|
|
518
|
-
|
|
519
|
-
document.getElementById('btn-new').addEventListener('click', function() {
|
|
520
|
-
history.replaceState(null, '', '/new');
|
|
521
|
-
startNewDocument();
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
document.getElementById('right-header').addEventListener('click', function() {
|
|
526
|
-
if (window.innerWidth <= 768) {
|
|
527
|
-
document.body.classList.toggle('mobile-sheet-open');
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
document.getElementById('export-panel-header').addEventListener('click', function() {
|
|
532
|
-
if (window.innerWidth <= 768) {
|
|
533
|
-
document.body.classList.toggle('mobile-export-open');
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
document.getElementById('factory-reset-styles').addEventListener('click', function() {
|
|
538
|
-
S.resetAllStyles();
|
|
539
|
-
S.currentMeta = Object.assign({}, S.currentMeta, { styles: S.collectStyles() });
|
|
540
|
-
S.rawEl.value = SDocYaml.serializeFrontMatter(S.currentMeta) + '\n' + S.currentBody;
|
|
541
|
-
render();
|
|
542
|
-
syncAll('load');
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
document.getElementById('toolbar-brand').addEventListener('click', function(e) {
|
|
546
|
-
e.preventDefault();
|
|
547
|
-
if (window.location.hash && window.location.hash.indexOf('md=') !== -1) {
|
|
548
|
-
window.open(window.location.origin + window.location.pathname, '_blank');
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
S.resetAllStyles();
|
|
552
|
-
loadText(DEFAULT_MD);
|
|
553
|
-
S._isDefaultState = true;
|
|
554
|
-
clearTimeout(S._hashTimer);
|
|
555
|
-
history.replaceState(null, '', window.location.pathname);
|
|
556
|
-
setMode('read');
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
// ── Collapsible panels ──────────────────────────────
|
|
560
|
-
|
|
561
|
-
document.querySelectorAll('.panel-header').forEach(function(h) {
|
|
562
|
-
h.addEventListener('click', function() {
|
|
563
|
-
var body = document.getElementById(h.dataset.target);
|
|
564
|
-
var open = body.classList.toggle('open');
|
|
565
|
-
h.classList.toggle('open', open);
|
|
566
|
-
});
|
|
567
|
-
});
|
|
568
|
-
document.querySelectorAll('.sub-header').forEach(function(h) {
|
|
569
|
-
h.addEventListener('click', function() {
|
|
570
|
-
var body = document.getElementById(h.dataset.target);
|
|
571
|
-
var open = body.classList.toggle('open');
|
|
572
|
-
h.classList.toggle('open', open);
|
|
573
|
-
});
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
// ── Default content ──────────────────────────────────
|
|
577
|
-
|
|
578
|
-
var DEFAULT_MD = '';
|
|
579
|
-
var _defaultReady = fetch('/public/sdoc.md').then(function(r) { return r.text(); }).then(function(t) { DEFAULT_MD = t; });
|
|
580
|
-
|
|
581
|
-
// ── Register on SDocs for cross-module access ──────────
|
|
582
|
-
|
|
583
|
-
function startNewDocument() {
|
|
584
|
-
S.resetAllStyles();
|
|
585
|
-
S.currentBody = '';
|
|
586
|
-
S.currentMeta = {};
|
|
587
|
-
S._isDefaultState = false;
|
|
588
|
-
clearTimeout(S._hashTimer);
|
|
589
|
-
render();
|
|
590
|
-
setMode('write', true);
|
|
591
|
-
var w = S.writeEl;
|
|
592
|
-
w.innerHTML = '<h1><br></h1>';
|
|
593
|
-
w.focus();
|
|
594
|
-
var range = document.createRange();
|
|
595
|
-
range.selectNodeContents(w.querySelector('h1'));
|
|
596
|
-
range.collapse(false);
|
|
597
|
-
var sel = window.getSelection();
|
|
598
|
-
sel.removeAllRanges();
|
|
599
|
-
sel.addRange(range);
|
|
600
|
-
setTimeout(function() { S.updateToolbarState(); }, 0);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
S.syncAll = syncAll;
|
|
604
|
-
S.setStatus = setStatus;
|
|
605
|
-
S.setMode = setMode;
|
|
606
|
-
S.render = render;
|
|
607
|
-
S.loadText = loadText;
|
|
608
|
-
S.renderFileInfoCard = renderFileInfoCard;
|
|
609
|
-
|
|
610
|
-
// Clear runtime-only local metadata (paths) once the user has edited the
|
|
611
|
-
// document, since the content no longer corresponds to the file on disk.
|
|
612
|
-
// Suppressed while a document is loading — style changes during load come from
|
|
613
|
-
// applying saved styles, not from user edits.
|
|
614
|
-
S.invalidateLocalMeta = function() {
|
|
615
|
-
if (S._loadingDocument) return;
|
|
616
|
-
if (!S.localMeta || Object.keys(S.localMeta).length === 0) return;
|
|
617
|
-
S.localMeta = {};
|
|
618
|
-
renderFileInfoCard();
|
|
619
|
-
};
|
|
620
|
-
|
|
621
|
-
// Sync theme tabs to initial theme
|
|
622
|
-
S.updateThemeTabs(S.activeTheme);
|
|
623
|
-
|
|
624
|
-
// ── Load document from URL hash ──────────────────────────────────
|
|
625
|
-
|
|
626
|
-
var _lastLoadedHash = null;
|
|
627
|
-
|
|
628
|
-
async function loadFromHash() {
|
|
629
|
-
var hash = window.location.hash.slice(1);
|
|
630
|
-
if (hash === _lastLoadedHash) return;
|
|
631
|
-
_lastLoadedHash = hash;
|
|
632
|
-
|
|
633
|
-
clearTimeout(S._hashTimer);
|
|
634
|
-
|
|
635
|
-
var params = hash ? new URLSearchParams(hash) : new URLSearchParams();
|
|
636
|
-
var mdParam = params.get('md');
|
|
637
|
-
var modeParam = params.get('mode');
|
|
638
|
-
var stylesParam = params.get('styles');
|
|
639
|
-
var themeParam = params.get('theme');
|
|
640
|
-
var secParam = params.get('sec');
|
|
641
|
-
var localParam = params.get('local');
|
|
642
|
-
|
|
643
|
-
// Read &local=<base64url-json> into memory, then strip it from the URL bar
|
|
644
|
-
// so anything the user copies/shares no longer contains it.
|
|
645
|
-
if (localParam) {
|
|
646
|
-
try {
|
|
647
|
-
var b64 = localParam.replace(/-/g, '+').replace(/_/g, '/');
|
|
648
|
-
while (b64.length % 4) b64 += '=';
|
|
649
|
-
S.localMeta = JSON.parse(atob(b64)) || {};
|
|
650
|
-
} catch (e) {
|
|
651
|
-
S.localMeta = {};
|
|
652
|
-
}
|
|
653
|
-
params.delete('local');
|
|
654
|
-
var newHash = params.toString();
|
|
655
|
-
var newUrl = window.location.pathname + (newHash ? '#' + newHash : '');
|
|
656
|
-
window.history.replaceState(null, '', newUrl);
|
|
657
|
-
_lastLoadedHash = newHash; // prevent re-trigger
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
S._loadingDocument = true;
|
|
661
|
-
S.resetAllStyles();
|
|
662
|
-
|
|
663
|
-
if (mdParam) {
|
|
664
|
-
try {
|
|
665
|
-
S._isDefaultState = false;
|
|
666
|
-
var text = await decompressText(mdParam);
|
|
667
|
-
loadText(text);
|
|
668
|
-
} catch (e) {
|
|
669
|
-
console.warn('sdocs-dev: could not decode hash', e);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
if (themeParam === 'light' || themeParam === 'dark') {
|
|
674
|
-
var savedPref = localStorage.getItem('sdocs-theme');
|
|
675
|
-
S.switchThemeAndUpdate(themeParam);
|
|
676
|
-
// Restore localStorage — URL theme is view-only, not persistent
|
|
677
|
-
if (savedPref) localStorage.setItem('sdocs-theme', savedPref);
|
|
678
|
-
else localStorage.removeItem('sdocs-theme');
|
|
679
|
-
} else {
|
|
680
|
-
// Restore user's actual preferred theme (previous hash may have overridden it)
|
|
681
|
-
var preferred = S.getPreferredTheme();
|
|
682
|
-
if (preferred !== S.activeTheme) {
|
|
683
|
-
S.switchThemeAndUpdate(preferred);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
if (modeParam && ['read', 'style', 'write', 'raw', 'export'].includes(modeParam)) {
|
|
688
|
-
setMode(modeParam, true);
|
|
689
|
-
} else {
|
|
690
|
-
setMode('read', true);
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
if (!mdParam) {
|
|
694
|
-
S._isDefaultState = true;
|
|
695
|
-
loadText(DEFAULT_MD);
|
|
696
|
-
if (stylesParam) {
|
|
697
|
-
try {
|
|
698
|
-
var styles = JSON.parse(atob(decodeURIComponent(stylesParam)));
|
|
699
|
-
S.applyStylesFromMeta(styles);
|
|
700
|
-
} catch (e) {
|
|
701
|
-
console.warn('sdocs-dev: could not decode #styles hash', e);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
if (secParam) {
|
|
707
|
-
setTimeout(function() {
|
|
708
|
-
var target = document.getElementById(secParam);
|
|
709
|
-
if (!target) return;
|
|
710
|
-
|
|
711
|
-
var ownSection = target.closest('.md-section');
|
|
712
|
-
if (ownSection) {
|
|
713
|
-
var ownBody = ownSection.querySelector(':scope > .md-section-body');
|
|
714
|
-
if (ownBody) { ownBody.classList.add('open'); }
|
|
715
|
-
var ownToggle = ownSection.querySelector(':scope > h1 > .section-toggle, :scope > h2 > .section-toggle, :scope > h3 > .section-toggle, :scope > h4 > .section-toggle');
|
|
716
|
-
if (ownToggle) { ownToggle.classList.add('open'); }
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
var el = target.closest('.md-section-body');
|
|
720
|
-
while (el) {
|
|
721
|
-
el.classList.add('open');
|
|
722
|
-
var parentSection = el.closest('.md-section');
|
|
723
|
-
if (parentSection) {
|
|
724
|
-
var toggle = parentSection.querySelector(':scope > h2 > .section-toggle, :scope > h3 > .section-toggle, :scope > h4 > .section-toggle');
|
|
725
|
-
if (toggle) toggle.classList.add('open');
|
|
726
|
-
}
|
|
727
|
-
el = el.parentElement ? el.parentElement.closest('.md-section-body') : null;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
var spacerNeeded = contentArea.clientHeight - (contentArea.scrollHeight - target.offsetTop);
|
|
731
|
-
if (spacerNeeded > 0) {
|
|
732
|
-
var spacer = document.createElement('div');
|
|
733
|
-
spacer.className = 'sec-scroll-spacer';
|
|
734
|
-
spacer.style.height = spacerNeeded + 'px';
|
|
735
|
-
S.renderedEl.appendChild(spacer);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
739
|
-
}, 200);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
if (!secParam) {
|
|
743
|
-
contentArea.scrollTop = 0;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// ── Init ──────────────────────────────────
|
|
748
|
-
|
|
749
|
-
(async function () {
|
|
750
|
-
await _defaultReady;
|
|
751
|
-
if (window.location.pathname === '/new') {
|
|
752
|
-
startNewDocument();
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
await loadFromHash();
|
|
756
|
-
}());
|
|
757
|
-
|
|
758
|
-
window.addEventListener('hashchange', function () {
|
|
759
|
-
loadFromHash();
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
window.addEventListener('popstate', function () {
|
|
763
|
-
loadFromHash();
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
// ── Toolbar scroll hints (fade + bounce-peek) ──────
|
|
767
|
-
|
|
768
|
-
function initScrollHint(el) {
|
|
769
|
-
function update() {
|
|
770
|
-
var hasOverflow = el.scrollWidth > el.clientWidth + 1;
|
|
771
|
-
el.classList.toggle('has-overflow', hasOverflow);
|
|
772
|
-
if (hasOverflow) {
|
|
773
|
-
var atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 2;
|
|
774
|
-
el.classList.toggle('scrolled-end', atEnd);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
el.addEventListener('scroll', update, { passive: true });
|
|
779
|
-
window.addEventListener('resize', update);
|
|
780
|
-
|
|
781
|
-
// Bounce-peek: briefly scroll right then back on first show
|
|
782
|
-
var peeked = false;
|
|
783
|
-
function peek() {
|
|
784
|
-
if (peeked) return;
|
|
785
|
-
if (el.scrollWidth <= el.clientWidth + 1) return;
|
|
786
|
-
peeked = true;
|
|
787
|
-
el.scrollTo({ left: 28, behavior: 'smooth' });
|
|
788
|
-
setTimeout(function() { el.scrollTo({ left: 0, behavior: 'smooth' }); }, 400);
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
// Run initial check; peek after a short delay
|
|
792
|
-
update();
|
|
793
|
-
return { update: update, peek: peek };
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
var leftHint = initScrollHint(document.getElementById('left-toolbar'));
|
|
797
|
-
var writeHint = initScrollHint(document.getElementById('write-toolbar'));
|
|
798
|
-
|
|
799
|
-
// Re-check and peek when entering write mode
|
|
800
|
-
var _origSetMode = setMode;
|
|
801
|
-
setMode = function(mode, skipHash) {
|
|
802
|
-
_origSetMode(mode, skipHash);
|
|
803
|
-
if (mode === 'write') {
|
|
804
|
-
setTimeout(function() { writeHint.update(); writeHint.peek(); }, 100);
|
|
805
|
-
}
|
|
806
|
-
};
|
|
807
|
-
S.setMode = setMode;
|
|
808
|
-
|
|
809
|
-
// Check left toolbar on load
|
|
810
|
-
setTimeout(function() { leftHint.update(); leftHint.peek(); }, 500);
|
|
811
|
-
|
|
812
|
-
})();
|