@teleporthq/teleport-project-plugin-parse-embed 0.44.0-alpha.0 → 0.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,21 +7,89 @@ interface ParseEmbedPluginConfig {
7
7
  projectType: SUPPORTED_PROJECT_TYPES
8
8
  }
9
9
 
10
+ const SCRIPT_CLOSE_PH = '__THQ_SC__'
11
+
12
+ /**
13
+ * Replace all `</script>` except the last one with a placeholder so
14
+ * hast-util-from-html doesn't prematurely close the script element
15
+ * when the JS code inside contains a nested `</script>` (e.g. in a
16
+ * template-literal string).
17
+ */
18
+ function escapeNestedScriptClose(html: string): string {
19
+ const regex = /<\/script\s*>/gi
20
+ const indices: Array<{ idx: number; len: number }> = []
21
+ let m = regex.exec(html)
22
+ while (m !== null) {
23
+ indices.push({ idx: m.index, len: m[0].length })
24
+ m = regex.exec(html)
25
+ }
26
+ if (indices.length <= 1) {
27
+ return html
28
+ }
29
+
30
+ let result = ''
31
+ let lastEnd = 0
32
+ for (let i = 0; i < indices.length - 1; i++) {
33
+ result += html.substring(lastEnd, indices[i].idx)
34
+ result += SCRIPT_CLOSE_PH
35
+ lastEnd = indices[i].idx + indices[i].len
36
+ }
37
+ result += html.substring(lastEnd)
38
+ return result
39
+ }
40
+
41
+ /**
42
+ * hast-util-to-jsx-inline-script wraps each <script> body in an outer {`…`}
43
+ * template literal with NO escaping of its own (its <style> branch goes through
44
+ * JSON.stringify; the script branch never got that treatment). A raw backtick
45
+ * or `${` in the body therefore terminates/interpolates the OUTER literal and
46
+ * the generated component does not compile.
47
+ *
48
+ * Escape both here, parity-aware and idempotent — the same algorithm as
49
+ * teleport-plugin-common's addRawAttributeToJSXTag (the non-advanced-embeds
50
+ * React path): a backtick behind an ODD number of backslashes is already
51
+ * escaped and is left alone, so content that editors pre-escaped keeps exactly
52
+ * one level of escaping, while raw content (the canonical storage form) gains
53
+ * the one level it needs. Runs on the placeholder form produced by
54
+ * escapeNestedScriptClose, so an inner `</script>` cannot end the match early.
55
+ *
56
+ * Exported for tests.
57
+ */
58
+ export function escapeScriptBodiesForJsxTemplateLiteral(html: string): string {
59
+ return html.replace(
60
+ /(<script\b[^>]*>)([\s\S]*?)(<\/script\s*>)/gi,
61
+ (_fullMatch: string, openTag: string, body: string, closeTag: string) =>
62
+ openTag +
63
+ body
64
+ .replace(/\\*`/g, (match: string) => {
65
+ const backslashCount = match.length - 1
66
+ return backslashCount % 2 === 0 ? match.slice(0, backslashCount) + '\\`' : match
67
+ })
68
+ .replace(/\\*\$\{/g, (match: string) => {
69
+ const backslashCount = match.length - 2
70
+ return backslashCount % 2 === 0 ? match.slice(0, backslashCount) + '\\${' : match
71
+ }) +
72
+ closeTag
73
+ )
74
+ }
75
+
10
76
  const NODE_MAPPER: Record<
11
77
  SUPPORTED_PROJECT_TYPES,
12
78
  Promise<(content: unknown, options: unknown) => string>
13
79
  > = {
14
- html: import('hast-util-to-html').then((mod) => mod.toHtml),
15
- jsx: import('hast-util-to-jsx-inline-script').then((mod) => mod.default),
16
80
  'teleport-project-html': import('hast-util-to-html').then((mod) => mod.toHtml),
81
+ 'teleport-project-angular': import('hast-util-to-html').then((mod) => mod.toHtml),
82
+ 'teleport-project-vue': import('hast-util-to-html').then((mod) => mod.toHtml),
83
+ 'teleport-project-nuxt': import('hast-util-to-html').then((mod) => mod.toHtml),
17
84
  'teleport-project-react': import('hast-util-to-jsx-inline-script').then((mod) => mod.default),
18
85
  'teleport-project-next': import('hast-util-to-jsx-inline-script').then((mod) => mod.default),
19
86
  }
20
87
 
21
88
  const COMPONENT_CHUNK_NAMES: Record<SUPPORTED_PROJECT_TYPES, string> = {
22
- html: 'html-chunk',
23
- jsx: 'jsx-component',
24
89
  'teleport-project-html': 'html-chunk',
90
+ 'teleport-project-angular': 'template-chunk',
91
+ 'teleport-project-vue': 'template-chunk',
92
+ 'teleport-project-nuxt': 'template-chunk',
25
93
  'teleport-project-next': 'jsx-component',
26
94
  'teleport-project-react': 'jsx-component',
27
95
  }
@@ -51,12 +119,50 @@ export const createParseEmbedPlugin: ComponentPluginFactory<ParseEmbedPluginConf
51
119
  (elementType === 'dangerous-html' || element.dependency?.path === 'dangerous-html') &&
52
120
  attrs?.html
53
121
  ) {
54
- const hastNodes = fromHtml(element.attrs.html.content as string, {
122
+ if (attrs.html.type === 'raw' && (attrs.html as any).dynamic) {
123
+ return
124
+ }
125
+
126
+ let rawHtmlContent = element.attrs.html.content as string
127
+
128
+ if (
129
+ projectType === 'teleport-project-html' ||
130
+ projectType === 'teleport-project-angular' ||
131
+ projectType === 'teleport-project-vue' ||
132
+ projectType === 'teleport-project-nuxt'
133
+ ) {
134
+ // Strip pre-escaped backticks (\` → `) for HTML/template frameworks.
135
+ // In these contexts backticks are just regular characters and the
136
+ // escaping would be a JavaScript syntax error inside <script> tags.
137
+ rawHtmlContent = rawHtmlContent.replace(/\\`/g, '`')
138
+ }
139
+ // Escape nested </script> tags so hast-util-from-html doesn't truncate
140
+ // script content at an inner </script> (e.g. one inside a template literal).
141
+ rawHtmlContent = escapeNestedScriptClose(rawHtmlContent)
142
+
143
+ if (
144
+ projectType === 'teleport-project-react' ||
145
+ projectType === 'teleport-project-next'
146
+ ) {
147
+ // For React/Next the fork wraps script bodies in an outer {`…`}
148
+ // template literal — make them safe for it ourselves. Parity-aware:
149
+ // already-escaped input (the GUI mapper pre-escapes today) passes
150
+ // through unchanged, raw input gets escaped, so this is safe on both
151
+ // sides of the GUI dropping its pre-escape step.
152
+ rawHtmlContent = escapeScriptBodiesForJsxTemplateLiteral(rawHtmlContent)
153
+ }
154
+
155
+ const hastNodes = fromHtml(rawHtmlContent, {
55
156
  fragment: true,
56
157
  })
57
158
  const content = hastToJsxOrHtml(hastNodes, { wrapper: 'fragment' })
58
159
 
59
- if (projectType === 'teleport-project-html' || projectType === 'html') {
160
+ if (
161
+ projectType === 'teleport-project-html' ||
162
+ projectType === 'teleport-project-angular' ||
163
+ projectType === 'teleport-project-vue' ||
164
+ projectType === 'teleport-project-nuxt'
165
+ ) {
60
166
  const node = compontnChunk.meta.nodesLookup[key] as HastNode
61
167
  if (!node) {
62
168
  return
@@ -67,7 +173,10 @@ export const createParseEmbedPlugin: ComponentPluginFactory<ParseEmbedPluginConf
67
173
 
68
174
  Object.assign(node, {
69
175
  type: 'text',
70
- value: content,
176
+ // Restore nested </script> with <\/script> — the backslash prevents
177
+ // the browser's HTML parser from closing the script tag prematurely,
178
+ // and \/ evaluates to / in JavaScript at runtime.
179
+ value: content.replace(new RegExp(SCRIPT_CLOSE_PH, 'g'), '<\\/script>'),
71
180
  })
72
181
 
73
182
  delete node.children
@@ -83,7 +192,13 @@ export const createParseEmbedPlugin: ComponentPluginFactory<ParseEmbedPluginConf
83
192
  node.openingElement.attributes = []
84
193
  node.children.push({
85
194
  type: 'JSXText' as const,
86
- value: content,
195
+ // Double-escape </script> for the outer template literal {`…`}:
196
+ // file: <\\/script>
197
+ // outer `` eval: <\/script> (\\→\, HTML parser won't close)
198
+ // inner `` eval: </script> (\/→/, correct runtime value)
199
+ value: content
200
+ .replace(new RegExp(SCRIPT_CLOSE_PH, 'g'), '<\\\\/script>')
201
+ .replace(/<\\\/script/g, '<\\\\/script'),
87
202
  } as JSXText)
88
203
  }
89
204
  }
package/src/utils.ts CHANGED
@@ -4,8 +4,9 @@ export type SUPPORTED_PROJECT_TYPES =
4
4
  | 'teleport-project-html'
5
5
  | 'teleport-project-react'
6
6
  | 'teleport-project-next'
7
- | 'html'
8
- | 'jsx'
7
+ | 'teleport-project-angular'
8
+ | 'teleport-project-vue'
9
+ | 'teleport-project-nuxt'
9
10
 
10
11
  export const JS_EXECUTION_DEPENDENCIES: Record<string, UIDLExternalDependency> = {
11
12
  'teleport-project-react': {