@humanspeak/svelte-markdown 0.7.4 → 0.7.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/dist/Parser.svelte +62 -5
- package/dist/Parser.svelte.d.ts +37 -0
- package/dist/SvelteMarkdown.svelte +47 -37
- package/dist/SvelteMarkdown.svelte.d.ts +5 -10
- package/dist/index.d.ts +2 -1
- package/dist/renderers/TableCell.svelte +5 -2
- package/dist/types.d.ts +28 -0
- package/dist/types.js +20 -0
- package/dist/utils/markdown-parser.d.ts +66 -2
- package/dist/utils/markdown-parser.js +25 -0
- package/dist/utils/token-cleanup.d.ts +122 -7
- package/dist/utils/token-cleanup.js +196 -68
- package/package.json +1 -1
package/dist/Parser.svelte
CHANGED
|
@@ -1,4 +1,42 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* @component Parser
|
|
4
|
+
*
|
|
5
|
+
* Recursive markdown token parser that transforms tokens into Svelte components.
|
|
6
|
+
* This component is the core rendering engine of the markdown system, handling
|
|
7
|
+
* the transformation of parsed markdown tokens into their corresponding Svelte components.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```svelte
|
|
11
|
+
* <Parser
|
|
12
|
+
* tokens={parsedTokens}
|
|
13
|
+
* renderers={customRenderers}
|
|
14
|
+
* type="paragraph"
|
|
15
|
+
* />
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Features:
|
|
19
|
+
* - Recursive token parsing
|
|
20
|
+
* - Custom renderer support
|
|
21
|
+
* - Special handling for tables, lists, and HTML content
|
|
22
|
+
* - Type-safe component rendering
|
|
23
|
+
*
|
|
24
|
+
* @typedef {Object} Props
|
|
25
|
+
* @property {string} [type] - Token type for direct component rendering
|
|
26
|
+
* @property {Token[] | TokensList} [tokens] - Markdown tokens to be rendered
|
|
27
|
+
* @property {Tokens.TableCell[]} [header] - Table header cells for table rendering
|
|
28
|
+
* @property {Tokens.TableCell[][]} [rows] - Table row cells for table rendering
|
|
29
|
+
* @property {boolean} [ordered=false] - Whether the list is ordered (for list rendering)
|
|
30
|
+
* @property {Renderers} renderers - Component mapping for markdown elements
|
|
31
|
+
*
|
|
32
|
+
* Implementation Notes:
|
|
33
|
+
* - Uses recursive rendering for nested tokens
|
|
34
|
+
* - Implements special logic for tables, lists, and HTML content
|
|
35
|
+
* - Handles component prop spreading carefully to avoid conflicts
|
|
36
|
+
* - Maintains type safety through TypeScript interfaces
|
|
37
|
+
*
|
|
38
|
+
*/
|
|
39
|
+
|
|
2
40
|
import Parser from './Parser.svelte'
|
|
3
41
|
import Html from './renderers/html/index.js'
|
|
4
42
|
import type {
|
|
@@ -44,10 +82,11 @@
|
|
|
44
82
|
<renderers.tablehead {...rest}>
|
|
45
83
|
<renderers.tablerow {...rest}>
|
|
46
84
|
{#each header ?? [] as headerItem, i}
|
|
85
|
+
{@const { align: _align, ...cellRest } = rest}
|
|
47
86
|
<renderers.tablecell
|
|
48
87
|
header={true}
|
|
49
|
-
align={(rest.align as string[])[i]
|
|
50
|
-
{...
|
|
88
|
+
align={(rest.align as string[])[i]}
|
|
89
|
+
{...cellRest}
|
|
51
90
|
>
|
|
52
91
|
<Parser tokens={headerItem.tokens} {renderers} />
|
|
53
92
|
</renderers.tablecell>
|
|
@@ -58,12 +97,30 @@
|
|
|
58
97
|
{#each rows ?? [] as row}
|
|
59
98
|
<renderers.tablerow {...rest}>
|
|
60
99
|
{#each row ?? [] as cells, i}
|
|
100
|
+
{@const { align: _align, ...cellRest } = rest}
|
|
61
101
|
<renderers.tablecell
|
|
62
102
|
header={false}
|
|
63
|
-
align={(rest.align as string[])[i]
|
|
64
|
-
{...
|
|
103
|
+
align={(rest.align as string[])[i]}
|
|
104
|
+
{...cellRest}
|
|
65
105
|
>
|
|
66
|
-
|
|
106
|
+
{#if cells.type === 'html'}
|
|
107
|
+
{@const { tag, ...localRest } = cells}
|
|
108
|
+
{@const htmlTag = cells.tag as keyof typeof Html}
|
|
109
|
+
{#if htmlTag in Html}
|
|
110
|
+
{@const HtmlComponent = Html[htmlTag]}
|
|
111
|
+
<HtmlComponent {...cells}>
|
|
112
|
+
{#if cells.tokens?.length}
|
|
113
|
+
<Parser
|
|
114
|
+
tokens={cells.tokens}
|
|
115
|
+
{renderers}
|
|
116
|
+
{...localRest}
|
|
117
|
+
/>
|
|
118
|
+
{/if}
|
|
119
|
+
</HtmlComponent>
|
|
120
|
+
{/if}
|
|
121
|
+
{:else}
|
|
122
|
+
<Parser tokens={cells.tokens} {renderers} />
|
|
123
|
+
{/if}
|
|
67
124
|
</renderers.tablecell>
|
|
68
125
|
{/each}
|
|
69
126
|
</renderers.tablerow>
|
package/dist/Parser.svelte.d.ts
CHANGED
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @component Parser
|
|
3
|
+
*
|
|
4
|
+
* Recursive markdown token parser that transforms tokens into Svelte components.
|
|
5
|
+
* This component is the core rendering engine of the markdown system, handling
|
|
6
|
+
* the transformation of parsed markdown tokens into their corresponding Svelte components.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```svelte
|
|
10
|
+
* <Parser
|
|
11
|
+
* tokens={parsedTokens}
|
|
12
|
+
* renderers={customRenderers}
|
|
13
|
+
* type="paragraph"
|
|
14
|
+
* />
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Features:
|
|
18
|
+
* - Recursive token parsing
|
|
19
|
+
* - Custom renderer support
|
|
20
|
+
* - Special handling for tables, lists, and HTML content
|
|
21
|
+
* - Type-safe component rendering
|
|
22
|
+
*
|
|
23
|
+
* @typedef {Object} Props
|
|
24
|
+
* @property {string} [type] - Token type for direct component rendering
|
|
25
|
+
* @property {Token[] | TokensList} [tokens] - Markdown tokens to be rendered
|
|
26
|
+
* @property {Tokens.TableCell[]} [header] - Table header cells for table rendering
|
|
27
|
+
* @property {Tokens.TableCell[][]} [rows] - Table row cells for table rendering
|
|
28
|
+
* @property {boolean} [ordered=false] - Whether the list is ordered (for list rendering)
|
|
29
|
+
* @property {Renderers} renderers - Component mapping for markdown elements
|
|
30
|
+
*
|
|
31
|
+
* Implementation Notes:
|
|
32
|
+
* - Uses recursive rendering for nested tokens
|
|
33
|
+
* - Implements special logic for tables, lists, and HTML content
|
|
34
|
+
* - Handles component prop spreading carefully to avoid conflicts
|
|
35
|
+
* - Maintains type safety through TypeScript interfaces
|
|
36
|
+
*
|
|
37
|
+
*/
|
|
1
38
|
import Parser from './Parser.svelte';
|
|
2
39
|
import type { Renderers, Token, TokensList, Tokens } from './utils/markdown-parser.js';
|
|
3
40
|
declare const Parser: import("svelte").Component<{
|
|
@@ -23,6 +23,29 @@
|
|
|
23
23
|
@property {function} [parsed] - Callback function called with the parsed tokens
|
|
24
24
|
-->
|
|
25
25
|
<script lang="ts">
|
|
26
|
+
/**
|
|
27
|
+
* Component Evolution & Design Notes:
|
|
28
|
+
*
|
|
29
|
+
* 1. Core Purpose:
|
|
30
|
+
* - Serves as the main entry point for markdown rendering in Svelte
|
|
31
|
+
* - Handles both string input and pre-parsed tokens for flexibility
|
|
32
|
+
*
|
|
33
|
+
* 2. Key Design Decisions:
|
|
34
|
+
* - Uses a separate Parser component for actual rendering to maintain separation of concerns
|
|
35
|
+
* - Implements token cleanup via shrinkHtmlTokens to optimize HTML token handling
|
|
36
|
+
* - Maintains state synchronization using Svelte 5's $state and $effect
|
|
37
|
+
*
|
|
38
|
+
* 3. Performance Considerations:
|
|
39
|
+
* - Caches previous source to prevent unnecessary re-parsing
|
|
40
|
+
* - Uses key directive for proper component rerendering when source changes
|
|
41
|
+
* - Intentionally avoids reactive tokens to prevent double processing
|
|
42
|
+
*
|
|
43
|
+
* 4. Extensibility:
|
|
44
|
+
* - Supports custom renderers through composition pattern
|
|
45
|
+
* - Allows parser configuration via options prop
|
|
46
|
+
* - Provides parsed callback for external token access
|
|
47
|
+
*/
|
|
48
|
+
|
|
26
49
|
import {
|
|
27
50
|
Lexer,
|
|
28
51
|
defaultOptions,
|
|
@@ -30,19 +53,11 @@
|
|
|
30
53
|
Slugger,
|
|
31
54
|
type Token,
|
|
32
55
|
type TokensList,
|
|
33
|
-
type SvelteMarkdownOptions
|
|
34
|
-
type Renderers
|
|
56
|
+
type SvelteMarkdownOptions
|
|
35
57
|
} from './utils/markdown-parser.js'
|
|
36
58
|
import Parser from './Parser.svelte'
|
|
37
59
|
import { shrinkHtmlTokens } from './utils/token-cleanup.js'
|
|
38
|
-
|
|
39
|
-
interface Props {
|
|
40
|
-
source: Token[] | string
|
|
41
|
-
renderers?: Partial<Renderers>
|
|
42
|
-
options?: SvelteMarkdownOptions
|
|
43
|
-
isInline?: boolean
|
|
44
|
-
parsed?: (tokens: Token[] | TokensList) => void // eslint-disable-line no-unused-vars
|
|
45
|
-
}
|
|
60
|
+
import { type SvelteMarkdownProps } from './types.js'
|
|
46
61
|
|
|
47
62
|
const {
|
|
48
63
|
source = [],
|
|
@@ -51,34 +66,31 @@
|
|
|
51
66
|
isInline = false,
|
|
52
67
|
parsed = () => {},
|
|
53
68
|
...rest
|
|
54
|
-
}:
|
|
69
|
+
}: SvelteMarkdownProps & {
|
|
55
70
|
[key: string]: unknown
|
|
56
71
|
} = $props()
|
|
57
|
-
// @ts-expect-error - Intentionally not using $state for tokens
|
|
58
|
-
let tokens: Token[] | undefined // eslint-disable-line svelte/valid-compile
|
|
59
|
-
let previousSource = $state<string | Token[] | undefined>(undefined)
|
|
60
|
-
let lexer: Lexer
|
|
61
72
|
|
|
62
|
-
const slugger = source ? new Slugger() : undefined
|
|
63
73
|
const combinedOptions = { ...defaultOptions, ...options }
|
|
74
|
+
const slugger = source ? new Slugger() : undefined
|
|
75
|
+
let lexer: Lexer
|
|
64
76
|
|
|
65
|
-
$
|
|
66
|
-
if (
|
|
67
|
-
previousSource = source
|
|
68
|
-
|
|
69
|
-
if (Array.isArray(source)) {
|
|
70
|
-
tokens = shrinkHtmlTokens(source) as Token[]
|
|
71
|
-
} else {
|
|
77
|
+
const tokens = $derived.by(() => {
|
|
78
|
+
if (!lexer) {
|
|
72
79
|
lexer = new Lexer(combinedOptions)
|
|
73
|
-
tokens = shrinkHtmlTokens(
|
|
74
|
-
isInline ? lexer.inlineTokens(source as string) : lexer.lex(source as string)
|
|
75
|
-
)
|
|
76
80
|
}
|
|
77
|
-
|
|
81
|
+
if (Array.isArray(source)) {
|
|
82
|
+
return source as Token[]
|
|
83
|
+
}
|
|
84
|
+
return source
|
|
85
|
+
? (shrinkHtmlTokens(
|
|
86
|
+
isInline ? lexer.inlineTokens(source as string) : lexer.lex(source as string)
|
|
87
|
+
) as Token[])
|
|
88
|
+
: []
|
|
89
|
+
}) satisfies Token[] | TokensList | undefined
|
|
78
90
|
|
|
79
91
|
$effect(() => {
|
|
80
92
|
if (!tokens) return
|
|
81
|
-
parsed(
|
|
93
|
+
parsed(tokens)
|
|
82
94
|
})
|
|
83
95
|
|
|
84
96
|
const combinedRenderers = {
|
|
@@ -91,12 +103,10 @@
|
|
|
91
103
|
}
|
|
92
104
|
</script>
|
|
93
105
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
/>
|
|
102
|
-
{/key}
|
|
106
|
+
<Parser
|
|
107
|
+
{tokens}
|
|
108
|
+
{...rest}
|
|
109
|
+
options={combinedOptions}
|
|
110
|
+
slug={(val: string): string => (slugger ? slugger.slug(val) : '')}
|
|
111
|
+
renderers={combinedRenderers}
|
|
112
|
+
/>
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type SvelteMarkdownProps } from './types.js';
|
|
2
|
+
type $$ComponentProps = SvelteMarkdownProps & {
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
};
|
|
2
5
|
/**
|
|
3
6
|
* A Svelte component that renders Markdown content into HTML using a customizable parser.
|
|
4
7
|
* Supports both string input and pre-parsed markdown tokens, with configurable rendering
|
|
@@ -21,14 +24,6 @@ import { type Token, type TokensList, type SvelteMarkdownOptions, type Renderers
|
|
|
21
24
|
* @property {boolean} [isInline=false] - Whether to parse the content as inline markdown
|
|
22
25
|
* @property {function} [parsed] - Callback function called with the parsed tokens
|
|
23
26
|
*/
|
|
24
|
-
declare const SvelteMarkdown: import("svelte").Component
|
|
25
|
-
source: Token[] | string;
|
|
26
|
-
renderers?: Partial<Renderers>;
|
|
27
|
-
options?: SvelteMarkdownOptions;
|
|
28
|
-
isInline?: boolean;
|
|
29
|
-
parsed?: (tokens: Token[] | TokensList) => void;
|
|
30
|
-
} & {
|
|
31
|
-
[key: string]: unknown;
|
|
32
|
-
}, {}, "">;
|
|
27
|
+
declare const SvelteMarkdown: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
33
28
|
type SvelteMarkdown = ReturnType<typeof SvelteMarkdown>;
|
|
34
29
|
export default SvelteMarkdown;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SvelteMarkdownOptions, Token, TokensList } from './utils/markdown-parser.js';
|
|
2
2
|
import SvelteMarkdown from './SvelteMarkdown.svelte';
|
|
3
|
+
import type { SvelteMarkdownProps } from './types.js';
|
|
3
4
|
export default SvelteMarkdown;
|
|
4
|
-
export type { SvelteMarkdownOptions, Token, TokensList };
|
|
5
|
+
export type { SvelteMarkdownOptions, SvelteMarkdownProps, Token, TokensList };
|
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const { header, align, children }: Props = $props()
|
|
11
|
+
|
|
12
|
+
// Convert alignment to style object if alignment is specified
|
|
13
|
+
const style = $derived(align ? `text-align: ${align}` : undefined)
|
|
11
14
|
</script>
|
|
12
15
|
|
|
13
16
|
{#if header}
|
|
14
|
-
<th {
|
|
17
|
+
<th {style}>{@render children?.()}</th>
|
|
15
18
|
{:else}
|
|
16
|
-
<td {
|
|
19
|
+
<td {style}>{@render children?.()}</td>
|
|
17
20
|
{/if}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Svelte Markdown component.
|
|
3
|
+
*
|
|
4
|
+
* This module provides TypeScript type definitions for the core functionality
|
|
5
|
+
* of the Svelte Markdown parser and renderer. It defines the primary interface
|
|
6
|
+
* for component props and integrates with the marked library's token system.
|
|
7
|
+
*
|
|
8
|
+
* Typical usage example:
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import type { SvelteMarkdownProps } from './types';
|
|
11
|
+
*
|
|
12
|
+
* const markdownProps: SvelteMarkdownProps = {
|
|
13
|
+
* source: "# Hello World",
|
|
14
|
+
* isInline: false
|
|
15
|
+
* };
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
import type { Token, TokensList } from 'marked';
|
|
21
|
+
import type { Renderers, SvelteMarkdownOptions } from './utils/markdown-parser.js';
|
|
22
|
+
export type SvelteMarkdownProps = {
|
|
23
|
+
source: Token[] | string;
|
|
24
|
+
renderers?: Partial<Renderers>;
|
|
25
|
+
options?: SvelteMarkdownOptions;
|
|
26
|
+
isInline?: boolean;
|
|
27
|
+
parsed?: (tokens: Token[] | TokensList) => void;
|
|
28
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Svelte Markdown component.
|
|
3
|
+
*
|
|
4
|
+
* This module provides TypeScript type definitions for the core functionality
|
|
5
|
+
* of the Svelte Markdown parser and renderer. It defines the primary interface
|
|
6
|
+
* for component props and integrates with the marked library's token system.
|
|
7
|
+
*
|
|
8
|
+
* Typical usage example:
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import type { SvelteMarkdownProps } from './types';
|
|
11
|
+
*
|
|
12
|
+
* const markdownProps: SvelteMarkdownProps = {
|
|
13
|
+
* source: "# Hello World",
|
|
14
|
+
* isInline: false
|
|
15
|
+
* };
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @packageDocumentation
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
@@ -3,10 +3,25 @@ export { Lexer, type Token, type Tokens, type TokensList } from 'marked';
|
|
|
3
3
|
import type { Component } from 'svelte';
|
|
4
4
|
import { type HtmlRenderers } from '../renderers/html/index.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Represents a Svelte component that can be used as a renderer.
|
|
7
|
+
* Allows for flexible component types while maintaining type safety.
|
|
8
|
+
*
|
|
9
|
+
* @typedef {Component<any, any, any> | undefined | null} RendererComponent
|
|
8
10
|
*/
|
|
9
11
|
export type RendererComponent = Component<any, any, any> | undefined | null;
|
|
12
|
+
/**
|
|
13
|
+
* Comprehensive mapping of markdown elements to their renderer components.
|
|
14
|
+
* Structured in categories for better organization and maintainability.
|
|
15
|
+
*
|
|
16
|
+
* Categories:
|
|
17
|
+
* - HTML: Special renderer for HTML content
|
|
18
|
+
* - Block elements: Major structural elements
|
|
19
|
+
* - Table elements: Table-specific components
|
|
20
|
+
* - Inline elements: Text-level components
|
|
21
|
+
* - List variations: Specialized list item renderers
|
|
22
|
+
*
|
|
23
|
+
* @interface Renderers
|
|
24
|
+
*/
|
|
10
25
|
export type Renderers = {
|
|
11
26
|
html: HtmlRenderers;
|
|
12
27
|
heading: RendererComponent;
|
|
@@ -32,12 +47,48 @@ export type Renderers = {
|
|
|
32
47
|
orderedlistitem: RendererComponent;
|
|
33
48
|
unorderedlistitem: RendererComponent;
|
|
34
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* Default renderer configuration mapping markdown elements to Svelte components.
|
|
52
|
+
* Provides out-of-the-box rendering capabilities while allowing for customization.
|
|
53
|
+
*
|
|
54
|
+
* Implementation notes:
|
|
55
|
+
* - All components are lazy-loaded for better performance
|
|
56
|
+
* - Null values indicate optional renderers
|
|
57
|
+
* - Components are type-checked against the Renderers interface
|
|
58
|
+
*
|
|
59
|
+
* @const {Renderers}
|
|
60
|
+
*/
|
|
35
61
|
export declare const defaultRenderers: Renderers;
|
|
62
|
+
/**
|
|
63
|
+
* Configuration options for SvelteMarkdown parser.
|
|
64
|
+
* Extends marked options with additional Svelte-specific configurations.
|
|
65
|
+
*
|
|
66
|
+
* @interface SvelteMarkdownOptions
|
|
67
|
+
*
|
|
68
|
+
* @property {string|null} baseUrl - Base URL for relative links
|
|
69
|
+
* @property {boolean} breaks - Enable line breaks in output
|
|
70
|
+
* @property {boolean} gfm - Enable GitHub Flavored Markdown
|
|
71
|
+
* @property {boolean} headerIds - Auto-generate header IDs
|
|
72
|
+
* @property {string} headerPrefix - Prefix for header IDs
|
|
73
|
+
* @property {Function|null} highlight - Syntax highlighting function
|
|
74
|
+
* @property {string} langPrefix - Prefix for code block language classes
|
|
75
|
+
* @property {boolean} mangle - Encode email addresses
|
|
76
|
+
* @property {boolean} pedantic - Conform to original markdown spec
|
|
77
|
+
* @property {Object|null} renderer - Custom renderer
|
|
78
|
+
* @property {boolean} sanitize - Sanitize HTML input
|
|
79
|
+
* @property {Function|null} sanitizer - Custom sanitizer function
|
|
80
|
+
* @property {boolean} silent - Suppress error output
|
|
81
|
+
* @property {boolean} smartLists - Use smarter list behavior
|
|
82
|
+
* @property {boolean} smartypants - Use smart punctuation
|
|
83
|
+
* @property {Object|null} tokenizer - Custom tokenizer
|
|
84
|
+
* @property {boolean} xhtml - Generate XHTML-compliant tags
|
|
85
|
+
*/
|
|
36
86
|
export type SvelteMarkdownOptions = {
|
|
37
87
|
baseUrl: string | null;
|
|
38
88
|
breaks: boolean;
|
|
39
89
|
gfm: boolean;
|
|
40
90
|
headerIds: boolean;
|
|
91
|
+
tables: boolean;
|
|
41
92
|
headerPrefix: string;
|
|
42
93
|
highlight: null;
|
|
43
94
|
langPrefix: string;
|
|
@@ -52,4 +103,17 @@ export type SvelteMarkdownOptions = {
|
|
|
52
103
|
tokenizer: null;
|
|
53
104
|
xhtml: boolean;
|
|
54
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Default configuration options for the markdown parser.
|
|
108
|
+
* Provides sensible defaults while allowing for customization.
|
|
109
|
+
*
|
|
110
|
+
* Notable defaults:
|
|
111
|
+
* - GitHub Flavored Markdown enabled
|
|
112
|
+
* - Header IDs generated automatically
|
|
113
|
+
* - No syntax highlighting by default
|
|
114
|
+
* - HTML sanitization disabled
|
|
115
|
+
* - Standard markdown parsing rules
|
|
116
|
+
*
|
|
117
|
+
* @const {SvelteMarkdownOptions}
|
|
118
|
+
*/
|
|
55
119
|
export declare const defaultOptions: SvelteMarkdownOptions;
|
|
@@ -2,6 +2,17 @@ export { default as Slugger } from 'github-slugger';
|
|
|
2
2
|
export { Lexer } from 'marked';
|
|
3
3
|
import {} from '../renderers/html/index.js';
|
|
4
4
|
import { Blockquote, Br, Code, Codespan, Del, Em, Heading, Hr, Html, Image, Link, List, ListItem, Paragraph, Strong, Table, TableBody, TableCell, TableHead, TableRow, Text } from '../renderers/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Default renderer configuration mapping markdown elements to Svelte components.
|
|
7
|
+
* Provides out-of-the-box rendering capabilities while allowing for customization.
|
|
8
|
+
*
|
|
9
|
+
* Implementation notes:
|
|
10
|
+
* - All components are lazy-loaded for better performance
|
|
11
|
+
* - Null values indicate optional renderers
|
|
12
|
+
* - Components are type-checked against the Renderers interface
|
|
13
|
+
*
|
|
14
|
+
* @const {Renderers}
|
|
15
|
+
*/
|
|
5
16
|
export const defaultRenderers = {
|
|
6
17
|
heading: Heading,
|
|
7
18
|
paragraph: Paragraph,
|
|
@@ -27,10 +38,24 @@ export const defaultRenderers = {
|
|
|
27
38
|
code: Code,
|
|
28
39
|
br: Br
|
|
29
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Default configuration options for the markdown parser.
|
|
43
|
+
* Provides sensible defaults while allowing for customization.
|
|
44
|
+
*
|
|
45
|
+
* Notable defaults:
|
|
46
|
+
* - GitHub Flavored Markdown enabled
|
|
47
|
+
* - Header IDs generated automatically
|
|
48
|
+
* - No syntax highlighting by default
|
|
49
|
+
* - HTML sanitization disabled
|
|
50
|
+
* - Standard markdown parsing rules
|
|
51
|
+
*
|
|
52
|
+
* @const {SvelteMarkdownOptions}
|
|
53
|
+
*/
|
|
30
54
|
export const defaultOptions = {
|
|
31
55
|
baseUrl: null,
|
|
32
56
|
breaks: false,
|
|
33
57
|
gfm: true,
|
|
58
|
+
tables: true,
|
|
34
59
|
headerIds: true,
|
|
35
60
|
headerPrefix: '',
|
|
36
61
|
highlight: null,
|
|
@@ -1,17 +1,132 @@
|
|
|
1
1
|
import type { Token } from 'marked';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* @
|
|
3
|
+
* Analyzes a string to determine if it contains an HTML tag and its characteristics.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} raw - Raw string potentially containing an HTML tag
|
|
6
|
+
* @returns {Object|null} Returns null if no tag found, otherwise returns:
|
|
7
|
+
* {
|
|
8
|
+
* tag: string - The name of the HTML tag
|
|
9
|
+
* isOpening: bool - True if opening tag, false if closing
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* isHtmlOpenTag('<div class="test">') // Returns { tag: 'div', isOpening: true }
|
|
14
|
+
* isHtmlOpenTag('</span>') // Returns { tag: 'span', isOpening: false }
|
|
15
|
+
* isHtmlOpenTag('plain text') // Returns null
|
|
6
16
|
*/
|
|
7
17
|
export declare const isHtmlOpenTag: (raw: string) => {
|
|
8
18
|
tag: string;
|
|
9
19
|
isOpening: boolean;
|
|
10
20
|
} | null;
|
|
11
21
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* @
|
|
22
|
+
* Parses HTML attributes from a tag string into a structured object.
|
|
23
|
+
* Handles both single and double quoted attributes.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} raw - Raw HTML tag string containing attributes
|
|
26
|
+
* @returns {Record<string, string>} Map of attribute names to their values
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* extractAttributes('<div class="foo" id="bar">')
|
|
30
|
+
* // Returns { class: 'foo', id: 'bar' }
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export declare const extractAttributes: (raw: string) => Record<string, string>;
|
|
35
|
+
/**
|
|
36
|
+
* Converts an HTML string into a sequence of tokens using htmlparser2.
|
|
37
|
+
* Handles complex nested structures while maintaining proper order and relationships.
|
|
38
|
+
*
|
|
39
|
+
* Key features:
|
|
40
|
+
* - Preserves original HTML structure without automatic tag closing
|
|
41
|
+
* - Handles self-closing tags with proper XML syntax (e.g., <br/> instead of <br>)
|
|
42
|
+
* - Gracefully handles malformed HTML by preserving the original structure
|
|
43
|
+
* - Maintains attribute information in opening tags
|
|
44
|
+
* - Processes text content between tags
|
|
45
|
+
*
|
|
46
|
+
* @param {string} html - HTML string to be parsed
|
|
47
|
+
* @returns {Token[]} Array of tokens representing the HTML structure
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Well-formed HTML
|
|
51
|
+
* parseHtmlBlock('<div>Hello <span>world</span></div>')
|
|
52
|
+
* // Returns [
|
|
53
|
+
* // { type: 'html', raw: '<div>', ... },
|
|
54
|
+
* // { type: 'text', raw: 'Hello ', ... },
|
|
55
|
+
* // { type: 'html', raw: '<span>', ... },
|
|
56
|
+
* // { type: 'text', raw: 'world', ... },
|
|
57
|
+
* // { type: 'html', raw: '</span>', ... },
|
|
58
|
+
* // { type: 'html', raw: '</div>', ... }
|
|
59
|
+
* // ]
|
|
60
|
+
*
|
|
61
|
+
* // Self-closing tags
|
|
62
|
+
* parseHtmlBlock('<div>Before<br/>After</div>')
|
|
63
|
+
* // Returns [
|
|
64
|
+
* // { type: 'html', raw: '<div>', ... },
|
|
65
|
+
* // { type: 'text', raw: 'Before', ... },
|
|
66
|
+
* // { type: 'html', raw: '<br/>', ... },
|
|
67
|
+
* // { type: 'text', raw: 'After', ... },
|
|
68
|
+
* // { type: 'html', raw: '</div>', ... }
|
|
69
|
+
* // ]
|
|
70
|
+
*
|
|
71
|
+
* // Malformed HTML
|
|
72
|
+
* parseHtmlBlock('<div>Unclosed')
|
|
73
|
+
* // Returns [
|
|
74
|
+
* // { type: 'html', raw: '<div>', ... },
|
|
75
|
+
* // { type: 'text', raw: 'Unclosed', ... }
|
|
76
|
+
* // ]
|
|
77
|
+
*
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
export declare const parseHtmlBlock: (html: string) => Token[];
|
|
81
|
+
/**
|
|
82
|
+
* Determines if an HTML string contains multiple distinct tags.
|
|
83
|
+
* Used as a preprocessing step to optimize token processing.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} html - HTML string to analyze
|
|
86
|
+
* @returns {boolean} True if multiple tags are present
|
|
87
|
+
*
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
export declare const containsMultipleTags: (html: string) => boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Primary entry point for HTML token processing. Transforms flat token arrays
|
|
93
|
+
* into properly nested structures while preserving HTML semantics.
|
|
94
|
+
*
|
|
95
|
+
* Key features:
|
|
96
|
+
* - Breaks down complex HTML structures into atomic tokens
|
|
97
|
+
* - Maintains attribute information
|
|
98
|
+
* - Preserves proper nesting relationships
|
|
99
|
+
* - Handles malformed HTML gracefully
|
|
100
|
+
*
|
|
101
|
+
* @param {Token[]} tokens - Array of tokens to process
|
|
102
|
+
* @returns {Token[]} Processed and properly nested token array
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* const tokens = [
|
|
106
|
+
* { type: 'html', raw: '<div class="wrapper">' },
|
|
107
|
+
* { type: 'text', raw: 'content' },
|
|
108
|
+
* { type: 'html', raw: '</div>' }
|
|
109
|
+
* ];
|
|
110
|
+
* shrinkHtmlTokens(tokens);
|
|
111
|
+
* // Returns nested structure with proper token relationships
|
|
112
|
+
*
|
|
113
|
+
* @public
|
|
16
114
|
*/
|
|
17
115
|
export declare const shrinkHtmlTokens: (tokens: Token[]) => Token[];
|
|
116
|
+
/**
|
|
117
|
+
* Core token processing logic that handles the complexities of HTML nesting.
|
|
118
|
+
* Uses a stack-based approach to match opening and closing tags while
|
|
119
|
+
* maintaining proper hierarchical relationships.
|
|
120
|
+
*
|
|
121
|
+
* Implementation details:
|
|
122
|
+
* - Maintains a stack of opening tags
|
|
123
|
+
* - Processes nested tokens recursively
|
|
124
|
+
* - Preserves HTML attributes
|
|
125
|
+
* - Handles malformed HTML gracefully
|
|
126
|
+
*
|
|
127
|
+
* @param {Token[]} tokens - Tokens to be processed
|
|
128
|
+
* @returns {Token[]} Processed tokens with proper nesting structure
|
|
129
|
+
*
|
|
130
|
+
* @internal
|
|
131
|
+
*/
|
|
132
|
+
export declare const processHtmlTokens: (tokens: Token[]) => Token[];
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
import { Parser } from 'htmlparser2';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Matches HTML tags with comprehensive coverage of edge cases.
|
|
4
|
+
* Pattern breakdown:
|
|
5
|
+
* - <\/? : Matches opening < and optional /
|
|
6
|
+
* - [a-zA-Z] : Tag must start with letter
|
|
7
|
+
* - [a-zA-Z0-9-] : Subsequent chars can be letters, numbers, or hyphens
|
|
8
|
+
* - (?:\s+[^>]*)?: Optional attributes
|
|
9
|
+
* - > : Closing bracket
|
|
10
|
+
*
|
|
11
|
+
* @const {RegExp}
|
|
6
12
|
*/
|
|
7
13
|
const HTML_TAG_PATTERN = /<\/?([a-zA-Z][a-zA-Z0-9-]{0,})(?:\s+[^>]*)?>/;
|
|
8
14
|
const htmlTagRegex = new RegExp(HTML_TAG_PATTERN);
|
|
9
15
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* @
|
|
16
|
+
* Analyzes a string to determine if it contains an HTML tag and its characteristics.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} raw - Raw string potentially containing an HTML tag
|
|
19
|
+
* @returns {Object|null} Returns null if no tag found, otherwise returns:
|
|
20
|
+
* {
|
|
21
|
+
* tag: string - The name of the HTML tag
|
|
22
|
+
* isOpening: bool - True if opening tag, false if closing
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* isHtmlOpenTag('<div class="test">') // Returns { tag: 'div', isOpening: true }
|
|
27
|
+
* isHtmlOpenTag('</span>') // Returns { tag: 'span', isOpening: false }
|
|
28
|
+
* isHtmlOpenTag('plain text') // Returns null
|
|
13
29
|
*/
|
|
14
30
|
export const isHtmlOpenTag = (raw) => {
|
|
15
31
|
// First check if the string contains any HTML tags at all (faster than full regex match)
|
|
@@ -22,36 +38,89 @@ export const isHtmlOpenTag = (raw) => {
|
|
|
22
38
|
return { tag: match[1], isOpening: !raw.startsWith('</') };
|
|
23
39
|
};
|
|
24
40
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
41
|
+
* Parses HTML attributes from a tag string into a structured object.
|
|
42
|
+
* Handles both single and double quoted attributes.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} raw - Raw HTML tag string containing attributes
|
|
45
|
+
* @returns {Record<string, string>} Map of attribute names to their values
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* extractAttributes('<div class="foo" id="bar">')
|
|
49
|
+
* // Returns { class: 'foo', id: 'bar' }
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
28
52
|
*/
|
|
29
|
-
const extractAttributes = (raw) => {
|
|
53
|
+
export const extractAttributes = (raw) => {
|
|
30
54
|
const attributes = {};
|
|
31
|
-
//
|
|
32
|
-
const
|
|
55
|
+
// First pass: handle regular and unclosed quoted attributes
|
|
56
|
+
const quotedRegex = /([a-zA-Z][\w-]*?)=["']([^"']*?)(?:["']|$)/g;
|
|
33
57
|
let match;
|
|
34
|
-
|
|
35
|
-
while ((match = attributeRegex.exec(raw)) !== null) {
|
|
58
|
+
while ((match = quotedRegex.exec(raw)) !== null) {
|
|
36
59
|
const [, key, value] = match;
|
|
37
60
|
attributes[key] = value.trim();
|
|
38
61
|
}
|
|
62
|
+
// Second pass: handle boolean attributes
|
|
63
|
+
const booleanRegex = /(?:^|\s)([a-zA-Z][\w-]*?)(?=[\s>]|$)/g;
|
|
64
|
+
while ((match = booleanRegex.exec(raw)) !== null) {
|
|
65
|
+
const [, key] = match;
|
|
66
|
+
if (key && !attributes[key]) {
|
|
67
|
+
attributes[key] = '';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
39
70
|
return attributes;
|
|
40
71
|
};
|
|
41
72
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
73
|
+
* Converts an HTML string into a sequence of tokens using htmlparser2.
|
|
74
|
+
* Handles complex nested structures while maintaining proper order and relationships.
|
|
75
|
+
*
|
|
76
|
+
* Key features:
|
|
77
|
+
* - Preserves original HTML structure without automatic tag closing
|
|
78
|
+
* - Handles self-closing tags with proper XML syntax (e.g., <br/> instead of <br>)
|
|
79
|
+
* - Gracefully handles malformed HTML by preserving the original structure
|
|
80
|
+
* - Maintains attribute information in opening tags
|
|
81
|
+
* - Processes text content between tags
|
|
82
|
+
*
|
|
83
|
+
* @param {string} html - HTML string to be parsed
|
|
84
|
+
* @returns {Token[]} Array of tokens representing the HTML structure
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* // Well-formed HTML
|
|
88
|
+
* parseHtmlBlock('<div>Hello <span>world</span></div>')
|
|
89
|
+
* // Returns [
|
|
90
|
+
* // { type: 'html', raw: '<div>', ... },
|
|
91
|
+
* // { type: 'text', raw: 'Hello ', ... },
|
|
92
|
+
* // { type: 'html', raw: '<span>', ... },
|
|
93
|
+
* // { type: 'text', raw: 'world', ... },
|
|
94
|
+
* // { type: 'html', raw: '</span>', ... },
|
|
95
|
+
* // { type: 'html', raw: '</div>', ... }
|
|
96
|
+
* // ]
|
|
97
|
+
*
|
|
98
|
+
* // Self-closing tags
|
|
99
|
+
* parseHtmlBlock('<div>Before<br/>After</div>')
|
|
100
|
+
* // Returns [
|
|
101
|
+
* // { type: 'html', raw: '<div>', ... },
|
|
102
|
+
* // { type: 'text', raw: 'Before', ... },
|
|
103
|
+
* // { type: 'html', raw: '<br/>', ... },
|
|
104
|
+
* // { type: 'text', raw: 'After', ... },
|
|
105
|
+
* // { type: 'html', raw: '</div>', ... }
|
|
106
|
+
* // ]
|
|
107
|
+
*
|
|
108
|
+
* // Malformed HTML
|
|
109
|
+
* parseHtmlBlock('<div>Unclosed')
|
|
110
|
+
* // Returns [
|
|
111
|
+
* // { type: 'html', raw: '<div>', ... },
|
|
112
|
+
* // { type: 'text', raw: 'Unclosed', ... }
|
|
113
|
+
* // ]
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
46
116
|
*/
|
|
47
|
-
const parseHtmlBlock = (html) => {
|
|
117
|
+
export const parseHtmlBlock = (html) => {
|
|
48
118
|
const tokens = [];
|
|
49
|
-
// Buffer for accumulating text content between tags
|
|
50
119
|
let currentText = '';
|
|
120
|
+
const selfClosingTags = /^(br|hr|img|input|link|meta|area|base|col|embed|keygen|param|source|track|wbr)$/i;
|
|
121
|
+
const openTags = [];
|
|
51
122
|
const parser = new Parser({
|
|
52
|
-
// Called when an opening tag is encountered (<div>, <span>, etc.)
|
|
53
123
|
onopentag: (name, attributes) => {
|
|
54
|
-
// If we have accumulated any text, create a text token first
|
|
55
124
|
if (currentText.trim()) {
|
|
56
125
|
tokens.push({
|
|
57
126
|
type: 'text',
|
|
@@ -60,23 +129,32 @@ const parseHtmlBlock = (html) => {
|
|
|
60
129
|
});
|
|
61
130
|
currentText = '';
|
|
62
131
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
132
|
+
openTags.push(name);
|
|
133
|
+
if (selfClosingTags.test(name)) {
|
|
134
|
+
tokens.push({
|
|
135
|
+
type: 'html',
|
|
136
|
+
raw: `<${name}${Object.entries(attributes)
|
|
137
|
+
.map(([key, value]) => ` ${key}="${value}"`)
|
|
138
|
+
.join('')}/>`,
|
|
139
|
+
tag: name,
|
|
140
|
+
attributes
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
tokens.push({
|
|
145
|
+
type: 'html',
|
|
146
|
+
raw: `<${name}${Object.entries(attributes)
|
|
147
|
+
.map(([key, value]) => ` ${key}="${value}"`)
|
|
148
|
+
.join('')}>`,
|
|
149
|
+
tag: name,
|
|
150
|
+
attributes
|
|
151
|
+
});
|
|
152
|
+
}
|
|
72
153
|
},
|
|
73
|
-
// Called for text content between tags
|
|
74
154
|
ontext: (text) => {
|
|
75
155
|
currentText += text;
|
|
76
156
|
},
|
|
77
|
-
// Called when a closing tag is encountered (</div>, </span>, etc.)
|
|
78
157
|
onclosetag: (name) => {
|
|
79
|
-
// Push any accumulated text before the closing tag
|
|
80
158
|
if (currentText.trim()) {
|
|
81
159
|
tokens.push({
|
|
82
160
|
type: 'text',
|
|
@@ -85,41 +163,95 @@ const parseHtmlBlock = (html) => {
|
|
|
85
163
|
});
|
|
86
164
|
currentText = '';
|
|
87
165
|
}
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
166
|
+
// Only add closing tag if we found its opening tag
|
|
167
|
+
// and it's not a self-closing tag
|
|
168
|
+
if (openTags.includes(name) && !selfClosingTags.test(name)) {
|
|
169
|
+
if (html.includes(`</${name}>`)) {
|
|
170
|
+
tokens.push({
|
|
171
|
+
type: 'html',
|
|
172
|
+
raw: `</${name}>`,
|
|
173
|
+
tag: name
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
openTags.splice(openTags.indexOf(name), 1);
|
|
177
|
+
}
|
|
94
178
|
}
|
|
179
|
+
}, {
|
|
180
|
+
xmlMode: true,
|
|
181
|
+
// Add this to prevent automatic tag closing
|
|
182
|
+
recognizeSelfClosing: true
|
|
95
183
|
});
|
|
96
|
-
// Process the HTML string
|
|
97
184
|
parser.write(html);
|
|
98
185
|
parser.end();
|
|
186
|
+
if (currentText.trim()) {
|
|
187
|
+
tokens.push({
|
|
188
|
+
type: 'text',
|
|
189
|
+
raw: currentText,
|
|
190
|
+
text: currentText
|
|
191
|
+
});
|
|
192
|
+
}
|
|
99
193
|
return tokens;
|
|
100
194
|
};
|
|
101
195
|
/**
|
|
102
|
-
*
|
|
103
|
-
* Used
|
|
104
|
-
*
|
|
105
|
-
* @
|
|
196
|
+
* Determines if an HTML string contains multiple distinct tags.
|
|
197
|
+
* Used as a preprocessing step to optimize token processing.
|
|
198
|
+
*
|
|
199
|
+
* @param {string} html - HTML string to analyze
|
|
200
|
+
* @returns {boolean} True if multiple tags are present
|
|
201
|
+
*
|
|
202
|
+
* @internal
|
|
106
203
|
*/
|
|
107
|
-
const containsMultipleTags = (html) => {
|
|
204
|
+
export const containsMultipleTags = (html) => {
|
|
108
205
|
// Count the number of opening tags (excluding self-closing)
|
|
109
206
|
const openingTags = html.match(/<[a-zA-Z][^>]*>/g) || [];
|
|
110
207
|
const closingTags = html.match(/<\/[a-zA-Z][^>]*>/g) || [];
|
|
111
208
|
return openingTags.length > 1 || closingTags.length > 1;
|
|
112
209
|
};
|
|
113
210
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
211
|
+
* Primary entry point for HTML token processing. Transforms flat token arrays
|
|
212
|
+
* into properly nested structures while preserving HTML semantics.
|
|
213
|
+
*
|
|
214
|
+
* Key features:
|
|
215
|
+
* - Breaks down complex HTML structures into atomic tokens
|
|
216
|
+
* - Maintains attribute information
|
|
217
|
+
* - Preserves proper nesting relationships
|
|
218
|
+
* - Handles malformed HTML gracefully
|
|
219
|
+
*
|
|
220
|
+
* @param {Token[]} tokens - Array of tokens to process
|
|
221
|
+
* @returns {Token[]} Processed and properly nested token array
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* const tokens = [
|
|
225
|
+
* { type: 'html', raw: '<div class="wrapper">' },
|
|
226
|
+
* { type: 'text', raw: 'content' },
|
|
227
|
+
* { type: 'html', raw: '</div>' }
|
|
228
|
+
* ];
|
|
229
|
+
* shrinkHtmlTokens(tokens);
|
|
230
|
+
* // Returns nested structure with proper token relationships
|
|
231
|
+
*
|
|
232
|
+
* @public
|
|
118
233
|
*/
|
|
119
234
|
export const shrinkHtmlTokens = (tokens) => {
|
|
120
235
|
const result = [];
|
|
121
236
|
for (const token of tokens) {
|
|
122
|
-
if (token.type === '
|
|
237
|
+
if (token.type === 'table') {
|
|
238
|
+
// Process header cells
|
|
239
|
+
if (token.header) {
|
|
240
|
+
token.header = token.header.map((cell) => ({
|
|
241
|
+
...cell,
|
|
242
|
+
tokens: cell.tokens ? shrinkHtmlTokens(cell.tokens) : []
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
// Process row cells
|
|
246
|
+
if (token.rows) {
|
|
247
|
+
token.rows = token.rows.map((row) => row.map((cell) => ({
|
|
248
|
+
...cell,
|
|
249
|
+
tokens: cell.tokens ? shrinkHtmlTokens(cell.tokens) : []
|
|
250
|
+
})));
|
|
251
|
+
}
|
|
252
|
+
result.push(token);
|
|
253
|
+
}
|
|
254
|
+
else if (token.type === 'html' && containsMultipleTags(token.raw)) {
|
|
123
255
|
// Parse HTML with multiple tags into separate tokens
|
|
124
256
|
result.push(...parseHtmlBlock(token.raw));
|
|
125
257
|
}
|
|
@@ -131,26 +263,22 @@ export const shrinkHtmlTokens = (tokens) => {
|
|
|
131
263
|
return processHtmlTokens(result);
|
|
132
264
|
};
|
|
133
265
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
266
|
+
* Core token processing logic that handles the complexities of HTML nesting.
|
|
267
|
+
* Uses a stack-based approach to match opening and closing tags while
|
|
268
|
+
* maintaining proper hierarchical relationships.
|
|
137
269
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
270
|
+
* Implementation details:
|
|
271
|
+
* - Maintains a stack of opening tags
|
|
272
|
+
* - Processes nested tokens recursively
|
|
273
|
+
* - Preserves HTML attributes
|
|
274
|
+
* - Handles malformed HTML gracefully
|
|
140
275
|
*
|
|
141
|
-
* @
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* { type: 'html', raw: '</div>' }
|
|
146
|
-
* ]
|
|
147
|
-
* Output: [
|
|
148
|
-
* { type: 'html', tag: 'div', tokens: [
|
|
149
|
-
* { type: 'text', raw: 'Hello' }
|
|
150
|
-
* ]}
|
|
151
|
-
* ]
|
|
276
|
+
* @param {Token[]} tokens - Tokens to be processed
|
|
277
|
+
* @returns {Token[]} Processed tokens with proper nesting structure
|
|
278
|
+
*
|
|
279
|
+
* @internal
|
|
152
280
|
*/
|
|
153
|
-
const processHtmlTokens = (tokens) => {
|
|
281
|
+
export const processHtmlTokens = (tokens) => {
|
|
154
282
|
const result = [];
|
|
155
283
|
// Stack to keep track of opening tags and their positions
|
|
156
284
|
const stack = [];
|