rapier-markdown-kit 1.1.84 → 1.1.86
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/LICENSE +4 -0
- package/README.md +5 -11
- package/dist/kit/render-markdown.mjs +27 -15
- package/dist/kit/render-sanitize.mjs +4 -2
- package/dist/spec/md-layout.mjs +48 -7
- package/package.json +1 -1
- package/style.css +7 -12
package/LICENSE
CHANGED
|
@@ -49,3 +49,7 @@ re-imports and re-exports that surface unchanged (one owner, no copy) and keeps
|
|
|
49
49
|
needs the AGPL decoders, so it is AGPL-3.0-only like the rest of the editor. The `rapier-markdown-kit`
|
|
50
50
|
package re-exports `spec/md-assets.mjs` directly, never `images/assets.mjs`, so its import graph
|
|
51
51
|
never touches AGPL code.
|
|
52
|
+
|
|
53
|
+
The JPEG XL encoder and worker modules in `images/jxl/` carry their MIT license in
|
|
54
|
+
`images/jxl/LICENSE`. The MIT-marked `images/encoder.mjs` and `images/codec.mjs`
|
|
55
|
+
re-export those modules. Their runtime import graph contains no editor code.
|
package/README.md
CHANGED
|
@@ -30,9 +30,8 @@ import {parseAssets} from 'rapier-markdown-kit/assets'; // the picture appendi
|
|
|
30
30
|
|
|
31
31
|
`rapier-markdown-kit/render` exports `createRenderer`, `createMarkdownRenderer`,
|
|
32
32
|
`createRenderStyles`, `createRenderSanitizer` and `createPrintRenderer`. These are the
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
no factory reaches into an editor session to find its document. `style.css` is the exact
|
|
33
|
+
functions used by the editor and exported pages. Each factory takes explicit host dependencies
|
|
34
|
+
in its first argument. `style.css` is the
|
|
36
35
|
MIT `spec/markdown-style.css`, including the numbered-list, checkbox, callout, table,
|
|
37
36
|
footnote and picture-layout treatment.
|
|
38
37
|
|
|
@@ -52,20 +51,15 @@ Every existing lower-level method is also returned for hosts that compose the st
|
|
|
52
51
|
Create a new bound instance when its parser or host state changes; do not share mutable
|
|
53
52
|
DOM objects between document realms.
|
|
54
53
|
|
|
55
|
-
This
|
|
56
|
-
image decoder. Supplying only `{source}` or running it in bare Node without those ports
|
|
57
|
-
is not supported. A host must provide a real DOM and sanitizer, the Rapier grammar,
|
|
54
|
+
This **DOM-hosted library** requires a DOM and sanitizer, the Rapier grammar,
|
|
58
55
|
portable image preparation, style access and the named codec/layout ports it uses.
|
|
59
56
|
Those dependencies keep their own licences: the MIT grant here does not relicense a
|
|
60
57
|
separately supplied host or vendor. The repository's `editor/render-host.js` binds the
|
|
61
58
|
editor; `server/` provides a complete local host with a pinned Chromium process. Its
|
|
62
59
|
Word exporter and filesystem service are AGPL, not part of this MIT package.
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
adapters: it proves serialization and source recovery, not browser appearance or
|
|
67
|
-
sanitizer security. The server's integration cells additionally use a real DOM and
|
|
68
|
-
round-trip the actual HTTP output.
|
|
61
|
+
Serialization tests preserve source bytes through HTML export and recovery. Server integration
|
|
62
|
+
tests also check the exported HTTP response through a browser DOM.
|
|
69
63
|
|
|
70
64
|
## What it gives you
|
|
71
65
|
|
|
@@ -119,6 +119,17 @@ function initMarkdownIt() {
|
|
|
119
119
|
if (!env.rapierPage && typeof globalThis.RapierEmbeddedImages?.imageHtml === 'function' &&
|
|
120
120
|
(_rapierEmbedAssetSource?.(src) || asset && (reference || asset.codec === 'image/jxl' || asset.codec === 'image/svg+xml'))) return globalThis.RapierEmbeddedImages.imageHtml(
|
|
121
121
|
reference, alt, title, size, layout, rawAlt, token.meta?.mdImage?.source, src);
|
|
122
|
+
let loadable = false;
|
|
123
|
+
try { loadable = !!src.trim() && /^(?:https?|file|blob|data):$/.test(new URL(src, globalThis.document?.baseURI || globalThis.location?.href || 'https://rapier.invalid/').protocol); } catch (_) {}
|
|
124
|
+
if (!loadable) {
|
|
125
|
+
const escape = md.utils.escapeHtml;
|
|
126
|
+
return '<span data-rapier-markdown-image="" data-rapier-remote-src="' + escape(src) + '" data-rapier-remote-alt="' + escape(alt) + '"'
|
|
127
|
+
+ (title ? ' data-rapier-remote-title="' + escape(title) + '"' : '')
|
|
128
|
+
+ (size ? ' data-rapier-image-alt-source="' + escape(rawAlt) + '"' : '')
|
|
129
|
+
+ (layout ? ' data-rapier-image-layout="' + escape(encodeURIComponent(layout)) + '"' : '')
|
|
130
|
+
+ (token.meta?.mdImage?.source ? ' data-rapier-image-source="' + escape(encodeURIComponent(token.meta.mdImage.source)) + '"' : '')
|
|
131
|
+
+ '>' + escape(alt || src) + '</span>';
|
|
132
|
+
}
|
|
122
133
|
if (!env.rapierPage && !_rapierRemoteContent.allowed && _rapierRemoteSubresourceOrigin(src)) {
|
|
123
134
|
const html = _rapierRemoteImagePlaceholder(src, alt, title, size, layout, rawAlt);
|
|
124
135
|
return html.replace('<span ', '<span data-rapier-markdown-image="" ' +
|
|
@@ -906,9 +917,9 @@ function _rapierProjectPortableRoot(semanticRoot, options) {
|
|
|
906
917
|
// consumers that read HTML -- publishing/fragment export, TXT. DOCX is not an HTML consumer: its
|
|
907
918
|
// writer (interchange/docx.mjs) reads the layout comment itself, so stripping it first and
|
|
908
919
|
// handing back CSS only throws the fact away before the writer ever sees it. `keepLayoutData`
|
|
909
|
-
// leaves
|
|
910
|
-
//
|
|
911
|
-
//
|
|
920
|
+
// leaves the image and text layout records, ink meaning and paragraph alignment exactly as
|
|
921
|
+
// the semantic root carries them. Ordinary paragraph/list CSS measurements also reach their
|
|
922
|
+
// native reader; no derived private CSS is needed.
|
|
912
923
|
const keepLayoutData = !!opts.keepLayoutData;
|
|
913
924
|
const root = semanticRoot.cloneNode(true), nativeElements = new Set();
|
|
914
925
|
// A source soft break is a reading space, never the editor's invisible token.
|
|
@@ -986,10 +997,16 @@ function _rapierProjectPortableRoot(semanticRoot, options) {
|
|
|
986
997
|
root.querySelectorAll('*').forEach(element => {
|
|
987
998
|
if (nativeElements.has(element)) return;
|
|
988
999
|
const lang = element.tagName === 'CODE' ? _rapierLanguageClass(element) : '';
|
|
1000
|
+
// The native note reader needs these transient roles to omit the renderer's
|
|
1001
|
+
// separator and return-link spacing before it compares source-owned review notes.
|
|
1002
|
+
const footnoteRole = keepLayoutData ? ['footnote-backref', 'footnotes-sep']
|
|
1003
|
+
.filter(name => element.classList.contains(name)).join(' ') : '';
|
|
989
1004
|
const columnAlignment = /^(TH|TD)$/.test(element.tagName) && /^(left|center|right)$/.test(element.style.textAlign)
|
|
990
1005
|
? element.style.textAlign : '';
|
|
991
1006
|
const alignment = globalThis.RapierMarkdownLayout.parseLayoutAttribute(element.getAttribute('data-md-layout'))?.align || columnAlignment;
|
|
992
1007
|
const imageLayout = element.tagName === 'IMG' ? globalThis.RapierMarkdownLayout.parseLayoutAttribute(element.getAttribute('data-rapier-image-layout')) : null;
|
|
1008
|
+
const measurements = keepLayoutData && /^(?:P|H[1-6]|LI|OL|UL)$/.test(element.tagName)
|
|
1009
|
+
? ['text-indent', 'margin-inline-start'].map(property => [property, element.style.getPropertyValue(property)]) : [];
|
|
993
1010
|
|
|
994
1011
|
const colorHex = element.tagName === 'SPAN' ? element.getAttribute('data-md-color') : null;
|
|
995
1012
|
|
|
@@ -1002,14 +1019,16 @@ function _rapierProjectPortableRoot(semanticRoot, options) {
|
|
|
1002
1019
|
const isPageBreak = element.hasAttribute('data-md-break');
|
|
1003
1020
|
Array.from(element.attributes).forEach(attribute => {
|
|
1004
1021
|
const name = attribute.name.toLowerCase();
|
|
1005
|
-
if (keepLayoutData && (name === 'data-rapier-image-layout' || name === 'data-md-align')) return;
|
|
1022
|
+
if (keepLayoutData && (name === 'data-rapier-image-layout' || name === 'data-md-layout' || name === 'data-md-align' || name === 'data-rapier-ink')) return;
|
|
1006
1023
|
if (name === 'class' || name === 'style' || name.startsWith('data-')
|
|
1007
1024
|
|| name === 'contenteditable' || name === 'spellcheck'
|
|
1008
1025
|
|| name === 'tabindex' || name === 'disabled' || name === 'aria-disabled') {
|
|
1009
1026
|
element.removeAttribute(attribute.name);
|
|
1010
1027
|
}
|
|
1011
1028
|
});
|
|
1029
|
+
if (footnoteRole) element.className = footnoteRole;
|
|
1012
1030
|
if (lang) element.className = 'language-' + lang;
|
|
1031
|
+
for (const [property, value] of measurements) if (value) element.style.setProperty(property, value);
|
|
1013
1032
|
if (alignment) element.style.textAlign = alignment;
|
|
1014
1033
|
if (imageLayout && !keepLayoutData) {
|
|
1015
1034
|
const pictureStyle = globalThis.RapierMarkdownLayout.imageStyle(imageLayout);
|
|
@@ -1051,11 +1070,11 @@ function _rapierPictureWords(alt, src) {
|
|
|
1051
1070
|
return alt ? '[Image: ' + alt + ']' : '[Image]';
|
|
1052
1071
|
}
|
|
1053
1072
|
|
|
1054
|
-
//
|
|
1055
|
-
//
|
|
1073
|
+
// Held Markdown and HTML pictures keep their addresses as inert data. Text export reads them before
|
|
1074
|
+
// the portable projection removes editor attributes, without restoring a fetching image source.
|
|
1056
1075
|
function _rapierHeldPicturesAsWords(root) {
|
|
1057
1076
|
const copy = root.cloneNode(true);
|
|
1058
|
-
copy.querySelectorAll('span[data-rapier-remote-src]').forEach(held => held.replaceWith(document.createTextNode(
|
|
1077
|
+
copy.querySelectorAll('span[data-rapier-remote-src],img[data-rapier-remote-src]').forEach(held => held.replaceWith(document.createTextNode(
|
|
1059
1078
|
_rapierPictureWords(String(held.getAttribute('data-rapier-remote-alt') || '').trim(), String(held.getAttribute('data-rapier-remote-src') || '').trim()))));
|
|
1060
1079
|
return copy;
|
|
1061
1080
|
}
|
|
@@ -1133,7 +1152,7 @@ function _rapierPortablePlainText(root, options) {
|
|
|
1133
1152
|
const opts = options || {};
|
|
1134
1153
|
const blocks = [];
|
|
1135
1154
|
const blockTags = new Set([
|
|
1136
|
-
'ADDRESS','ARTICLE','ASIDE','BLOCKQUOTE','DETAILS','DIV','DL','FIELDSET','FIGURE','FOOTER',
|
|
1155
|
+
'ADDRESS','ARTICLE','ASIDE','BLOCKQUOTE','DETAILS','DIV','DL','FIELDSET','FIGCAPTION','FIGURE','FOOTER',
|
|
1137
1156
|
'FORM','H1','H2','H3','H4','H5','H6','HEADER','HR','MAIN','NAV','OL','P','PRE','SECTION','TABLE','UL'
|
|
1138
1157
|
]);
|
|
1139
1158
|
|
|
@@ -1235,13 +1254,6 @@ function _rapierPortablePlainText(root, options) {
|
|
|
1235
1254
|
Array.from(node.children).filter(child => child !== summary).forEach(collectBlock);
|
|
1236
1255
|
return;
|
|
1237
1256
|
}
|
|
1238
|
-
if (tag === 'FIGURE') {
|
|
1239
|
-
const image = node.querySelector(':scope > img');
|
|
1240
|
-
if (image) pushBlock(_rapierPortableInlineText(image, opts), false);
|
|
1241
|
-
const caption = node.querySelector(':scope > figcaption');
|
|
1242
|
-
if (caption) pushBlock(_rapierNormalizeInlineText(_rapierPortableInlineText(caption, opts)), false);
|
|
1243
|
-
return;
|
|
1244
|
-
}
|
|
1245
1257
|
collectChildren(node);
|
|
1246
1258
|
};
|
|
1247
1259
|
|
|
@@ -270,7 +270,7 @@ function _rapierInstallSanitizeHooks() {
|
|
|
270
270
|
&& _rapierSafeRasterDataUrl(compact);
|
|
271
271
|
if (!rasterAllowed) data.keepAttr = false;
|
|
272
272
|
});
|
|
273
|
-
// Paste and
|
|
273
|
+
// Paste, render and export sanitise: a remote <img src> would fetch the instant its markup lands in the page,
|
|
274
274
|
// so the src the hook below is about to strip never survives. Move it to a data-* attribute first
|
|
275
275
|
// (DOMPurify already leaves data-* alone, and the sanitizer's own inert document never fetches it
|
|
276
276
|
// either), so the picture and its alt text still reach Turndown's rapierImage rule as a live
|
|
@@ -279,7 +279,7 @@ function _rapierInstallSanitizeHooks() {
|
|
|
279
279
|
// only the three this hook sets on the node it holds back stay. A picture inside a link is the same
|
|
280
280
|
// node with the same fix: the outer <a> rule reads this img's own converted markdown as its content.
|
|
281
281
|
DOMPurify.addHook('uponSanitizeElement', node => {
|
|
282
|
-
if (_rapierRemoteContent.allowed || !/^(?:paste|render|raw)$/.test(_rapierSanitizeRuntime.context)) return;
|
|
282
|
+
if (_rapierRemoteContent.allowed || !/^(?:paste|render|raw|export)$/.test(_rapierSanitizeRuntime.context)) return;
|
|
283
283
|
if (!node || node.nodeName !== 'IMG' || node.hasAttribute('data-rapier-remote-src')) return;
|
|
284
284
|
const src = node.getAttribute('src') || '';
|
|
285
285
|
if (!src || !_rapierRemoteSubresourceOrigin(src)) return;
|
|
@@ -318,6 +318,8 @@ function _rapierInstallSanitizeHooks() {
|
|
|
318
318
|
return;
|
|
319
319
|
}
|
|
320
320
|
const attribute = String(data.attrName || '').toLowerCase();
|
|
321
|
+
// These attributes carry authored text, never resource requests.
|
|
322
|
+
if (attribute === 'alt' || attribute === 'title') return;
|
|
321
323
|
if (/^data-rapier-remote-(?:src|alt|title)$/.test(attribute) && node && node._rapierHeldBack === true) { data.forceKeepAttr = true; return; }
|
|
322
324
|
if (attribute.startsWith('data-')) return;
|
|
323
325
|
const element = String(node && node.nodeName || '').toUpperCase();
|
package/dist/spec/md-layout.mjs
CHANGED
|
@@ -4,17 +4,25 @@
|
|
|
4
4
|
// `opacity`: a picture's fade, a whole percent from 5 to 100, omitted at 100; the picture's bytes never change.
|
|
5
5
|
// `lines`: a picture's height in lines of the text beside it, set as a drop cap is (CSS `initial-letter`): its top on the first line's cap height, its foot on the
|
|
6
6
|
// Nth line's baseline, its width from its own aspect. A whole number 1 to 12; never with `width`.
|
|
7
|
-
// `first`/`indent`:
|
|
8
|
-
// only.
|
|
7
|
+
// `first`/`indent`: signed levels of 2em, at most six decimals, in [-64,64], omitted at zero. `marker` moves a list
|
|
8
|
+
// item's marker independently of its words, on the same scale; only the item's leading paragraph carries it.
|
|
9
9
|
// `lock=on`: a picture locked in place: a tap goes to the words, never the picture; a hold takes it to unlock it. Omitted when off.
|
|
10
|
-
export const fields = new Set(['align', 'width', 'lines', 'wrap', 'x', 'y', 'rotate', 'opacity', 'first', 'indent', 'lock']);
|
|
10
|
+
export const fields = new Set(['align', 'width', 'lines', 'wrap', 'x', 'y', 'rotate', 'opacity', 'first', 'indent', 'marker', 'lock']);
|
|
11
11
|
export const alignments = new Set(['left', 'center', 'right', 'justify']);
|
|
12
12
|
// `behind`/`front`: out of flow; the paragraph lays out as though the picture were absent.
|
|
13
13
|
export const wraps = new Set(['around', 'box', 'behind', 'front']);
|
|
14
14
|
const has = (value, key) => Object.hasOwn(value, key);
|
|
15
15
|
const percent = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 100;
|
|
16
16
|
const lineCount = value => Number.isInteger(value) && value >= 1 && value <= 12;
|
|
17
|
-
const
|
|
17
|
+
export const INDENT_LIMIT = 64;
|
|
18
|
+
export const indentLevel = value => typeof value === 'number' && Number.isFinite(value) && value !== 0 &&
|
|
19
|
+
Math.abs(value) <= INDENT_LIMIT && Number(value.toFixed(6)) === value;
|
|
20
|
+
// A key press advances an imported measure by one level without truncating its fraction or jumping across zero.
|
|
21
|
+
export function stepIndent(value, step) {
|
|
22
|
+
const next = Number(((value || 0) + step).toFixed(6));
|
|
23
|
+
if (Math.abs(next) > INDENT_LIMIT) return value || 0;
|
|
24
|
+
return !value && step < 0 || value && Math.sign(value) !== Math.sign(next) ? 0 : next;
|
|
25
|
+
}
|
|
18
26
|
const oneDecimalDegrees = value => typeof value === 'number' && Number.isFinite(value) &&
|
|
19
27
|
value > -180 && value <= 180 && Math.abs(value * 10 - Math.round(value * 10)) < 1e-9;
|
|
20
28
|
|
|
@@ -30,7 +38,8 @@ export function validLayout(value) {
|
|
|
30
38
|
(!has(value, 'y') || typeof value.y === 'number' && Number.isFinite(value.y) && value.y >= -50 && has(value, 'wrap')) &&
|
|
31
39
|
(!has(value, 'rotate') || oneDecimalDegrees(value.rotate)) &&
|
|
32
40
|
(!has(value, 'opacity') || Number.isInteger(value.opacity) && value.opacity >= 5 && value.opacity <= 100) &&
|
|
33
|
-
(!has(value, 'first') ||
|
|
41
|
+
(!has(value, 'first') || indentLevel(value.first)) && (!has(value, 'indent') || indentLevel(value.indent)) &&
|
|
42
|
+
(!has(value, 'marker') || indentLevel(value.marker)) &&
|
|
34
43
|
(!has(value, 'lock') || value.lock === 'on') &&
|
|
35
44
|
!(has(value, 'align') && (has(value, 'wrap') || has(value, 'x')));
|
|
36
45
|
}
|
|
@@ -69,8 +78,8 @@ export function parseLayout(comment) {
|
|
|
69
78
|
} else if (key === 'lines') {
|
|
70
79
|
if (!/^[1-9]\d?$/.test(raw)) return null;
|
|
71
80
|
value.lines = Number(raw);
|
|
72
|
-
} else if (key === 'first' || key === 'indent') {
|
|
73
|
-
if (
|
|
81
|
+
} else if (key === 'first' || key === 'indent' || key === 'marker') {
|
|
82
|
+
if (!/^-?(?:0|[1-9]\d?)(?:\.\d{1,6})?$/.test(raw)) return null;
|
|
74
83
|
value[key] = Number(raw);
|
|
75
84
|
} else value[key] = raw;
|
|
76
85
|
}
|
|
@@ -85,6 +94,38 @@ export function decodeLayoutAttribute(value) {
|
|
|
85
94
|
|
|
86
95
|
export const parseLayoutAttribute = value => parseLayout(decodeLayoutAttribute(value));
|
|
87
96
|
|
|
97
|
+
// Indentation uses the document's body type, including on headings. Native table measurements
|
|
98
|
+
// become ordinary CSS points; an item's margin is compensated by its paragraph and nested list.
|
|
99
|
+
export function textStyle(value, {portable = false, pointSize, indentOffset = 0} = {}) {
|
|
100
|
+
if (!validLayout(value)) return '';
|
|
101
|
+
const property = {first: 'text-indent', indent: 'margin-inline-start'};
|
|
102
|
+
return (portable ? ['first', 'indent'] : ['first', 'indent', 'marker'])
|
|
103
|
+
.filter(key => has(value, key) || key === 'indent' && indentOffset).map(key => {
|
|
104
|
+
const level = (value[key] || 0) + (key === 'indent' ? indentOffset : 0);
|
|
105
|
+
const measure = decimal(Number((level * 2).toFixed(6)));
|
|
106
|
+
return (portable ? property[key] : '--md-' + key) + ':' + (portable ?
|
|
107
|
+
pointSize ? decimal(Math.round(level * pointSize * 40) / 20) + 'pt' : measure + 'em' :
|
|
108
|
+
'calc(' + measure + ' * var(--md-text-body))');
|
|
109
|
+
}).join(';');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Raw HTML tables carry ordinary paragraph CSS. Read the browser's parsed declarations on the same
|
|
113
|
+
// admitted scale; the native caller supplies the body's point size. A list marker belongs to its own li,
|
|
114
|
+
// never to a paragraph or a picture.
|
|
115
|
+
export function textLayout(style, {listMarker = false, pointSize, offset = 0} = {}) {
|
|
116
|
+
const value = {};
|
|
117
|
+
for (const [key, property] of (listMarker ? [['marker', 'margin-inline-start']] : [['first', 'text-indent'], ['indent', 'margin-inline-start']])) {
|
|
118
|
+
const raw = style?.getPropertyValue(property).trim() || '';
|
|
119
|
+
const match = /^(-?(?:0|[1-9]\d*)(?:\.\d{1,6})?)(em|pt)$/.exec(raw);
|
|
120
|
+
const level = Number(((match ? Number(match[1]) / (match[2] === 'pt' ? pointSize : 1) / 2 : 0) +
|
|
121
|
+
(key === 'first' ? 0 : offset)).toFixed(6));
|
|
122
|
+
// Public CSS may compensate an enclosing item's margin; the resolved native coordinate,
|
|
123
|
+
// rather than each intermediate declaration, is the measurement the writer must keep.
|
|
124
|
+
if (Number.isFinite(level) && level) value[key] = level;
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
|
|
88
129
|
// The picture's own CSS: its normal-flow width and x, its height in lines, and its fade. `1cap` is the cap height of the
|
|
89
130
|
// picture's own text; a renderer that sets the picture beside its anchor measures that paragraph's line and cap instead.
|
|
90
131
|
export function imageStyle(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-markdown-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.86",
|
|
4
4
|
"description": "Read, render, write and style Self-contained Markdown, and carry its optional edit ledger: the shared HTML writer, semantic renderer, stylesheet, layout, marks, pictures, Will/1, replay, authorship and offline merge. MIT, no npm dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
package/style.css
CHANGED
|
@@ -132,14 +132,8 @@
|
|
|
132
132
|
:where(.md-render) [data-md-align="center"]{text-align:center!important}
|
|
133
133
|
:where(.md-render) [data-md-align="right"]{text-align:right!important}
|
|
134
134
|
:where(.md-render) [data-md-align="justify"]{text-align:justify!important}
|
|
135
|
-
:where(.md-render) p[data-md-first
|
|
136
|
-
|
|
137
|
-
:where(.md-render) p[data-md-first="3"]{text-indent:6em}
|
|
138
|
-
:where(.md-render) p[data-md-first="4"]{text-indent:8em}
|
|
139
|
-
:where(.md-render) p[data-md-indent="1"]{margin-inline-start:2em}
|
|
140
|
-
:where(.md-render) p[data-md-indent="2"]{margin-inline-start:4em}
|
|
141
|
-
:where(.md-render) p[data-md-indent="3"]{margin-inline-start:6em}
|
|
142
|
-
:where(.md-render) p[data-md-indent="4"]{margin-inline-start:8em}
|
|
135
|
+
:where(.md-render) p[data-md-first]{--md-first:0px;text-indent:var(--md-first)}
|
|
136
|
+
.md-render :is(p,h1,h2,h3,h4,h5,h6)[data-md-indent]{--md-indent:0px;margin-inline-start:var(--md-indent)}
|
|
143
137
|
.md-render p[data-md-align="center"]>img{margin-inline:auto}
|
|
144
138
|
.md-render p[data-md-align="right"]>img{margin-left:auto;margin-right:0}
|
|
145
139
|
:where(.md-render) p[data-md-layout-tight]{margin-block:0!important}
|
|
@@ -264,10 +258,10 @@
|
|
|
264
258
|
.md-render p{margin:0 0 var(--md-line)}
|
|
265
259
|
.md-render ul,.md-render ol{margin:0 0 var(--md-line);padding-inline-start:var(--md-space-5)}
|
|
266
260
|
.md-render ul{list-style:none}
|
|
267
|
-
.md-render ul>li{position:relative}
|
|
261
|
+
.md-render ul>li{position:relative;--md-marker:0px}
|
|
268
262
|
|
|
269
263
|
.md-render ul>li::before{
|
|
270
|
-
content:'';position:absolute;inset-inline-start:calc(-1*var(--md-space-4));top:.66em;
|
|
264
|
+
content:'';position:absolute;inset-inline-start:calc(-1*var(--md-space-4) + var(--md-marker, 0px));top:.66em;
|
|
271
265
|
width:7px;height:7px;border-radius:50%;background:currentColor}
|
|
272
266
|
.md-render ul ul>li::before{
|
|
273
267
|
box-sizing:border-box;background:transparent;border:2px solid currentColor}
|
|
@@ -347,6 +341,7 @@
|
|
|
347
341
|
}
|
|
348
342
|
.md-render ol>li{
|
|
349
343
|
position:relative;
|
|
344
|
+
--md-marker:0px;
|
|
350
345
|
counter-increment:rapier-ol;
|
|
351
346
|
min-height:calc(2 * var(--md-unit));
|
|
352
347
|
padding-top:calc(0.15 * var(--md-unit));
|
|
@@ -355,7 +350,7 @@
|
|
|
355
350
|
content:counter(rapier-ol);
|
|
356
351
|
position:absolute;
|
|
357
352
|
/* The circle is centred on the item's first line: the li's own padding, plus half of what the 2rem circle is taller than one line. */
|
|
358
|
-
inset-inline-start:calc(-2.75 * var(--md-unit)); top:calc(calc(0.15 * var(--md-unit)) + (var(--md-line) - calc(2 * var(--md-unit))) / 2);
|
|
353
|
+
inset-inline-start:calc(-2.75 * var(--md-unit) + var(--md-marker, 0px)); top:calc(calc(0.15 * var(--md-unit)) + (var(--md-line) - calc(2 * var(--md-unit))) / 2);
|
|
359
354
|
width:calc(2 * var(--md-unit)); height:calc(2 * var(--md-unit));
|
|
360
355
|
display:flex; align-items:center; justify-content:center;
|
|
361
356
|
border:2px solid var(--md-color-line);
|
|
@@ -372,7 +367,7 @@
|
|
|
372
367
|
content:'';
|
|
373
368
|
position:absolute;
|
|
374
369
|
|
|
375
|
-
inset-inline-start:calc(calc(-1.75 * var(--md-unit)) - 1.5px);
|
|
370
|
+
inset-inline-start:calc(calc(-1.75 * var(--md-unit)) - 1.5px + var(--md-marker, 0px));
|
|
376
371
|
top:calc(2.2 * var(--md-unit));
|
|
377
372
|
bottom:calc(var(--md-line) / -4);
|
|
378
373
|
border-inline-start:3px dotted var(--md-color-line);
|