react-markdown-canvas 0.0.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.
@@ -0,0 +1,451 @@
1
+ /**
2
+ * @template T
3
+ * @typedef {import('react').ComponentType<T>} ComponentType<T>
4
+ */
5
+
6
+ /**
7
+ * @template {import('react').ElementType} T
8
+ * @typedef {import('react').ComponentPropsWithoutRef<T>} ComponentPropsWithoutRef<T>
9
+ */
10
+
11
+ /**
12
+ * @typedef {import('react').ReactNode} ReactNode
13
+ * @typedef {import('unist').Position} Position
14
+ * @typedef {import('hast').Element} Element
15
+ * @typedef {import('hast').ElementContent} ElementContent
16
+ * @typedef {import('hast').Root} Root
17
+ * @typedef {import('hast').Text} Text
18
+ * @typedef {import('hast').Comment} Comment
19
+ * @typedef {import('hast').DocType} Doctype
20
+ * @typedef {import('property-information').Info} Info
21
+ * @typedef {import('property-information').Schema} Schema
22
+ * @typedef {import('./complex-types.js').ReactMarkdownProps} ReactMarkdownProps
23
+ *
24
+ * @typedef Raw
25
+ * @property {'raw'} type
26
+ * @property {string} value
27
+ *
28
+ * @typedef Context
29
+ * @property {Options} options
30
+ * @property {Schema} schema
31
+ * @property {number} listDepth
32
+ *
33
+ * @callback TransformLink
34
+ * @param {string} href
35
+ * @param {Array<ElementContent>} children
36
+ * @param {string?} title
37
+ * @returns {string}
38
+ *
39
+ * @callback TransformImage
40
+ * @param {string} src
41
+ * @param {string} alt
42
+ * @param {string?} title
43
+ * @returns {string}
44
+ *
45
+ * @typedef {import('react').HTMLAttributeAnchorTarget} TransformLinkTargetType
46
+ *
47
+ * @callback TransformLinkTarget
48
+ * @param {string} href
49
+ * @param {Array<ElementContent>} children
50
+ * @param {string?} title
51
+ * @returns {TransformLinkTargetType|undefined}
52
+ *
53
+ * @typedef {keyof JSX.IntrinsicElements} ReactMarkdownNames
54
+ *
55
+ * To do: is `data-sourcepos` typeable?
56
+ *
57
+ * @typedef {ComponentPropsWithoutRef<'code'> & ReactMarkdownProps & {inline?: boolean}} CodeProps
58
+ * @typedef {ComponentPropsWithoutRef<'h1'> & ReactMarkdownProps & {level: number}} HeadingProps
59
+ * @typedef {ComponentPropsWithoutRef<'li'> & ReactMarkdownProps & {checked: boolean|null, index: number, ordered: boolean}} LiProps
60
+ * @typedef {ComponentPropsWithoutRef<'ol'> & ReactMarkdownProps & {depth: number, ordered: true}} OrderedListProps
61
+ * @typedef {ComponentPropsWithoutRef<'td'> & ReactMarkdownProps & {style?: Record<string, unknown>, isHeader: false}} TableDataCellProps
62
+ * @typedef {ComponentPropsWithoutRef<'th'> & ReactMarkdownProps & {style?: Record<string, unknown>, isHeader: true}} TableHeaderCellProps
63
+ * @typedef {ComponentPropsWithoutRef<'tr'> & ReactMarkdownProps & {isHeader: boolean}} TableRowProps
64
+ * @typedef {ComponentPropsWithoutRef<'ul'> & ReactMarkdownProps & {depth: number, ordered: false}} UnorderedListProps
65
+ *
66
+ * @typedef {ComponentType<CodeProps>} CodeComponent
67
+ * @typedef {ComponentType<HeadingProps>} HeadingComponent
68
+ * @typedef {ComponentType<LiProps>} LiComponent
69
+ * @typedef {ComponentType<OrderedListProps>} OrderedListComponent
70
+ * @typedef {ComponentType<TableDataCellProps>} TableDataCellComponent
71
+ * @typedef {ComponentType<TableHeaderCellProps>} TableHeaderCellComponent
72
+ * @typedef {ComponentType<TableRowProps>} TableRowComponent
73
+ * @typedef {ComponentType<UnorderedListProps>} UnorderedListComponent
74
+ *
75
+ * @typedef SpecialComponents
76
+ * @property {CodeComponent|ReactMarkdownNames} code
77
+ * @property {HeadingComponent|ReactMarkdownNames} h1
78
+ * @property {HeadingComponent|ReactMarkdownNames} h2
79
+ * @property {HeadingComponent|ReactMarkdownNames} h3
80
+ * @property {HeadingComponent|ReactMarkdownNames} h4
81
+ * @property {HeadingComponent|ReactMarkdownNames} h5
82
+ * @property {HeadingComponent|ReactMarkdownNames} h6
83
+ * @property {LiComponent|ReactMarkdownNames} li
84
+ * @property {OrderedListComponent|ReactMarkdownNames} ol
85
+ * @property {TableDataCellComponent|ReactMarkdownNames} td
86
+ * @property {TableHeaderCellComponent|ReactMarkdownNames} th
87
+ * @property {TableRowComponent|ReactMarkdownNames} tr
88
+ * @property {UnorderedListComponent|ReactMarkdownNames} ul
89
+ *
90
+ * @typedef {Partial<Omit<import('./complex-types.js').NormalComponents, keyof SpecialComponents> & SpecialComponents>} Components
91
+ *
92
+ * @typedef Options
93
+ * @property {boolean} [sourcePos=false]
94
+ * @property {boolean} [rawSourcePos=false]
95
+ * @property {boolean} [skipHtml=false]
96
+ * @property {boolean} [includeElementIndex=false]
97
+ * @property {null|false|TransformLink} [transformLinkUri]
98
+ * @property {TransformImage} [transformImageUri]
99
+ * @property {TransformLinkTargetType|TransformLinkTarget} [linkTarget]
100
+ * @property {Components} [components]
101
+ */
102
+
103
+ import React from 'react'
104
+ import ReactIs from 'react-is'
105
+ import {whitespace} from 'hast-util-whitespace'
106
+ import {svg, find, hastToReact} from 'property-information'
107
+ import {stringify as spaces} from 'space-separated-tokens'
108
+ import {stringify as commas} from 'comma-separated-tokens'
109
+ import style from 'style-to-object'
110
+ import {uriTransformer} from './uri-transformer.js'
111
+
112
+ const own = {}.hasOwnProperty
113
+
114
+ // The table-related elements that must not contain whitespace text according
115
+ // to React.
116
+ const tableElements = new Set(['table', 'thead', 'tbody', 'tfoot', 'tr'])
117
+
118
+ /**
119
+ * @param {Context} context
120
+ * @param {Element|Root} node
121
+ */
122
+ export function childrenToReact(context, node) {
123
+ /** @type {Array<ReactNode>} */
124
+ const children = []
125
+ let childIndex = -1
126
+ /** @type {Comment|Doctype|Element|Raw|Text} */
127
+ let child
128
+
129
+ while (++childIndex < node.children.length) {
130
+ child = node.children[childIndex]
131
+
132
+ if (child.type === 'element') {
133
+ children.push(toReact(context, child, childIndex, node))
134
+ } else if (child.type === 'text') {
135
+ // Currently, a warning is triggered by react for *any* white space in
136
+ // tables.
137
+ // So we drop it.
138
+ // See: <https://github.com/facebook/react/pull/7081>.
139
+ // See: <https://github.com/facebook/react/pull/7515>.
140
+ // See: <https://github.com/remarkjs/remark-react/issues/64>.
141
+ // See: <https://github.com/remarkjs/react-markdown/issues/576>.
142
+ if (
143
+ node.type !== 'element' ||
144
+ !tableElements.has(node.tagName) ||
145
+ !whitespace(child)
146
+ ) {
147
+ children.push(child.value)
148
+ }
149
+ } else if (child.type === 'raw' && !context.options.skipHtml) {
150
+ // Default behavior is to show (encoded) HTML.
151
+ children.push(child.value)
152
+ }
153
+ }
154
+
155
+ return children
156
+ }
157
+
158
+ /**
159
+ * @param {Context} context
160
+ * @param {Element} node
161
+ * @param {number} index
162
+ * @param {Element|Root} parent
163
+ */
164
+ function toReact(context, node, index, parent) {
165
+ const options = context.options
166
+ const transform =
167
+ options.transformLinkUri === undefined
168
+ ? uriTransformer
169
+ : options.transformLinkUri
170
+ const parentSchema = context.schema
171
+ /** @type {ReactMarkdownNames} */
172
+ // @ts-expect-error assume a known HTML/SVG element.
173
+ const name = node.tagName
174
+ /** @type {Record<string, unknown>} */
175
+ const properties = {}
176
+ let schema = parentSchema
177
+ /** @type {string} */
178
+ let property
179
+
180
+ if (parentSchema.space === 'html' && name === 'svg') {
181
+ schema = svg
182
+ context.schema = schema
183
+ }
184
+
185
+ if (node.properties) {
186
+ for (property in node.properties) {
187
+ if (own.call(node.properties, property)) {
188
+ addProperty(properties, property, node.properties[property], context)
189
+ }
190
+ }
191
+ }
192
+
193
+ if (name === 'ol' || name === 'ul') {
194
+ context.listDepth++
195
+ }
196
+
197
+ const children = childrenToReact(context, node)
198
+
199
+ if (name === 'ol' || name === 'ul') {
200
+ context.listDepth--
201
+ }
202
+
203
+ // Restore parent schema.
204
+ context.schema = parentSchema
205
+
206
+ // Nodes created by plugins do not have positional info, in which case we use
207
+ // an object that matches the position interface.
208
+ const position = node.position || {
209
+ start: {line: null, column: null, offset: null},
210
+ end: {line: null, column: null, offset: null}
211
+ }
212
+ const component =
213
+ options.components && own.call(options.components, name)
214
+ ? options.components[name]
215
+ : name
216
+ const basic = typeof component === 'string' || component === React.Fragment
217
+
218
+ if (!ReactIs.isValidElementType(component)) {
219
+ throw new TypeError(
220
+ `Component for name \`${name}\` not defined or is not renderable`
221
+ )
222
+ }
223
+
224
+ properties.key = index
225
+
226
+ if (name === 'a' && options.linkTarget) {
227
+ properties.target =
228
+ typeof options.linkTarget === 'function'
229
+ ? options.linkTarget(
230
+ String(properties.href || ''),
231
+ node.children,
232
+ typeof properties.title === 'string' ? properties.title : null
233
+ )
234
+ : options.linkTarget
235
+ }
236
+
237
+ if (name === 'a' && transform) {
238
+ properties.href = transform(
239
+ String(properties.href || ''),
240
+ node.children,
241
+ typeof properties.title === 'string' ? properties.title : null
242
+ )
243
+ }
244
+
245
+ if (
246
+ !basic &&
247
+ name === 'code' &&
248
+ parent.type === 'element' &&
249
+ parent.tagName !== 'pre'
250
+ ) {
251
+ properties.inline = true
252
+ }
253
+
254
+ if (
255
+ !basic &&
256
+ (name === 'h1' ||
257
+ name === 'h2' ||
258
+ name === 'h3' ||
259
+ name === 'h4' ||
260
+ name === 'h5' ||
261
+ name === 'h6')
262
+ ) {
263
+ properties.level = Number.parseInt(name.charAt(1), 10)
264
+ }
265
+
266
+ if (name === 'img' && options.transformImageUri) {
267
+ properties.src = options.transformImageUri(
268
+ String(properties.src || ''),
269
+ String(properties.alt || ''),
270
+ typeof properties.title === 'string' ? properties.title : null
271
+ )
272
+ }
273
+
274
+ if (!basic && name === 'li' && parent.type === 'element') {
275
+ const input = getInputElement(node)
276
+ properties.checked =
277
+ input && input.properties ? Boolean(input.properties.checked) : null
278
+ properties.index = getElementsBeforeCount(parent, node)
279
+ properties.ordered = parent.tagName === 'ol'
280
+ }
281
+
282
+ if (!basic && (name === 'ol' || name === 'ul')) {
283
+ properties.ordered = name === 'ol'
284
+ properties.depth = context.listDepth
285
+ }
286
+
287
+ if (name === 'td' || name === 'th') {
288
+ if (properties.align) {
289
+ if (!properties.style) properties.style = {}
290
+ // @ts-expect-error assume `style` is an object
291
+ properties.style.textAlign = properties.align
292
+ delete properties.align
293
+ }
294
+
295
+ if (!basic) {
296
+ properties.isHeader = name === 'th'
297
+ }
298
+ }
299
+
300
+ if (!basic && name === 'tr' && parent.type === 'element') {
301
+ properties.isHeader = Boolean(parent.tagName === 'thead')
302
+ }
303
+
304
+ // If `sourcePos` is given, pass source information (line/column info from markdown source).
305
+ if (options.sourcePos) {
306
+ properties['data-sourcepos'] = flattenPosition(position)
307
+ }
308
+
309
+ if (!basic && options.rawSourcePos) {
310
+ properties.sourcePosition = node.position
311
+ }
312
+
313
+ // If `includeElementIndex` is given, pass node index info to components.
314
+ if (!basic && options.includeElementIndex) {
315
+ properties.index = getElementsBeforeCount(parent, node)
316
+ properties.siblingCount = getElementsBeforeCount(parent)
317
+ }
318
+
319
+ if (!basic) {
320
+ properties.node = node
321
+ }
322
+
323
+ // Ensure no React warnings are emitted for void elements w/ children.
324
+ return children.length > 0
325
+ ? React.createElement(component, properties, children)
326
+ : React.createElement(component, properties)
327
+ }
328
+
329
+ /**
330
+ * @param {Element|Root} node
331
+ * @returns {Element?}
332
+ */
333
+ function getInputElement(node) {
334
+ let index = -1
335
+
336
+ while (++index < node.children.length) {
337
+ const child = node.children[index]
338
+
339
+ if (child.type === 'element' && child.tagName === 'input') {
340
+ return child
341
+ }
342
+ }
343
+
344
+ return null
345
+ }
346
+
347
+ /**
348
+ * @param {Element|Root} parent
349
+ * @param {Element} [node]
350
+ * @returns {number}
351
+ */
352
+ function getElementsBeforeCount(parent, node) {
353
+ let index = -1
354
+ let count = 0
355
+
356
+ while (++index < parent.children.length) {
357
+ if (parent.children[index] === node) break
358
+ if (parent.children[index].type === 'element') count++
359
+ }
360
+
361
+ return count
362
+ }
363
+
364
+ /**
365
+ * @param {Record<string, unknown>} props
366
+ * @param {string} prop
367
+ * @param {unknown} value
368
+ * @param {Context} ctx
369
+ */
370
+ function addProperty(props, prop, value, ctx) {
371
+ const info = find(ctx.schema, prop)
372
+ let result = value
373
+
374
+ // Ignore nullish and `NaN` values.
375
+ // eslint-disable-next-line no-self-compare
376
+ if (result === null || result === undefined || result !== result) {
377
+ return
378
+ }
379
+
380
+ // Accept `array`.
381
+ // Most props are space-separated.
382
+ if (Array.isArray(result)) {
383
+ result = info.commaSeparated ? commas(result) : spaces(result)
384
+ }
385
+
386
+ if (info.property === 'style' && typeof result === 'string') {
387
+ result = parseStyle(result)
388
+ }
389
+
390
+ if (info.space && info.property) {
391
+ props[
392
+ own.call(hastToReact, info.property)
393
+ ? hastToReact[info.property]
394
+ : info.property
395
+ ] = result
396
+ } else if (info.attribute) {
397
+ props[info.attribute] = result
398
+ }
399
+ }
400
+
401
+ /**
402
+ * @param {string} value
403
+ * @returns {Record<string, string>}
404
+ */
405
+ function parseStyle(value) {
406
+ /** @type {Record<string, string>} */
407
+ const result = {}
408
+
409
+ try {
410
+ style(value, iterator)
411
+ } catch {
412
+ // Silent.
413
+ }
414
+
415
+ return result
416
+
417
+ /**
418
+ * @param {string} name
419
+ * @param {string} v
420
+ */
421
+ function iterator(name, v) {
422
+ const k = name.slice(0, 4) === '-ms-' ? `ms-${name.slice(4)}` : name
423
+ result[k.replace(/-([a-z])/g, styleReplacer)] = v
424
+ }
425
+ }
426
+
427
+ /**
428
+ * @param {unknown} _
429
+ * @param {string} $1
430
+ */
431
+ function styleReplacer(_, $1) {
432
+ return $1.toUpperCase()
433
+ }
434
+
435
+ /**
436
+ * @param {Position|{start: {line: null, column: null, offset: null}, end: {line: null, column: null, offset: null}}} pos
437
+ * @returns {string}
438
+ */
439
+ function flattenPosition(pos) {
440
+ return [
441
+ pos.start.line,
442
+ ':',
443
+ pos.start.column,
444
+ '-',
445
+ pos.end.line,
446
+ ':',
447
+ pos.end.column
448
+ ]
449
+ .map(String)
450
+ .join('')
451
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * @typedef {import('react').ReactNode} ReactNode
3
+ * @typedef {import('react').ReactElement<{}>} ReactElement
4
+ * @typedef {import('unified').PluggableList} PluggableList
5
+ * @typedef {import('hast').Root} Root
6
+ * @typedef {import('./rehype-filter.js').Options} FilterOptions
7
+ * @typedef {import('./ast-to-react.js').Options} TransformOptions
8
+ *
9
+ * @typedef CoreOptions
10
+ * @property {string} children
11
+ *
12
+ * @typedef PluginOptions
13
+ * @property {PluggableList} [remarkPlugins=[]]
14
+ * @property {PluggableList} [rehypePlugins=[]]
15
+ * @property {import('remark-rehype').Options | undefined} [remarkRehypeOptions={}]
16
+ *
17
+ * @typedef LayoutOptions
18
+ * @property {string} [className]
19
+ *
20
+ * @typedef {CoreOptions & PluginOptions & LayoutOptions & FilterOptions & TransformOptions} ReactMarkdownOptions
21
+ *
22
+ * @typedef Deprecation
23
+ * @property {string} id
24
+ * @property {string} [to]
25
+ */
26
+
27
+ import React from 'react'
28
+ import {VFile} from 'vfile'
29
+ import {unified} from 'unified'
30
+ import remarkParse from 'remark-parse'
31
+ import remarkRehype from 'remark-rehype'
32
+ import PropTypes from 'prop-types'
33
+ import {html} from 'property-information'
34
+ import rehypeFilter from './rehype-filter.js'
35
+ import {childrenToReact} from './ast-to-react.js'
36
+
37
+ const own = {}.hasOwnProperty
38
+ const changelog =
39
+ 'https://github.com/remarkjs/react-markdown/blob/main/changelog.md'
40
+
41
+ /** @type {Record<string, Deprecation>} */
42
+ const deprecated = {
43
+ plugins: {to: 'remarkPlugins', id: 'change-plugins-to-remarkplugins'},
44
+ renderers: {to: 'components', id: 'change-renderers-to-components'},
45
+ astPlugins: {id: 'remove-buggy-html-in-markdown-parser'},
46
+ allowDangerousHtml: {id: 'remove-buggy-html-in-markdown-parser'},
47
+ escapeHtml: {id: 'remove-buggy-html-in-markdown-parser'},
48
+ source: {to: 'children', id: 'change-source-to-children'},
49
+ allowNode: {
50
+ to: 'allowElement',
51
+ id: 'replace-allownode-allowedtypes-and-disallowedtypes'
52
+ },
53
+ allowedTypes: {
54
+ to: 'allowedElements',
55
+ id: 'replace-allownode-allowedtypes-and-disallowedtypes'
56
+ },
57
+ disallowedTypes: {
58
+ to: 'disallowedElements',
59
+ id: 'replace-allownode-allowedtypes-and-disallowedtypes'
60
+ },
61
+ includeNodeIndex: {
62
+ to: 'includeElementIndex',
63
+ id: 'change-includenodeindex-to-includeelementindex'
64
+ }
65
+ }
66
+
67
+ /**
68
+ * React component to render markdown.
69
+ *
70
+ * @param {ReactMarkdownOptions} options
71
+ * @returns {ReactElement}
72
+ */
73
+ export function ReactMarkdown(options) {
74
+ for (const key in deprecated) {
75
+ if (own.call(deprecated, key) && own.call(options, key)) {
76
+ const deprecation = deprecated[key]
77
+ console.warn(
78
+ `[react-markdown] Warning: please ${
79
+ deprecation.to ? `use \`${deprecation.to}\` instead of` : 'remove'
80
+ } \`${key}\` (see <${changelog}#${deprecation.id}> for more info)`
81
+ )
82
+ delete deprecated[key]
83
+ }
84
+ }
85
+
86
+ const processor = unified()
87
+ .use(remarkParse)
88
+ .use(options.remarkPlugins || [])
89
+ .use(remarkRehype, {
90
+ ...options.remarkRehypeOptions,
91
+ allowDangerousHtml: true
92
+ })
93
+ .use(options.rehypePlugins || [])
94
+ .use(rehypeFilter, options)
95
+
96
+ const file = new VFile()
97
+
98
+ if (typeof options.children === 'string') {
99
+ file.value = options.children
100
+ } else if (options.children !== undefined && options.children !== null) {
101
+ console.warn(
102
+ `[react-markdown] Warning: please pass a string as \`children\` (not: \`${options.children}\`)`
103
+ )
104
+ }
105
+
106
+ const hastNode = processor.runSync(processor.parse(file), file)
107
+
108
+ if (hastNode.type !== 'root') {
109
+ throw new TypeError('Expected a `root` node')
110
+ }
111
+
112
+ /** @type {ReactElement} */
113
+ let result = React.createElement(
114
+ React.Fragment,
115
+ {},
116
+ childrenToReact({options, schema: html, listDepth: 0}, hastNode)
117
+ )
118
+
119
+ if (options.className) {
120
+ result = React.createElement('div', {className: options.className}, result)
121
+ }
122
+
123
+ return result
124
+ }
125
+
126
+ ReactMarkdown.propTypes = {
127
+ // Core options:
128
+ children: PropTypes.string,
129
+ // Layout options:
130
+ className: PropTypes.string,
131
+ // Filter options:
132
+ allowElement: PropTypes.func,
133
+ allowedElements: PropTypes.arrayOf(PropTypes.string),
134
+ disallowedElements: PropTypes.arrayOf(PropTypes.string),
135
+ unwrapDisallowed: PropTypes.bool,
136
+ // Plugin options:
137
+ remarkPlugins: PropTypes.arrayOf(
138
+ PropTypes.oneOfType([
139
+ PropTypes.object,
140
+ PropTypes.func,
141
+ PropTypes.arrayOf(
142
+ PropTypes.oneOfType([
143
+ PropTypes.bool,
144
+ PropTypes.string,
145
+ PropTypes.object,
146
+ PropTypes.func,
147
+ PropTypes.arrayOf(
148
+ // prettier-ignore
149
+ // type-coverage:ignore-next-line
150
+ PropTypes.any
151
+ )
152
+ ])
153
+ )
154
+ ])
155
+ ),
156
+ rehypePlugins: PropTypes.arrayOf(
157
+ PropTypes.oneOfType([
158
+ PropTypes.object,
159
+ PropTypes.func,
160
+ PropTypes.arrayOf(
161
+ PropTypes.oneOfType([
162
+ PropTypes.bool,
163
+ PropTypes.string,
164
+ PropTypes.object,
165
+ PropTypes.func,
166
+ PropTypes.arrayOf(
167
+ // prettier-ignore
168
+ // type-coverage:ignore-next-line
169
+ PropTypes.any
170
+ )
171
+ ])
172
+ )
173
+ ])
174
+ ),
175
+ // Transform options:
176
+ sourcePos: PropTypes.bool,
177
+ rawSourcePos: PropTypes.bool,
178
+ skipHtml: PropTypes.bool,
179
+ includeElementIndex: PropTypes.bool,
180
+ transformLinkUri: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
181
+ linkTarget: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
182
+ transformImageUri: PropTypes.func,
183
+ components: PropTypes.object
184
+ }
@@ -0,0 +1,66 @@
1
+ import {visit} from 'unist-util-visit'
2
+
3
+ /**
4
+ * @typedef {import('unist').Node} Node
5
+ * @typedef {import('hast').Root} Root
6
+ * @typedef {import('hast').Element} Element
7
+ *
8
+ * @callback AllowElement
9
+ * @param {Element} element
10
+ * @param {number} index
11
+ * @param {Element|Root} parent
12
+ * @returns {boolean|undefined}
13
+ *
14
+ * @typedef Options
15
+ * @property {Array<string>} [allowedElements]
16
+ * @property {Array<string>} [disallowedElements=[]]
17
+ * @property {AllowElement} [allowElement]
18
+ * @property {boolean} [unwrapDisallowed=false]
19
+ */
20
+
21
+ /**
22
+ * @type {import('unified').Plugin<[Options], Root>}
23
+ */
24
+ export default function rehypeFilter(options) {
25
+ if (options.allowedElements && options.disallowedElements) {
26
+ throw new TypeError(
27
+ 'Only one of `allowedElements` and `disallowedElements` should be defined'
28
+ )
29
+ }
30
+
31
+ if (
32
+ options.allowedElements ||
33
+ options.disallowedElements ||
34
+ options.allowElement
35
+ ) {
36
+ return (tree) => {
37
+ visit(tree, 'element', (node, index, parent_) => {
38
+ const parent = /** @type {Element|Root} */ (parent_)
39
+ /** @type {boolean|undefined} */
40
+ let remove
41
+
42
+ if (options.allowedElements) {
43
+ remove = !options.allowedElements.includes(node.tagName)
44
+ } else if (options.disallowedElements) {
45
+ remove = options.disallowedElements.includes(node.tagName)
46
+ }
47
+
48
+ if (!remove && options.allowElement && typeof index === 'number') {
49
+ remove = !options.allowElement(node, index, parent)
50
+ }
51
+
52
+ if (remove && typeof index === 'number') {
53
+ if (options.unwrapDisallowed && node.children) {
54
+ parent.children.splice(index, 1, ...node.children)
55
+ } else {
56
+ parent.children.splice(index, 1)
57
+ }
58
+
59
+ return index
60
+ }
61
+
62
+ return undefined
63
+ })
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,45 @@
1
+ const protocols = ['http', 'https', 'mailto', 'tel']
2
+
3
+ /**
4
+ * @param {string} uri
5
+ * @returns {string}
6
+ */
7
+ export function uriTransformer(uri) {
8
+ const url = (uri || '').trim()
9
+ const first = url.charAt(0)
10
+
11
+ if (first === '#' || first === '/') {
12
+ return url
13
+ }
14
+
15
+ const colon = url.indexOf(':')
16
+ if (colon === -1) {
17
+ return url
18
+ }
19
+
20
+ let index = -1
21
+
22
+ while (++index < protocols.length) {
23
+ const protocol = protocols[index]
24
+
25
+ if (
26
+ colon === protocol.length &&
27
+ url.slice(0, protocol.length).toLowerCase() === protocol
28
+ ) {
29
+ return url
30
+ }
31
+ }
32
+
33
+ index = url.indexOf('?')
34
+ if (index !== -1 && colon > index) {
35
+ return url
36
+ }
37
+
38
+ index = url.indexOf('#')
39
+ if (index !== -1 && colon > index) {
40
+ return url
41
+ }
42
+
43
+ // eslint-disable-next-line no-script-url
44
+ return 'javascript:void(0)'
45
+ }
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "react-markdown-canvas",
3
+ "version": "0.0.1",
4
+ "description": "react-markdown-canvas",
5
+ "main": "index.js",
6
+ "directories": {
7
+ "lib": "lib"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "author": "busf4ctor",
13
+ "license": "ISC"
14
+ }