@sakura-ui/sakura-ui 0.5.3 → 0.5.5
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/package.json +4 -4
- package/packages/core/package.json +1 -1
- package/packages/core/src/components/Link.tsx +17 -0
- package/packages/core/tests/Link.test.tsx +41 -0
- package/packages/forms/.turbo/turbo-build.log +2 -2
- package/packages/helper/.turbo/turbo-build.log +2 -2
- package/packages/markdown/.turbo/turbo-build.log +5 -5
- package/packages/markdown/README.md +17 -1
- package/packages/markdown/dist/index.cjs.js +22 -22
- package/packages/markdown/dist/index.es.js +369 -335
- package/packages/markdown/dist/types/components/Markdown.d.ts.map +1 -1
- package/packages/markdown/dist/types/decorate.d.ts +2 -5
- package/packages/markdown/dist/types/decorate.d.ts.map +1 -1
- package/packages/markdown/dist/types/marked/attributes.d.ts +4 -7
- package/packages/markdown/dist/types/marked/attributes.d.ts.map +1 -1
- package/packages/markdown/dist/types/marked/directives/image.d.ts +3 -0
- package/packages/markdown/dist/types/marked/directives/image.d.ts.map +1 -0
- package/packages/markdown/dist/types/marked/directives/index.d.ts.map +1 -1
- package/packages/markdown/dist/types/marked/html.d.ts.map +1 -1
- package/packages/markdown/dist/types/marked/registry.d.ts.map +1 -1
- package/packages/markdown/dist/types/marked/renderer.d.ts +4 -6
- package/packages/markdown/dist/types/marked/renderer.d.ts.map +1 -1
- package/packages/markdown/dist/types/marked/tokenizer.d.ts.map +1 -1
- package/packages/markdown/dist/types/sanitize.d.ts.map +1 -1
- package/packages/markdown/package.json +1 -1
- package/packages/markdown/src/components/Markdown.tsx +12 -6
- package/packages/markdown/src/decorate.ts +23 -16
- package/packages/markdown/src/marked/attributes.ts +4 -7
- package/packages/markdown/src/marked/directives/card.ts +2 -2
- package/packages/markdown/src/marked/directives/image.ts +29 -0
- package/packages/markdown/src/marked/directives/index.ts +2 -0
- package/packages/markdown/src/marked/html.ts +17 -4
- package/packages/markdown/src/marked/registry.ts +3 -5
- package/packages/markdown/src/marked/renderer.ts +7 -9
- package/packages/markdown/src/marked/tokenizer.ts +5 -7
- package/packages/markdown/src/sanitize.ts +9 -2
- package/packages/markdown/tests/__snapshots__/Markdown.test.tsx.snap +61 -61
- package/packages/markdown/tests/image.test.ts +64 -0
- package/packages/markdown/tests/sanitize.test.ts +17 -0
- package/packages/tailwind-theme-plugin/.turbo/turbo-build.log +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../../src/components/Markdown.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,KAAK,WAAW,EAA6B,MAAM,WAAW,CAAA;AASvE,UAAU,QAAQ;IAChB,KAAK,EAAE,WAAW,EAAE,CAAA;CACrB;AAED,eAAO,MAAM,eAAe,GAAI,WAAW,QAAQ,sBAelD,CAAA;AAED,yBAAiB,QAAQ,CAAC;IACxB,UAAiB,KAAK;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,oDAAoD;QACpD,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,0DAA0D;QAC1D,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,QAAQ,EAAE,MAAM,CAAA;KACjB;CACF;AAED,eAAO,MAAM,QAAQ,GAAI,4DAMtB,QAAQ,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../../src/components/Markdown.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,KAAK,WAAW,EAA6B,MAAM,WAAW,CAAA;AASvE,UAAU,QAAQ;IAChB,KAAK,EAAE,WAAW,EAAE,CAAA;CACrB;AAED,eAAO,MAAM,eAAe,GAAI,WAAW,QAAQ,sBAelD,CAAA;AAED,yBAAiB,QAAQ,CAAC;IACxB,UAAiB,KAAK;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,oDAAoD;QACpD,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,0DAA0D;QAC1D,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,QAAQ,EAAE,MAAM,CAAA;KACjB;CACF;AAED,eAAO,MAAM,QAAQ,GAAI,4DAMtB,QAAQ,CAAC,KAAK,sBAkDhB,CAAA"}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Applies the look of the design system to the finished document.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the same as one written with pipes. The pipeline this replaces had the same
|
|
7
|
-
* property, because rehype-raw turned the raw HTML into real nodes before the
|
|
8
|
-
* tag to component mapping ran.
|
|
4
|
+
* Runs over the sanitised DOM rather than inside the markdown renderer, so that
|
|
5
|
+
* a table written as raw HTML is styled the same as one written with pipes.
|
|
9
6
|
*/
|
|
10
7
|
export type HeadingItem = {
|
|
11
8
|
id?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decorate.d.ts","sourceRoot":"","sources":["../../src/decorate.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"decorate.d.ts","sourceRoot":"","sources":["../../src/decorate.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;CACzB,CAAA;AAoED,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,WAAW,EAAE,CAAA;CACxB,CAAA;AAID,eAAO,MAAM,QAAQ,GACnB,MAAM,gBAAgB,GAAG,OAAO,EAChC,UAAU,QAAQ,EAClB,SAAS,eAAe,KACvB,cAuEF,CAAA"}
|
|
@@ -2,14 +2,11 @@ export type Attrs = Record<string, string>;
|
|
|
2
2
|
/**
|
|
3
3
|
* Where `{#name}` is kept, apart from anything written as `key=value`.
|
|
4
4
|
*
|
|
5
|
-
* A sigil
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* attribute called `id` take the anchor's place, which is how the video on a
|
|
9
|
-
* youtube embed came to be called `id` and stayed that way.
|
|
10
|
-
*
|
|
5
|
+
* A sigil describes the element and means the same on every directive; an
|
|
6
|
+
* attribute belongs to whichever directive reads it. Held apart so that a
|
|
7
|
+
* directive wanting an attribute called `id` cannot take the anchor's place.
|
|
11
8
|
* A key beginning with `#` cannot be written as `key=value`, since an attribute
|
|
12
|
-
* name has to start with a letter
|
|
9
|
+
* name has to start with a letter.
|
|
13
10
|
*/
|
|
14
11
|
export declare const ANCHOR = "#id";
|
|
15
12
|
/** Parses the inside of `{...}`: `key=value key2="値 2" #id .cls` */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/marked/attributes.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAE1C
|
|
1
|
+
{"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/marked/attributes.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAE1C;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,QAAQ,CAAA;AAE3B,oEAAoE;AACpE,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,GAAG,IAAI,KAAG,KAkBhD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,MAAM,EACX,OAAO,MAAM,EACb,MAAM,MAAM,EACZ,OAAO,MAAM,KACZ;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAkBnC,CAAA;AAED,mFAAmF;AACnF,eAAO,MAAM,QAAQ,GACnB,KAAK,MAAM,EACX,GAAG,MAAM,KACR;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAcnD,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../../src/marked/directives/image.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAmBlD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAQ5D,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/marked/directives/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/marked/directives/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAgBlD;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,iBAAiB,GAAG,SACN,CAAA;AAE1D,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,MAAM,EACN,eAAe,EAChB,MAAM,WAAW,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../src/marked/html.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,MACe,CAAA;AAgB3D;;;GAGG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAS5D,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../src/marked/html.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,MACe,CAAA;AAgB3D;;;GAGG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAS5D,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAA;AAS3D,2DAA2D;AAC3D,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,KAAG,MAUlD,CAAA;AAEb;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,GAAG,QAAQ,CAAC,MAAM,GAAG,SAAS,GAAG,KAAK,CAAC,EAAE,KAAG,MACE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/marked/registry.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,cAAc,CAAA;AACpC,eAAO,MAAM,IAAI,SAAS,CAAA;AAC1B,eAAO,MAAM,IAAI,SAAS,CAAA;AAE1B,MAAM,MAAM,aAAa,GAAG,OAAO,SAAS,GAAG,OAAO,IAAI,GAAG,OAAO,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/marked/registry.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,cAAc,CAAA;AACpC,eAAO,MAAM,IAAI,SAAS,CAAA;AAC1B,eAAO,MAAM,IAAI,SAAS,CAAA;AAE1B,MAAM,MAAM,aAAa,GAAG,OAAO,SAAS,GAAG,OAAO,IAAI,GAAG,OAAO,IAAI,CAAA;AAuBxE,+EAA+E;AAC/E,eAAO,MAAM,OAAO,QAAiC,CAAA;AAErD,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,EAAE,MAAM,aAAa,KAAG,OACmB,CAAA"}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { DirectiveToken } from './tokenizer';
|
|
2
2
|
import { type Parser, type RendererOptions } from './directives';
|
|
3
3
|
/**
|
|
4
|
-
* Builds
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* a title has to know whether the card gave it an href.
|
|
4
|
+
* Builds the context, then hands the token to the renderer that knows its name.
|
|
5
|
+
* The renderers live in `directives/`, a file to a feature, so that a card and
|
|
6
|
+
* everything written inside one stay together.
|
|
8
7
|
*
|
|
9
|
-
* A name with no renderer falls through to its own children
|
|
10
|
-
* directive the registry allows but nobody has drawn yet looks like.
|
|
8
|
+
* A name with no renderer falls through to its own children.
|
|
11
9
|
*/
|
|
12
10
|
export declare function directiveRenderer(this: {
|
|
13
11
|
parser: Parser;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/marked/renderer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,eAAe,EACrB,MAAM,cAAc,CAAA;AAErB
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/marked/renderer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,eAAe,EACrB,MAAM,cAAc,CAAA;AAErB;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EACxB,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,eAAe,GACvB,MAAM,CAyBR;AAED,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenizer.d.ts","sourceRoot":"","sources":["../../../src/marked/tokenizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC3C,OAAO,EAAE,KAAK,KAAK,EAAY,MAAM,cAAc,CAAA;AACnD,OAAO,EAAa,KAAK,aAAa,EAAuB,MAAM,YAAY,CAAA;AAE/E,MAAM,WAAW,cAAe,SAAQ,MAAM,CAAC,OAAO;IACpD,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,KAAK,EAAE,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,4EAA4E;IAC5E,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9C,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AA4BD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAGjD,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAGlD,CAAA;
|
|
1
|
+
{"version":3,"file":"tokenizer.d.ts","sourceRoot":"","sources":["../../../src/marked/tokenizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC3C,OAAO,EAAE,KAAK,KAAK,EAAY,MAAM,cAAc,CAAA;AACnD,OAAO,EAAa,KAAK,aAAa,EAAuB,MAAM,YAAY,CAAA;AAE/E,MAAM,WAAW,cAAe,SAAQ,MAAM,CAAC,OAAO;IACpD,IAAI,EAAE,WAAW,CAAA;IACjB,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,KAAK,EAAE,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,4EAA4E;IAC5E,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9C,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AA4BD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAGjD,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAGlD,CAAA;AA6CD,wBAAgB,cAAc,CAC5B,IAAI,EAAE;IACJ,KAAK,EAAE;QACL,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,KAAK,EAAE,CAAA;QACrC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAA;KAC/C,CAAA;CACF,EACD,GAAG,EAAE,MAAM,GACV,cAAc,GAAG,SAAS,CAsC5B;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE;IAAE,KAAK,EAAE;QAAE,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,KAAK,EAAE,CAAA;KAAE,CAAA;CAAE,EAC3D,GAAG,EAAE,MAAM,GACV,cAAc,GAAG,SAAS,CAiB5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/sanitize.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/sanitize.ts"],"names":[],"mappings":"AAyLA;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,KAAG,gBAQvC,CAAA;AAED,eAAO,MAAM,SAAS;yBAzGM,MAAM,KAAG,MAAM;;CAyGqB,CAAA"}
|
|
@@ -75,10 +75,8 @@ export const Markdown = ({
|
|
|
75
75
|
{showToc && headings.length > 0 && (
|
|
76
76
|
<nav className="rounded-3xl p-10 bg-yellow-50">
|
|
77
77
|
{/*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
that separates it from the text above it. This is the label on a
|
|
81
|
-
box, at the top of it, with nothing above to separate it from.
|
|
78
|
+
The label on a box, not a heading in the document, so it does not
|
|
79
|
+
use core's H2 and its heading size and spacing.
|
|
82
80
|
*/}
|
|
83
81
|
<h2 className="mb-4 font-bold text-h-xs-m sm:text-h-xs">
|
|
84
82
|
{tocTitle}
|
|
@@ -88,8 +86,16 @@ export const Markdown = ({
|
|
|
88
86
|
</Ul>
|
|
89
87
|
</nav>
|
|
90
88
|
)}
|
|
91
|
-
{/*
|
|
92
|
-
|
|
89
|
+
{/*
|
|
90
|
+
The document's blocks are children of this element, not of the wrapper,
|
|
91
|
+
so the space between them is set here.
|
|
92
|
+
|
|
93
|
+
The markup is sanitised in src/sanitize.ts before it gets here.
|
|
94
|
+
*/}
|
|
95
|
+
<div
|
|
96
|
+
className="flex flex-col gap-8"
|
|
97
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
98
|
+
/>
|
|
93
99
|
</div>
|
|
94
100
|
)
|
|
95
101
|
}
|
|
@@ -5,11 +5,8 @@ import { classNames } from './marked/html'
|
|
|
5
5
|
/**
|
|
6
6
|
* Applies the look of the design system to the finished document.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the same as one written with pipes. The pipeline this replaces had the same
|
|
11
|
-
* property, because rehype-raw turned the raw HTML into real nodes before the
|
|
12
|
-
* tag to component mapping ran.
|
|
8
|
+
* Runs over the sanitised DOM rather than inside the markdown renderer, so that
|
|
9
|
+
* a table written as raw HTML is styled the same as one written with pipes.
|
|
13
10
|
*/
|
|
14
11
|
|
|
15
12
|
export type HeadingItem = {
|
|
@@ -41,7 +38,21 @@ const CLASS_BY_TAG: Record<string, string> = {
|
|
|
41
38
|
|
|
42
39
|
/** Elements a directive rendered are already dressed; leave them be. */
|
|
43
40
|
const isDirectiveOutput = (element: Element) =>
|
|
44
|
-
element.hasAttribute('data-
|
|
41
|
+
element.hasAttribute('data-styled')
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A link that opens elsewhere must not hand the opener to the page it opens.
|
|
45
|
+
* Merged with what the document asked for rather than replacing it, so a
|
|
46
|
+
* `rel="nofollow"` does not take `noopener` away with it.
|
|
47
|
+
*/
|
|
48
|
+
const externalRel = (rel: string | null): string =>
|
|
49
|
+
Array.from(
|
|
50
|
+
new Set([
|
|
51
|
+
'noopener',
|
|
52
|
+
'noreferrer',
|
|
53
|
+
...(rel ?? '').split(/\s+/).filter(Boolean)
|
|
54
|
+
])
|
|
55
|
+
).join(' ')
|
|
45
56
|
|
|
46
57
|
const addClass = (element: Element, value: string) => {
|
|
47
58
|
const merged = classNames(element.getAttribute('class') ?? '', value)
|
|
@@ -56,10 +67,10 @@ const HEADINGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
|
|
|
56
67
|
* the Icon component does.
|
|
57
68
|
*/
|
|
58
69
|
const appendNewTabIcon = (anchor: Element, document: Document) => {
|
|
59
|
-
if (anchor.querySelector('[data-
|
|
70
|
+
if (anchor.querySelector('[data-new-tab]')) return
|
|
60
71
|
const glyph = document.createElement('span')
|
|
61
72
|
glyph.setAttribute('aria-hidden', 'true')
|
|
62
|
-
glyph.setAttribute('data-
|
|
73
|
+
glyph.setAttribute('data-new-tab', 'true')
|
|
63
74
|
glyph.setAttribute(
|
|
64
75
|
'class',
|
|
65
76
|
classNames(styles.iconSizeStyle[16], styles.iconStyle, 'ml-0.5')
|
|
@@ -113,9 +124,7 @@ export const decorate = (
|
|
|
113
124
|
const tag = element.tagName.toLowerCase()
|
|
114
125
|
|
|
115
126
|
// One id, generated once, for headings from markdown and from raw HTML
|
|
116
|
-
// alike
|
|
117
|
-
// disagreed whenever a heading held an image, raw HTML or a directive, and
|
|
118
|
-
// the table of contents then linked to anchors that were not there.
|
|
127
|
+
// alike, so the table of contents and the body cannot disagree.
|
|
119
128
|
if (HEADINGS.has(tag)) {
|
|
120
129
|
const text = element.textContent ?? ''
|
|
121
130
|
const asked = element.getAttribute('id')
|
|
@@ -136,9 +145,7 @@ export const decorate = (
|
|
|
136
145
|
element.getAttribute('target') === '_blank'
|
|
137
146
|
) {
|
|
138
147
|
element.setAttribute('target', '_blank')
|
|
139
|
-
|
|
140
|
-
// leaves the opened page holding a handle on this one.
|
|
141
|
-
element.setAttribute('rel', 'noopener noreferrer')
|
|
148
|
+
element.setAttribute('rel', externalRel(element.getAttribute('rel')))
|
|
142
149
|
appendNewTabIcon(element, document)
|
|
143
150
|
}
|
|
144
151
|
}
|
|
@@ -150,10 +157,10 @@ export const decorate = (
|
|
|
150
157
|
// A wide table scrolls inside its own box rather than pushing the page.
|
|
151
158
|
for (const table of Array.from(root.querySelectorAll('table'))) {
|
|
152
159
|
const parent = table.parentElement
|
|
153
|
-
if (parent?.dataset?.
|
|
160
|
+
if (parent?.dataset?.tableContainer === 'true') continue
|
|
154
161
|
const container = document.createElement('div')
|
|
155
162
|
container.setAttribute('class', classNames(styles.overflowContainerStyle))
|
|
156
|
-
container.dataset.
|
|
163
|
+
container.dataset.tableContainer = 'true'
|
|
157
164
|
table.replaceWith(container)
|
|
158
165
|
container.appendChild(table)
|
|
159
166
|
}
|
|
@@ -6,14 +6,11 @@ export type Attrs = Record<string, string>
|
|
|
6
6
|
/**
|
|
7
7
|
* Where `{#name}` is kept, apart from anything written as `key=value`.
|
|
8
8
|
*
|
|
9
|
-
* A sigil
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* attribute called `id` take the anchor's place, which is how the video on a
|
|
13
|
-
* youtube embed came to be called `id` and stayed that way.
|
|
14
|
-
*
|
|
9
|
+
* A sigil describes the element and means the same on every directive; an
|
|
10
|
+
* attribute belongs to whichever directive reads it. Held apart so that a
|
|
11
|
+
* directive wanting an attribute called `id` cannot take the anchor's place.
|
|
15
12
|
* A key beginning with `#` cannot be written as `key=value`, since an attribute
|
|
16
|
-
* name has to start with a letter
|
|
13
|
+
* name has to start with a letter.
|
|
17
14
|
*/
|
|
18
15
|
export const ANCHOR = '#id'
|
|
19
16
|
|
|
@@ -12,8 +12,8 @@ const iconHtml = (name: string, altText: string): string =>
|
|
|
12
12
|
|
|
13
13
|
export const cardRenderers: Record<string, DirectiveRenderer> = {
|
|
14
14
|
card: ({ token, attrs, root, body, nl }) => {
|
|
15
|
-
//
|
|
16
|
-
// link
|
|
15
|
+
// Set by the tokenizer, and only when the card has a title to put the
|
|
16
|
+
// link on. A card without one must not look as though it were clickable.
|
|
17
17
|
const isLink = Boolean(token.linked)
|
|
18
18
|
const cls = classNames(
|
|
19
19
|
styles.cardStyle,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { classNames, cleanUrl } from '../html'
|
|
2
|
+
import type { DirectiveRenderer } from './context'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `width` and `height` are pixels. `max-width` goes with a width so that the
|
|
6
|
+
* image shrinks inside its column on a narrow screen rather than overflowing
|
|
7
|
+
* it; with a height alone there is nothing to overflow, since the width
|
|
8
|
+
* follows the proportions.
|
|
9
|
+
*/
|
|
10
|
+
const sizeStyle = (
|
|
11
|
+
width: string | undefined,
|
|
12
|
+
height: string | undefined
|
|
13
|
+
): string | undefined => {
|
|
14
|
+
const rules: string[] = []
|
|
15
|
+
if (width) rules.push(`width:${width}px`)
|
|
16
|
+
if (height) rules.push(`height:${height}px`)
|
|
17
|
+
if (width) rules.push('max-width:100%')
|
|
18
|
+
return rules.length > 0 ? rules.join(';') : undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const imageRenderers: Record<string, DirectiveRenderer> = {
|
|
22
|
+
img: ({ attrs, root, nl }) =>
|
|
23
|
+
`<img${root({
|
|
24
|
+
class: classNames(attrs.class),
|
|
25
|
+
src: cleanUrl(attrs.src),
|
|
26
|
+
alt: attrs.alt ?? '',
|
|
27
|
+
style: sizeStyle(attrs.width, attrs.height)
|
|
28
|
+
})}>${nl}`
|
|
29
|
+
}
|
|
@@ -2,6 +2,7 @@ import { GRID_RE } from '../registry'
|
|
|
2
2
|
import { cardRenderers } from './card'
|
|
3
3
|
import type { DirectiveRenderer } from './context'
|
|
4
4
|
import { faqRenderers } from './faq'
|
|
5
|
+
import { imageRenderers } from './image'
|
|
5
6
|
import { cellRenderers, grid } from './grid'
|
|
6
7
|
import { linkButtonRenderers } from './linkButton'
|
|
7
8
|
import { youtubeRenderers } from './youtube'
|
|
@@ -10,6 +11,7 @@ const BY_NAME: Record<string, DirectiveRenderer> = {
|
|
|
10
11
|
...cardRenderers,
|
|
11
12
|
...cellRenderers,
|
|
12
13
|
...faqRenderers,
|
|
14
|
+
...imageRenderers,
|
|
13
15
|
...youtubeRenderers,
|
|
14
16
|
...linkButtonRenderers
|
|
15
17
|
}
|
|
@@ -31,19 +31,32 @@ export const cleanUrl = (href: string | undefined): string | undefined => {
|
|
|
31
31
|
if (href == null || href === '') return undefined
|
|
32
32
|
const trimmed = stripControl(String(href)).trim()
|
|
33
33
|
if (/^(?:javascript|vbscript|file):/i.test(trimmed)) return undefined
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
34
|
+
// Refused here and in the sanitiser both: DOMPurify allows the scheme on an
|
|
35
|
+
// img whatever ALLOWED_URI_REGEXP says, so this is the only place a directive
|
|
36
|
+
// writing one is turned back.
|
|
37
37
|
if (/^data:/i.test(trimmed)) return undefined
|
|
38
38
|
return trimmed
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export type AttrValue = string | boolean | undefined | null
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Attributes where an empty value says something, and so is written out. On an
|
|
45
|
+
* image an empty alt marks it as decoration; leaving the attribute off instead
|
|
46
|
+
* makes a screen reader read the file name.
|
|
47
|
+
*/
|
|
48
|
+
const KEEP_WHEN_EMPTY = new Set(['alt'])
|
|
49
|
+
|
|
43
50
|
/** `{a: 'x', b: true, c: undefined}` becomes ` a="x" b` */
|
|
44
51
|
export const attrsToHtml = (attrs: Record<string, AttrValue>): string =>
|
|
45
52
|
Object.entries(attrs)
|
|
46
|
-
.filter(
|
|
53
|
+
.filter(
|
|
54
|
+
([k, v]) =>
|
|
55
|
+
v !== undefined &&
|
|
56
|
+
v !== null &&
|
|
57
|
+
v !== false &&
|
|
58
|
+
(v !== '' || KEEP_WHEN_EMPTY.has(k))
|
|
59
|
+
)
|
|
47
60
|
.map(([k, v]) => (v === true ? ` ${k}` : ` ${k}="${escapeHtml(v)}"`))
|
|
48
61
|
.join('')
|
|
49
62
|
|
|
@@ -6,11 +6,8 @@ export type DirectiveKind = typeof CONTAINER | typeof LEAF | typeof TEXT
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The whitelist. A name that is not here is not a directive, and the text
|
|
9
|
-
* carrying it is left alone
|
|
10
|
-
*
|
|
11
|
-
* The remark pipeline this replaces turned any name into a tag of the same
|
|
12
|
-
* name, so ordinary prose lost characters: "HH:MM" rendered as "HH", and
|
|
13
|
-
* "chat:write" as "chat".
|
|
9
|
+
* carrying it is left alone — which is what keeps "HH:MM" and "chat:write"
|
|
10
|
+
* intact in the prose.
|
|
14
11
|
*/
|
|
15
12
|
const STATIC: Record<string, DirectiveKind[]> = {
|
|
16
13
|
card: [CONTAINER],
|
|
@@ -23,6 +20,7 @@ const STATIC: Record<string, DirectiveKind[]> = {
|
|
|
23
20
|
'faq-a': [LEAF, CONTAINER],
|
|
24
21
|
cell: [CONTAINER],
|
|
25
22
|
'cell-img': [LEAF],
|
|
23
|
+
img: [LEAF],
|
|
26
24
|
youtube: [LEAF, CONTAINER],
|
|
27
25
|
'link-button': [TEXT]
|
|
28
26
|
}
|
|
@@ -10,13 +10,11 @@ import {
|
|
|
10
10
|
} from './directives'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Builds
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* a title has to know whether the card gave it an href.
|
|
13
|
+
* Builds the context, then hands the token to the renderer that knows its name.
|
|
14
|
+
* The renderers live in `directives/`, a file to a feature, so that a card and
|
|
15
|
+
* everything written inside one stay together.
|
|
17
16
|
*
|
|
18
|
-
* A name with no renderer falls through to its own children
|
|
19
|
-
* directive the registry allows but nobody has drawn yet looks like.
|
|
17
|
+
* A name with no renderer falls through to its own children.
|
|
20
18
|
*/
|
|
21
19
|
export function directiveRenderer(
|
|
22
20
|
this: { parser: Parser },
|
|
@@ -26,10 +24,10 @@ export function directiveRenderer(
|
|
|
26
24
|
const { name, kind, attrs, tokens } = token
|
|
27
25
|
const nl = kind === TEXT ? '' : '\n'
|
|
28
26
|
|
|
29
|
-
// Marks
|
|
30
|
-
//
|
|
27
|
+
// Marks the element as dressed, so that the pass which styles the rest of
|
|
28
|
+
// the document leaves it alone. The value names the directive it came from.
|
|
31
29
|
const own: DirectiveContext['own'] = (values) =>
|
|
32
|
-
attrsToHtml({ ...values, 'data-
|
|
30
|
+
attrsToHtml({ ...values, 'data-styled': name })
|
|
33
31
|
|
|
34
32
|
const context: DirectiveContext = {
|
|
35
33
|
token,
|
|
@@ -60,14 +60,12 @@ export const inlineStart = (src: string): number | undefined => {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* A grid of cards is a list
|
|
64
|
-
* there are is part of what the page says
|
|
65
|
-
* cards
|
|
66
|
-
* two of something. Nothing is written to ask for this, the way nothing is
|
|
67
|
-
* written to ask a run of "- " for a <ul>.
|
|
63
|
+
* A grid of cards is a list: the cards are things of one kind, and how many
|
|
64
|
+
* there are is part of what the page says. A grid holding anything else, or
|
|
65
|
+
* cards mixed with something else, is a layout.
|
|
68
66
|
*
|
|
69
|
-
* Marked here rather than
|
|
70
|
-
*
|
|
67
|
+
* Marked here rather than while rendering, because by then the children are one
|
|
68
|
+
* string with no boundaries between them.
|
|
71
69
|
*/
|
|
72
70
|
const markCardList = (token: DirectiveToken): void => {
|
|
73
71
|
if (!/^grid-cols-\d+$/.test(token.name)) return
|
|
@@ -168,9 +168,16 @@ const purifier = (): DOMPurify => {
|
|
|
168
168
|
element.removeAttribute('style')
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
-
// A link that opens elsewhere must not hand the opener over with it.
|
|
171
|
+
// A link that opens elsewhere must not hand the opener over with it. What
|
|
172
|
+
// the document asked for is kept alongside rather than replaced.
|
|
172
173
|
if (element.getAttribute('target') === '_blank') {
|
|
173
|
-
element.
|
|
174
|
+
const asked = (element.getAttribute('rel') ?? '').split(/\s+/)
|
|
175
|
+
element.setAttribute(
|
|
176
|
+
'rel',
|
|
177
|
+
Array.from(
|
|
178
|
+
new Set(['noopener', 'noreferrer', ...asked.filter(Boolean)])
|
|
179
|
+
).join(' ')
|
|
180
|
+
)
|
|
174
181
|
}
|
|
175
182
|
})
|
|
176
183
|
return instance
|