sunsama-api 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/cjs/{client → src/client}/index.js +320 -24
- package/dist/cjs/src/client/index.js.map +1 -0
- package/dist/cjs/src/errors/index.js.map +1 -0
- package/dist/cjs/src/index.js.map +1 -0
- package/dist/cjs/src/queries/fragments/index.js.map +1 -0
- package/dist/cjs/src/queries/fragments/mutation-responses.js.map +1 -0
- package/dist/cjs/src/queries/fragments/stream.js.map +1 -0
- package/dist/cjs/src/queries/fragments/task.js.map +1 -0
- package/dist/cjs/src/queries/index.js.map +1 -0
- package/dist/cjs/src/queries/streams/index.js.map +1 -0
- package/dist/cjs/src/queries/streams/queries.js.map +1 -0
- package/dist/cjs/src/queries/tasks/index.js.map +1 -0
- package/dist/cjs/{queries → src/queries}/tasks/mutations.js +74 -1
- package/dist/cjs/src/queries/tasks/mutations.js.map +1 -0
- package/dist/cjs/src/queries/tasks/queries.js.map +1 -0
- package/dist/cjs/src/queries/user/index.js.map +1 -0
- package/dist/cjs/{queries → src/queries}/user/queries.js.map +1 -1
- package/dist/{esm → cjs/src}/types/api.js.map +1 -1
- package/dist/cjs/src/types/client.js.map +1 -0
- package/dist/cjs/src/types/common.js.map +1 -0
- package/dist/cjs/src/types/index.js.map +1 -0
- package/dist/cjs/src/utils/conversion.js +693 -0
- package/dist/cjs/src/utils/conversion.js.map +1 -0
- package/dist/cjs/src/utils/index.js.map +1 -0
- package/dist/cjs/src/utils/validation.js.map +1 -0
- package/dist/cjs/vitest.config.js +32 -0
- package/dist/cjs/vitest.config.js.map +1 -0
- package/dist/esm/{client → src/client}/index.js +322 -26
- package/dist/esm/src/client/index.js.map +1 -0
- package/dist/esm/src/errors/index.js.map +1 -0
- package/dist/esm/src/index.js.map +1 -0
- package/dist/esm/src/queries/fragments/index.js.map +1 -0
- package/dist/esm/src/queries/fragments/mutation-responses.js.map +1 -0
- package/dist/esm/src/queries/fragments/stream.js.map +1 -0
- package/dist/esm/src/queries/fragments/task.js.map +1 -0
- package/dist/esm/src/queries/index.js.map +1 -0
- package/dist/esm/src/queries/streams/index.js.map +1 -0
- package/dist/esm/src/queries/streams/queries.js.map +1 -0
- package/dist/esm/src/queries/tasks/index.js.map +1 -0
- package/dist/esm/{queries → src/queries}/tasks/mutations.js +73 -0
- package/dist/esm/src/queries/tasks/mutations.js.map +1 -0
- package/dist/esm/src/queries/tasks/queries.js.map +1 -0
- package/dist/esm/src/queries/user/index.js.map +1 -0
- package/dist/esm/{queries → src/queries}/user/queries.js.map +1 -1
- package/dist/{cjs → esm/src}/types/api.js.map +1 -1
- package/dist/esm/src/types/client.js.map +1 -0
- package/dist/esm/src/types/common.js.map +1 -0
- package/dist/esm/src/types/index.js.map +1 -0
- package/dist/esm/src/utils/conversion.js +684 -0
- package/dist/esm/src/utils/conversion.js.map +1 -0
- package/dist/esm/src/utils/index.js.map +1 -0
- package/dist/esm/src/utils/validation.js.map +1 -0
- package/dist/esm/vitest.config.js +30 -0
- package/dist/esm/vitest.config.js.map +1 -0
- package/dist/types/{client → src/client}/index.d.ts +117 -0
- package/dist/types/src/client/index.d.ts.map +1 -0
- package/dist/types/src/errors/index.d.ts.map +1 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/queries/fragments/index.d.ts.map +1 -0
- package/dist/types/src/queries/fragments/mutation-responses.d.ts.map +1 -0
- package/dist/types/src/queries/fragments/stream.d.ts.map +1 -0
- package/dist/types/src/queries/fragments/task.d.ts.map +1 -0
- package/dist/types/src/queries/index.d.ts.map +1 -0
- package/dist/types/src/queries/streams/index.d.ts.map +1 -0
- package/dist/types/src/queries/streams/queries.d.ts.map +1 -0
- package/dist/types/src/queries/tasks/index.d.ts.map +1 -0
- package/dist/types/{queries → src/queries}/tasks/mutations.d.ts +41 -0
- package/dist/types/src/queries/tasks/mutations.d.ts.map +1 -0
- package/dist/types/src/queries/tasks/queries.d.ts.map +1 -0
- package/dist/types/src/queries/user/index.d.ts.map +1 -0
- package/dist/types/src/queries/user/queries.d.ts.map +1 -0
- package/dist/types/{types → src/types}/api.d.ts +57 -0
- package/dist/types/src/types/api.d.ts.map +1 -0
- package/dist/types/src/types/client.d.ts.map +1 -0
- package/dist/types/src/types/common.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/{utils → src/utils}/conversion.d.ts +102 -0
- package/dist/types/src/utils/conversion.d.ts.map +1 -0
- package/dist/types/src/utils/index.d.ts.map +1 -0
- package/dist/types/src/utils/validation.d.ts.map +1 -0
- package/dist/types/vitest.config.d.ts +3 -0
- package/dist/types/vitest.config.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/cjs/client/index.js.map +0 -1
- package/dist/cjs/errors/index.js.map +0 -1
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/queries/fragments/index.js.map +0 -1
- package/dist/cjs/queries/fragments/mutation-responses.js.map +0 -1
- package/dist/cjs/queries/fragments/stream.js.map +0 -1
- package/dist/cjs/queries/fragments/task.js.map +0 -1
- package/dist/cjs/queries/index.js.map +0 -1
- package/dist/cjs/queries/streams/index.js.map +0 -1
- package/dist/cjs/queries/streams/queries.js.map +0 -1
- package/dist/cjs/queries/tasks/index.js.map +0 -1
- package/dist/cjs/queries/tasks/mutations.js.map +0 -1
- package/dist/cjs/queries/tasks/queries.js.map +0 -1
- package/dist/cjs/queries/user/index.js.map +0 -1
- package/dist/cjs/types/client.js.map +0 -1
- package/dist/cjs/types/common.js.map +0 -1
- package/dist/cjs/types/index.js.map +0 -1
- package/dist/cjs/utils/conversion.js +0 -236
- package/dist/cjs/utils/conversion.js.map +0 -1
- package/dist/cjs/utils/index.js.map +0 -1
- package/dist/cjs/utils/validation.js.map +0 -1
- package/dist/esm/client/index.js.map +0 -1
- package/dist/esm/errors/index.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/queries/fragments/index.js.map +0 -1
- package/dist/esm/queries/fragments/mutation-responses.js.map +0 -1
- package/dist/esm/queries/fragments/stream.js.map +0 -1
- package/dist/esm/queries/fragments/task.js.map +0 -1
- package/dist/esm/queries/index.js.map +0 -1
- package/dist/esm/queries/streams/index.js.map +0 -1
- package/dist/esm/queries/streams/queries.js.map +0 -1
- package/dist/esm/queries/tasks/index.js.map +0 -1
- package/dist/esm/queries/tasks/mutations.js.map +0 -1
- package/dist/esm/queries/tasks/queries.js.map +0 -1
- package/dist/esm/queries/user/index.js.map +0 -1
- package/dist/esm/types/client.js.map +0 -1
- package/dist/esm/types/common.js.map +0 -1
- package/dist/esm/types/index.js.map +0 -1
- package/dist/esm/utils/conversion.js +0 -229
- package/dist/esm/utils/conversion.js.map +0 -1
- package/dist/esm/utils/index.js.map +0 -1
- package/dist/esm/utils/validation.js.map +0 -1
- package/dist/types/client/index.d.ts.map +0 -1
- package/dist/types/errors/index.d.ts.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/queries/fragments/index.d.ts.map +0 -1
- package/dist/types/queries/fragments/mutation-responses.d.ts.map +0 -1
- package/dist/types/queries/fragments/stream.d.ts.map +0 -1
- package/dist/types/queries/fragments/task.d.ts.map +0 -1
- package/dist/types/queries/index.d.ts.map +0 -1
- package/dist/types/queries/streams/index.d.ts.map +0 -1
- package/dist/types/queries/streams/queries.d.ts.map +0 -1
- package/dist/types/queries/tasks/index.d.ts.map +0 -1
- package/dist/types/queries/tasks/mutations.d.ts.map +0 -1
- package/dist/types/queries/tasks/queries.d.ts.map +0 -1
- package/dist/types/queries/user/index.d.ts.map +0 -1
- package/dist/types/queries/user/queries.d.ts.map +0 -1
- package/dist/types/types/api.d.ts.map +0 -1
- package/dist/types/types/client.d.ts.map +0 -1
- package/dist/types/types/common.d.ts.map +0 -1
- package/dist/types/types/index.d.ts.map +0 -1
- package/dist/types/utils/conversion.d.ts.map +0 -1
- package/dist/types/utils/index.d.ts.map +0 -1
- package/dist/types/utils/validation.d.ts.map +0 -1
- /package/dist/cjs/{errors → src/errors}/index.js +0 -0
- /package/dist/cjs/{index.js → src/index.js} +0 -0
- /package/dist/cjs/{queries → src/queries}/fragments/index.js +0 -0
- /package/dist/cjs/{queries → src/queries}/fragments/mutation-responses.js +0 -0
- /package/dist/cjs/{queries → src/queries}/fragments/stream.js +0 -0
- /package/dist/cjs/{queries → src/queries}/fragments/task.js +0 -0
- /package/dist/cjs/{queries → src/queries}/index.js +0 -0
- /package/dist/cjs/{queries → src/queries}/streams/index.js +0 -0
- /package/dist/cjs/{queries → src/queries}/streams/queries.js +0 -0
- /package/dist/cjs/{queries → src/queries}/tasks/index.js +0 -0
- /package/dist/cjs/{queries → src/queries}/tasks/queries.js +0 -0
- /package/dist/cjs/{queries → src/queries}/user/index.js +0 -0
- /package/dist/cjs/{queries → src/queries}/user/queries.js +0 -0
- /package/dist/cjs/{types → src/types}/api.js +0 -0
- /package/dist/cjs/{types → src/types}/client.js +0 -0
- /package/dist/cjs/{types → src/types}/common.js +0 -0
- /package/dist/cjs/{types → src/types}/index.js +0 -0
- /package/dist/cjs/{utils → src/utils}/index.js +0 -0
- /package/dist/cjs/{utils → src/utils}/validation.js +0 -0
- /package/dist/esm/{errors → src/errors}/index.js +0 -0
- /package/dist/esm/{index.js → src/index.js} +0 -0
- /package/dist/esm/{queries → src/queries}/fragments/index.js +0 -0
- /package/dist/esm/{queries → src/queries}/fragments/mutation-responses.js +0 -0
- /package/dist/esm/{queries → src/queries}/fragments/stream.js +0 -0
- /package/dist/esm/{queries → src/queries}/fragments/task.js +0 -0
- /package/dist/esm/{queries → src/queries}/index.js +0 -0
- /package/dist/esm/{queries → src/queries}/streams/index.js +0 -0
- /package/dist/esm/{queries → src/queries}/streams/queries.js +0 -0
- /package/dist/esm/{queries → src/queries}/tasks/index.js +0 -0
- /package/dist/esm/{queries → src/queries}/tasks/queries.js +0 -0
- /package/dist/esm/{queries → src/queries}/user/index.js +0 -0
- /package/dist/esm/{queries → src/queries}/user/queries.js +0 -0
- /package/dist/esm/{types → src/types}/api.js +0 -0
- /package/dist/esm/{types → src/types}/client.js +0 -0
- /package/dist/esm/{types → src/types}/common.js +0 -0
- /package/dist/esm/{types → src/types}/index.js +0 -0
- /package/dist/esm/{utils → src/utils}/index.js +0 -0
- /package/dist/esm/{utils → src/utils}/validation.js +0 -0
- /package/dist/types/{errors → src/errors}/index.d.ts +0 -0
- /package/dist/types/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/types/{queries → src/queries}/fragments/index.d.ts +0 -0
- /package/dist/types/{queries → src/queries}/fragments/mutation-responses.d.ts +0 -0
- /package/dist/types/{queries → src/queries}/fragments/stream.d.ts +0 -0
- /package/dist/types/{queries → src/queries}/fragments/task.d.ts +0 -0
- /package/dist/types/{queries → src/queries}/index.d.ts +0 -0
- /package/dist/types/{queries → src/queries}/streams/index.d.ts +0 -0
- /package/dist/types/{queries → src/queries}/streams/queries.d.ts +0 -0
- /package/dist/types/{queries → src/queries}/tasks/index.d.ts +0 -0
- /package/dist/types/{queries → src/queries}/tasks/queries.d.ts +0 -0
- /package/dist/types/{queries → src/queries}/user/index.d.ts +0 -0
- /package/dist/types/{queries → src/queries}/user/queries.d.ts +0 -0
- /package/dist/types/{types → src/types}/client.d.ts +0 -0
- /package/dist/types/{types → src/types}/common.d.ts +0 -0
- /package/dist/types/{types → src/types}/index.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/index.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/validation.d.ts +0 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HTML ↔ Markdown Conversion Utilities
|
|
4
|
+
*
|
|
5
|
+
* This module provides utilities for converting between HTML and Markdown formats.
|
|
6
|
+
* It uses specialized libraries for optimal performance:
|
|
7
|
+
* - Turndown for HTML → Markdown conversion
|
|
8
|
+
* - Marked for Markdown → HTML conversion
|
|
9
|
+
*
|
|
10
|
+
* These utilities are particularly useful for Sunsama API task notes and comments
|
|
11
|
+
* where content can be provided in either format and needs conversion to the other.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.htmlToMarkdown = htmlToMarkdown;
|
|
15
|
+
exports.markdownToHtml = markdownToHtml;
|
|
16
|
+
exports.sanitizeHtml = sanitizeHtml;
|
|
17
|
+
exports.convertContent = convertContent;
|
|
18
|
+
exports.parseMarkdownToSegments = parseMarkdownToSegments;
|
|
19
|
+
exports.parseMarkdownToBlocks = parseMarkdownToBlocks;
|
|
20
|
+
const tslib_1 = require("tslib");
|
|
21
|
+
const marked_1 = require("marked");
|
|
22
|
+
const turndown_1 = tslib_1.__importDefault(require("turndown"));
|
|
23
|
+
const zod_1 = require("zod");
|
|
24
|
+
const index_js_1 = require("../errors/index.js");
|
|
25
|
+
/**
|
|
26
|
+
* Decodes common HTML entities back to their original characters.
|
|
27
|
+
* This is needed because marked's lexer HTML-encodes some characters.
|
|
28
|
+
*/
|
|
29
|
+
function decodeHtmlEntities(text) {
|
|
30
|
+
return text
|
|
31
|
+
.replace(/&/g, '&')
|
|
32
|
+
.replace(/</g, '<')
|
|
33
|
+
.replace(/>/g, '>')
|
|
34
|
+
.replace(/"/g, '"')
|
|
35
|
+
.replace(/'/g, "'")
|
|
36
|
+
.replace(/'/g, "'")
|
|
37
|
+
.replace(/'/g, "'")
|
|
38
|
+
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)))
|
|
39
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_, code) => String.fromCharCode(parseInt(code, 16)));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validation schema for HTML input
|
|
43
|
+
*/
|
|
44
|
+
const htmlInputSchema = zod_1.z.string().trim().min(1, 'HTML content cannot be empty');
|
|
45
|
+
/**
|
|
46
|
+
* Validation schema for Markdown input
|
|
47
|
+
*/
|
|
48
|
+
const markdownInputSchema = zod_1.z.string().trim().min(1, 'Markdown content cannot be empty');
|
|
49
|
+
/**
|
|
50
|
+
* Default configuration for Turndown (HTML → Markdown)
|
|
51
|
+
*/
|
|
52
|
+
const defaultTurndownOptions = {
|
|
53
|
+
preserveHtml: false,
|
|
54
|
+
gfm: true,
|
|
55
|
+
linkStyle: 'inlined',
|
|
56
|
+
br: '\n',
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Default configuration for Marked (Markdown → HTML)
|
|
60
|
+
*/
|
|
61
|
+
const defaultMarkedOptions = {
|
|
62
|
+
sanitize: true,
|
|
63
|
+
gfm: true,
|
|
64
|
+
breaks: true,
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Initialize Turndown service with configuration
|
|
68
|
+
*/
|
|
69
|
+
function createTurndownService(options = {}) {
|
|
70
|
+
const config = { ...defaultTurndownOptions, ...options };
|
|
71
|
+
const turndownService = new turndown_1.default({
|
|
72
|
+
headingStyle: 'atx',
|
|
73
|
+
hr: '---',
|
|
74
|
+
bulletListMarker: '-',
|
|
75
|
+
codeBlockStyle: 'fenced',
|
|
76
|
+
fence: '```',
|
|
77
|
+
emDelimiter: '*',
|
|
78
|
+
strongDelimiter: '**',
|
|
79
|
+
linkStyle: config.linkStyle,
|
|
80
|
+
linkReferenceStyle: 'full',
|
|
81
|
+
br: config.br,
|
|
82
|
+
});
|
|
83
|
+
// Add GitHub Flavored Markdown support
|
|
84
|
+
if (config.gfm) {
|
|
85
|
+
// Support for strikethrough
|
|
86
|
+
turndownService.addRule('strikethrough', {
|
|
87
|
+
filter: ['del', 's'],
|
|
88
|
+
replacement: function (content) {
|
|
89
|
+
return '~~' + content + '~~';
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
// Support for task lists
|
|
93
|
+
turndownService.addRule('taskListItems', {
|
|
94
|
+
filter: function (node) {
|
|
95
|
+
return (node.nodeName === 'LI' &&
|
|
96
|
+
node.querySelector &&
|
|
97
|
+
node.querySelector('input[type="checkbox"]') !== null);
|
|
98
|
+
},
|
|
99
|
+
replacement: function (content, node) {
|
|
100
|
+
const checkbox = node.querySelector
|
|
101
|
+
? node.querySelector('input[type="checkbox"]')
|
|
102
|
+
: null;
|
|
103
|
+
const isChecked = checkbox && checkbox.checked;
|
|
104
|
+
return (isChecked ? '- [x] ' : '- [ ] ') + content;
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// Apply custom rules if provided
|
|
109
|
+
if (config.customRules) {
|
|
110
|
+
Object.entries(config.customRules).forEach(([name, rule]) => {
|
|
111
|
+
turndownService.addRule(name, rule);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return turndownService;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Initialize Marked with configuration
|
|
118
|
+
*/
|
|
119
|
+
function configureMarked(options = {}) {
|
|
120
|
+
const config = { ...defaultMarkedOptions, ...options };
|
|
121
|
+
marked_1.marked.setOptions({
|
|
122
|
+
gfm: config.gfm,
|
|
123
|
+
breaks: config.breaks,
|
|
124
|
+
// Note: sanitize option is deprecated in newer versions of marked
|
|
125
|
+
// We'll handle sanitization separately if needed
|
|
126
|
+
});
|
|
127
|
+
if (config.renderer) {
|
|
128
|
+
marked_1.marked.use({ renderer: config.renderer });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Converts HTML content to Markdown format
|
|
133
|
+
*
|
|
134
|
+
* @param html - The HTML content to convert
|
|
135
|
+
* @param options - Configuration options for conversion
|
|
136
|
+
* @returns The converted Markdown content
|
|
137
|
+
* @throws SunsamaAuthError if input validation fails
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const html = '<h1>Hello World</h1><p>This is <strong>bold</strong> text.</p>';
|
|
142
|
+
* const markdown = htmlToMarkdown(html);
|
|
143
|
+
* console.log(markdown); // "# Hello World\n\nThis is **bold** text."
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
function htmlToMarkdown(html, options = {}) {
|
|
147
|
+
try {
|
|
148
|
+
// Validate input
|
|
149
|
+
htmlInputSchema.parse(html);
|
|
150
|
+
// Create Turndown service with options
|
|
151
|
+
const turndownService = createTurndownService(options);
|
|
152
|
+
// Convert HTML to Markdown
|
|
153
|
+
const markdown = turndownService.turndown(html);
|
|
154
|
+
// Clean up the result (remove excessive whitespace)
|
|
155
|
+
return markdown.trim().replace(/\n{3,}/g, '\n\n');
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (error instanceof zod_1.z.ZodError) {
|
|
159
|
+
throw new index_js_1.SunsamaAuthError(`HTML to Markdown conversion failed: ${error.message}`);
|
|
160
|
+
}
|
|
161
|
+
throw new index_js_1.SunsamaAuthError(`HTML to Markdown conversion failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Converts Markdown content to HTML format
|
|
166
|
+
*
|
|
167
|
+
* @param markdown - The Markdown content to convert
|
|
168
|
+
* @param options - Configuration options for conversion
|
|
169
|
+
* @returns The converted HTML content
|
|
170
|
+
* @throws SunsamaAuthError if input validation fails
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```typescript
|
|
174
|
+
* const markdown = '# Hello World\n\nThis is **bold** text.';
|
|
175
|
+
* const html = markdownToHtml(markdown);
|
|
176
|
+
* console.log(html); // "<h1>Hello World</h1>\n<p>This is <strong>bold</strong> text.</p>"
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
function markdownToHtml(markdown, options = {}) {
|
|
180
|
+
try {
|
|
181
|
+
// Validate input
|
|
182
|
+
markdownInputSchema.parse(markdown);
|
|
183
|
+
// Configure Marked with options
|
|
184
|
+
configureMarked(options);
|
|
185
|
+
// Convert Markdown to HTML
|
|
186
|
+
const html = marked_1.marked.parse(markdown);
|
|
187
|
+
// Return the result (marked.parse returns a Promise<string> in some versions, but string in others)
|
|
188
|
+
return typeof html === 'string' ? html : html.toString();
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (error instanceof zod_1.z.ZodError) {
|
|
192
|
+
throw new index_js_1.SunsamaAuthError(`Markdown to HTML conversion failed: ${error.message}`);
|
|
193
|
+
}
|
|
194
|
+
throw new index_js_1.SunsamaAuthError(`Markdown to HTML conversion failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Sanitizes HTML content to prevent XSS attacks
|
|
199
|
+
* This is a basic implementation - consider using a dedicated library like DOMPurify for production
|
|
200
|
+
*
|
|
201
|
+
* @param html - The HTML content to sanitize
|
|
202
|
+
* @returns Sanitized HTML content
|
|
203
|
+
*/
|
|
204
|
+
function sanitizeHtml(html) {
|
|
205
|
+
if (!html)
|
|
206
|
+
return '';
|
|
207
|
+
// Basic HTML sanitization - remove script tags and dangerous attributes
|
|
208
|
+
let sanitized = html
|
|
209
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
210
|
+
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
|
|
211
|
+
.replace(/on\w+\s*=\s*"[^"]*"/gi, '')
|
|
212
|
+
.replace(/on\w+\s*=\s*'[^']*'/gi, '')
|
|
213
|
+
.replace(/on\w+\s*=\s*[^\s>]+/gi, '')
|
|
214
|
+
.replace(/javascript:/gi, '')
|
|
215
|
+
.replace(/vbscript:/gi, '')
|
|
216
|
+
.replace(/data:/gi, '');
|
|
217
|
+
// Clean up any extra spaces left behind after removing attributes
|
|
218
|
+
sanitized = sanitized.replace(/\s+>/g, '>').replace(/<\s+/g, '<');
|
|
219
|
+
return sanitized;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Utility function to safely convert between HTML and Markdown with validation
|
|
223
|
+
*
|
|
224
|
+
* @param content - The content to convert
|
|
225
|
+
* @param fromFormat - Source format ('html' or 'markdown')
|
|
226
|
+
* @param toFormat - Target format ('html' or 'markdown')
|
|
227
|
+
* @param options - Conversion options
|
|
228
|
+
* @returns Converted content
|
|
229
|
+
* @throws SunsamaAuthError if conversion fails or formats are invalid
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* const html = '<p>Hello <strong>world</strong></p>';
|
|
234
|
+
* const markdown = convertContent(html, 'html', 'markdown');
|
|
235
|
+
* console.log(markdown); // "Hello **world**"
|
|
236
|
+
*
|
|
237
|
+
* const convertedBack = convertContent(markdown, 'markdown', 'html');
|
|
238
|
+
* console.log(convertedBack); // "<p>Hello <strong>world</strong></p>"
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
function convertContent(content, fromFormat, toFormat, options = {}) {
|
|
242
|
+
if (fromFormat === toFormat) {
|
|
243
|
+
return content; // No conversion needed
|
|
244
|
+
}
|
|
245
|
+
if (fromFormat === 'html' && toFormat === 'markdown') {
|
|
246
|
+
return htmlToMarkdown(content, options.htmlToMarkdown);
|
|
247
|
+
}
|
|
248
|
+
if (fromFormat === 'markdown' && toFormat === 'html') {
|
|
249
|
+
const html = markdownToHtml(content, options.markdownToHtml);
|
|
250
|
+
return options.markdownToHtml?.sanitize !== false ? sanitizeHtml(html) : html;
|
|
251
|
+
}
|
|
252
|
+
throw new index_js_1.SunsamaAuthError(`Invalid conversion format: ${fromFormat} to ${toFormat}`);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Parses inline tokens from marked and converts them to formatted segments
|
|
256
|
+
* with Yjs-compatible attributes
|
|
257
|
+
*
|
|
258
|
+
* @param tokens - Array of marked inline tokens
|
|
259
|
+
* @param inheritedAttributes - Attributes inherited from parent elements
|
|
260
|
+
* @returns Array of formatted segments with text and attributes
|
|
261
|
+
* @internal
|
|
262
|
+
*/
|
|
263
|
+
function parseInlineTokens(tokens, inheritedAttributes = {}) {
|
|
264
|
+
const segments = [];
|
|
265
|
+
for (const token of tokens) {
|
|
266
|
+
switch (token.type) {
|
|
267
|
+
case 'text': {
|
|
268
|
+
const textToken = token;
|
|
269
|
+
// Handle text tokens that may have nested tokens (from inline parsing)
|
|
270
|
+
if ('tokens' in textToken && textToken.tokens && textToken.tokens.length > 0) {
|
|
271
|
+
segments.push(...parseInlineTokens(textToken.tokens, inheritedAttributes));
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
const attrs = Object.keys(inheritedAttributes).length > 0 ? inheritedAttributes : undefined;
|
|
275
|
+
segments.push({ text: decodeHtmlEntities(textToken.text), attributes: attrs });
|
|
276
|
+
}
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
case 'strong': {
|
|
280
|
+
const strongToken = token;
|
|
281
|
+
const newAttrs = { ...inheritedAttributes, bold: true };
|
|
282
|
+
if ('tokens' in strongToken && strongToken.tokens && strongToken.tokens.length > 0) {
|
|
283
|
+
segments.push(...parseInlineTokens(strongToken.tokens, newAttrs));
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
segments.push({ text: decodeHtmlEntities(strongToken.text), attributes: newAttrs });
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case 'em': {
|
|
291
|
+
const emToken = token;
|
|
292
|
+
const newAttrs = { ...inheritedAttributes, italic: true };
|
|
293
|
+
if ('tokens' in emToken && emToken.tokens && emToken.tokens.length > 0) {
|
|
294
|
+
segments.push(...parseInlineTokens(emToken.tokens, newAttrs));
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
segments.push({ text: decodeHtmlEntities(emToken.text), attributes: newAttrs });
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case 'link': {
|
|
302
|
+
const linkToken = token;
|
|
303
|
+
// Sunsama expects link as a nested object with href property
|
|
304
|
+
const newAttrs = { ...inheritedAttributes, link: { href: linkToken.href } };
|
|
305
|
+
if ('tokens' in linkToken && linkToken.tokens && linkToken.tokens.length > 0) {
|
|
306
|
+
segments.push(...parseInlineTokens(linkToken.tokens, newAttrs));
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
segments.push({ text: decodeHtmlEntities(linkToken.text), attributes: newAttrs });
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
case 'codespan': {
|
|
314
|
+
const codeToken = token;
|
|
315
|
+
const newAttrs = { ...inheritedAttributes, code: true };
|
|
316
|
+
segments.push({ text: decodeHtmlEntities(codeToken.text), attributes: newAttrs });
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
case 'del': {
|
|
320
|
+
// Note: Sunsama's editor doesn't support strikethrough marks
|
|
321
|
+
// So we render it as plain text with ~~ delimiters preserved
|
|
322
|
+
const delToken = token;
|
|
323
|
+
if ('tokens' in delToken && delToken.tokens && delToken.tokens.length > 0) {
|
|
324
|
+
segments.push({ text: '~~' });
|
|
325
|
+
segments.push(...parseInlineTokens(delToken.tokens, inheritedAttributes));
|
|
326
|
+
segments.push({ text: '~~' });
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
segments.push({
|
|
330
|
+
text: `~~${decodeHtmlEntities(delToken.text)}~~`,
|
|
331
|
+
attributes: inheritedAttributes,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
case 'br': {
|
|
337
|
+
segments.push({ text: '\n' });
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
case 'escape': {
|
|
341
|
+
const escapeToken = token;
|
|
342
|
+
const attrs = Object.keys(inheritedAttributes).length > 0 ? inheritedAttributes : undefined;
|
|
343
|
+
segments.push({ text: decodeHtmlEntities(escapeToken.text), attributes: attrs });
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
default: {
|
|
347
|
+
// For any other token types, try to extract text
|
|
348
|
+
if ('text' in token && typeof token.text === 'string') {
|
|
349
|
+
const attrs = Object.keys(inheritedAttributes).length > 0 ? inheritedAttributes : undefined;
|
|
350
|
+
segments.push({
|
|
351
|
+
text: decodeHtmlEntities(token.text),
|
|
352
|
+
attributes: attrs,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
else if ('raw' in token && typeof token.raw === 'string') {
|
|
356
|
+
const attrs = Object.keys(inheritedAttributes).length > 0 ? inheritedAttributes : undefined;
|
|
357
|
+
segments.push({ text: token.raw, attributes: attrs });
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return segments;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Parses markdown content into formatted segments suitable for Yjs XmlText insertion.
|
|
367
|
+
*
|
|
368
|
+
* This function converts markdown text into an array of segments, where each segment
|
|
369
|
+
* contains the text content and optional formatting attributes (bold, italic, link, etc.).
|
|
370
|
+
* The segments can be used to insert rich text into a Yjs document with proper formatting.
|
|
371
|
+
*
|
|
372
|
+
* @param markdown - The markdown content to parse
|
|
373
|
+
* @returns Array of formatted segments with text and Yjs-compatible attributes
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* ```typescript
|
|
377
|
+
* const segments = parseMarkdownToSegments('This is **bold** and *italic* text');
|
|
378
|
+
* // Returns:
|
|
379
|
+
* // [
|
|
380
|
+
* // { text: 'This is ' },
|
|
381
|
+
* // { text: 'bold', attributes: { bold: true } },
|
|
382
|
+
* // { text: ' and ' },
|
|
383
|
+
* // { text: 'italic', attributes: { italic: true } },
|
|
384
|
+
* // { text: ' text' }
|
|
385
|
+
* // ]
|
|
386
|
+
*
|
|
387
|
+
* const linkSegments = parseMarkdownToSegments('Visit [Google](https://google.com)');
|
|
388
|
+
* // Returns:
|
|
389
|
+
* // [
|
|
390
|
+
* // { text: 'Visit ' },
|
|
391
|
+
* // { text: 'Google', attributes: { link: 'https://google.com' } }
|
|
392
|
+
* // ]
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
function parseMarkdownToSegments(markdown) {
|
|
396
|
+
if (!markdown || markdown.trim() === '') {
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
const segments = [];
|
|
400
|
+
// Configure marked for GFM (GitHub Flavored Markdown) support
|
|
401
|
+
marked_1.marked.setOptions({
|
|
402
|
+
gfm: true,
|
|
403
|
+
breaks: true,
|
|
404
|
+
});
|
|
405
|
+
// Tokenize the markdown
|
|
406
|
+
const tokens = marked_1.marked.lexer(markdown);
|
|
407
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
408
|
+
const token = tokens[i];
|
|
409
|
+
switch (token.type) {
|
|
410
|
+
case 'paragraph': {
|
|
411
|
+
const paragraphToken = token;
|
|
412
|
+
if (paragraphToken.tokens) {
|
|
413
|
+
segments.push(...parseInlineTokens(paragraphToken.tokens));
|
|
414
|
+
}
|
|
415
|
+
// Add newline after paragraph if not the last token
|
|
416
|
+
if (i < tokens.length - 1) {
|
|
417
|
+
segments.push({ text: '\n' });
|
|
418
|
+
}
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
case 'heading': {
|
|
422
|
+
const headingToken = token;
|
|
423
|
+
if (headingToken.tokens) {
|
|
424
|
+
// Apply bold formatting to headings
|
|
425
|
+
segments.push(...parseInlineTokens(headingToken.tokens, { bold: true }));
|
|
426
|
+
}
|
|
427
|
+
segments.push({ text: '\n' });
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
case 'list': {
|
|
431
|
+
const listToken = token;
|
|
432
|
+
for (let j = 0; j < listToken.items.length; j++) {
|
|
433
|
+
const item = listToken.items[j];
|
|
434
|
+
// Add list marker
|
|
435
|
+
const marker = listToken.ordered ? `${j + 1}. ` : '• ';
|
|
436
|
+
segments.push({ text: marker });
|
|
437
|
+
if (item.tokens) {
|
|
438
|
+
// Process list item content
|
|
439
|
+
for (const itemToken of item.tokens) {
|
|
440
|
+
if (itemToken.type === 'text' &&
|
|
441
|
+
'tokens' in itemToken &&
|
|
442
|
+
itemToken.tokens) {
|
|
443
|
+
segments.push(...parseInlineTokens(itemToken.tokens));
|
|
444
|
+
}
|
|
445
|
+
else if ('tokens' in itemToken && itemToken.tokens) {
|
|
446
|
+
segments.push(...parseInlineTokens(itemToken.tokens));
|
|
447
|
+
}
|
|
448
|
+
else if ('text' in itemToken) {
|
|
449
|
+
segments.push({ text: decodeHtmlEntities(itemToken.text) });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
segments.push({ text: '\n' });
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
case 'code': {
|
|
458
|
+
const codeToken = token;
|
|
459
|
+
segments.push({ text: decodeHtmlEntities(codeToken.text), attributes: { code: true } });
|
|
460
|
+
segments.push({ text: '\n' });
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case 'blockquote': {
|
|
464
|
+
const blockquoteToken = token;
|
|
465
|
+
if (blockquoteToken.tokens) {
|
|
466
|
+
for (const innerToken of blockquoteToken.tokens) {
|
|
467
|
+
if (innerToken.type === 'paragraph' && 'tokens' in innerToken) {
|
|
468
|
+
segments.push({ text: '> ' });
|
|
469
|
+
segments.push(...parseInlineTokens(innerToken.tokens));
|
|
470
|
+
segments.push({ text: '\n' });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
case 'hr': {
|
|
477
|
+
segments.push({ text: '---\n' });
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
case 'space': {
|
|
481
|
+
segments.push({ text: '\n' });
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case 'text': {
|
|
485
|
+
const textToken = token;
|
|
486
|
+
if ('tokens' in textToken && textToken.tokens) {
|
|
487
|
+
segments.push(...parseInlineTokens(textToken.tokens));
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
segments.push({ text: decodeHtmlEntities(textToken.text) });
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
default: {
|
|
495
|
+
// Handle any other token types by extracting raw text
|
|
496
|
+
if ('raw' in token && typeof token.raw === 'string') {
|
|
497
|
+
segments.push({ text: decodeHtmlEntities(token.raw) });
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// Merge adjacent segments with the same attributes for efficiency
|
|
504
|
+
return mergeAdjacentSegments(segments);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Merges adjacent segments that have the same attributes
|
|
508
|
+
* @param segments - Array of formatted segments
|
|
509
|
+
* @returns Merged array of segments
|
|
510
|
+
* @internal
|
|
511
|
+
*/
|
|
512
|
+
function mergeAdjacentSegments(segments) {
|
|
513
|
+
if (segments.length === 0)
|
|
514
|
+
return [];
|
|
515
|
+
const merged = [];
|
|
516
|
+
let current = segments[0];
|
|
517
|
+
for (let i = 1; i < segments.length; i++) {
|
|
518
|
+
const next = segments[i];
|
|
519
|
+
// Check if attributes are the same (or both undefined/empty)
|
|
520
|
+
const currentAttrs = current.attributes || {};
|
|
521
|
+
const nextAttrs = next.attributes || {};
|
|
522
|
+
const attrsEqual = JSON.stringify(currentAttrs) === JSON.stringify(nextAttrs);
|
|
523
|
+
if (attrsEqual) {
|
|
524
|
+
// Merge the text
|
|
525
|
+
current = {
|
|
526
|
+
text: current.text + next.text,
|
|
527
|
+
attributes: Object.keys(currentAttrs).length > 0 ? currentAttrs : undefined,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
merged.push(current);
|
|
532
|
+
current = next;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
merged.push(current);
|
|
536
|
+
return merged;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Parses markdown content into a document structure with block-level elements.
|
|
540
|
+
*
|
|
541
|
+
* This function converts markdown text into an array of document blocks, where each block
|
|
542
|
+
* represents a block-level element (paragraph, blockquote, horizontal rule, etc.).
|
|
543
|
+
* This structure is suitable for creating proper Yjs XmlElement hierarchies that match
|
|
544
|
+
* Sunsama's rich text editor format.
|
|
545
|
+
*
|
|
546
|
+
* @param markdown - The markdown content to parse
|
|
547
|
+
* @returns Array of document blocks representing the document structure
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* ```typescript
|
|
551
|
+
* const blocks = parseMarkdownToBlocks('Hello **world**\n\n> A quote\n\n---');
|
|
552
|
+
* // Returns:
|
|
553
|
+
* // [
|
|
554
|
+
* // { type: 'paragraph', segments: [{ text: 'Hello ' }, { text: 'world', attributes: { bold: true } }] },
|
|
555
|
+
* // { type: 'blockquote', children: [{ type: 'paragraph', segments: [{ text: 'A quote' }] }] },
|
|
556
|
+
* // { type: 'horizontalRule' }
|
|
557
|
+
* // ]
|
|
558
|
+
* ```
|
|
559
|
+
*/
|
|
560
|
+
function parseMarkdownToBlocks(markdown) {
|
|
561
|
+
if (!markdown || markdown.trim() === '') {
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
const blocks = [];
|
|
565
|
+
// Configure marked for GFM support
|
|
566
|
+
marked_1.marked.setOptions({
|
|
567
|
+
gfm: true,
|
|
568
|
+
breaks: true,
|
|
569
|
+
});
|
|
570
|
+
// Tokenize the markdown
|
|
571
|
+
const tokens = marked_1.marked.lexer(markdown);
|
|
572
|
+
for (const token of tokens) {
|
|
573
|
+
switch (token.type) {
|
|
574
|
+
case 'paragraph': {
|
|
575
|
+
const paragraphToken = token;
|
|
576
|
+
if (paragraphToken.tokens) {
|
|
577
|
+
const segments = parseInlineTokens(paragraphToken.tokens);
|
|
578
|
+
if (segments.length > 0) {
|
|
579
|
+
blocks.push({ type: 'paragraph', segments: mergeAdjacentSegments(segments) });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
case 'heading': {
|
|
585
|
+
// Convert headings to bold paragraphs (Sunsama may not support native headings)
|
|
586
|
+
const headingToken = token;
|
|
587
|
+
if (headingToken.tokens) {
|
|
588
|
+
const segments = parseInlineTokens(headingToken.tokens, { bold: true });
|
|
589
|
+
if (segments.length > 0) {
|
|
590
|
+
blocks.push({ type: 'paragraph', segments: mergeAdjacentSegments(segments) });
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
case 'blockquote': {
|
|
596
|
+
const blockquoteToken = token;
|
|
597
|
+
const children = [];
|
|
598
|
+
if (blockquoteToken.tokens) {
|
|
599
|
+
for (const innerToken of blockquoteToken.tokens) {
|
|
600
|
+
if (innerToken.type === 'paragraph' && 'tokens' in innerToken) {
|
|
601
|
+
const segments = parseInlineTokens(innerToken.tokens);
|
|
602
|
+
if (segments.length > 0) {
|
|
603
|
+
children.push({ type: 'paragraph', segments: mergeAdjacentSegments(segments) });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (children.length > 0) {
|
|
609
|
+
blocks.push({ type: 'blockquote', children });
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
case 'hr': {
|
|
614
|
+
blocks.push({ type: 'horizontalRule' });
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
case 'code': {
|
|
618
|
+
const codeToken = token;
|
|
619
|
+
blocks.push({
|
|
620
|
+
type: 'codeBlock',
|
|
621
|
+
segments: [{ text: decodeHtmlEntities(codeToken.text) }],
|
|
622
|
+
});
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
case 'list': {
|
|
626
|
+
// Create proper list structure with listItem elements
|
|
627
|
+
const listToken = token;
|
|
628
|
+
const items = [];
|
|
629
|
+
for (const item of listToken.items) {
|
|
630
|
+
const segments = [];
|
|
631
|
+
if (item.tokens) {
|
|
632
|
+
for (const itemToken of item.tokens) {
|
|
633
|
+
if (itemToken.type === 'text' &&
|
|
634
|
+
'tokens' in itemToken &&
|
|
635
|
+
itemToken.tokens) {
|
|
636
|
+
segments.push(...parseInlineTokens(itemToken.tokens));
|
|
637
|
+
}
|
|
638
|
+
else if ('tokens' in itemToken && itemToken.tokens) {
|
|
639
|
+
segments.push(...parseInlineTokens(itemToken.tokens));
|
|
640
|
+
}
|
|
641
|
+
else if ('text' in itemToken) {
|
|
642
|
+
segments.push({ text: decodeHtmlEntities(itemToken.text) });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (segments.length > 0) {
|
|
647
|
+
items.push({ segments: mergeAdjacentSegments(segments) });
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (items.length > 0) {
|
|
651
|
+
blocks.push({
|
|
652
|
+
type: listToken.ordered ? 'orderedList' : 'bulletList',
|
|
653
|
+
items,
|
|
654
|
+
start: listToken.ordered && typeof listToken.start === 'number'
|
|
655
|
+
? listToken.start
|
|
656
|
+
: undefined,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
case 'space': {
|
|
662
|
+
// Skip pure space tokens - they're handled by paragraph breaks
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
case 'text': {
|
|
666
|
+
const textToken = token;
|
|
667
|
+
const segments = [];
|
|
668
|
+
if ('tokens' in textToken && textToken.tokens) {
|
|
669
|
+
segments.push(...parseInlineTokens(textToken.tokens));
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
segments.push({ text: decodeHtmlEntities(textToken.text) });
|
|
673
|
+
}
|
|
674
|
+
if (segments.length > 0) {
|
|
675
|
+
blocks.push({ type: 'paragraph', segments: mergeAdjacentSegments(segments) });
|
|
676
|
+
}
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
default: {
|
|
680
|
+
// Handle any other token types by extracting raw text
|
|
681
|
+
if ('raw' in token && typeof token.raw === 'string') {
|
|
682
|
+
const raw = token.raw.trim();
|
|
683
|
+
if (raw) {
|
|
684
|
+
blocks.push({ type: 'paragraph', segments: [{ text: raw }] });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return blocks;
|
|
692
|
+
}
|
|
693
|
+
//# sourceMappingURL=conversion.js.map
|