@upstart.gg/vite-plugins 0.1.22 → 0.1.24
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/vite-plugin-upstart-attrs.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-attrs.js +28 -10
- package/dist/vite-plugin-upstart-attrs.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.js +10 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +10 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/tests/vite-plugin-upstart-attrs.test.ts +41 -0
- package/src/vite-plugin-upstart-attrs.ts +46 -11
- package/src/vite-plugin-upstart-editor/runtime/index.ts +4 -0
- package/src/vite-plugin-upstart-editor/runtime/types.ts +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin-upstart-attrs.d.ts","names":[],"sources":["../src/vite-plugin-upstart-attrs.ts"],"mappings":";;;;UAeU,OAAA;EACR,OAAA;EACA,YAAA;AAAA;AAAA,UAwCe,aAAA;EACf,IAAA;EACA,IAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,OAAA;IAAW,SAAA;EAAA;AAAA;AAAA,iBAcG,WAAA,CAAA,GAAe,MAAA,SAAe,aAAA;AAAA,iBAK9B,aAAA,CAAA;AAAA,cAeH,aAAA,EAAa,QAAA,CAAA,gBAAA,CAAA,OAAA;AAAA,iBAoEV,gBAAA,CAAiB,IAAA,UAAc,QAAA;;OAAf,aAAA,CAAA,SAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"vite-plugin-upstart-attrs.d.ts","names":[],"sources":["../src/vite-plugin-upstart-attrs.ts"],"mappings":";;;;UAeU,OAAA;EACR,OAAA;EACA,YAAA;AAAA;AAAA,UAwCe,aAAA;EACf,IAAA;EACA,IAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,OAAA;IAAW,SAAA;EAAA;AAAA;AAAA,iBAcG,WAAA,CAAA,GAAe,MAAA,SAAe,aAAA;AAAA,iBAK9B,aAAA,CAAA;AAAA,cAeH,aAAA,EAAa,QAAA,CAAA,gBAAA,CAAA,OAAA;AAAA,iBAoEV,gBAAA,CAAiB,IAAA,UAAc,QAAA;;OAAf,aAAA,CAAA,SAAA;AAAA;AAAA,cAmP/B,QAAA"}
|
|
@@ -86,9 +86,9 @@ function transformWithOxc(code, filePath) {
|
|
|
86
86
|
for (const decl of node.declarations) if (decl.type === "VariableDeclarator" && decl.id?.type === "Identifier" && decl.init?.type === "Literal" && typeof decl.init.value === "string") state.constants.set(decl.id.name, decl.init.value);
|
|
87
87
|
}
|
|
88
88
|
if (node.type === "CallExpression") {
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
state.loopStack.push(
|
|
89
|
+
const mapCallInfo = detectAndPatchMapCall(node, state);
|
|
90
|
+
if (mapCallInfo) {
|
|
91
|
+
state.loopStack.push(mapCallInfo);
|
|
92
92
|
next();
|
|
93
93
|
state.loopStack.pop();
|
|
94
94
|
return;
|
|
@@ -106,9 +106,9 @@ function transformWithOxc(code, filePath) {
|
|
|
106
106
|
const attributes = [];
|
|
107
107
|
if (hasRange(jsxNode)) {
|
|
108
108
|
const hash = hashContent(state.code.slice(jsxNode.start, jsxNode.end));
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
const suffix =
|
|
109
|
+
const loopSuffixes = state.loopStack.map((l) => l.indexName ? `\${${l.indexName}}` : "0");
|
|
110
|
+
if (loopSuffixes.length > 0) {
|
|
111
|
+
const suffix = loopSuffixes.join("-");
|
|
112
112
|
attributes.push(`data-upstart-hash={\`${hash}-${suffix}\`}`);
|
|
113
113
|
} else attributes.push(`data-upstart-hash="${hash}"`);
|
|
114
114
|
}
|
|
@@ -265,7 +265,7 @@ function exprToString(expr, code) {
|
|
|
265
265
|
}
|
|
266
266
|
return "";
|
|
267
267
|
}
|
|
268
|
-
function
|
|
268
|
+
function detectAndPatchMapCall(node, state) {
|
|
269
269
|
if (node.callee.type !== "MemberExpression" || node.callee.property.type !== "Identifier" || node.callee.property.name !== "map") return null;
|
|
270
270
|
const callback = node.arguments[0];
|
|
271
271
|
if (!callback) return null;
|
|
@@ -274,10 +274,28 @@ function detectMapCall(node, code) {
|
|
|
274
274
|
const itemParam = params[0];
|
|
275
275
|
const indexParam = params[1];
|
|
276
276
|
if (!itemParam) return null;
|
|
277
|
+
const itemName = itemParam.type === "Identifier" ? itemParam.name : "item";
|
|
278
|
+
let indexName = indexParam?.type === "Identifier" ? indexParam.name : null;
|
|
279
|
+
if (!indexName && callback.type === "ArrowFunctionExpression") {
|
|
280
|
+
indexName = "__i";
|
|
281
|
+
const lastParam = params[params.length - 1];
|
|
282
|
+
if (!hasRange(lastParam)) {
|
|
283
|
+
indexName = null;
|
|
284
|
+
return {
|
|
285
|
+
itemName,
|
|
286
|
+
indexName,
|
|
287
|
+
arrayExpr: exprToString(node.callee.object, state.code)
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
let searchPos = lastParam.end;
|
|
291
|
+
while (searchPos < state.code.length && state.code[searchPos] !== ")") searchPos++;
|
|
292
|
+
if (searchPos < state.code.length && state.code[searchPos] === ")") state.s.appendLeft(searchPos, ", __i");
|
|
293
|
+
}
|
|
294
|
+
const arrayExpr = exprToString(node.callee.object, state.code);
|
|
277
295
|
return {
|
|
278
|
-
itemName
|
|
279
|
-
indexName
|
|
280
|
-
arrayExpr
|
|
296
|
+
itemName,
|
|
297
|
+
indexName,
|
|
298
|
+
arrayExpr
|
|
281
299
|
};
|
|
282
300
|
}
|
|
283
301
|
function escapeProp(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin-upstart-attrs.js","names":[],"sources":["../src/vite-plugin-upstart-attrs.ts"],"sourcesContent":["import { createUnplugin } from \"unplugin\";\nimport { parseSync } from \"oxc-parser\";\nimport { walk } from \"zimmerframe\";\nimport MagicString from \"magic-string\";\nimport path from \"path\";\nimport type {\n Program,\n Node,\n JSXElement,\n JSXOpeningElement,\n JSXAttribute,\n Expression,\n CallExpression,\n} from \"estree-jsx\";\n\ninterface Options {\n enabled: boolean;\n emitRegistry?: boolean;\n}\n\ntype NodeWithRange = Node & { start: number; end: number };\n\nfunction hasRange(node: any): node is NodeWithRange {\n return node && typeof node.start === \"number\" && typeof node.end === \"number\";\n}\n\n// Fast, stable hash function (djb2 variant)\n// Produces a short hex string that's stable across rebuilds\nfunction hashContent(content: string): string {\n let hash = 5381;\n for (let i = 0; i < content.length; i++) {\n hash = ((hash << 5) + hash) ^ content.charCodeAt(i);\n }\n // Convert to unsigned 32-bit and then to hex\n return (hash >>> 0).toString(16);\n}\n\ninterface LoopContext {\n itemName: string;\n indexName: string | null;\n arrayExpr: string;\n}\n\ninterface I18nKeyInfo {\n /** The resolved key with namespace, e.g. \"dashboard:features.title\" */\n fullKey: string;\n /** Just the translation key, e.g. \"features.title\" */\n key: string;\n /** The namespace, e.g. \"dashboard\" */\n namespace: string;\n /** Source expression for i18nKey when dynamic (e.g. \"stat.labelKey\"), null when static */\n keyExpr: string | null;\n /** Source expression for namespace when dynamic, null when static */\n nsExpr: string | null;\n}\n\n// Registry entry for editable elements (text and className)\nexport interface EditableEntry {\n file: string;\n type: \"text\" | \"className\";\n startOffset: number;\n endOffset: number;\n originalContent: string;\n context: { parentTag: string };\n}\n\n// Module-level registry (collected across all files during build)\nconst editableRegistry = new Map<string, EditableEntry>();\n\n// Generate stable ID for editable elements\n// Replace the counter-based ID generation with position-based\nfunction generateId(filePath: string, node: NodeWithRange): string {\n // Use file path + start position for a stable, deterministic ID\n return `${filePath}:${node.start}`;\n}\n\n// Export for testing - get a copy of the current registry\nexport function getRegistry(): Record<string, EditableEntry> {\n return Object.fromEntries(editableRegistry);\n}\n\n// Export for testing - clear registry and counters\nexport function clearRegistry(): void {\n editableRegistry.clear();\n}\n\ninterface TransformState {\n filePath: string;\n code: string;\n s: MagicString;\n loopStack: LoopContext[];\n /** Tracks const varName = \"literal\" declarations for resolving dynamic i18nKey expressions */\n constants: Map<string, string>;\n /** Maps t-function variable names to their default namespace, e.g. { \"t\": \"dashboard\" } */\n tFunctions: Map<string, string>;\n}\n\nexport const upstartEditor = createUnplugin<Options>((options) => {\n if (!options.enabled) {\n return { name: \"upstart-editor-disabled\" };\n }\n\n const emitRegistry = options.emitRegistry ?? true;\n let root = process.cwd();\n\n return {\n name: \"upstart-editor\",\n enforce: \"pre\",\n\n vite: {\n configResolved(config) {\n root = config.root;\n },\n generateBundle() {\n if (!emitRegistry || editableRegistry.size === 0) {\n return;\n }\n\n const registry = {\n version: 1,\n generatedAt: new Date().toISOString(),\n elements: Object.fromEntries(editableRegistry),\n };\n\n this.emitFile({\n type: \"asset\",\n fileName: \"upstart-registry.json\",\n source: JSON.stringify(registry, null, 2),\n });\n\n // Clear for next build\n editableRegistry.clear();\n },\n },\n\n transformInclude(id) {\n return /\\.(tsx|jsx)$/.test(id) && !id.includes(\"node_modules\");\n },\n\n transform(code, id) {\n // Fast path: skip files without JSX\n if (!code.includes(\"<\")) {\n return null;\n }\n\n try {\n const relativePath = path.relative(root, id);\n const result = transformWithOxc(code, relativePath);\n\n if (!result) {\n return null;\n }\n\n return {\n code: result.code,\n map: result.map,\n };\n } catch (error) {\n console.error(`Error transforming ${id}:`, error);\n return null;\n }\n },\n };\n});\n\nexport function transformWithOxc(code: string, filePath: string) {\n // Parse with oxc (super fast!)\n const ast = parseSync(filePath, code, {\n sourceType: \"module\",\n });\n\n if (!ast.program) {\n return null;\n }\n\n const s = new MagicString(code);\n const state: TransformState = {\n filePath,\n code,\n s,\n loopStack: [],\n constants: new Map(),\n tFunctions: new Map(),\n };\n\n let modified = false;\n\n // Use zimmerframe to walk and transform the AST\n walk(ast.program as Program, state, {\n _(node: Node, { state, next }: { state: TransformState; next: () => void }) {\n // Track useTranslation() calls to resolve t() function namespaces\n if (node.type === \"VariableDeclaration\") {\n trackUseTranslation(node, state);\n\n // Track const varName = \"literal\" for resolving dynamic i18nKey expressions\n for (const decl of (node as any).declarations) {\n if (\n decl.type === \"VariableDeclarator\" &&\n decl.id?.type === \"Identifier\" &&\n decl.init?.type === \"Literal\" &&\n typeof decl.init.value === \"string\"\n ) {\n state.constants.set(decl.id.name, decl.init.value);\n }\n }\n }\n\n // Handle .map() calls to track loops (must be before JSXElement)\n if (node.type === \"CallExpression\") {\n const loopInfo = detectMapCall(node as CallExpression, state.code);\n\n if (loopInfo) {\n // Push loop context\n state.loopStack.push(loopInfo);\n\n // Continue walking into the callback\n next();\n\n // Pop loop context after traversing\n state.loopStack.pop();\n return;\n }\n }\n\n // Handle JSX elements\n if (node.type === \"JSXElement\") {\n const jsxNode = node as JSXElement;\n const opening = jsxNode.openingElement;\n const tagName = getJSXElementName(opening);\n\n // Skip ALL attribute injection for <Trans> elements.\n // <Trans> renders to a text node at runtime, so DOM attributes are lost.\n // The i18n attributes are promoted to the parent element instead.\n if (tagName === \"Trans\") {\n next();\n return;\n }\n\n const insertPos = getAttributeInsertPosition(opening, state.code);\n\n // Track attributes to inject\n const attributes: string[] = [];\n\n // Compute stable hash from the element's source code (includes all children)\n // This hash changes if ANY part of the element or its children change\n if (hasRange(jsxNode)) {\n const elementSource = state.code.slice(jsxNode.start, jsxNode.end);\n const hash = hashContent(elementSource);\n\n // Collect index variables from all enclosing loops so elements\n // inside .map() get a unique hash per iteration at runtime.\n const loopIndices = state.loopStack.map((l) => l.indexName).filter((n): n is string => n !== null);\n\n if (loopIndices.length > 0) {\n const suffix = loopIndices.map((n) => `\\${${n}}`).join(\"-\");\n attributes.push(`data-upstart-hash={\\`${hash}-${suffix}\\`}`);\n } else {\n attributes.push(`data-upstart-hash=\"${hash}\"`);\n }\n }\n\n // --- Editable text detection ---\n // Priority: i18n (true) > non-i18n text (false) > no text (no attribute)\n\n // Step 1: Check for Trans children AND t() calls (editable via i18n, gets \"true\")\n const transChildren = findTransInChildren(jsxNode, state.code, state.constants);\n const tCallChildren = findTCallsInChildren(jsxNode, state.code, state);\n const allI18nKeys = [...transChildren, ...tCallChildren];\n const hasI18n = allI18nKeys.length > 0;\n\n if (hasI18n) {\n attributes.push('data-upstart-editable-text=\"true\"');\n attributes.push('data-upstart-editable-text-mode=\"plain\"');\n\n const hasDynamic = allI18nKeys.some((t) => t.keyExpr || t.nsExpr);\n if (hasDynamic) {\n // Build a runtime JSX template expression for dynamic i18n keys\n const parts = allI18nKeys.map((t) => {\n const nsPart = t.nsExpr ? `\\${${t.nsExpr}}` : t.namespace;\n const keyPart = t.keyExpr ? `\\${${t.keyExpr}}` : t.key;\n return `${nsPart}:${keyPart}`;\n });\n attributes.push(`data-upstart-i18n={\\`${parts.join(\",\")}\\`}`);\n } else {\n const keys = allI18nKeys.map((t: I18nKeyInfo) => escapeProp(t.fullKey)).join(\",\");\n attributes.push(`data-upstart-i18n=\"${keys}\"`);\n }\n }\n\n // Step 2: Text leaf elements — registry tracking + non-editable flag\n if (isTextLeafElement(jsxNode)) {\n if (!hasI18n) {\n attributes.push('data-upstart-editable-text=\"false\"');\n }\n\n // Find the actual JSXText node to track its location\n const textChild = jsxNode.children.find((c) => c.type === \"JSXText\" && (c as any).value?.trim());\n\n if (textChild && hasRange(textChild)) {\n const id = generateId(state.filePath, textChild);\n const textValue = (textChild as any).value as string;\n\n // Calculate trimmed offsets (exclude leading/trailing whitespace)\n const trimmedStart = textChild.start + (textValue.length - textValue.trimStart().length);\n const trimmedEnd = textChild.end - (textValue.length - textValue.trimEnd().length);\n\n editableRegistry.set(id, {\n file: state.filePath,\n type: \"text\",\n startOffset: trimmedStart,\n endOffset: trimmedEnd,\n originalContent: textValue.trim(),\n context: { parentTag: tagName || \"unknown\" },\n });\n\n attributes.push(`data-upstart-id=\"${id}\"`);\n }\n }\n // Step 3: Non-leaf elements with visible text content (expressions, mixed content)\n else if (!hasI18n && hasVisibleTextContent(jsxNode)) {\n attributes.push('data-upstart-editable-text=\"false\"');\n }\n\n // Track className attribute if it's a string literal\n const classNameAttr = opening.attributes.find(\n (attr): attr is JSXAttribute =>\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n attr.name.name === \"className\" &&\n attr.value?.type === \"Literal\" &&\n typeof (attr.value as any).value === \"string\",\n );\n\n if (classNameAttr && classNameAttr.value && hasRange(classNameAttr.value)) {\n const id = generateId(state.filePath, classNameAttr.value);\n const classValue = (classNameAttr.value as any).value as string;\n\n // +1 and -1 to exclude the quotes\n editableRegistry.set(id, {\n file: state.filePath,\n type: \"className\",\n startOffset: classNameAttr.value.start + 1,\n endOffset: classNameAttr.value.end - 1,\n originalContent: classValue,\n context: { parentTag: tagName || \"unknown\" },\n });\n\n attributes.push(`data-upstart-classname-id=\"${id}\"`);\n }\n\n // Process PascalCase components for additional tracking\n if (tagName && /^[A-Z]/.test(tagName)) {\n // File and component tracking\n attributes.push(`data-upstart-file=\"${escapeProp(state.filePath)}\"`);\n attributes.push(`data-upstart-component=\"${escapeProp(tagName)}\"`);\n\n // Loop context tracking\n if (state.loopStack.length > 0) {\n const loop = state.loopStack[state.loopStack.length - 1];\n attributes.push(`data-upstart-loop-item=\"${escapeProp(loop.itemName)}\"`);\n if (loop.indexName) {\n attributes.push(`data-upstart-loop-index={${loop.indexName}}`);\n }\n attributes.push(`data-upstart-loop-array=\"${escapeProp(loop.arrayExpr)}\"`);\n }\n\n // Analyze each prop for data bindings\n for (const attr of opening.attributes) {\n if (attr.type !== \"JSXAttribute\" || attr.name.type !== \"JSXIdentifier\") {\n continue;\n }\n\n const propName = attr.name.name;\n const binding = analyzeBinding(attr.value, state.code);\n\n if (binding) {\n attributes.push(`data-upstart-prop-${propName.toLowerCase()}=\"${escapeProp(binding.path)}\"`);\n\n // Track conditional expressions\n if (binding.isConditional) {\n attributes.push(`data-upstart-conditional-${propName.toLowerCase()}=\"true\"`);\n }\n }\n }\n }\n\n // Inject attributes if any\n if (insertPos !== -1 && attributes.length > 0) {\n const attrString = \" \" + attributes.join(\" \");\n state.s.appendLeft(insertPos, attrString);\n modified = true;\n }\n }\n\n next();\n },\n });\n\n if (!modified) {\n return null;\n }\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: true }),\n };\n}\n\n// Helper: Get JSX element name\nfunction getJSXElementName(opening: JSXOpeningElement): string | null {\n if (opening.name.type === \"JSXIdentifier\") {\n return opening.name.name;\n }\n\n // Handle JSXMemberExpression like <Foo.Bar>\n if (opening.name.type === \"JSXMemberExpression\") {\n let current = opening.name;\n while (current.property) {\n if (current.property.type === \"JSXIdentifier\") {\n return current.property.name;\n }\n if (current.type === \"JSXMemberExpression\") {\n current = current.property as any;\n } else {\n break;\n }\n }\n }\n\n return null;\n}\n\n// Helper: Find where to insert attributes in JSX opening tag\nfunction getAttributeInsertPosition(opening: JSXOpeningElement, code: string): number {\n // If there are existing attributes, insert before the first one\n if (opening.attributes.length > 0) {\n const firstAttr = opening.attributes[0];\n if (hasRange(firstAttr)) {\n return firstAttr.start;\n }\n }\n\n // Otherwise, insert after the tag name\n if (opening.name.type === \"JSXIdentifier\" && hasRange(opening.name)) {\n return opening.name.end;\n }\n\n if (opening.name.type === \"JSXMemberExpression\" && hasRange(opening.name)) {\n return opening.name.end;\n }\n\n return -1;\n}\n\n// Helper: Analyze a prop value to extract data binding\nfunction analyzeBinding(\n value: JSXAttribute[\"value\"],\n code: string,\n): {\n path: string;\n isConditional?: boolean;\n} | null {\n if (!value) {\n return null;\n }\n\n if (value.type === \"JSXExpressionContainer\") {\n const expr = value.expression;\n\n if (expr.type === \"JSXEmptyExpression\") {\n return null;\n }\n\n return analyzeExpression(expr, code);\n }\n\n return null;\n}\n\n// Helper: Analyze any expression to extract binding info\nfunction analyzeExpression(\n expr: Expression,\n code: string,\n): {\n path: string;\n isConditional?: boolean;\n} | null {\n // Handle member expressions: user.name, product.price, etc.\n if (expr.type === \"MemberExpression\") {\n const path = exprToString(expr, code);\n return { path };\n }\n\n // Handle identifiers: userName, price, etc.\n if (expr.type === \"Identifier\") {\n return { path: expr.name };\n }\n\n // Handle conditional expressions: condition ? value1 : value2\n if (expr.type === \"ConditionalExpression\") {\n // Try to extract from the consequent\n const consequent = analyzeExpression(expr.consequent, code);\n if (consequent) {\n return {\n ...consequent,\n isConditional: true,\n path: exprToString(expr, code),\n };\n }\n }\n\n // Handle logical expressions: value1 && value2, value1 || value2\n if (expr.type === \"LogicalExpression\") {\n const right = analyzeExpression(expr.right, code);\n if (right) {\n return {\n ...right,\n isConditional: true,\n path: exprToString(expr, code),\n };\n }\n }\n\n // For other complex expressions, just return the path\n const exprWithRange = expr as any;\n if (exprWithRange.start !== undefined && exprWithRange.end !== undefined) {\n return {\n path: code.slice(exprWithRange.start, exprWithRange.end),\n };\n }\n\n return null;\n}\n\n// Helper: Convert expression AST to string\nfunction exprToString(expr: Expression, code: string): string {\n // Use the source range to get the actual code\n const exprWithRange = expr as any;\n if (exprWithRange.start !== undefined && exprWithRange.end !== undefined) {\n return code.slice(exprWithRange.start, exprWithRange.end);\n }\n\n // Fallback: reconstruct from AST\n if (expr.type === \"Identifier\") {\n return expr.name;\n }\n\n if (expr.type === \"MemberExpression\") {\n const obj = exprToString(expr.object as Expression, code);\n const prop =\n expr.property.type === \"Identifier\" && !expr.computed\n ? expr.property.name\n : exprToString(expr.property as Expression, code);\n return expr.computed ? `${obj}[${prop}]` : `${obj}.${prop}`;\n }\n\n if (expr.type === \"ConditionalExpression\") {\n const test = exprToString(expr.test, code);\n const consequent = exprToString(expr.consequent, code);\n const alternate = exprToString(expr.alternate, code);\n return `${test} ? ${consequent} : ${alternate}`;\n }\n\n if (expr.type === \"LogicalExpression\") {\n const left = exprToString(expr.left, code);\n const right = exprToString(expr.right, code);\n return `${left} ${expr.operator} ${right}`;\n }\n\n return \"\";\n}\n\n// Helper: Detect .map() calls and extract loop context\nfunction detectMapCall(node: CallExpression, code: string): LoopContext | null {\n // Check if this is a .map() call\n if (\n node.callee.type !== \"MemberExpression\" ||\n node.callee.property.type !== \"Identifier\" ||\n node.callee.property.name !== \"map\"\n ) {\n return null;\n }\n\n const callback = node.arguments[0];\n\n if (!callback) {\n return null;\n }\n\n // Check if callback is an arrow function or function expression\n if (callback.type !== \"ArrowFunctionExpression\" && callback.type !== \"FunctionExpression\") {\n return null;\n }\n\n const params = callback.params;\n const itemParam = params[0];\n const indexParam = params[1];\n\n if (!itemParam) {\n return null;\n }\n\n // Extract parameter names\n const itemName = itemParam.type === \"Identifier\" ? itemParam.name : \"item\";\n const indexName = indexParam?.type === \"Identifier\" ? indexParam.name : null;\n\n // Get the array expression\n const arrayExpr = exprToString(node.callee.object as Expression, code);\n\n return {\n itemName,\n indexName,\n arrayExpr,\n };\n}\n\n// Helper: Escape prop values for JSX attributes\nfunction escapeProp(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\n// Helper: Track useTranslation() calls to map t-function variable names to their namespace\nfunction trackUseTranslation(node: Node, state: TransformState): void {\n if (node.type !== \"VariableDeclaration\") return;\n\n const decl = node as any;\n for (const declarator of decl.declarations) {\n if (\n declarator.id?.type !== \"ObjectPattern\" ||\n !declarator.init ||\n declarator.init.type !== \"CallExpression\"\n ) {\n continue;\n }\n\n const callExpr = declarator.init;\n if (callExpr.callee?.type !== \"Identifier\" || callExpr.callee.name !== \"useTranslation\") {\n continue;\n }\n\n const namespace = extractUseTranslationNamespace(callExpr);\n\n for (const prop of declarator.id.properties) {\n if (prop.type !== \"Property\") continue;\n if (prop.key?.type !== \"Identifier\" || prop.key.name !== \"t\") continue;\n\n // Handle aliased destructuring: { t: translate } → maps \"translate\" to namespace\n const localName = prop.value?.type === \"Identifier\" ? prop.value.name : \"t\";\n state.tFunctions.set(localName, namespace);\n }\n }\n}\n\n// Helper: Extract namespace from useTranslation() call arguments\nfunction extractUseTranslationNamespace(callExpr: any): string {\n const firstArg = callExpr.arguments?.[0];\n if (!firstArg) return \"translation\";\n\n // String literal: useTranslation(\"dashboard\")\n if (firstArg.type === \"Literal\" && typeof firstArg.value === \"string\") {\n return firstArg.value;\n }\n\n // Array expression: useTranslation([\"dashboard\", \"common\"])\n if (firstArg.type === \"ArrayExpression\" && firstArg.elements?.length > 0) {\n const first = firstArg.elements[0];\n if (first?.type === \"Literal\" && typeof first.value === \"string\") {\n return first.value;\n }\n }\n\n return \"translation\";\n}\n\n// Helper: Resolve a JSX attribute value to a string.\n// Handles string literals directly, and JSXExpressionContainer by resolving\n// Identifier references via the constants map, or returning the expression source for dynamic values.\nfunction resolveJSXAttrValue(\n value: any,\n code: string,\n constants: Map<string, string>,\n): { value: string; expr: string | null } | null {\n if (!value) return null;\n\n if (value.type === \"Literal\" && typeof value.value === \"string\") {\n return { value: value.value, expr: null };\n }\n\n if (value.type === \"JSXExpressionContainer\") {\n const expression = value.expression;\n if (!expression) return null;\n\n // Resolve simple identifiers via constants map (e.g. i18nKey={labelKey} where const labelKey = \"...\")\n if (expression.type === \"Identifier\" && constants.has(expression.name)) {\n return { value: constants.get(expression.name)!, expr: null };\n }\n\n // Dynamic expression — return source text so caller can emit a runtime JSX expression\n if (hasRange(expression)) {\n const src = code.slice(expression.start, expression.end);\n return { value: src, expr: src };\n }\n }\n\n return null;\n}\n\n// Helper: Detect <Trans i18nKey=\"...\" /> component and extract i18n key info\nfunction detectTransComponent(\n jsxElement: JSXElement,\n code: string,\n constants: Map<string, string>,\n): I18nKeyInfo | null {\n const opening = jsxElement.openingElement;\n const tagName = getJSXElementName(opening);\n\n if (tagName !== \"Trans\") return null;\n\n let keyResult: { value: string; expr: string | null } | null = null;\n let nsResult: { value: string; expr: string | null } | null = null;\n\n for (const attr of opening.attributes) {\n if (attr.type !== \"JSXAttribute\" || attr.name.type !== \"JSXIdentifier\") continue;\n\n const attrName = attr.name.name;\n\n if (attrName === \"i18nKey\") {\n keyResult = resolveJSXAttrValue(attr.value, code, constants);\n }\n\n if (attrName === \"ns\") {\n nsResult = resolveJSXAttrValue(attr.value, code, constants);\n }\n }\n\n if (!keyResult) {\n return null;\n }\n\n const key = keyResult.value;\n const namespace = nsResult?.value ?? \"translation\";\n const keyExpr = keyResult.expr;\n const nsExpr = nsResult?.expr ?? null;\n\n return {\n fullKey: `${namespace}:${key}`,\n key,\n namespace,\n keyExpr,\n nsExpr,\n };\n}\n\n// Helper: Scan an element's direct children for <Trans> components and extract i18n key info.\n// Handles direct <Trans> children and <Trans> inside JSXExpressionContainer (e.g. {show && <Trans>}).\nfunction findTransInChildren(\n jsxElement: JSXElement,\n code: string,\n constants: Map<string, string>,\n): I18nKeyInfo[] {\n const results: I18nKeyInfo[] = [];\n\n for (const child of jsxElement.children) {\n if (child.type === \"JSXElement\") {\n const info = detectTransComponent(child as JSXElement, code, constants);\n if (info) results.push(info);\n }\n\n if (child.type === \"JSXExpressionContainer\") {\n const expr = (child as any).expression;\n if (expr && expr.type !== \"JSXEmptyExpression\") {\n findTransInExpression(expr, code, results, constants);\n }\n }\n }\n\n return results;\n}\n\n// Helper: Recursively search an expression tree for <Trans> JSXElements.\n// Handles LogicalExpression (&&, ||), ConditionalExpression (?:), CallExpression (.map()),\n// ArrowFunctionExpression/FunctionExpression (callbacks), BlockStatement, and direct JSXElement.\nfunction findTransInExpression(\n expr: any,\n code: string,\n results: I18nKeyInfo[],\n constants: Map<string, string>,\n): void {\n if (!expr || !expr.type) return;\n\n if (expr.type === \"JSXElement\") {\n const info = detectTransComponent(expr as JSXElement, code, constants);\n if (info) results.push(info);\n return;\n }\n\n if (expr.type === \"LogicalExpression\") {\n findTransInExpression(expr.left, code, results, constants);\n findTransInExpression(expr.right, code, results, constants);\n return;\n }\n\n if (expr.type === \"ConditionalExpression\") {\n findTransInExpression(expr.consequent, code, results, constants);\n findTransInExpression(expr.alternate, code, results, constants);\n return;\n }\n\n // Handle .map() and other call expressions — recurse into arguments\n if (expr.type === \"CallExpression\") {\n for (const arg of expr.arguments) {\n findTransInExpression(arg, code, results, constants);\n }\n return;\n }\n\n // Handle arrow/function expressions — recurse into body\n if (expr.type === \"ArrowFunctionExpression\" || expr.type === \"FunctionExpression\") {\n findTransInExpression(expr.body, code, results, constants);\n return;\n }\n\n // Handle block bodies (arrow functions with braces)\n if (expr.type === \"BlockStatement\") {\n for (const stmt of expr.body) {\n if (stmt.type === \"ReturnStatement\" && stmt.argument) {\n findTransInExpression(stmt.argument, code, results, constants);\n }\n }\n return;\n }\n}\n\n// Helper: Scan an element's direct children for t() function calls and extract i18n key info.\nfunction findTCallsInChildren(jsxElement: JSXElement, code: string, state: TransformState): I18nKeyInfo[] {\n if (state.tFunctions.size === 0) return [];\n\n const results: I18nKeyInfo[] = [];\n\n for (const child of jsxElement.children) {\n if (child.type === \"JSXExpressionContainer\") {\n const expr = (child as any).expression;\n if (expr && expr.type !== \"JSXEmptyExpression\") {\n findTCallInExpression(expr, code, results, state);\n }\n }\n }\n\n return results;\n}\n\n// Helper: Recursively search an expression tree for t() calls.\n// Handles LogicalExpression (&&, ||), ConditionalExpression (?:), CallExpression (.map()),\n// ArrowFunctionExpression/FunctionExpression (callbacks), BlockStatement, and direct CallExpression.\nfunction findTCallInExpression(expr: any, code: string, results: I18nKeyInfo[], state: TransformState): void {\n if (!expr || !expr.type) return;\n\n if (expr.type === \"CallExpression\") {\n const info = detectTCall(expr, code, state);\n if (info) {\n results.push(info);\n return;\n }\n // Not a t() call — recurse into arguments (for .map() callbacks etc.)\n for (const arg of expr.arguments) {\n findTCallInExpression(arg, code, results, state);\n }\n return;\n }\n\n if (expr.type === \"LogicalExpression\") {\n findTCallInExpression(expr.left, code, results, state);\n findTCallInExpression(expr.right, code, results, state);\n return;\n }\n\n if (expr.type === \"ConditionalExpression\") {\n findTCallInExpression(expr.consequent, code, results, state);\n findTCallInExpression(expr.alternate, code, results, state);\n return;\n }\n\n if (expr.type === \"ArrowFunctionExpression\" || expr.type === \"FunctionExpression\") {\n findTCallInExpression(expr.body, code, results, state);\n return;\n }\n\n if (expr.type === \"BlockStatement\") {\n for (const stmt of expr.body) {\n if (stmt.type === \"ReturnStatement\" && stmt.argument) {\n findTCallInExpression(stmt.argument, code, results, state);\n }\n }\n return;\n }\n}\n\n// Helper: Detect a t(\"key\") call and extract i18n key info.\nfunction detectTCall(callExpr: any, code: string, state: TransformState): I18nKeyInfo | null {\n if (callExpr.callee?.type !== \"Identifier\") return null;\n\n const calleeName = callExpr.callee.name;\n const namespace = state.tFunctions.get(calleeName);\n if (namespace === undefined) return null;\n\n const firstArg = callExpr.arguments?.[0];\n if (!firstArg) return null;\n\n // Static string key: t(\"features.title\")\n if (firstArg.type === \"Literal\" && typeof firstArg.value === \"string\") {\n const key = firstArg.value;\n return { fullKey: `${namespace}:${key}`, key, namespace, keyExpr: null, nsExpr: null };\n }\n\n // Dynamic key — try constant resolution first, then fall back to expression\n if (hasRange(firstArg)) {\n const exprSrc = code.slice(firstArg.start, firstArg.end);\n\n if (firstArg.type === \"Identifier\" && state.constants.has(firstArg.name)) {\n const key = state.constants.get(firstArg.name)!;\n return { fullKey: `${namespace}:${key}`, key, namespace, keyExpr: null, nsExpr: null };\n }\n\n return { fullKey: `${namespace}:${exprSrc}`, key: exprSrc, namespace, keyExpr: exprSrc, nsExpr: null };\n }\n\n return null;\n}\n\n// Helper: Check if element is a \"leaf\" with only static text (no nested elements or expressions)\nfunction isTextLeafElement(jsxElement: JSXElement): boolean {\n let hasText = false;\n\n for (const child of jsxElement.children) {\n if (child.type === \"JSXText\") {\n const textValue = (child as any).value;\n if (textValue?.trim()) {\n hasText = true;\n }\n } else if (\n child.type === \"JSXElement\" ||\n child.type === \"JSXFragment\" ||\n child.type === \"JSXExpressionContainer\" ||\n child.type === \"JSXSpreadChild\"\n ) {\n // Has non-text children, not a leaf element\n return false;\n }\n }\n\n return hasText;\n}\n\n// Helper: Check if element has any visible text content (static or dynamic).\n// Broader than isTextLeafElement — returns true even if the element has other child types.\n// Detects JSXText with non-whitespace content and expression containers with\n// text-producing expressions (Identifier, MemberExpression, Literal, TemplateLiteral).\n// Skips CallExpression, ConditionalExpression, LogicalExpression (these typically produce elements).\nfunction hasVisibleTextContent(jsxElement: JSXElement): boolean {\n for (const child of jsxElement.children) {\n if (child.type === \"JSXText\") {\n const textValue = (child as any).value;\n if (textValue?.trim()) {\n return true;\n }\n continue;\n }\n\n if (child.type === \"JSXExpressionContainer\") {\n const expr = (child as any).expression;\n if (!expr || expr.type === \"JSXEmptyExpression\") {\n continue;\n }\n if (\n expr.type === \"Identifier\" ||\n expr.type === \"MemberExpression\" ||\n expr.type === \"Literal\" ||\n expr.type === \"TemplateLiteral\"\n ) {\n return true;\n }\n }\n }\n\n return false;\n}\n\nexport default upstartEditor.vite;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vite-plugin-upstart-attrs.js","names":[],"sources":["../src/vite-plugin-upstart-attrs.ts"],"sourcesContent":["import { createUnplugin } from \"unplugin\";\nimport { parseSync } from \"oxc-parser\";\nimport { walk } from \"zimmerframe\";\nimport MagicString from \"magic-string\";\nimport path from \"path\";\nimport type {\n Program,\n Node,\n JSXElement,\n JSXOpeningElement,\n JSXAttribute,\n Expression,\n CallExpression,\n} from \"estree-jsx\";\n\ninterface Options {\n enabled: boolean;\n emitRegistry?: boolean;\n}\n\ntype NodeWithRange = Node & { start: number; end: number };\n\nfunction hasRange(node: any): node is NodeWithRange {\n return node && typeof node.start === \"number\" && typeof node.end === \"number\";\n}\n\n// Fast, stable hash function (djb2 variant)\n// Produces a short hex string that's stable across rebuilds\nfunction hashContent(content: string): string {\n let hash = 5381;\n for (let i = 0; i < content.length; i++) {\n hash = ((hash << 5) + hash) ^ content.charCodeAt(i);\n }\n // Convert to unsigned 32-bit and then to hex\n return (hash >>> 0).toString(16);\n}\n\ninterface LoopContext {\n itemName: string;\n indexName: string | null;\n arrayExpr: string;\n}\n\ninterface I18nKeyInfo {\n /** The resolved key with namespace, e.g. \"dashboard:features.title\" */\n fullKey: string;\n /** Just the translation key, e.g. \"features.title\" */\n key: string;\n /** The namespace, e.g. \"dashboard\" */\n namespace: string;\n /** Source expression for i18nKey when dynamic (e.g. \"stat.labelKey\"), null when static */\n keyExpr: string | null;\n /** Source expression for namespace when dynamic, null when static */\n nsExpr: string | null;\n}\n\n// Registry entry for editable elements (text and className)\nexport interface EditableEntry {\n file: string;\n type: \"text\" | \"className\";\n startOffset: number;\n endOffset: number;\n originalContent: string;\n context: { parentTag: string };\n}\n\n// Module-level registry (collected across all files during build)\nconst editableRegistry = new Map<string, EditableEntry>();\n\n// Generate stable ID for editable elements\n// Replace the counter-based ID generation with position-based\nfunction generateId(filePath: string, node: NodeWithRange): string {\n // Use file path + start position for a stable, deterministic ID\n return `${filePath}:${node.start}`;\n}\n\n// Export for testing - get a copy of the current registry\nexport function getRegistry(): Record<string, EditableEntry> {\n return Object.fromEntries(editableRegistry);\n}\n\n// Export for testing - clear registry and counters\nexport function clearRegistry(): void {\n editableRegistry.clear();\n}\n\ninterface TransformState {\n filePath: string;\n code: string;\n s: MagicString;\n loopStack: LoopContext[];\n /** Tracks const varName = \"literal\" declarations for resolving dynamic i18nKey expressions */\n constants: Map<string, string>;\n /** Maps t-function variable names to their default namespace, e.g. { \"t\": \"dashboard\" } */\n tFunctions: Map<string, string>;\n}\n\nexport const upstartEditor = createUnplugin<Options>((options) => {\n if (!options.enabled) {\n return { name: \"upstart-editor-disabled\" };\n }\n\n const emitRegistry = options.emitRegistry ?? true;\n let root = process.cwd();\n\n return {\n name: \"upstart-editor\",\n enforce: \"pre\",\n\n vite: {\n configResolved(config) {\n root = config.root;\n },\n generateBundle() {\n if (!emitRegistry || editableRegistry.size === 0) {\n return;\n }\n\n const registry = {\n version: 1,\n generatedAt: new Date().toISOString(),\n elements: Object.fromEntries(editableRegistry),\n };\n\n this.emitFile({\n type: \"asset\",\n fileName: \"upstart-registry.json\",\n source: JSON.stringify(registry, null, 2),\n });\n\n // Clear for next build\n editableRegistry.clear();\n },\n },\n\n transformInclude(id) {\n return /\\.(tsx|jsx)$/.test(id) && !id.includes(\"node_modules\");\n },\n\n transform(code, id) {\n // Fast path: skip files without JSX\n if (!code.includes(\"<\")) {\n return null;\n }\n\n try {\n const relativePath = path.relative(root, id);\n const result = transformWithOxc(code, relativePath);\n\n if (!result) {\n return null;\n }\n\n return {\n code: result.code,\n map: result.map,\n };\n } catch (error) {\n console.error(`Error transforming ${id}:`, error);\n return null;\n }\n },\n };\n});\n\nexport function transformWithOxc(code: string, filePath: string) {\n // Parse with oxc (super fast!)\n const ast = parseSync(filePath, code, {\n sourceType: \"module\",\n });\n\n if (!ast.program) {\n return null;\n }\n\n const s = new MagicString(code);\n const state: TransformState = {\n filePath,\n code,\n s,\n loopStack: [],\n constants: new Map(),\n tFunctions: new Map(),\n };\n\n let modified = false;\n\n // Use zimmerframe to walk and transform the AST\n walk(ast.program as Program, state, {\n _(node: Node, { state, next }: { state: TransformState; next: () => void }) {\n // Track useTranslation() calls to resolve t() function namespaces\n if (node.type === \"VariableDeclaration\") {\n trackUseTranslation(node, state);\n\n // Track const varName = \"literal\" for resolving dynamic i18nKey expressions\n for (const decl of (node as any).declarations) {\n if (\n decl.type === \"VariableDeclarator\" &&\n decl.id?.type === \"Identifier\" &&\n decl.init?.type === \"Literal\" &&\n typeof decl.init.value === \"string\"\n ) {\n state.constants.set(decl.id.name, decl.init.value);\n }\n }\n }\n\n // Handle .map() calls to track loops (must be before JSXElement)\n if (node.type === \"CallExpression\") {\n const mapCallInfo = detectAndPatchMapCall(node as CallExpression, state);\n\n if (mapCallInfo) {\n // Push loop context\n state.loopStack.push(mapCallInfo);\n\n // Continue walking into the callback\n next();\n\n // Pop loop context after traversing\n state.loopStack.pop();\n return;\n }\n }\n\n // Handle JSX elements\n if (node.type === \"JSXElement\") {\n const jsxNode = node as JSXElement;\n const opening = jsxNode.openingElement;\n const tagName = getJSXElementName(opening);\n\n // Skip ALL attribute injection for <Trans> elements.\n // <Trans> renders to a text node at runtime, so DOM attributes are lost.\n // The i18n attributes are promoted to the parent element instead.\n if (tagName === \"Trans\") {\n next();\n return;\n }\n\n const insertPos = getAttributeInsertPosition(opening, state.code);\n\n // Track attributes to inject\n const attributes: string[] = [];\n\n // Compute stable hash from the element's source code (includes all children)\n // This hash changes if ANY part of the element or its children change\n if (hasRange(jsxNode)) {\n const elementSource = state.code.slice(jsxNode.start, jsxNode.end);\n const hash = hashContent(elementSource);\n\n // Collect suffix expressions from all enclosing loops so elements\n // inside .map() get a unique hash per iteration at runtime.\n // detectAndPatchMapCall() attempts to auto-inject an index param when missing.\n // If injection is not possible (no stable source range), fall back to \"0\".\n const loopSuffixes = state.loopStack.map((l) => (l.indexName ? `\\${${l.indexName}}` : \"0\"));\n\n if (loopSuffixes.length > 0) {\n const suffix = loopSuffixes.join(\"-\");\n attributes.push(`data-upstart-hash={\\`${hash}-${suffix}\\`}`);\n } else {\n attributes.push(`data-upstart-hash=\"${hash}\"`);\n }\n }\n\n // --- Editable text detection ---\n // Priority: i18n (true) > non-i18n text (false) > no text (no attribute)\n\n // Step 1: Check for Trans children AND t() calls (editable via i18n, gets \"true\")\n const transChildren = findTransInChildren(jsxNode, state.code, state.constants);\n const tCallChildren = findTCallsInChildren(jsxNode, state.code, state);\n const allI18nKeys = [...transChildren, ...tCallChildren];\n const hasI18n = allI18nKeys.length > 0;\n\n if (hasI18n) {\n attributes.push('data-upstart-editable-text=\"true\"');\n attributes.push('data-upstart-editable-text-mode=\"plain\"');\n\n const hasDynamic = allI18nKeys.some((t) => t.keyExpr || t.nsExpr);\n if (hasDynamic) {\n // Build a runtime JSX template expression for dynamic i18n keys\n const parts = allI18nKeys.map((t) => {\n const nsPart = t.nsExpr ? `\\${${t.nsExpr}}` : t.namespace;\n const keyPart = t.keyExpr ? `\\${${t.keyExpr}}` : t.key;\n return `${nsPart}:${keyPart}`;\n });\n attributes.push(`data-upstart-i18n={\\`${parts.join(\",\")}\\`}`);\n } else {\n const keys = allI18nKeys.map((t: I18nKeyInfo) => escapeProp(t.fullKey)).join(\",\");\n attributes.push(`data-upstart-i18n=\"${keys}\"`);\n }\n }\n\n // Step 2: Text leaf elements — registry tracking + non-editable flag\n if (isTextLeafElement(jsxNode)) {\n if (!hasI18n) {\n attributes.push('data-upstart-editable-text=\"false\"');\n }\n\n // Find the actual JSXText node to track its location\n const textChild = jsxNode.children.find((c) => c.type === \"JSXText\" && (c as any).value?.trim());\n\n if (textChild && hasRange(textChild)) {\n const id = generateId(state.filePath, textChild);\n const textValue = (textChild as any).value as string;\n\n // Calculate trimmed offsets (exclude leading/trailing whitespace)\n const trimmedStart = textChild.start + (textValue.length - textValue.trimStart().length);\n const trimmedEnd = textChild.end - (textValue.length - textValue.trimEnd().length);\n\n editableRegistry.set(id, {\n file: state.filePath,\n type: \"text\",\n startOffset: trimmedStart,\n endOffset: trimmedEnd,\n originalContent: textValue.trim(),\n context: { parentTag: tagName || \"unknown\" },\n });\n\n attributes.push(`data-upstart-id=\"${id}\"`);\n }\n }\n // Step 3: Non-leaf elements with visible text content (expressions, mixed content)\n else if (!hasI18n && hasVisibleTextContent(jsxNode)) {\n attributes.push('data-upstart-editable-text=\"false\"');\n }\n\n // Track className attribute if it's a string literal\n const classNameAttr = opening.attributes.find(\n (attr): attr is JSXAttribute =>\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n attr.name.name === \"className\" &&\n attr.value?.type === \"Literal\" &&\n typeof (attr.value as any).value === \"string\",\n );\n\n if (classNameAttr && classNameAttr.value && hasRange(classNameAttr.value)) {\n const id = generateId(state.filePath, classNameAttr.value);\n const classValue = (classNameAttr.value as any).value as string;\n\n // +1 and -1 to exclude the quotes\n editableRegistry.set(id, {\n file: state.filePath,\n type: \"className\",\n startOffset: classNameAttr.value.start + 1,\n endOffset: classNameAttr.value.end - 1,\n originalContent: classValue,\n context: { parentTag: tagName || \"unknown\" },\n });\n\n attributes.push(`data-upstart-classname-id=\"${id}\"`);\n }\n\n // Process PascalCase components for additional tracking\n if (tagName && /^[A-Z]/.test(tagName)) {\n // File and component tracking\n attributes.push(`data-upstart-file=\"${escapeProp(state.filePath)}\"`);\n attributes.push(`data-upstart-component=\"${escapeProp(tagName)}\"`);\n\n // Loop context tracking\n if (state.loopStack.length > 0) {\n const loop = state.loopStack[state.loopStack.length - 1];\n attributes.push(`data-upstart-loop-item=\"${escapeProp(loop.itemName)}\"`);\n if (loop.indexName) {\n attributes.push(`data-upstart-loop-index={${loop.indexName}}`);\n }\n attributes.push(`data-upstart-loop-array=\"${escapeProp(loop.arrayExpr)}\"`);\n }\n\n // Analyze each prop for data bindings\n for (const attr of opening.attributes) {\n if (attr.type !== \"JSXAttribute\" || attr.name.type !== \"JSXIdentifier\") {\n continue;\n }\n\n const propName = attr.name.name;\n const binding = analyzeBinding(attr.value, state.code);\n\n if (binding) {\n attributes.push(`data-upstart-prop-${propName.toLowerCase()}=\"${escapeProp(binding.path)}\"`);\n\n // Track conditional expressions\n if (binding.isConditional) {\n attributes.push(`data-upstart-conditional-${propName.toLowerCase()}=\"true\"`);\n }\n }\n }\n }\n\n // Inject attributes if any\n if (insertPos !== -1 && attributes.length > 0) {\n const attrString = \" \" + attributes.join(\" \");\n state.s.appendLeft(insertPos, attrString);\n modified = true;\n }\n }\n\n next();\n },\n });\n\n if (!modified) {\n return null;\n }\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: true }),\n };\n}\n\n// Helper: Get JSX element name\nfunction getJSXElementName(opening: JSXOpeningElement): string | null {\n if (opening.name.type === \"JSXIdentifier\") {\n return opening.name.name;\n }\n\n // Handle JSXMemberExpression like <Foo.Bar>\n if (opening.name.type === \"JSXMemberExpression\") {\n let current = opening.name;\n while (current.property) {\n if (current.property.type === \"JSXIdentifier\") {\n return current.property.name;\n }\n if (current.type === \"JSXMemberExpression\") {\n current = current.property as any;\n } else {\n break;\n }\n }\n }\n\n return null;\n}\n\n// Helper: Find where to insert attributes in JSX opening tag\nfunction getAttributeInsertPosition(opening: JSXOpeningElement, code: string): number {\n // If there are existing attributes, insert before the first one\n if (opening.attributes.length > 0) {\n const firstAttr = opening.attributes[0];\n if (hasRange(firstAttr)) {\n return firstAttr.start;\n }\n }\n\n // Otherwise, insert after the tag name\n if (opening.name.type === \"JSXIdentifier\" && hasRange(opening.name)) {\n return opening.name.end;\n }\n\n if (opening.name.type === \"JSXMemberExpression\" && hasRange(opening.name)) {\n return opening.name.end;\n }\n\n return -1;\n}\n\n// Helper: Analyze a prop value to extract data binding\nfunction analyzeBinding(\n value: JSXAttribute[\"value\"],\n code: string,\n): {\n path: string;\n isConditional?: boolean;\n} | null {\n if (!value) {\n return null;\n }\n\n if (value.type === \"JSXExpressionContainer\") {\n const expr = value.expression;\n\n if (expr.type === \"JSXEmptyExpression\") {\n return null;\n }\n\n return analyzeExpression(expr, code);\n }\n\n return null;\n}\n\n// Helper: Analyze any expression to extract binding info\nfunction analyzeExpression(\n expr: Expression,\n code: string,\n): {\n path: string;\n isConditional?: boolean;\n} | null {\n // Handle member expressions: user.name, product.price, etc.\n if (expr.type === \"MemberExpression\") {\n const path = exprToString(expr, code);\n return { path };\n }\n\n // Handle identifiers: userName, price, etc.\n if (expr.type === \"Identifier\") {\n return { path: expr.name };\n }\n\n // Handle conditional expressions: condition ? value1 : value2\n if (expr.type === \"ConditionalExpression\") {\n // Try to extract from the consequent\n const consequent = analyzeExpression(expr.consequent, code);\n if (consequent) {\n return {\n ...consequent,\n isConditional: true,\n path: exprToString(expr, code),\n };\n }\n }\n\n // Handle logical expressions: value1 && value2, value1 || value2\n if (expr.type === \"LogicalExpression\") {\n const right = analyzeExpression(expr.right, code);\n if (right) {\n return {\n ...right,\n isConditional: true,\n path: exprToString(expr, code),\n };\n }\n }\n\n // For other complex expressions, just return the path\n const exprWithRange = expr as any;\n if (exprWithRange.start !== undefined && exprWithRange.end !== undefined) {\n return {\n path: code.slice(exprWithRange.start, exprWithRange.end),\n };\n }\n\n return null;\n}\n\n// Helper: Convert expression AST to string\nfunction exprToString(expr: Expression, code: string): string {\n // Use the source range to get the actual code\n const exprWithRange = expr as any;\n if (exprWithRange.start !== undefined && exprWithRange.end !== undefined) {\n return code.slice(exprWithRange.start, exprWithRange.end);\n }\n\n // Fallback: reconstruct from AST\n if (expr.type === \"Identifier\") {\n return expr.name;\n }\n\n if (expr.type === \"MemberExpression\") {\n const obj = exprToString(expr.object as Expression, code);\n const prop =\n expr.property.type === \"Identifier\" && !expr.computed\n ? expr.property.name\n : exprToString(expr.property as Expression, code);\n return expr.computed ? `${obj}[${prop}]` : `${obj}.${prop}`;\n }\n\n if (expr.type === \"ConditionalExpression\") {\n const test = exprToString(expr.test, code);\n const consequent = exprToString(expr.consequent, code);\n const alternate = exprToString(expr.alternate, code);\n return `${test} ? ${consequent} : ${alternate}`;\n }\n\n if (expr.type === \"LogicalExpression\") {\n const left = exprToString(expr.left, code);\n const right = exprToString(expr.right, code);\n return `${left} ${expr.operator} ${right}`;\n }\n\n return \"\";\n}\n\n// Helper: Detect .map() calls and auto-inject index parameter if missing\nfunction detectAndPatchMapCall(node: CallExpression, state: TransformState): LoopContext | null {\n // Check if this is a .map() call\n if (\n node.callee.type !== \"MemberExpression\" ||\n node.callee.property.type !== \"Identifier\" ||\n node.callee.property.name !== \"map\"\n ) {\n return null;\n }\n\n const callback = node.arguments[0];\n\n if (!callback) {\n return null;\n }\n\n // Check if callback is an arrow function or function expression\n if (callback.type !== \"ArrowFunctionExpression\" && callback.type !== \"FunctionExpression\") {\n return null;\n }\n\n const params = callback.params;\n const itemParam = params[0];\n const indexParam = params[1];\n\n if (!itemParam) {\n return null;\n }\n\n // Extract parameter names\n const itemName = itemParam.type === \"Identifier\" ? itemParam.name : \"item\";\n let indexName = indexParam?.type === \"Identifier\" ? indexParam.name : null;\n\n // If no index parameter exists in arrow function, auto-inject it\n if (!indexName && callback.type === \"ArrowFunctionExpression\") {\n indexName = \"__i\";\n\n // Find the closing paren of the parameter list safely\n const lastParam = params[params.length - 1];\n\n // ESTree `Pattern` type doesn't guarantee source range fields in typings.\n // Guard before reading offsets.\n if (!hasRange(lastParam)) {\n // Can't patch callback params without a stable source offset.\n // Keep indexName as null so hash suffix generation can handle this case.\n indexName = null;\n return {\n itemName,\n indexName,\n arrayExpr: exprToString(node.callee.object as Expression, state.code),\n };\n }\n\n let searchPos = lastParam.end;\n\n // Find the next ) after the last parameter\n while (searchPos < state.code.length && state.code[searchPos] !== \")\") {\n searchPos++;\n }\n\n if (searchPos < state.code.length && state.code[searchPos] === \")\") {\n // Insert the parameter before the closing paren\n state.s.appendLeft(searchPos, \", __i\");\n }\n }\n\n // Get the array expression\n const arrayExpr = exprToString(node.callee.object as Expression, state.code);\n\n return {\n itemName,\n indexName,\n arrayExpr,\n };\n}\n\n// Helper: Escape prop values for JSX attributes\nfunction escapeProp(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\n// Helper: Track useTranslation() calls to map t-function variable names to their namespace\nfunction trackUseTranslation(node: Node, state: TransformState): void {\n if (node.type !== \"VariableDeclaration\") return;\n\n const decl = node as any;\n for (const declarator of decl.declarations) {\n if (\n declarator.id?.type !== \"ObjectPattern\" ||\n !declarator.init ||\n declarator.init.type !== \"CallExpression\"\n ) {\n continue;\n }\n\n const callExpr = declarator.init;\n if (callExpr.callee?.type !== \"Identifier\" || callExpr.callee.name !== \"useTranslation\") {\n continue;\n }\n\n const namespace = extractUseTranslationNamespace(callExpr);\n\n for (const prop of declarator.id.properties) {\n if (prop.type !== \"Property\") continue;\n if (prop.key?.type !== \"Identifier\" || prop.key.name !== \"t\") continue;\n\n // Handle aliased destructuring: { t: translate } → maps \"translate\" to namespace\n const localName = prop.value?.type === \"Identifier\" ? prop.value.name : \"t\";\n state.tFunctions.set(localName, namespace);\n }\n }\n}\n\n// Helper: Extract namespace from useTranslation() call arguments\nfunction extractUseTranslationNamespace(callExpr: any): string {\n const firstArg = callExpr.arguments?.[0];\n if (!firstArg) return \"translation\";\n\n // String literal: useTranslation(\"dashboard\")\n if (firstArg.type === \"Literal\" && typeof firstArg.value === \"string\") {\n return firstArg.value;\n }\n\n // Array expression: useTranslation([\"dashboard\", \"common\"])\n if (firstArg.type === \"ArrayExpression\" && firstArg.elements?.length > 0) {\n const first = firstArg.elements[0];\n if (first?.type === \"Literal\" && typeof first.value === \"string\") {\n return first.value;\n }\n }\n\n return \"translation\";\n}\n\n// Helper: Resolve a JSX attribute value to a string.\n// Handles string literals directly, and JSXExpressionContainer by resolving\n// Identifier references via the constants map, or returning the expression source for dynamic values.\nfunction resolveJSXAttrValue(\n value: any,\n code: string,\n constants: Map<string, string>,\n): { value: string; expr: string | null } | null {\n if (!value) return null;\n\n if (value.type === \"Literal\" && typeof value.value === \"string\") {\n return { value: value.value, expr: null };\n }\n\n if (value.type === \"JSXExpressionContainer\") {\n const expression = value.expression;\n if (!expression) return null;\n\n // Resolve simple identifiers via constants map (e.g. i18nKey={labelKey} where const labelKey = \"...\")\n if (expression.type === \"Identifier\" && constants.has(expression.name)) {\n return { value: constants.get(expression.name)!, expr: null };\n }\n\n // Dynamic expression — return source text so caller can emit a runtime JSX expression\n if (hasRange(expression)) {\n const src = code.slice(expression.start, expression.end);\n return { value: src, expr: src };\n }\n }\n\n return null;\n}\n\n// Helper: Detect <Trans i18nKey=\"...\" /> component and extract i18n key info\nfunction detectTransComponent(\n jsxElement: JSXElement,\n code: string,\n constants: Map<string, string>,\n): I18nKeyInfo | null {\n const opening = jsxElement.openingElement;\n const tagName = getJSXElementName(opening);\n\n if (tagName !== \"Trans\") return null;\n\n let keyResult: { value: string; expr: string | null } | null = null;\n let nsResult: { value: string; expr: string | null } | null = null;\n\n for (const attr of opening.attributes) {\n if (attr.type !== \"JSXAttribute\" || attr.name.type !== \"JSXIdentifier\") continue;\n\n const attrName = attr.name.name;\n\n if (attrName === \"i18nKey\") {\n keyResult = resolveJSXAttrValue(attr.value, code, constants);\n }\n\n if (attrName === \"ns\") {\n nsResult = resolveJSXAttrValue(attr.value, code, constants);\n }\n }\n\n if (!keyResult) {\n return null;\n }\n\n const key = keyResult.value;\n const namespace = nsResult?.value ?? \"translation\";\n const keyExpr = keyResult.expr;\n const nsExpr = nsResult?.expr ?? null;\n\n return {\n fullKey: `${namespace}:${key}`,\n key,\n namespace,\n keyExpr,\n nsExpr,\n };\n}\n\n// Helper: Scan an element's direct children for <Trans> components and extract i18n key info.\n// Handles direct <Trans> children and <Trans> inside JSXExpressionContainer (e.g. {show && <Trans>}).\nfunction findTransInChildren(\n jsxElement: JSXElement,\n code: string,\n constants: Map<string, string>,\n): I18nKeyInfo[] {\n const results: I18nKeyInfo[] = [];\n\n for (const child of jsxElement.children) {\n if (child.type === \"JSXElement\") {\n const info = detectTransComponent(child as JSXElement, code, constants);\n if (info) results.push(info);\n }\n\n if (child.type === \"JSXExpressionContainer\") {\n const expr = (child as any).expression;\n if (expr && expr.type !== \"JSXEmptyExpression\") {\n findTransInExpression(expr, code, results, constants);\n }\n }\n }\n\n return results;\n}\n\n// Helper: Recursively search an expression tree for <Trans> JSXElements.\n// Handles LogicalExpression (&&, ||), ConditionalExpression (?:), CallExpression (.map()),\n// ArrowFunctionExpression/FunctionExpression (callbacks), BlockStatement, and direct JSXElement.\nfunction findTransInExpression(\n expr: any,\n code: string,\n results: I18nKeyInfo[],\n constants: Map<string, string>,\n): void {\n if (!expr || !expr.type) return;\n\n if (expr.type === \"JSXElement\") {\n const info = detectTransComponent(expr as JSXElement, code, constants);\n if (info) results.push(info);\n return;\n }\n\n if (expr.type === \"LogicalExpression\") {\n findTransInExpression(expr.left, code, results, constants);\n findTransInExpression(expr.right, code, results, constants);\n return;\n }\n\n if (expr.type === \"ConditionalExpression\") {\n findTransInExpression(expr.consequent, code, results, constants);\n findTransInExpression(expr.alternate, code, results, constants);\n return;\n }\n\n // Handle .map() and other call expressions — recurse into arguments\n if (expr.type === \"CallExpression\") {\n for (const arg of expr.arguments) {\n findTransInExpression(arg, code, results, constants);\n }\n return;\n }\n\n // Handle arrow/function expressions — recurse into body\n if (expr.type === \"ArrowFunctionExpression\" || expr.type === \"FunctionExpression\") {\n findTransInExpression(expr.body, code, results, constants);\n return;\n }\n\n // Handle block bodies (arrow functions with braces)\n if (expr.type === \"BlockStatement\") {\n for (const stmt of expr.body) {\n if (stmt.type === \"ReturnStatement\" && stmt.argument) {\n findTransInExpression(stmt.argument, code, results, constants);\n }\n }\n return;\n }\n}\n\n// Helper: Scan an element's direct children for t() function calls and extract i18n key info.\nfunction findTCallsInChildren(jsxElement: JSXElement, code: string, state: TransformState): I18nKeyInfo[] {\n if (state.tFunctions.size === 0) return [];\n\n const results: I18nKeyInfo[] = [];\n\n for (const child of jsxElement.children) {\n if (child.type === \"JSXExpressionContainer\") {\n const expr = (child as any).expression;\n if (expr && expr.type !== \"JSXEmptyExpression\") {\n findTCallInExpression(expr, code, results, state);\n }\n }\n }\n\n return results;\n}\n\n// Helper: Recursively search an expression tree for t() calls.\n// Handles LogicalExpression (&&, ||), ConditionalExpression (?:), CallExpression (.map()),\n// ArrowFunctionExpression/FunctionExpression (callbacks), BlockStatement, and direct CallExpression.\nfunction findTCallInExpression(expr: any, code: string, results: I18nKeyInfo[], state: TransformState): void {\n if (!expr || !expr.type) return;\n\n if (expr.type === \"CallExpression\") {\n const info = detectTCall(expr, code, state);\n if (info) {\n results.push(info);\n return;\n }\n // Not a t() call — recurse into arguments (for .map() callbacks etc.)\n for (const arg of expr.arguments) {\n findTCallInExpression(arg, code, results, state);\n }\n return;\n }\n\n if (expr.type === \"LogicalExpression\") {\n findTCallInExpression(expr.left, code, results, state);\n findTCallInExpression(expr.right, code, results, state);\n return;\n }\n\n if (expr.type === \"ConditionalExpression\") {\n findTCallInExpression(expr.consequent, code, results, state);\n findTCallInExpression(expr.alternate, code, results, state);\n return;\n }\n\n if (expr.type === \"ArrowFunctionExpression\" || expr.type === \"FunctionExpression\") {\n findTCallInExpression(expr.body, code, results, state);\n return;\n }\n\n if (expr.type === \"BlockStatement\") {\n for (const stmt of expr.body) {\n if (stmt.type === \"ReturnStatement\" && stmt.argument) {\n findTCallInExpression(stmt.argument, code, results, state);\n }\n }\n return;\n }\n}\n\n// Helper: Detect a t(\"key\") call and extract i18n key info.\nfunction detectTCall(callExpr: any, code: string, state: TransformState): I18nKeyInfo | null {\n if (callExpr.callee?.type !== \"Identifier\") return null;\n\n const calleeName = callExpr.callee.name;\n const namespace = state.tFunctions.get(calleeName);\n if (namespace === undefined) return null;\n\n const firstArg = callExpr.arguments?.[0];\n if (!firstArg) return null;\n\n // Static string key: t(\"features.title\")\n if (firstArg.type === \"Literal\" && typeof firstArg.value === \"string\") {\n const key = firstArg.value;\n return { fullKey: `${namespace}:${key}`, key, namespace, keyExpr: null, nsExpr: null };\n }\n\n // Dynamic key — try constant resolution first, then fall back to expression\n if (hasRange(firstArg)) {\n const exprSrc = code.slice(firstArg.start, firstArg.end);\n\n if (firstArg.type === \"Identifier\" && state.constants.has(firstArg.name)) {\n const key = state.constants.get(firstArg.name)!;\n return { fullKey: `${namespace}:${key}`, key, namespace, keyExpr: null, nsExpr: null };\n }\n\n return { fullKey: `${namespace}:${exprSrc}`, key: exprSrc, namespace, keyExpr: exprSrc, nsExpr: null };\n }\n\n return null;\n}\n\n// Helper: Check if element is a \"leaf\" with only static text (no nested elements or expressions)\nfunction isTextLeafElement(jsxElement: JSXElement): boolean {\n let hasText = false;\n\n for (const child of jsxElement.children) {\n if (child.type === \"JSXText\") {\n const textValue = (child as any).value;\n if (textValue?.trim()) {\n hasText = true;\n }\n } else if (\n child.type === \"JSXElement\" ||\n child.type === \"JSXFragment\" ||\n child.type === \"JSXExpressionContainer\" ||\n child.type === \"JSXSpreadChild\"\n ) {\n // Has non-text children, not a leaf element\n return false;\n }\n }\n\n return hasText;\n}\n\n// Helper: Check if element has any visible text content (static or dynamic).\n// Broader than isTextLeafElement — returns true even if the element has other child types.\n// Detects JSXText with non-whitespace content and expression containers with\n// text-producing expressions (Identifier, MemberExpression, Literal, TemplateLiteral).\n// Skips CallExpression, ConditionalExpression, LogicalExpression (these typically produce elements).\nfunction hasVisibleTextContent(jsxElement: JSXElement): boolean {\n for (const child of jsxElement.children) {\n if (child.type === \"JSXText\") {\n const textValue = (child as any).value;\n if (textValue?.trim()) {\n return true;\n }\n continue;\n }\n\n if (child.type === \"JSXExpressionContainer\") {\n const expr = (child as any).expression;\n if (!expr || expr.type === \"JSXEmptyExpression\") {\n continue;\n }\n if (\n expr.type === \"Identifier\" ||\n expr.type === \"MemberExpression\" ||\n expr.type === \"Literal\" ||\n expr.type === \"TemplateLiteral\"\n ) {\n return true;\n }\n }\n }\n\n return false;\n}\n\nexport default upstartEditor.vite;\n"],"mappings":"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"}
|
|
@@ -98,7 +98,16 @@ function handleParentMessage(event) {
|
|
|
98
98
|
} else if (message.type === "preview-classname") {
|
|
99
99
|
const el = document.querySelector(`[data-upstart-classname-id="${message.classNameId}"]`);
|
|
100
100
|
if (el) el.className = message.className;
|
|
101
|
-
}
|
|
101
|
+
} else if (message.type === "request-scroll-position") sendToParent({
|
|
102
|
+
type: "scroll-position",
|
|
103
|
+
x: window.scrollX,
|
|
104
|
+
y: window.scrollY
|
|
105
|
+
});
|
|
106
|
+
else if (message.type === "restore-scroll-position") window.scrollTo({
|
|
107
|
+
left: message.x,
|
|
108
|
+
top: message.y,
|
|
109
|
+
behavior: "auto"
|
|
110
|
+
});
|
|
102
111
|
}
|
|
103
112
|
function enableEditMode() {
|
|
104
113
|
console.log("[Upstart Editor] Edit mode enabled");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/index.ts"],"sourcesContent":["import { initClickHandler } from \"./click-handler.js\";\nimport { initHoverOverlay, hideOverlays } from \"./hover-overlay.js\";\nimport { initErrorHandler } from \"./error-handler.js\";\nimport { initTextEditor, activateAllEditors, destroyAllActiveEditors } from \"./text-editor.js\";\nimport { sendToParent } from \"./utils.js\";\nimport { getCurrentMode, setCurrentMode } from \"./state.js\";\nimport type { EditorMode, UpstartParentMessage } from \"./types.js\";\n\nlet isInitialized = false;\n\nexport { getCurrentMode };\n\n/**\n * Set the current editor mode.\n */\nexport function setMode(mode: EditorMode): void {\n setCurrentMode(mode);\n console.log(`[Upstart Editor] Setting mode to: ${mode}`);\n if (mode === \"edit\") {\n enableEditMode();\n } else {\n disableEditMode();\n }\n}\n\nexport function waitForHydration(callback: () => void): void {\n // Remix/React 18 wraps hydrateRoot in startTransition, making hydration a\n // concurrent (low-priority) operation that can span many frames after the\n // load event. Instead of guessing a delay, we wait for the DOM to stabilise:\n // once 200 ms pass without any childList mutations, hydration is done.\n const STABILITY_MS = 200;\n\n const onReady = () => {\n let timer: number | null = null;\n\n const settle = () => {\n if (timer) clearTimeout(timer);\n timer = window.setTimeout(() => {\n observer.disconnect();\n callback();\n }, STABILITY_MS);\n };\n\n const observer = new MutationObserver(settle);\n observer.observe(document.documentElement, { childList: true, subtree: true });\n\n // Kick off the first timer (covers case where no mutations occur after load)\n settle();\n };\n\n if (document.readyState === \"complete\") {\n onReady();\n } else {\n window.addEventListener(\"load\", onReady, { once: true });\n }\n}\n\n/**\n * Initialize the Upstart editor runtime.\n */\nexport function initUpstartEditor(): void {\n if (isInitialized) {\n console.log(\"[Upstart Editor] Editor is already initialized\");\n return;\n }\n\n try {\n console.log(\"[Upstart Editor] Initializing...\");\n\n isInitialized = true;\n\n window.addEventListener(\"message\", handleParentMessage);\n\n // Notify parent on SPA navigation so it can resend the current editMode\n window.addEventListener(\"popstate\", () => {\n sendToParent({ type: \"editor-navigated\" });\n });\n const originalPushState = history.pushState.bind(history);\n history.pushState = (...args) => {\n originalPushState(...args);\n sendToParent({ type: \"editor-navigated\" });\n };\n const originalReplaceState = history.replaceState.bind(history);\n history.replaceState = (...args) => {\n originalReplaceState(...args);\n sendToParent({ type: \"editor-navigated\" });\n };\n\n initTextEditor();\n initClickHandler();\n initHoverOverlay();\n initErrorHandler();\n\n sendToParent({ type: \"editor-ready\" });\n } catch (error) {\n console.error(\"[Upstart Editor] Initialization failed:\", error);\n sendToParent({\n type: \"editor-error\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n}\n\nconst ALLOWED_ORIGINS = [\"http://localhost:8080\", /upstart.gg$/];\n\nconst matchAllowedOrigins = (origin: string) => {\n return ALLOWED_ORIGINS.some((allowedOrigin) => {\n if (typeof allowedOrigin === \"string\") {\n return origin === allowedOrigin;\n } else if (allowedOrigin instanceof RegExp) {\n return allowedOrigin.test(origin);\n }\n return false;\n });\n};\n\nfunction handleParentMessage(event: MessageEvent): void {\n const message = event.data as UpstartParentMessage | undefined;\n\n console.log(\"[Upstart Editor] Received message from parent:\", { event, message });\n\n if (!message || !matchAllowedOrigins(event.origin)) {\n console.warn(\"[Upstart Editor] Ignoring message from unknown source:\", event.origin);\n return;\n }\n\n if (message.type === \"set-mode\") {\n console.log(\"Setting editor mode to:\", message.mode);\n setMode(message.mode);\n } else if (message.type === \"preview-classname\") {\n const el = document.querySelector<HTMLElement>(`[data-upstart-classname-id=\"${message.classNameId}\"]`);\n if (el) {\n el.className = message.className;\n }\n }\n}\n\nfunction enableEditMode(): void {\n console.log(\"[Upstart Editor] Edit mode enabled\");\n document.documentElement.setAttribute(\"data-upstart-edit-mode\", \"\");\n activateAllEditors();\n}\n\nfunction disableEditMode(): void {\n console.log(\"[Upstart Editor] Preview mode enabled\");\n document.documentElement.removeAttribute(\"data-upstart-edit-mode\");\n destroyAllActiveEditors();\n hideOverlays();\n}\n\nexport { initTextEditor } from \"./text-editor.js\";\nexport { initClickHandler } from \"./click-handler.js\";\nexport { initErrorHandler } from \"./error-handler.js\";\nexport { initHoverOverlay } from \"./hover-overlay.js\";\nexport { sendToParent } from \"./utils.js\";\nexport type { EditorMessage, UpstartEditorMessage } from \"./types.js\";\n"],"mappings":";;;;;;;AAQA,IAAI,gBAAgB;;;;AAOpB,SAAgB,QAAQ,MAAwB;AAC9C,gBAAe,KAAK;AACpB,SAAQ,IAAI,qCAAqC,OAAO;AACxD,KAAI,SAAS,OACX,iBAAgB;KAEhB,kBAAiB;;AAIrB,SAAgB,iBAAiB,UAA4B;CAK3D,MAAM,eAAe;CAErB,MAAM,gBAAgB;EACpB,IAAI,QAAuB;EAE3B,MAAM,eAAe;AACnB,OAAI,MAAO,cAAa,MAAM;AAC9B,WAAQ,OAAO,iBAAiB;AAC9B,aAAS,YAAY;AACrB,cAAU;MACT,aAAa;;EAGlB,MAAM,WAAW,IAAI,iBAAiB,OAAO;AAC7C,WAAS,QAAQ,SAAS,iBAAiB;GAAE,WAAW;GAAM,SAAS;GAAM,CAAC;AAG9E,UAAQ;;AAGV,KAAI,SAAS,eAAe,WAC1B,UAAS;KAET,QAAO,iBAAiB,QAAQ,SAAS,EAAE,MAAM,MAAM,CAAC;;;;;AAO5D,SAAgB,oBAA0B;AACxC,KAAI,eAAe;AACjB,UAAQ,IAAI,iDAAiD;AAC7D;;AAGF,KAAI;AACF,UAAQ,IAAI,mCAAmC;AAE/C,kBAAgB;AAEhB,SAAO,iBAAiB,WAAW,oBAAoB;AAGvD,SAAO,iBAAiB,kBAAkB;AACxC,gBAAa,EAAE,MAAM,oBAAoB,CAAC;IAC1C;EACF,MAAM,oBAAoB,QAAQ,UAAU,KAAK,QAAQ;AACzD,UAAQ,aAAa,GAAG,SAAS;AAC/B,qBAAkB,GAAG,KAAK;AAC1B,gBAAa,EAAE,MAAM,oBAAoB,CAAC;;EAE5C,MAAM,uBAAuB,QAAQ,aAAa,KAAK,QAAQ;AAC/D,UAAQ,gBAAgB,GAAG,SAAS;AAClC,wBAAqB,GAAG,KAAK;AAC7B,gBAAa,EAAE,MAAM,oBAAoB,CAAC;;AAG5C,kBAAgB;AAChB,oBAAkB;AAClB,oBAAkB;AAClB,oBAAkB;AAElB,eAAa,EAAE,MAAM,gBAAgB,CAAC;UAC/B,OAAO;AACd,UAAQ,MAAM,2CAA2C,MAAM;AAC/D,eAAa;GACX,MAAM;GACN,OAAO,iBAAiB,QAAQ,MAAM,UAAU;GACjD,CAAC;;;AAIN,MAAM,kBAAkB,CAAC,yBAAyB,cAAc;AAEhE,MAAM,uBAAuB,WAAmB;AAC9C,QAAO,gBAAgB,MAAM,kBAAkB;AAC7C,MAAI,OAAO,kBAAkB,SAC3B,QAAO,WAAW;WACT,yBAAyB,OAClC,QAAO,cAAc,KAAK,OAAO;AAEnC,SAAO;GACP;;AAGJ,SAAS,oBAAoB,OAA2B;CACtD,MAAM,UAAU,MAAM;AAEtB,SAAQ,IAAI,kDAAkD;EAAE;EAAO;EAAS,CAAC;AAEjF,KAAI,CAAC,WAAW,CAAC,oBAAoB,MAAM,OAAO,EAAE;AAClD,UAAQ,KAAK,0DAA0D,MAAM,OAAO;AACpF;;AAGF,KAAI,QAAQ,SAAS,YAAY;AAC/B,UAAQ,IAAI,2BAA2B,QAAQ,KAAK;AACpD,UAAQ,QAAQ,KAAK;YACZ,QAAQ,SAAS,qBAAqB;EAC/C,MAAM,KAAK,SAAS,cAA2B,+BAA+B,QAAQ,YAAY,IAAI;AACtG,MAAI,GACF,IAAG,YAAY,QAAQ
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/index.ts"],"sourcesContent":["import { initClickHandler } from \"./click-handler.js\";\nimport { initHoverOverlay, hideOverlays } from \"./hover-overlay.js\";\nimport { initErrorHandler } from \"./error-handler.js\";\nimport { initTextEditor, activateAllEditors, destroyAllActiveEditors } from \"./text-editor.js\";\nimport { sendToParent } from \"./utils.js\";\nimport { getCurrentMode, setCurrentMode } from \"./state.js\";\nimport type { EditorMode, UpstartParentMessage } from \"./types.js\";\n\nlet isInitialized = false;\n\nexport { getCurrentMode };\n\n/**\n * Set the current editor mode.\n */\nexport function setMode(mode: EditorMode): void {\n setCurrentMode(mode);\n console.log(`[Upstart Editor] Setting mode to: ${mode}`);\n if (mode === \"edit\") {\n enableEditMode();\n } else {\n disableEditMode();\n }\n}\n\nexport function waitForHydration(callback: () => void): void {\n // Remix/React 18 wraps hydrateRoot in startTransition, making hydration a\n // concurrent (low-priority) operation that can span many frames after the\n // load event. Instead of guessing a delay, we wait for the DOM to stabilise:\n // once 200 ms pass without any childList mutations, hydration is done.\n const STABILITY_MS = 200;\n\n const onReady = () => {\n let timer: number | null = null;\n\n const settle = () => {\n if (timer) clearTimeout(timer);\n timer = window.setTimeout(() => {\n observer.disconnect();\n callback();\n }, STABILITY_MS);\n };\n\n const observer = new MutationObserver(settle);\n observer.observe(document.documentElement, { childList: true, subtree: true });\n\n // Kick off the first timer (covers case where no mutations occur after load)\n settle();\n };\n\n if (document.readyState === \"complete\") {\n onReady();\n } else {\n window.addEventListener(\"load\", onReady, { once: true });\n }\n}\n\n/**\n * Initialize the Upstart editor runtime.\n */\nexport function initUpstartEditor(): void {\n if (isInitialized) {\n console.log(\"[Upstart Editor] Editor is already initialized\");\n return;\n }\n\n try {\n console.log(\"[Upstart Editor] Initializing...\");\n\n isInitialized = true;\n\n window.addEventListener(\"message\", handleParentMessage);\n\n // Notify parent on SPA navigation so it can resend the current editMode\n window.addEventListener(\"popstate\", () => {\n sendToParent({ type: \"editor-navigated\" });\n });\n const originalPushState = history.pushState.bind(history);\n history.pushState = (...args) => {\n originalPushState(...args);\n sendToParent({ type: \"editor-navigated\" });\n };\n const originalReplaceState = history.replaceState.bind(history);\n history.replaceState = (...args) => {\n originalReplaceState(...args);\n sendToParent({ type: \"editor-navigated\" });\n };\n\n initTextEditor();\n initClickHandler();\n initHoverOverlay();\n initErrorHandler();\n\n sendToParent({ type: \"editor-ready\" });\n } catch (error) {\n console.error(\"[Upstart Editor] Initialization failed:\", error);\n sendToParent({\n type: \"editor-error\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n}\n\nconst ALLOWED_ORIGINS = [\"http://localhost:8080\", /upstart.gg$/];\n\nconst matchAllowedOrigins = (origin: string) => {\n return ALLOWED_ORIGINS.some((allowedOrigin) => {\n if (typeof allowedOrigin === \"string\") {\n return origin === allowedOrigin;\n } else if (allowedOrigin instanceof RegExp) {\n return allowedOrigin.test(origin);\n }\n return false;\n });\n};\n\nfunction handleParentMessage(event: MessageEvent): void {\n const message = event.data as UpstartParentMessage | undefined;\n\n console.log(\"[Upstart Editor] Received message from parent:\", { event, message });\n\n if (!message || !matchAllowedOrigins(event.origin)) {\n console.warn(\"[Upstart Editor] Ignoring message from unknown source:\", event.origin);\n return;\n }\n\n if (message.type === \"set-mode\") {\n console.log(\"Setting editor mode to:\", message.mode);\n setMode(message.mode);\n } else if (message.type === \"preview-classname\") {\n const el = document.querySelector<HTMLElement>(`[data-upstart-classname-id=\"${message.classNameId}\"]`);\n if (el) {\n el.className = message.className;\n }\n } else if (message.type === \"request-scroll-position\") {\n sendToParent({ type: \"scroll-position\", x: window.scrollX, y: window.scrollY });\n } else if (message.type === \"restore-scroll-position\") {\n window.scrollTo({ left: message.x, top: message.y, behavior: \"auto\" });\n }\n}\n\nfunction enableEditMode(): void {\n console.log(\"[Upstart Editor] Edit mode enabled\");\n document.documentElement.setAttribute(\"data-upstart-edit-mode\", \"\");\n activateAllEditors();\n}\n\nfunction disableEditMode(): void {\n console.log(\"[Upstart Editor] Preview mode enabled\");\n document.documentElement.removeAttribute(\"data-upstart-edit-mode\");\n destroyAllActiveEditors();\n hideOverlays();\n}\n\nexport { initTextEditor } from \"./text-editor.js\";\nexport { initClickHandler } from \"./click-handler.js\";\nexport { initErrorHandler } from \"./error-handler.js\";\nexport { initHoverOverlay } from \"./hover-overlay.js\";\nexport { sendToParent } from \"./utils.js\";\nexport type { EditorMessage, UpstartEditorMessage } from \"./types.js\";\n"],"mappings":"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"}
|
|
@@ -40,6 +40,10 @@ type EditorMessage = {
|
|
|
40
40
|
type: "editor-ready";
|
|
41
41
|
} | {
|
|
42
42
|
type: "editor-navigated";
|
|
43
|
+
} | {
|
|
44
|
+
type: "scroll-position";
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
43
47
|
} | {
|
|
44
48
|
type: "editor-error";
|
|
45
49
|
error: string;
|
|
@@ -65,6 +69,12 @@ type ParentMessage = {
|
|
|
65
69
|
type: "preview-classname";
|
|
66
70
|
classNameId: string;
|
|
67
71
|
className: string;
|
|
72
|
+
} | {
|
|
73
|
+
type: "request-scroll-position";
|
|
74
|
+
} | {
|
|
75
|
+
type: "restore-scroll-position";
|
|
76
|
+
x: number;
|
|
77
|
+
y: number;
|
|
68
78
|
};
|
|
69
79
|
/**
|
|
70
80
|
* Message wrapper sent via postMessage from iframe.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/types.ts"],"mappings":";;;;;;AAMA;KAAY,UAAA;;;;KAKA,cAAA;;;;UAKK,aAAA;EACf,GAAA;EACA,IAAA;EACA,KAAA;EACA,MAAA;EACA,KAAA;EACA,MAAA;AAAA;;;;UAMe,cAAA;EACf,MAAA,EAAQ,MAAA;EACR,OAAA,EAAS,WAAA;EACT,IAAA;EACA,IAAA,EAAM,cAAA;AAAA;;;;KAMI,aAAA;EACN,IAAA;EAAmB,OAAA,EAAS,eAAA;AAAA;EAC5B,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;EAAsB,KAAA;AAAA;EAEtB,IAAA;EACA,IAAA;EACA,aAAA;EACA,QAAA;EACA,gBAAA;EACA,WAAA;EACA,YAAA;EACA,QAAA;EACA,WAAA,GAAc,MAAA;EACd,MAAA,EAAQ,aAAA;AAAA;;;;KAMF,aAAA;EACN,IAAA;EAAkB,IAAA,EAAM,UAAA;AAAA;EACxB,IAAA;EAA2B,WAAA;EAAqB,SAAA;AAAA;;;;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/types.ts"],"mappings":";;;;;;AAMA;KAAY,UAAA;;;;KAKA,cAAA;;;;UAKK,aAAA;EACf,GAAA;EACA,IAAA;EACA,KAAA;EACA,MAAA;EACA,KAAA;EACA,MAAA;AAAA;;;;UAMe,cAAA;EACf,MAAA,EAAQ,MAAA;EACR,OAAA,EAAS,WAAA;EACT,IAAA;EACA,IAAA,EAAM,cAAA;AAAA;;;;KAMI,aAAA;EACN,IAAA;EAAmB,OAAA,EAAS,eAAA;AAAA;EAC5B,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;EAAyB,CAAA;EAAW,CAAA;AAAA;EACpC,IAAA;EAAsB,KAAA;AAAA;EAEtB,IAAA;EACA,IAAA;EACA,aAAA;EACA,QAAA;EACA,gBAAA;EACA,WAAA;EACA,YAAA;EACA,QAAA;EACA,WAAA,GAAc,MAAA;EACd,MAAA,EAAQ,aAAA;AAAA;;;;KAMF,aAAA;EACN,IAAA;EAAkB,IAAA,EAAM,UAAA;AAAA;EACxB,IAAA;EAA2B,WAAA;EAAqB,SAAA;AAAA;EAChD,IAAA;AAAA;EACA,IAAA;EAAiC,CAAA;EAAW,CAAA;AAAA;;;;UAKjC,oBAAA;EACf,MAAA;EACA,IAAA,EAAM,aAAA;EAAA,CACL,GAAA;AAAA;;;;UAMc,oBAAA;EACf,MAAA;EACA,IAAA,EAAM,aAAA;EAAA,CACL,GAAA;AAAA;;;;UAMc,oBAAA;EAhBf;;;;EAqBA,gBAAA;EAde;;;;;EAqBf,sBAAA;EAnBM;;;;EAyBN,iBAAA;EAlBmC;;;;EAwBnC,UAAA;EANA;;;;EAYA,WAAA;EAMa;AAMf;;;EANE,aAAA;AAAA;;;;UAMe,0BAAA;;;;;EAKf,OAAA;;;;;EAMA,UAAA;AAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upstart.gg/vite-plugins",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"oxc-parser": "0.101.0",
|
|
24
24
|
"unplugin": "^2.3.11",
|
|
25
25
|
"zimmerframe": "^1.1.4",
|
|
26
|
-
"@upstart.gg/sdk": "^0.1.
|
|
26
|
+
"@upstart.gg/sdk": "^0.1.24"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
},
|
|
113
113
|
"peerDependencies": {
|
|
114
114
|
"zod": "4.3.6",
|
|
115
|
-
"@upstart.gg/sdk": "^0.1.
|
|
115
|
+
"@upstart.gg/sdk": "^0.1.24"
|
|
116
116
|
},
|
|
117
117
|
"author": "Upstart",
|
|
118
118
|
"publishConfig": {
|
|
@@ -505,6 +505,26 @@ describe("upstart-editor-vite-plugin", () => {
|
|
|
505
505
|
expect(result).toContain("${index}`}");
|
|
506
506
|
});
|
|
507
507
|
|
|
508
|
+
test("should produce unique hashes for elements inside .map() without index parameter", () => {
|
|
509
|
+
const code = `
|
|
510
|
+
export default function App() {
|
|
511
|
+
const navLinks = [{ href: "#a" }, { href: "#b" }];
|
|
512
|
+
return (
|
|
513
|
+
<div>
|
|
514
|
+
{navLinks.map((link) => (
|
|
515
|
+
<a key={link.href} href={link.href}>{link.href}</a>
|
|
516
|
+
))}
|
|
517
|
+
</div>
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
`;
|
|
521
|
+
const result = transform(code);
|
|
522
|
+
// Should auto-inject __i parameter and use it in hash
|
|
523
|
+
expect(result).toContain(".map((link, __i)");
|
|
524
|
+
expect(result).toContain("data-upstart-hash={`");
|
|
525
|
+
expect(result).toContain("${__i}`}");
|
|
526
|
+
});
|
|
527
|
+
|
|
508
528
|
test("should handle complex array expressions", () => {
|
|
509
529
|
const code = `
|
|
510
530
|
export default function App() {
|
|
@@ -524,6 +544,27 @@ describe("upstart-editor-vite-plugin", () => {
|
|
|
524
544
|
expect(result).toContain('data-upstart-loop-item="user"');
|
|
525
545
|
expect(result).toContain('data-upstart-loop-array="data.users"');
|
|
526
546
|
});
|
|
547
|
+
|
|
548
|
+
test("should auto-inject index parameter for destructured .map() callback", () => {
|
|
549
|
+
const code = `
|
|
550
|
+
export default function App() {
|
|
551
|
+
const skillsByCategory = { "Web": ["React"] };
|
|
552
|
+
return (
|
|
553
|
+
<div>
|
|
554
|
+
{Object.entries(skillsByCategory).map(([category, skills]) => (
|
|
555
|
+
<div key={category}>{category}</div>
|
|
556
|
+
))}
|
|
557
|
+
</div>
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
`;
|
|
561
|
+
|
|
562
|
+
const result = transform(code);
|
|
563
|
+
// Should auto-inject __i parameter
|
|
564
|
+
expect(result).toContain(".map(([category, skills], __i)");
|
|
565
|
+
// Hash should use the injected parameter
|
|
566
|
+
expect(result).toContain("${__i}`}");
|
|
567
|
+
});
|
|
527
568
|
});
|
|
528
569
|
|
|
529
570
|
describe("Edge Cases", () => {
|
|
@@ -207,11 +207,11 @@ export function transformWithOxc(code: string, filePath: string) {
|
|
|
207
207
|
|
|
208
208
|
// Handle .map() calls to track loops (must be before JSXElement)
|
|
209
209
|
if (node.type === "CallExpression") {
|
|
210
|
-
const
|
|
210
|
+
const mapCallInfo = detectAndPatchMapCall(node as CallExpression, state);
|
|
211
211
|
|
|
212
|
-
if (
|
|
212
|
+
if (mapCallInfo) {
|
|
213
213
|
// Push loop context
|
|
214
|
-
state.loopStack.push(
|
|
214
|
+
state.loopStack.push(mapCallInfo);
|
|
215
215
|
|
|
216
216
|
// Continue walking into the callback
|
|
217
217
|
next();
|
|
@@ -247,12 +247,14 @@ export function transformWithOxc(code: string, filePath: string) {
|
|
|
247
247
|
const elementSource = state.code.slice(jsxNode.start, jsxNode.end);
|
|
248
248
|
const hash = hashContent(elementSource);
|
|
249
249
|
|
|
250
|
-
// Collect
|
|
250
|
+
// Collect suffix expressions from all enclosing loops so elements
|
|
251
251
|
// inside .map() get a unique hash per iteration at runtime.
|
|
252
|
-
|
|
252
|
+
// detectAndPatchMapCall() attempts to auto-inject an index param when missing.
|
|
253
|
+
// If injection is not possible (no stable source range), fall back to "0".
|
|
254
|
+
const loopSuffixes = state.loopStack.map((l) => (l.indexName ? `\${${l.indexName}}` : "0"));
|
|
253
255
|
|
|
254
|
-
if (
|
|
255
|
-
const suffix =
|
|
256
|
+
if (loopSuffixes.length > 0) {
|
|
257
|
+
const suffix = loopSuffixes.join("-");
|
|
256
258
|
attributes.push(`data-upstart-hash={\`${hash}-${suffix}\`}`);
|
|
257
259
|
} else {
|
|
258
260
|
attributes.push(`data-upstart-hash="${hash}"`);
|
|
@@ -570,8 +572,8 @@ function exprToString(expr: Expression, code: string): string {
|
|
|
570
572
|
return "";
|
|
571
573
|
}
|
|
572
574
|
|
|
573
|
-
// Helper: Detect .map() calls and
|
|
574
|
-
function
|
|
575
|
+
// Helper: Detect .map() calls and auto-inject index parameter if missing
|
|
576
|
+
function detectAndPatchMapCall(node: CallExpression, state: TransformState): LoopContext | null {
|
|
575
577
|
// Check if this is a .map() call
|
|
576
578
|
if (
|
|
577
579
|
node.callee.type !== "MemberExpression" ||
|
|
@@ -602,10 +604,43 @@ function detectMapCall(node: CallExpression, code: string): LoopContext | null {
|
|
|
602
604
|
|
|
603
605
|
// Extract parameter names
|
|
604
606
|
const itemName = itemParam.type === "Identifier" ? itemParam.name : "item";
|
|
605
|
-
|
|
607
|
+
let indexName = indexParam?.type === "Identifier" ? indexParam.name : null;
|
|
608
|
+
|
|
609
|
+
// If no index parameter exists in arrow function, auto-inject it
|
|
610
|
+
if (!indexName && callback.type === "ArrowFunctionExpression") {
|
|
611
|
+
indexName = "__i";
|
|
612
|
+
|
|
613
|
+
// Find the closing paren of the parameter list safely
|
|
614
|
+
const lastParam = params[params.length - 1];
|
|
615
|
+
|
|
616
|
+
// ESTree `Pattern` type doesn't guarantee source range fields in typings.
|
|
617
|
+
// Guard before reading offsets.
|
|
618
|
+
if (!hasRange(lastParam)) {
|
|
619
|
+
// Can't patch callback params without a stable source offset.
|
|
620
|
+
// Keep indexName as null so hash suffix generation can handle this case.
|
|
621
|
+
indexName = null;
|
|
622
|
+
return {
|
|
623
|
+
itemName,
|
|
624
|
+
indexName,
|
|
625
|
+
arrayExpr: exprToString(node.callee.object as Expression, state.code),
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
let searchPos = lastParam.end;
|
|
630
|
+
|
|
631
|
+
// Find the next ) after the last parameter
|
|
632
|
+
while (searchPos < state.code.length && state.code[searchPos] !== ")") {
|
|
633
|
+
searchPos++;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (searchPos < state.code.length && state.code[searchPos] === ")") {
|
|
637
|
+
// Insert the parameter before the closing paren
|
|
638
|
+
state.s.appendLeft(searchPos, ", __i");
|
|
639
|
+
}
|
|
640
|
+
}
|
|
606
641
|
|
|
607
642
|
// Get the array expression
|
|
608
|
-
const arrayExpr = exprToString(node.callee.object as Expression, code);
|
|
643
|
+
const arrayExpr = exprToString(node.callee.object as Expression, state.code);
|
|
609
644
|
|
|
610
645
|
return {
|
|
611
646
|
itemName,
|
|
@@ -132,6 +132,10 @@ function handleParentMessage(event: MessageEvent): void {
|
|
|
132
132
|
if (el) {
|
|
133
133
|
el.className = message.className;
|
|
134
134
|
}
|
|
135
|
+
} else if (message.type === "request-scroll-position") {
|
|
136
|
+
sendToParent({ type: "scroll-position", x: window.scrollX, y: window.scrollY });
|
|
137
|
+
} else if (message.type === "restore-scroll-position") {
|
|
138
|
+
window.scrollTo({ left: message.x, top: message.y, behavior: "auto" });
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
|
|
@@ -40,6 +40,7 @@ export type EditorMessage =
|
|
|
40
40
|
| { type: "text-edit"; payload: PayloadEditText }
|
|
41
41
|
| { type: "editor-ready" }
|
|
42
42
|
| { type: "editor-navigated" }
|
|
43
|
+
| { type: "scroll-position"; x: number; y: number }
|
|
43
44
|
| { type: "editor-error"; error: string }
|
|
44
45
|
| {
|
|
45
46
|
type: "element-clicked";
|
|
@@ -59,7 +60,9 @@ export type EditorMessage =
|
|
|
59
60
|
*/
|
|
60
61
|
export type ParentMessage =
|
|
61
62
|
| { type: "set-mode"; mode: EditorMode }
|
|
62
|
-
| { type: "preview-classname"; classNameId: string; className: string }
|
|
63
|
+
| { type: "preview-classname"; classNameId: string; className: string }
|
|
64
|
+
| { type: "request-scroll-position" }
|
|
65
|
+
| { type: "restore-scroll-position"; x: number; y: number };
|
|
63
66
|
|
|
64
67
|
/**
|
|
65
68
|
* Message wrapper sent via postMessage from iframe.
|