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.
Files changed (204) hide show
  1. package/README.md +27 -0
  2. package/dist/cjs/{client → src/client}/index.js +320 -24
  3. package/dist/cjs/src/client/index.js.map +1 -0
  4. package/dist/cjs/src/errors/index.js.map +1 -0
  5. package/dist/cjs/src/index.js.map +1 -0
  6. package/dist/cjs/src/queries/fragments/index.js.map +1 -0
  7. package/dist/cjs/src/queries/fragments/mutation-responses.js.map +1 -0
  8. package/dist/cjs/src/queries/fragments/stream.js.map +1 -0
  9. package/dist/cjs/src/queries/fragments/task.js.map +1 -0
  10. package/dist/cjs/src/queries/index.js.map +1 -0
  11. package/dist/cjs/src/queries/streams/index.js.map +1 -0
  12. package/dist/cjs/src/queries/streams/queries.js.map +1 -0
  13. package/dist/cjs/src/queries/tasks/index.js.map +1 -0
  14. package/dist/cjs/{queries → src/queries}/tasks/mutations.js +74 -1
  15. package/dist/cjs/src/queries/tasks/mutations.js.map +1 -0
  16. package/dist/cjs/src/queries/tasks/queries.js.map +1 -0
  17. package/dist/cjs/src/queries/user/index.js.map +1 -0
  18. package/dist/cjs/{queries → src/queries}/user/queries.js.map +1 -1
  19. package/dist/{esm → cjs/src}/types/api.js.map +1 -1
  20. package/dist/cjs/src/types/client.js.map +1 -0
  21. package/dist/cjs/src/types/common.js.map +1 -0
  22. package/dist/cjs/src/types/index.js.map +1 -0
  23. package/dist/cjs/src/utils/conversion.js +693 -0
  24. package/dist/cjs/src/utils/conversion.js.map +1 -0
  25. package/dist/cjs/src/utils/index.js.map +1 -0
  26. package/dist/cjs/src/utils/validation.js.map +1 -0
  27. package/dist/cjs/vitest.config.js +32 -0
  28. package/dist/cjs/vitest.config.js.map +1 -0
  29. package/dist/esm/{client → src/client}/index.js +322 -26
  30. package/dist/esm/src/client/index.js.map +1 -0
  31. package/dist/esm/src/errors/index.js.map +1 -0
  32. package/dist/esm/src/index.js.map +1 -0
  33. package/dist/esm/src/queries/fragments/index.js.map +1 -0
  34. package/dist/esm/src/queries/fragments/mutation-responses.js.map +1 -0
  35. package/dist/esm/src/queries/fragments/stream.js.map +1 -0
  36. package/dist/esm/src/queries/fragments/task.js.map +1 -0
  37. package/dist/esm/src/queries/index.js.map +1 -0
  38. package/dist/esm/src/queries/streams/index.js.map +1 -0
  39. package/dist/esm/src/queries/streams/queries.js.map +1 -0
  40. package/dist/esm/src/queries/tasks/index.js.map +1 -0
  41. package/dist/esm/{queries → src/queries}/tasks/mutations.js +73 -0
  42. package/dist/esm/src/queries/tasks/mutations.js.map +1 -0
  43. package/dist/esm/src/queries/tasks/queries.js.map +1 -0
  44. package/dist/esm/src/queries/user/index.js.map +1 -0
  45. package/dist/esm/{queries → src/queries}/user/queries.js.map +1 -1
  46. package/dist/{cjs → esm/src}/types/api.js.map +1 -1
  47. package/dist/esm/src/types/client.js.map +1 -0
  48. package/dist/esm/src/types/common.js.map +1 -0
  49. package/dist/esm/src/types/index.js.map +1 -0
  50. package/dist/esm/src/utils/conversion.js +684 -0
  51. package/dist/esm/src/utils/conversion.js.map +1 -0
  52. package/dist/esm/src/utils/index.js.map +1 -0
  53. package/dist/esm/src/utils/validation.js.map +1 -0
  54. package/dist/esm/vitest.config.js +30 -0
  55. package/dist/esm/vitest.config.js.map +1 -0
  56. package/dist/types/{client → src/client}/index.d.ts +117 -0
  57. package/dist/types/src/client/index.d.ts.map +1 -0
  58. package/dist/types/src/errors/index.d.ts.map +1 -0
  59. package/dist/types/src/index.d.ts.map +1 -0
  60. package/dist/types/src/queries/fragments/index.d.ts.map +1 -0
  61. package/dist/types/src/queries/fragments/mutation-responses.d.ts.map +1 -0
  62. package/dist/types/src/queries/fragments/stream.d.ts.map +1 -0
  63. package/dist/types/src/queries/fragments/task.d.ts.map +1 -0
  64. package/dist/types/src/queries/index.d.ts.map +1 -0
  65. package/dist/types/src/queries/streams/index.d.ts.map +1 -0
  66. package/dist/types/src/queries/streams/queries.d.ts.map +1 -0
  67. package/dist/types/src/queries/tasks/index.d.ts.map +1 -0
  68. package/dist/types/{queries → src/queries}/tasks/mutations.d.ts +41 -0
  69. package/dist/types/src/queries/tasks/mutations.d.ts.map +1 -0
  70. package/dist/types/src/queries/tasks/queries.d.ts.map +1 -0
  71. package/dist/types/src/queries/user/index.d.ts.map +1 -0
  72. package/dist/types/src/queries/user/queries.d.ts.map +1 -0
  73. package/dist/types/{types → src/types}/api.d.ts +57 -0
  74. package/dist/types/src/types/api.d.ts.map +1 -0
  75. package/dist/types/src/types/client.d.ts.map +1 -0
  76. package/dist/types/src/types/common.d.ts.map +1 -0
  77. package/dist/types/src/types/index.d.ts.map +1 -0
  78. package/dist/types/{utils → src/utils}/conversion.d.ts +102 -0
  79. package/dist/types/src/utils/conversion.d.ts.map +1 -0
  80. package/dist/types/src/utils/index.d.ts.map +1 -0
  81. package/dist/types/src/utils/validation.d.ts.map +1 -0
  82. package/dist/types/vitest.config.d.ts +3 -0
  83. package/dist/types/vitest.config.d.ts.map +1 -0
  84. package/package.json +1 -1
  85. package/dist/cjs/client/index.js.map +0 -1
  86. package/dist/cjs/errors/index.js.map +0 -1
  87. package/dist/cjs/index.js.map +0 -1
  88. package/dist/cjs/queries/fragments/index.js.map +0 -1
  89. package/dist/cjs/queries/fragments/mutation-responses.js.map +0 -1
  90. package/dist/cjs/queries/fragments/stream.js.map +0 -1
  91. package/dist/cjs/queries/fragments/task.js.map +0 -1
  92. package/dist/cjs/queries/index.js.map +0 -1
  93. package/dist/cjs/queries/streams/index.js.map +0 -1
  94. package/dist/cjs/queries/streams/queries.js.map +0 -1
  95. package/dist/cjs/queries/tasks/index.js.map +0 -1
  96. package/dist/cjs/queries/tasks/mutations.js.map +0 -1
  97. package/dist/cjs/queries/tasks/queries.js.map +0 -1
  98. package/dist/cjs/queries/user/index.js.map +0 -1
  99. package/dist/cjs/types/client.js.map +0 -1
  100. package/dist/cjs/types/common.js.map +0 -1
  101. package/dist/cjs/types/index.js.map +0 -1
  102. package/dist/cjs/utils/conversion.js +0 -236
  103. package/dist/cjs/utils/conversion.js.map +0 -1
  104. package/dist/cjs/utils/index.js.map +0 -1
  105. package/dist/cjs/utils/validation.js.map +0 -1
  106. package/dist/esm/client/index.js.map +0 -1
  107. package/dist/esm/errors/index.js.map +0 -1
  108. package/dist/esm/index.js.map +0 -1
  109. package/dist/esm/queries/fragments/index.js.map +0 -1
  110. package/dist/esm/queries/fragments/mutation-responses.js.map +0 -1
  111. package/dist/esm/queries/fragments/stream.js.map +0 -1
  112. package/dist/esm/queries/fragments/task.js.map +0 -1
  113. package/dist/esm/queries/index.js.map +0 -1
  114. package/dist/esm/queries/streams/index.js.map +0 -1
  115. package/dist/esm/queries/streams/queries.js.map +0 -1
  116. package/dist/esm/queries/tasks/index.js.map +0 -1
  117. package/dist/esm/queries/tasks/mutations.js.map +0 -1
  118. package/dist/esm/queries/tasks/queries.js.map +0 -1
  119. package/dist/esm/queries/user/index.js.map +0 -1
  120. package/dist/esm/types/client.js.map +0 -1
  121. package/dist/esm/types/common.js.map +0 -1
  122. package/dist/esm/types/index.js.map +0 -1
  123. package/dist/esm/utils/conversion.js +0 -229
  124. package/dist/esm/utils/conversion.js.map +0 -1
  125. package/dist/esm/utils/index.js.map +0 -1
  126. package/dist/esm/utils/validation.js.map +0 -1
  127. package/dist/types/client/index.d.ts.map +0 -1
  128. package/dist/types/errors/index.d.ts.map +0 -1
  129. package/dist/types/index.d.ts.map +0 -1
  130. package/dist/types/queries/fragments/index.d.ts.map +0 -1
  131. package/dist/types/queries/fragments/mutation-responses.d.ts.map +0 -1
  132. package/dist/types/queries/fragments/stream.d.ts.map +0 -1
  133. package/dist/types/queries/fragments/task.d.ts.map +0 -1
  134. package/dist/types/queries/index.d.ts.map +0 -1
  135. package/dist/types/queries/streams/index.d.ts.map +0 -1
  136. package/dist/types/queries/streams/queries.d.ts.map +0 -1
  137. package/dist/types/queries/tasks/index.d.ts.map +0 -1
  138. package/dist/types/queries/tasks/mutations.d.ts.map +0 -1
  139. package/dist/types/queries/tasks/queries.d.ts.map +0 -1
  140. package/dist/types/queries/user/index.d.ts.map +0 -1
  141. package/dist/types/queries/user/queries.d.ts.map +0 -1
  142. package/dist/types/types/api.d.ts.map +0 -1
  143. package/dist/types/types/client.d.ts.map +0 -1
  144. package/dist/types/types/common.d.ts.map +0 -1
  145. package/dist/types/types/index.d.ts.map +0 -1
  146. package/dist/types/utils/conversion.d.ts.map +0 -1
  147. package/dist/types/utils/index.d.ts.map +0 -1
  148. package/dist/types/utils/validation.d.ts.map +0 -1
  149. /package/dist/cjs/{errors → src/errors}/index.js +0 -0
  150. /package/dist/cjs/{index.js → src/index.js} +0 -0
  151. /package/dist/cjs/{queries → src/queries}/fragments/index.js +0 -0
  152. /package/dist/cjs/{queries → src/queries}/fragments/mutation-responses.js +0 -0
  153. /package/dist/cjs/{queries → src/queries}/fragments/stream.js +0 -0
  154. /package/dist/cjs/{queries → src/queries}/fragments/task.js +0 -0
  155. /package/dist/cjs/{queries → src/queries}/index.js +0 -0
  156. /package/dist/cjs/{queries → src/queries}/streams/index.js +0 -0
  157. /package/dist/cjs/{queries → src/queries}/streams/queries.js +0 -0
  158. /package/dist/cjs/{queries → src/queries}/tasks/index.js +0 -0
  159. /package/dist/cjs/{queries → src/queries}/tasks/queries.js +0 -0
  160. /package/dist/cjs/{queries → src/queries}/user/index.js +0 -0
  161. /package/dist/cjs/{queries → src/queries}/user/queries.js +0 -0
  162. /package/dist/cjs/{types → src/types}/api.js +0 -0
  163. /package/dist/cjs/{types → src/types}/client.js +0 -0
  164. /package/dist/cjs/{types → src/types}/common.js +0 -0
  165. /package/dist/cjs/{types → src/types}/index.js +0 -0
  166. /package/dist/cjs/{utils → src/utils}/index.js +0 -0
  167. /package/dist/cjs/{utils → src/utils}/validation.js +0 -0
  168. /package/dist/esm/{errors → src/errors}/index.js +0 -0
  169. /package/dist/esm/{index.js → src/index.js} +0 -0
  170. /package/dist/esm/{queries → src/queries}/fragments/index.js +0 -0
  171. /package/dist/esm/{queries → src/queries}/fragments/mutation-responses.js +0 -0
  172. /package/dist/esm/{queries → src/queries}/fragments/stream.js +0 -0
  173. /package/dist/esm/{queries → src/queries}/fragments/task.js +0 -0
  174. /package/dist/esm/{queries → src/queries}/index.js +0 -0
  175. /package/dist/esm/{queries → src/queries}/streams/index.js +0 -0
  176. /package/dist/esm/{queries → src/queries}/streams/queries.js +0 -0
  177. /package/dist/esm/{queries → src/queries}/tasks/index.js +0 -0
  178. /package/dist/esm/{queries → src/queries}/tasks/queries.js +0 -0
  179. /package/dist/esm/{queries → src/queries}/user/index.js +0 -0
  180. /package/dist/esm/{queries → src/queries}/user/queries.js +0 -0
  181. /package/dist/esm/{types → src/types}/api.js +0 -0
  182. /package/dist/esm/{types → src/types}/client.js +0 -0
  183. /package/dist/esm/{types → src/types}/common.js +0 -0
  184. /package/dist/esm/{types → src/types}/index.js +0 -0
  185. /package/dist/esm/{utils → src/utils}/index.js +0 -0
  186. /package/dist/esm/{utils → src/utils}/validation.js +0 -0
  187. /package/dist/types/{errors → src/errors}/index.d.ts +0 -0
  188. /package/dist/types/{index.d.ts → src/index.d.ts} +0 -0
  189. /package/dist/types/{queries → src/queries}/fragments/index.d.ts +0 -0
  190. /package/dist/types/{queries → src/queries}/fragments/mutation-responses.d.ts +0 -0
  191. /package/dist/types/{queries → src/queries}/fragments/stream.d.ts +0 -0
  192. /package/dist/types/{queries → src/queries}/fragments/task.d.ts +0 -0
  193. /package/dist/types/{queries → src/queries}/index.d.ts +0 -0
  194. /package/dist/types/{queries → src/queries}/streams/index.d.ts +0 -0
  195. /package/dist/types/{queries → src/queries}/streams/queries.d.ts +0 -0
  196. /package/dist/types/{queries → src/queries}/tasks/index.d.ts +0 -0
  197. /package/dist/types/{queries → src/queries}/tasks/queries.d.ts +0 -0
  198. /package/dist/types/{queries → src/queries}/user/index.d.ts +0 -0
  199. /package/dist/types/{queries → src/queries}/user/queries.d.ts +0 -0
  200. /package/dist/types/{types → src/types}/client.d.ts +0 -0
  201. /package/dist/types/{types → src/types}/common.d.ts +0 -0
  202. /package/dist/types/{types → src/types}/index.d.ts +0 -0
  203. /package/dist/types/{utils → src/utils}/index.d.ts +0 -0
  204. /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(/&lt;/g, '<')
33
+ .replace(/&gt;/g, '>')
34
+ .replace(/&quot;/g, '"')
35
+ .replace(/&#39;/g, "'")
36
+ .replace(/&#x27;/g, "'")
37
+ .replace(/&apos;/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