@chromamark/renderer 0.1.1 → 0.2.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 +21 -2
- package/dist/chromamark.esm.js +94 -37
- package/dist/chromamark.min.js +10 -9
- package/package.json +1 -1
- package/src/browser-core.js +47 -3
- package/src/containers.js +19 -14
- package/src/critic.js +15 -1
- package/src/inline.js +54 -15
package/README.md
CHANGED
|
@@ -63,13 +63,32 @@ ChromaMark.autoRender();
|
|
|
63
63
|
| `autoRender(options?)` | Inject the theme, then render every target on the page. |
|
|
64
64
|
| `renderElement(target, opts?)` | Render one element (selector or node) in place / as a sibling. |
|
|
65
65
|
| `renderAll(selector?, opts?)` | Render all matching elements. |
|
|
66
|
+
| `renderSrc(target, opts?)` | Fetch the element's `data-chromamark-src` file and render it in place. |
|
|
66
67
|
| `injectTheme(doc?)` | Add the theme `<style>` once (idempotent). |
|
|
67
68
|
| `render(src, opts?)` | ChromaMark string → HTML. |
|
|
68
69
|
| `theme` | The theme CSS as a string. |
|
|
69
70
|
|
|
70
71
|
Targets default to `<script type="text/chromamark">`, `template.chromamark`,
|
|
71
|
-
`.chromamark`,
|
|
72
|
-
loading `<script>` runs `autoRender()`
|
|
72
|
+
`.chromamark`, `[data-chromamark]`, and `[data-chromamark-src]`. Adding
|
|
73
|
+
`data-chromamark-auto` to the loading `<script>` runs `autoRender()`
|
|
74
|
+
automatically once the DOM is ready.
|
|
75
|
+
|
|
76
|
+
### Load ChromaMark from an external file
|
|
77
|
+
|
|
78
|
+
Point an element at a `.cm` file with `data-chromamark-src` and the bundle
|
|
79
|
+
fetches and renders it — the same idea as an external script or stylesheet, so
|
|
80
|
+
the page itself stays lean:
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<div data-chromamark-src="report.cm"></div>
|
|
84
|
+
<script src="https://cdn.jsdelivr.net/npm/@chromamark/renderer/dist/chromamark.min.js"
|
|
85
|
+
data-chromamark-auto></script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The file is fetched with `fetch()`, so the page must be served over `http(s)`
|
|
89
|
+
(not opened as `file://`) and same-origin/CORS rules apply. If the file can't be
|
|
90
|
+
loaded the element keeps its existing content and gets a `data-chromamark-error`
|
|
91
|
+
attribute, so a missing file degrades gracefully instead of throwing.
|
|
73
92
|
|
|
74
93
|
## Theme
|
|
75
94
|
|
package/dist/chromamark.esm.js
CHANGED
|
@@ -2387,12 +2387,12 @@ function escapedSplit(str) {
|
|
|
2387
2387
|
const max = str.length;
|
|
2388
2388
|
let pos = 0;
|
|
2389
2389
|
let ch = str.charCodeAt(pos);
|
|
2390
|
-
let
|
|
2390
|
+
let isEscaped2 = false;
|
|
2391
2391
|
let lastPos = 0;
|
|
2392
2392
|
let current = "";
|
|
2393
2393
|
while (pos < max) {
|
|
2394
2394
|
if (ch === 124) {
|
|
2395
|
-
if (!
|
|
2395
|
+
if (!isEscaped2) {
|
|
2396
2396
|
result.push(current + str.substring(lastPos, pos));
|
|
2397
2397
|
current = "";
|
|
2398
2398
|
lastPos = pos + 1;
|
|
@@ -2401,7 +2401,7 @@ function escapedSplit(str) {
|
|
|
2401
2401
|
lastPos = pos;
|
|
2402
2402
|
}
|
|
2403
2403
|
}
|
|
2404
|
-
|
|
2404
|
+
isEscaped2 = ch === 92;
|
|
2405
2405
|
pos++;
|
|
2406
2406
|
ch = str.charCodeAt(pos);
|
|
2407
2407
|
}
|
|
@@ -5673,10 +5673,14 @@ var SIGILS = { "!": "pill", ".": "text", "=": "meter" };
|
|
|
5673
5673
|
function unescape(text2) {
|
|
5674
5674
|
return text2.replace(/\\([\s\S])/g, "$1");
|
|
5675
5675
|
}
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5676
|
+
var WS = /\s/;
|
|
5677
|
+
function splitInline(src, from, end) {
|
|
5678
|
+
let s = from;
|
|
5679
|
+
while (s < end && WS.test(src[s])) s++;
|
|
5680
|
+
let e = s;
|
|
5681
|
+
while (e < end && !WS.test(src[e])) e++;
|
|
5682
|
+
if (e === s) return null;
|
|
5683
|
+
return { specToken: src.slice(s, e), restStart: e };
|
|
5680
5684
|
}
|
|
5681
5685
|
function meterWidth(value) {
|
|
5682
5686
|
let pct;
|
|
@@ -5694,18 +5698,32 @@ function meterWidth(value) {
|
|
|
5694
5698
|
pct = Math.max(0, Math.min(100, pct));
|
|
5695
5699
|
return String(+pct.toFixed(2));
|
|
5696
5700
|
}
|
|
5697
|
-
function
|
|
5698
|
-
const
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5701
|
+
function nextIndex(state, slot, needle, from) {
|
|
5702
|
+
const c = state[slot];
|
|
5703
|
+
if (c !== void 0 && c.from <= from && (c.at === -1 || from <= c.at)) return c.at;
|
|
5704
|
+
const at = state.src.indexOf(needle, from);
|
|
5705
|
+
state[slot] = { from, at };
|
|
5706
|
+
return at;
|
|
5707
|
+
}
|
|
5708
|
+
function isEscaped(src, pos, floor2) {
|
|
5709
|
+
let n = 0;
|
|
5710
|
+
for (let i = pos - 1; i >= floor2 && src.charCodeAt(i) === 92; i--) n++;
|
|
5711
|
+
return (n & 1) === 1;
|
|
5712
|
+
}
|
|
5713
|
+
function nextUnescaped(state, slot, needle, from, floor2, max) {
|
|
5714
|
+
let at = nextIndex(state, slot, needle, from);
|
|
5715
|
+
while (at !== -1 && at < max && isEscaped(state.src, at, floor2)) {
|
|
5716
|
+
at = nextIndex(state, slot, needle, at + 1);
|
|
5707
5717
|
}
|
|
5708
|
-
return
|
|
5718
|
+
return at;
|
|
5719
|
+
}
|
|
5720
|
+
function findClose(state, from) {
|
|
5721
|
+
const max = state.posMax;
|
|
5722
|
+
const bracket = nextUnescaped(state, "_cmBr", "]", from, from, max);
|
|
5723
|
+
if (bracket === -1 || bracket >= max) return -1;
|
|
5724
|
+
const newline2 = nextUnescaped(state, "_cmNl", "\n", from, from, max);
|
|
5725
|
+
if (newline2 !== -1 && newline2 < bracket) return -1;
|
|
5726
|
+
return bracket;
|
|
5709
5727
|
}
|
|
5710
5728
|
function makeRule(enabled) {
|
|
5711
5729
|
return function chromaInline(state, silent) {
|
|
@@ -5715,12 +5733,12 @@ function makeRule(enabled) {
|
|
|
5715
5733
|
if (!kind || !enabled[kind]) return false;
|
|
5716
5734
|
const end = findClose(state, start + 2);
|
|
5717
5735
|
if (end === -1) return false;
|
|
5718
|
-
const
|
|
5719
|
-
const parts =
|
|
5736
|
+
const src = state.src;
|
|
5737
|
+
const parts = splitInline(src, start + 2, end);
|
|
5720
5738
|
if (!parts) return false;
|
|
5721
5739
|
const spec = parseSpec(parts.specToken);
|
|
5722
5740
|
if (!spec) return false;
|
|
5723
|
-
const rest = unescape(parts.
|
|
5741
|
+
const rest = unescape(src.slice(parts.restStart, end).trim());
|
|
5724
5742
|
let token;
|
|
5725
5743
|
if (kind === "meter") {
|
|
5726
5744
|
if (!rest) return false;
|
|
@@ -5780,6 +5798,14 @@ var KINDS = {
|
|
|
5780
5798
|
"==": { kind: "mark", close: "==}" },
|
|
5781
5799
|
">>": { kind: "comment", close: "<<}" }
|
|
5782
5800
|
};
|
|
5801
|
+
function findClose2(state, needle, from) {
|
|
5802
|
+
const slot = "_cmCritic" + needle;
|
|
5803
|
+
const c = state[slot];
|
|
5804
|
+
if (c !== void 0 && c.from <= from && (c.at === -1 || from <= c.at)) return c.at;
|
|
5805
|
+
const at = state.src.indexOf(needle, from);
|
|
5806
|
+
state[slot] = { from, at };
|
|
5807
|
+
return at;
|
|
5808
|
+
}
|
|
5783
5809
|
function criticRule(state, silent) {
|
|
5784
5810
|
const start = state.pos;
|
|
5785
5811
|
const src = state.src;
|
|
@@ -5787,7 +5813,7 @@ function criticRule(state, silent) {
|
|
|
5787
5813
|
const spec = KINDS[src.slice(start + 1, start + 3)];
|
|
5788
5814
|
if (!spec) return false;
|
|
5789
5815
|
const contentStart = start + 3;
|
|
5790
|
-
const closeIdx =
|
|
5816
|
+
const closeIdx = findClose2(state, spec.close, contentStart);
|
|
5791
5817
|
if (closeIdx === -1 || closeIdx + spec.close.length > state.posMax) return false;
|
|
5792
5818
|
const raw = src.slice(contentStart, closeIdx);
|
|
5793
5819
|
if (!silent) {
|
|
@@ -5955,6 +5981,15 @@ function containerPlugin(md, enabled) {
|
|
|
5955
5981
|
alt: ["paragraph", "reference", "blockquote", "list"]
|
|
5956
5982
|
});
|
|
5957
5983
|
const esc = md.utils.escapeHtml;
|
|
5984
|
+
const renderInlineSafe = (text2) => {
|
|
5985
|
+
const prevHtml = md.options.html;
|
|
5986
|
+
md.options.html = false;
|
|
5987
|
+
try {
|
|
5988
|
+
return md.renderInline(text2);
|
|
5989
|
+
} finally {
|
|
5990
|
+
md.options.html = prevHtml;
|
|
5991
|
+
}
|
|
5992
|
+
};
|
|
5958
5993
|
function decorate(meta) {
|
|
5959
5994
|
const custom = meta.color ? " cm-custom" : "";
|
|
5960
5995
|
const style = meta.color ? ` style="--fg:${esc(meta.color)}"` : "";
|
|
@@ -5966,25 +6001,19 @@ function containerPlugin(md, enabled) {
|
|
|
5966
6001
|
const { custom, style, tone } = decorate(meta);
|
|
5967
6002
|
if (meta.structure === "details") {
|
|
5968
6003
|
const open = meta.open ? " open" : "";
|
|
5969
|
-
return `<details class="cm-details${custom}"${tone}${style}${open}><summary>${
|
|
6004
|
+
return `<details class="cm-details${custom}"${tone}${style}${open}><summary>${renderInlineSafe(meta.summary)}</summary><div class="cm-body">`;
|
|
5970
6005
|
}
|
|
5971
6006
|
let html = `<div class="cm-block${custom}"${tone}${style}>`;
|
|
5972
|
-
if (meta.title) html += `<div class="cm-title">${
|
|
6007
|
+
if (meta.title) html += `<div class="cm-title">${renderInlineSafe(meta.title)}</div>`;
|
|
5973
6008
|
return html + '<div class="cm-body">';
|
|
5974
6009
|
};
|
|
5975
6010
|
md.renderer.rules.cm_container_close = (tokens, idx) => tokens[idx].meta.structure === "details" ? "</div></details>" : "</div></div>";
|
|
5976
6011
|
md.renderer.rules.cm_fields = (tokens, idx) => {
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
let html = '<dl class="cm-fields">';
|
|
5981
|
-
for (const [key, value] of tokens[idx].meta.rows) {
|
|
5982
|
-
html += `<dt>${esc(key)}</dt><dd>${md.renderInline(value)}</dd>`;
|
|
5983
|
-
}
|
|
5984
|
-
return html + "</dl>";
|
|
5985
|
-
} finally {
|
|
5986
|
-
md.options.html = prevHtml;
|
|
6012
|
+
let html = '<dl class="cm-fields">';
|
|
6013
|
+
for (const [key, value] of tokens[idx].meta.rows) {
|
|
6014
|
+
html += `<dt>${esc(key)}</dt><dd>${renderInlineSafe(value)}</dd>`;
|
|
5987
6015
|
}
|
|
6016
|
+
return html + "</dl>";
|
|
5988
6017
|
};
|
|
5989
6018
|
}
|
|
5990
6019
|
|
|
@@ -6033,7 +6062,9 @@ function render(src, options = {}) {
|
|
|
6033
6062
|
// src/browser-core.js
|
|
6034
6063
|
var STYLE_ID = "chromamark-theme";
|
|
6035
6064
|
var DONE_ATTR = "data-chromamark-done";
|
|
6036
|
-
var
|
|
6065
|
+
var SRC_ATTR = "data-chromamark-src";
|
|
6066
|
+
var ERR_ATTR = "data-chromamark-error";
|
|
6067
|
+
var DEFAULT_SELECTOR = 'script[type="text/chromamark"], template.chromamark, [data-chromamark], [data-chromamark-src], .chromamark';
|
|
6037
6068
|
var theme = "";
|
|
6038
6069
|
function configureTheme(css) {
|
|
6039
6070
|
theme = css || "";
|
|
@@ -6071,20 +6102,44 @@ function sourceOf(el, tag) {
|
|
|
6071
6102
|
function renderElement(target, options) {
|
|
6072
6103
|
const el = resolve(target);
|
|
6073
6104
|
if (!el || el.hasAttribute(DONE_ATTR)) return null;
|
|
6105
|
+
if (el.hasAttribute && el.hasAttribute(SRC_ATTR)) return renderSrc(el, options);
|
|
6106
|
+
const doc = el.ownerDocument || (typeof document !== "undefined" ? document : null);
|
|
6074
6107
|
const tag = (el.tagName || "").toLowerCase();
|
|
6075
6108
|
const html = render(dedent(sourceOf(el, tag)), options);
|
|
6076
6109
|
el.setAttribute(DONE_ATTR, "");
|
|
6077
6110
|
if (tag === "script" || tag === "template") {
|
|
6078
|
-
const out =
|
|
6111
|
+
const out = doc.createElement("div");
|
|
6079
6112
|
out.className = "chromamark-output";
|
|
6080
6113
|
out.innerHTML = html;
|
|
6081
|
-
el.parentNode.insertBefore(out, el.nextSibling);
|
|
6114
|
+
if (el.parentNode) el.parentNode.insertBefore(out, el.nextSibling);
|
|
6082
6115
|
return out;
|
|
6083
6116
|
}
|
|
6084
6117
|
el.innerHTML = html;
|
|
6085
6118
|
el.classList.add("chromamark-output");
|
|
6086
6119
|
return el;
|
|
6087
6120
|
}
|
|
6121
|
+
function renderSrc(target, options) {
|
|
6122
|
+
const el = resolve(target);
|
|
6123
|
+
if (!el || el.hasAttribute(DONE_ATTR)) return Promise.resolve(null);
|
|
6124
|
+
const url = el.getAttribute(SRC_ATTR);
|
|
6125
|
+
if (!url) return Promise.resolve(null);
|
|
6126
|
+
el.setAttribute(DONE_ATTR, "");
|
|
6127
|
+
const view = el.ownerDocument && el.ownerDocument.defaultView;
|
|
6128
|
+
const doFetch = view && view.fetch || (typeof fetch !== "undefined" ? fetch : null);
|
|
6129
|
+
const fail = (message) => {
|
|
6130
|
+
el.setAttribute(ERR_ATTR, message);
|
|
6131
|
+
return null;
|
|
6132
|
+
};
|
|
6133
|
+
if (!doFetch) return Promise.resolve(fail("ChromaMark: fetch is unavailable"));
|
|
6134
|
+
return Promise.resolve().then(() => doFetch(url)).then((res) => {
|
|
6135
|
+
if (!res || !res.ok) throw new Error(`HTTP ${res ? res.status : "?"}`);
|
|
6136
|
+
return res.text();
|
|
6137
|
+
}).then((text2) => {
|
|
6138
|
+
el.innerHTML = render(text2, options);
|
|
6139
|
+
el.classList.add("chromamark-output");
|
|
6140
|
+
return el;
|
|
6141
|
+
}).catch((err) => fail(`ChromaMark: failed to load ${url} (${err && err.message || err})`));
|
|
6142
|
+
}
|
|
6088
6143
|
function renderAll(selector, options) {
|
|
6089
6144
|
const nodes = document.querySelectorAll(selector || DEFAULT_SELECTOR);
|
|
6090
6145
|
return Array.from(nodes).map((el) => renderElement(el, options));
|
|
@@ -6097,6 +6152,7 @@ var ChromaMark = {
|
|
|
6097
6152
|
render: render2,
|
|
6098
6153
|
renderElement,
|
|
6099
6154
|
renderAll,
|
|
6155
|
+
renderSrc,
|
|
6100
6156
|
injectTheme,
|
|
6101
6157
|
autoRender,
|
|
6102
6158
|
createRenderer,
|
|
@@ -6128,5 +6184,6 @@ export {
|
|
|
6128
6184
|
render2 as render,
|
|
6129
6185
|
renderAll,
|
|
6130
6186
|
renderElement,
|
|
6187
|
+
renderSrc,
|
|
6131
6188
|
theme
|
|
6132
6189
|
};
|