@upstart.gg/vite-plugins 0.0.37
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 +29 -0
- package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-attrs.js +323 -0
- package/dist/vite-plugin-upstart-attrs.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/plugin.d.ts +15 -0
- package/dist/vite-plugin-upstart-editor/plugin.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/plugin.js +55 -0
- package/dist/vite-plugin-upstart-editor/plugin.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts +12 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +57 -0
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts +12 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js +91 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts +22 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.js +62 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts +15 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +292 -0
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +126 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.js +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.d.ts +15 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.js +26 -0
- package/dist/vite-plugin-upstart-editor/runtime/utils.js.map +1 -0
- package/dist/vite-plugin-upstart-theme.d.ts +22 -0
- package/dist/vite-plugin-upstart-theme.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-theme.js +179 -0
- package/dist/vite-plugin-upstart-theme.js.map +1 -0
- package/package.json +63 -0
- package/src/tests/fixtures/routes/default-layout.tsx +10 -0
- package/src/tests/fixtures/routes/dynamic-route.tsx +10 -0
- package/src/tests/fixtures/routes/missing-attributes.tsx +8 -0
- package/src/tests/fixtures/routes/missing-path.tsx +9 -0
- package/src/tests/fixtures/routes/valid-full.tsx +15 -0
- package/src/tests/fixtures/routes/valid-minimal.tsx +10 -0
- package/src/tests/fixtures/routes/with-comments.tsx +12 -0
- package/src/tests/fixtures/routes/with-nested-objects.tsx +15 -0
- package/src/tests/upstart-editor-api.test.ts +367 -0
- package/src/tests/vite-plugin-upstart-attrs.test.ts +1189 -0
- package/src/tests/vite-plugin-upstart-editor.test.ts +81 -0
- package/src/upstart-editor-api.ts +204 -0
- package/src/vite-plugin-upstart-attrs.ts +708 -0
- package/src/vite-plugin-upstart-editor/PLAN.md +1391 -0
- package/src/vite-plugin-upstart-editor/plugin.ts +73 -0
- package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +80 -0
- package/src/vite-plugin-upstart-editor/runtime/hover-overlay.ts +135 -0
- package/src/vite-plugin-upstart-editor/runtime/index.ts +90 -0
- package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +401 -0
- package/src/vite-plugin-upstart-editor/runtime/types.ts +120 -0
- package/src/vite-plugin-upstart-editor/runtime/utils.ts +34 -0
- package/src/vite-plugin-upstart-theme.ts +314 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as unplugin5 from "unplugin";
|
|
2
|
+
import * as magic_string0 from "magic-string";
|
|
3
|
+
|
|
4
|
+
//#region src/vite-plugin-upstart-attrs.d.ts
|
|
5
|
+
interface Options {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
emitRegistry?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface EditableEntry {
|
|
10
|
+
file: string;
|
|
11
|
+
type: "text" | "className";
|
|
12
|
+
startOffset: number;
|
|
13
|
+
endOffset: number;
|
|
14
|
+
originalContent: string;
|
|
15
|
+
context: {
|
|
16
|
+
parentTag: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
declare function getRegistry(): Record<string, EditableEntry>;
|
|
20
|
+
declare function clearRegistry(): void;
|
|
21
|
+
declare const upstartEditor: unplugin5.UnpluginInstance<Options, boolean>;
|
|
22
|
+
declare function transformWithOxc(code: string, filePath: string): {
|
|
23
|
+
code: string;
|
|
24
|
+
map: magic_string0.SourceMap;
|
|
25
|
+
} | null;
|
|
26
|
+
declare const _default: (options: Options) => unplugin5.VitePlugin<any> | unplugin5.VitePlugin<any>[];
|
|
27
|
+
//#endregion
|
|
28
|
+
export { EditableEntry, clearRegistry, _default as default, getRegistry, transformWithOxc, upstartEditor };
|
|
29
|
+
//# sourceMappingURL=vite-plugin-upstart-attrs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin-upstart-attrs.d.ts","names":[],"sources":["../src/vite-plugin-upstart-attrs.ts"],"sourcesContent":[],"mappings":";;;;UAgBU,OAAA;;;;AAAA,UAuCO,aAAA,CAvCA;EAuCA,IAAA,EAAA,MAAA;EAoBD,IAAA,EAAA,MAAA,GAAW,WAAmB;EAK9B,WAAA,EAAA,MAAa;EAWhB,SAAA,EAAA,MAkEX;EAEc,eAAA,EAAA,MAAgB;EA+L/B,OAAA,EAAA;;;;iBAnRe,WAAA,CAAA,GAAe,eAAe;iBAK9B,aAAA,CAAA;cAWH,eAAa,SAAA,CAAA,iBAAA;iBAoEV,gBAAA;;OAAgB,aAAA,CAAA;;cA+L/B"}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { createUnplugin } from "unplugin";
|
|
2
|
+
import { parseSync } from "oxc-parser";
|
|
3
|
+
import { walk } from "zimmerframe";
|
|
4
|
+
import MagicString from "magic-string";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
//#region src/vite-plugin-upstart-attrs.ts
|
|
8
|
+
function hasRange(node) {
|
|
9
|
+
return node && typeof node.start === "number" && typeof node.end === "number";
|
|
10
|
+
}
|
|
11
|
+
function hashContent(content) {
|
|
12
|
+
let hash = 5381;
|
|
13
|
+
for (let i = 0; i < content.length; i++) hash = (hash << 5) + hash ^ content.charCodeAt(i);
|
|
14
|
+
return (hash >>> 0).toString(16);
|
|
15
|
+
}
|
|
16
|
+
const editableRegistry = /* @__PURE__ */ new Map();
|
|
17
|
+
function generateId(filePath, node) {
|
|
18
|
+
return `${filePath}:${node.start}`;
|
|
19
|
+
}
|
|
20
|
+
function getRegistry() {
|
|
21
|
+
return Object.fromEntries(editableRegistry);
|
|
22
|
+
}
|
|
23
|
+
function clearRegistry() {
|
|
24
|
+
editableRegistry.clear();
|
|
25
|
+
}
|
|
26
|
+
const upstartEditor = createUnplugin((options) => {
|
|
27
|
+
if (!options.enabled) return { name: "upstart-editor-disabled" };
|
|
28
|
+
const emitRegistry = options.emitRegistry ?? true;
|
|
29
|
+
let root = process.cwd();
|
|
30
|
+
return {
|
|
31
|
+
name: "upstart-editor",
|
|
32
|
+
enforce: "pre",
|
|
33
|
+
vite: {
|
|
34
|
+
configResolved(config) {
|
|
35
|
+
root = config.root;
|
|
36
|
+
},
|
|
37
|
+
generateBundle() {
|
|
38
|
+
if (!emitRegistry || editableRegistry.size === 0) return;
|
|
39
|
+
const registry = {
|
|
40
|
+
version: 1,
|
|
41
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
42
|
+
elements: Object.fromEntries(editableRegistry)
|
|
43
|
+
};
|
|
44
|
+
this.emitFile({
|
|
45
|
+
type: "asset",
|
|
46
|
+
fileName: "upstart-registry.json",
|
|
47
|
+
source: JSON.stringify(registry, null, 2)
|
|
48
|
+
});
|
|
49
|
+
editableRegistry.clear();
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
transformInclude(id) {
|
|
53
|
+
return /\.(tsx|jsx)$/.test(id) && !id.includes("node_modules");
|
|
54
|
+
},
|
|
55
|
+
transform(code, id) {
|
|
56
|
+
if (!code.includes("<")) return null;
|
|
57
|
+
try {
|
|
58
|
+
const result = transformWithOxc(code, path.relative(root, id));
|
|
59
|
+
if (!result) return null;
|
|
60
|
+
return {
|
|
61
|
+
code: result.code,
|
|
62
|
+
map: result.map
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(`Error transforming ${id}:`, error);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
function transformWithOxc(code, filePath) {
|
|
72
|
+
const ast = parseSync(filePath, code, { sourceType: "module" });
|
|
73
|
+
if (!ast.program) return null;
|
|
74
|
+
const s = new MagicString(code);
|
|
75
|
+
const state = {
|
|
76
|
+
filePath,
|
|
77
|
+
code,
|
|
78
|
+
s,
|
|
79
|
+
loopStack: []
|
|
80
|
+
};
|
|
81
|
+
let modified = false;
|
|
82
|
+
walk(ast.program, state, { _(node, { state: state$1, next }) {
|
|
83
|
+
if (node.type === "VariableDeclaration") checkForbiddenUseTranslation(node, state$1.filePath);
|
|
84
|
+
if (node.type === "CallExpression") {
|
|
85
|
+
const loopInfo = detectMapCall(node, state$1.code);
|
|
86
|
+
if (loopInfo) {
|
|
87
|
+
state$1.loopStack.push(loopInfo);
|
|
88
|
+
next();
|
|
89
|
+
state$1.loopStack.pop();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (node.type === "JSXElement") {
|
|
94
|
+
const jsxNode = node;
|
|
95
|
+
const opening = jsxNode.openingElement;
|
|
96
|
+
const tagName = getJSXElementName(opening);
|
|
97
|
+
const insertPos = getAttributeInsertPosition(opening, state$1.code);
|
|
98
|
+
const attributes = [];
|
|
99
|
+
if (hasRange(jsxNode)) {
|
|
100
|
+
const hash = hashContent(state$1.code.slice(jsxNode.start, jsxNode.end));
|
|
101
|
+
attributes.push(`data-upstart-hash="${hash}"`);
|
|
102
|
+
}
|
|
103
|
+
if (isTextLeafElement(jsxNode)) {
|
|
104
|
+
attributes.push("data-upstart-editable-text=\"true\"");
|
|
105
|
+
const textChild = jsxNode.children.find((c) => c.type === "JSXText" && c.value?.trim());
|
|
106
|
+
if (textChild && hasRange(textChild)) {
|
|
107
|
+
const id = generateId(state$1.filePath, textChild);
|
|
108
|
+
const textValue = textChild.value;
|
|
109
|
+
const trimmedStart = textChild.start + (textValue.length - textValue.trimStart().length);
|
|
110
|
+
const trimmedEnd = textChild.end - (textValue.length - textValue.trimEnd().length);
|
|
111
|
+
editableRegistry.set(id, {
|
|
112
|
+
file: state$1.filePath,
|
|
113
|
+
type: "text",
|
|
114
|
+
startOffset: trimmedStart,
|
|
115
|
+
endOffset: trimmedEnd,
|
|
116
|
+
originalContent: textValue.trim(),
|
|
117
|
+
context: { parentTag: tagName || "unknown" }
|
|
118
|
+
});
|
|
119
|
+
attributes.push(`data-upstart-id="${id}"`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const transInfo = detectTransComponent(jsxNode, state$1.code);
|
|
123
|
+
if (transInfo) {
|
|
124
|
+
attributes.push("data-upstart-editable-text=\"true\"");
|
|
125
|
+
attributes.push(`data-i18n-key="${escapeProp(transInfo.fullKey)}"`);
|
|
126
|
+
}
|
|
127
|
+
const classNameAttr = opening.attributes.find((attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === "className" && attr.value?.type === "Literal" && typeof attr.value.value === "string");
|
|
128
|
+
if (classNameAttr && classNameAttr.value && hasRange(classNameAttr.value)) {
|
|
129
|
+
const id = generateId(state$1.filePath, classNameAttr.value);
|
|
130
|
+
const classValue = classNameAttr.value.value;
|
|
131
|
+
editableRegistry.set(id, {
|
|
132
|
+
file: state$1.filePath,
|
|
133
|
+
type: "className",
|
|
134
|
+
startOffset: classNameAttr.value.start + 1,
|
|
135
|
+
endOffset: classNameAttr.value.end - 1,
|
|
136
|
+
originalContent: classValue,
|
|
137
|
+
context: { parentTag: tagName || "unknown" }
|
|
138
|
+
});
|
|
139
|
+
attributes.push(`data-upstart-classname-id="${id}"`);
|
|
140
|
+
}
|
|
141
|
+
if (tagName && /^[A-Z]/.test(tagName)) {
|
|
142
|
+
attributes.push(`data-upstart-file="${escapeProp(state$1.filePath)}"`);
|
|
143
|
+
attributes.push(`data-upstart-component="${escapeProp(tagName)}"`);
|
|
144
|
+
if (state$1.loopStack.length > 0) {
|
|
145
|
+
const loop = state$1.loopStack[state$1.loopStack.length - 1];
|
|
146
|
+
attributes.push(`data-upstart-loop-item="${escapeProp(loop.itemName)}"`);
|
|
147
|
+
if (loop.indexName) attributes.push(`data-upstart-loop-index={${loop.indexName.toLowerCase()}}`);
|
|
148
|
+
attributes.push(`data-upstart-loop-array="${escapeProp(loop.arrayExpr)}"`);
|
|
149
|
+
}
|
|
150
|
+
for (const attr of opening.attributes) {
|
|
151
|
+
if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier") continue;
|
|
152
|
+
const propName = attr.name.name;
|
|
153
|
+
const binding = analyzeBinding(attr.value, state$1.code);
|
|
154
|
+
if (binding) {
|
|
155
|
+
attributes.push(`data-upstart-prop-${propName.toLowerCase()}="${escapeProp(binding.path)}"`);
|
|
156
|
+
if (binding.datasource) attributes.push(`data-upstart-datasource-${propName.toLowerCase()}="${escapeProp(binding.datasource)}"`);
|
|
157
|
+
if (binding.recordId) attributes.push(`data-upstart-record-id-${propName.toLowerCase()}={${binding.recordId}}`);
|
|
158
|
+
if (binding.isConditional) attributes.push(`data-upstart-conditional-${propName.toLowerCase()}="true"`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (insertPos !== -1 && attributes.length > 0) {
|
|
163
|
+
const attrString = " " + attributes.join(" ");
|
|
164
|
+
state$1.s.appendLeft(insertPos, attrString);
|
|
165
|
+
modified = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
next();
|
|
169
|
+
} });
|
|
170
|
+
if (!modified) return null;
|
|
171
|
+
return {
|
|
172
|
+
code: s.toString(),
|
|
173
|
+
map: s.generateMap({ hires: true })
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function getJSXElementName(opening) {
|
|
177
|
+
if (opening.name.type === "JSXIdentifier") return opening.name.name;
|
|
178
|
+
if (opening.name.type === "JSXMemberExpression") {
|
|
179
|
+
let current = opening.name;
|
|
180
|
+
while (current.property) {
|
|
181
|
+
if (current.property.type === "JSXIdentifier") return current.property.name;
|
|
182
|
+
if (current.type === "JSXMemberExpression") current = current.property;
|
|
183
|
+
else break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
function getAttributeInsertPosition(opening, code) {
|
|
189
|
+
if (opening.attributes.length > 0) {
|
|
190
|
+
const firstAttr = opening.attributes[0];
|
|
191
|
+
if (hasRange(firstAttr)) return firstAttr.start;
|
|
192
|
+
}
|
|
193
|
+
if (opening.name.type === "JSXIdentifier" && hasRange(opening.name)) return opening.name.end;
|
|
194
|
+
if (opening.name.type === "JSXMemberExpression" && hasRange(opening.name)) return opening.name.end;
|
|
195
|
+
return -1;
|
|
196
|
+
}
|
|
197
|
+
function analyzeBinding(value, code) {
|
|
198
|
+
if (!value) return null;
|
|
199
|
+
if (value.type === "JSXExpressionContainer") {
|
|
200
|
+
const expr = value.expression;
|
|
201
|
+
if (expr.type === "JSXEmptyExpression") return null;
|
|
202
|
+
return analyzeExpression(expr, code);
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
function analyzeExpression(expr, code) {
|
|
207
|
+
if (expr.type === "MemberExpression") return {
|
|
208
|
+
path: exprToString(expr, code),
|
|
209
|
+
datasource: extractDatasource(expr),
|
|
210
|
+
recordId: extractRecordId(expr)
|
|
211
|
+
};
|
|
212
|
+
if (expr.type === "Identifier") return { path: expr.name };
|
|
213
|
+
if (expr.type === "ConditionalExpression") {
|
|
214
|
+
const consequent = analyzeExpression(expr.consequent, code);
|
|
215
|
+
if (consequent) return {
|
|
216
|
+
...consequent,
|
|
217
|
+
isConditional: true,
|
|
218
|
+
path: exprToString(expr, code)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (expr.type === "LogicalExpression") {
|
|
222
|
+
const right = analyzeExpression(expr.right, code);
|
|
223
|
+
if (right) return {
|
|
224
|
+
...right,
|
|
225
|
+
isConditional: true,
|
|
226
|
+
path: exprToString(expr, code)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const exprWithRange = expr;
|
|
230
|
+
if (exprWithRange.start !== void 0 && exprWithRange.end !== void 0) return { path: code.slice(exprWithRange.start, exprWithRange.end) };
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
function exprToString(expr, code) {
|
|
234
|
+
const exprWithRange = expr;
|
|
235
|
+
if (exprWithRange.start !== void 0 && exprWithRange.end !== void 0) return code.slice(exprWithRange.start, exprWithRange.end);
|
|
236
|
+
if (expr.type === "Identifier") return expr.name;
|
|
237
|
+
if (expr.type === "MemberExpression") {
|
|
238
|
+
const obj = exprToString(expr.object, code);
|
|
239
|
+
const prop = expr.property.type === "Identifier" && !expr.computed ? expr.property.name : exprToString(expr.property, code);
|
|
240
|
+
return expr.computed ? `${obj}[${prop}]` : `${obj}.${prop}`;
|
|
241
|
+
}
|
|
242
|
+
if (expr.type === "ConditionalExpression") return `${exprToString(expr.test, code)} ? ${exprToString(expr.consequent, code)} : ${exprToString(expr.alternate, code)}`;
|
|
243
|
+
if (expr.type === "LogicalExpression") {
|
|
244
|
+
const left = exprToString(expr.left, code);
|
|
245
|
+
const right = exprToString(expr.right, code);
|
|
246
|
+
return `${left} ${expr.operator} ${right}`;
|
|
247
|
+
}
|
|
248
|
+
return "";
|
|
249
|
+
}
|
|
250
|
+
function extractDatasource(expr) {
|
|
251
|
+
let current = expr.object;
|
|
252
|
+
while (current.type === "MemberExpression") current = current.object;
|
|
253
|
+
if (current.type === "Identifier") return current.name;
|
|
254
|
+
}
|
|
255
|
+
function extractRecordId(expr) {
|
|
256
|
+
const obj = expr.object;
|
|
257
|
+
if (obj.type === "Identifier") return `${obj.name}.id`;
|
|
258
|
+
if (obj.type === "MemberExpression") {
|
|
259
|
+
const datasource = extractDatasource(obj);
|
|
260
|
+
if (datasource) return `${datasource}.id`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function detectMapCall(node, code) {
|
|
264
|
+
if (node.callee.type !== "MemberExpression" || node.callee.property.type !== "Identifier" || node.callee.property.name !== "map") return null;
|
|
265
|
+
const callback = node.arguments[0];
|
|
266
|
+
if (!callback) return null;
|
|
267
|
+
if (callback.type !== "ArrowFunctionExpression" && callback.type !== "FunctionExpression") return null;
|
|
268
|
+
const params = callback.params;
|
|
269
|
+
const itemParam = params[0];
|
|
270
|
+
const indexParam = params[1];
|
|
271
|
+
if (!itemParam) return null;
|
|
272
|
+
return {
|
|
273
|
+
itemName: itemParam.type === "Identifier" ? itemParam.name : "item",
|
|
274
|
+
indexName: indexParam?.type === "Identifier" ? indexParam.name : null,
|
|
275
|
+
arrayExpr: exprToString(node.callee.object, code)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function escapeProp(value) {
|
|
279
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
280
|
+
}
|
|
281
|
+
function checkForbiddenUseTranslation(node, filePath) {
|
|
282
|
+
if (node.type !== "VariableDeclaration") return;
|
|
283
|
+
const decl = node;
|
|
284
|
+
for (const declarator of decl.declarations) {
|
|
285
|
+
if (declarator.id?.type !== "ObjectPattern" || !declarator.init || declarator.init.type !== "CallExpression") continue;
|
|
286
|
+
const callExpr = declarator.init;
|
|
287
|
+
if (callExpr.callee?.type !== "Identifier" || callExpr.callee.name !== "useTranslation") continue;
|
|
288
|
+
for (const prop of declarator.id.properties) {
|
|
289
|
+
if (prop.type !== "Property") continue;
|
|
290
|
+
if (prop.key?.type === "Identifier" && prop.key.name === "t") throw new Error(`[${filePath}] useTranslation hook is forbidden for translations. Use <Trans i18nKey="..." /> component instead. Only { i18n } destructuring is allowed (for language switching).`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function detectTransComponent(jsxElement, code) {
|
|
295
|
+
const opening = jsxElement.openingElement;
|
|
296
|
+
if (getJSXElementName(opening) !== "Trans") return null;
|
|
297
|
+
let i18nKey = null;
|
|
298
|
+
let namespace = "translation";
|
|
299
|
+
for (const attr of opening.attributes) {
|
|
300
|
+
if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier") continue;
|
|
301
|
+
const attrName = attr.name.name;
|
|
302
|
+
if (attrName === "i18nKey" && attr.value?.type === "Literal") i18nKey = attr.value.value;
|
|
303
|
+
if (attrName === "ns" && attr.value?.type === "Literal") namespace = attr.value.value;
|
|
304
|
+
}
|
|
305
|
+
if (!i18nKey) return null;
|
|
306
|
+
return {
|
|
307
|
+
fullKey: `${namespace}:${i18nKey}`,
|
|
308
|
+
key: i18nKey,
|
|
309
|
+
namespace
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function isTextLeafElement(jsxElement) {
|
|
313
|
+
let hasText = false;
|
|
314
|
+
for (const child of jsxElement.children) if (child.type === "JSXText") {
|
|
315
|
+
if (child.value?.trim()) hasText = true;
|
|
316
|
+
} else if (child.type === "JSXElement" || child.type === "JSXFragment" || child.type === "JSXExpressionContainer" || child.type === "JSXSpreadChild") return false;
|
|
317
|
+
return hasText;
|
|
318
|
+
}
|
|
319
|
+
var vite_plugin_upstart_attrs_default = upstartEditor.vite;
|
|
320
|
+
|
|
321
|
+
//#endregion
|
|
322
|
+
export { clearRegistry, vite_plugin_upstart_attrs_default as default, getRegistry, transformWithOxc, upstartEditor };
|
|
323
|
+
//# sourceMappingURL=vite-plugin-upstart-attrs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-plugin-upstart-attrs.js","names":["state: TransformState","state","attributes: string[]","current: Expression","i18nKey: string | null"],"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 MemberExpression,\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\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}\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}\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 };\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 // ENFORCE: Throw error if useTranslation is used for translations (destructuring `t`)\n if (node.type === \"VariableDeclaration\") {\n checkForbiddenUseTranslation(node, state.filePath);\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 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 attributes.push(`data-upstart-hash=\"${hash}\"`);\n }\n\n // Check for text leaf elements (any element with only static text)\n if (isTextLeafElement(jsxNode)) {\n attributes.push('data-upstart-editable-text=\"true\"');\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\n // Check if this element IS a <Trans> component - add i18n tracking attributes\n const transInfo = detectTransComponent(jsxNode, state.code);\n if (transInfo) {\n attributes.push('data-upstart-editable-text=\"true\"');\n attributes.push(`data-i18n-key=\"${escapeProp(transInfo.fullKey)}\"`);\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.toLowerCase()}}`);\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 if (binding.datasource) {\n attributes.push(\n `data-upstart-datasource-${propName.toLowerCase()}=\"${escapeProp(binding.datasource)}\"`,\n );\n }\n\n if (binding.recordId) {\n attributes.push(`data-upstart-record-id-${propName.toLowerCase()}={${binding.recordId}}`);\n }\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 datasource?: string;\n recordId?: 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 datasource?: string;\n recordId?: 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 const datasource = extractDatasource(expr);\n const recordId = extractRecordId(expr);\n\n return { path, datasource, recordId };\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: Extract datasource name from member expression\nfunction extractDatasource(expr: MemberExpression): string | undefined {\n let current: Expression = expr.object as Expression;\n\n // Traverse to the root object\n while (current.type === \"MemberExpression\") {\n current = current.object as Expression;\n }\n\n if (current.type === \"Identifier\") {\n return current.name;\n }\n\n return undefined;\n}\n\n// Helper: Extract record ID reference\nfunction extractRecordId(expr: MemberExpression): string | undefined {\n const obj = expr.object;\n\n if (obj.type === \"Identifier\") {\n // Assume the ID field is `${object}.id`\n return `${obj.name}.id`;\n }\n\n if (obj.type === \"MemberExpression\") {\n // For nested objects like user.profile.id\n const datasource = extractDatasource(obj);\n if (datasource) {\n return `${datasource}.id`;\n }\n }\n\n return undefined;\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: ENFORCE forbidden useTranslation usage - throw error if `t` is destructured\n// Only `{ i18n }` is allowed (for language switching), not `{ t }` (use <Trans> instead)\nfunction checkForbiddenUseTranslation(node: Node, filePath: string): 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 // Check if `t` is being destructured (forbidden)\n for (const prop of declarator.id.properties) {\n if (prop.type !== \"Property\") continue;\n\n if (prop.key?.type === \"Identifier\" && prop.key.name === \"t\") {\n throw new Error(\n `[${filePath}] useTranslation hook is forbidden for translations. ` +\n `Use <Trans i18nKey=\"...\" /> component instead. ` +\n `Only { i18n } destructuring is allowed (for language switching).`,\n );\n }\n }\n }\n}\n\n// Helper: Detect <Trans i18nKey=\"...\" /> component and extract i18n key info\nfunction detectTransComponent(jsxElement: JSXElement, code: string): I18nKeyInfo | null {\n const opening = jsxElement.openingElement;\n const tagName = getJSXElementName(opening);\n\n if (tagName !== \"Trans\") return null;\n\n let i18nKey: string | null = null;\n let namespace = \"translation\";\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 // Extract i18nKey prop value\n if (attrName === \"i18nKey\" && attr.value?.type === \"Literal\") {\n i18nKey = (attr.value as any).value as string;\n }\n\n // Extract ns prop value (optional namespace override)\n if (attrName === \"ns\" && attr.value?.type === \"Literal\") {\n namespace = (attr.value as any).value as string;\n }\n }\n\n if (!i18nKey) return null;\n\n return {\n fullKey: `${namespace}:${i18nKey}`,\n key: i18nKey,\n namespace,\n };\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\nexport default upstartEditor.vite;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { UpstartEditorPluginOptions } from "./runtime/types.js";
|
|
2
|
+
import * as unplugin2 from "unplugin";
|
|
3
|
+
|
|
4
|
+
//#region src/vite-plugin-upstart-editor/plugin.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Upstart Visual Editor Vite plugin (build-time)
|
|
8
|
+
*
|
|
9
|
+
* Injects the editor runtime into the app entry during build.
|
|
10
|
+
*/
|
|
11
|
+
declare const upstartEditor: unplugin2.UnpluginInstance<UpstartEditorPluginOptions, boolean>;
|
|
12
|
+
declare const _default: (options: UpstartEditorPluginOptions) => unplugin2.VitePlugin<any> | unplugin2.VitePlugin<any>[];
|
|
13
|
+
//#endregion
|
|
14
|
+
export { _default as default, upstartEditor };
|
|
15
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/vite-plugin-upstart-editor/plugin.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAeA;AAuDG;cAvDU,eAAa,SAAA,CAAA,iBAAA;cAuDvB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createUnplugin } from "unplugin";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
//#region src/vite-plugin-upstart-editor/plugin.ts
|
|
6
|
+
const DEFAULT_OPTIONS = {
|
|
7
|
+
enabled: false,
|
|
8
|
+
autoInject: true
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Upstart Visual Editor Vite plugin (build-time)
|
|
12
|
+
*
|
|
13
|
+
* Injects the editor runtime into the app entry during build.
|
|
14
|
+
*/
|
|
15
|
+
const upstartEditor = createUnplugin((options = {}) => {
|
|
16
|
+
const { enabled, autoInject } = {
|
|
17
|
+
...DEFAULT_OPTIONS,
|
|
18
|
+
...options
|
|
19
|
+
};
|
|
20
|
+
if (!enabled) return { name: "upstart-editor-disabled" };
|
|
21
|
+
const runtimePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "./runtime/index");
|
|
22
|
+
return {
|
|
23
|
+
name: "upstart-editor",
|
|
24
|
+
enforce: "pre",
|
|
25
|
+
transform(code, id) {
|
|
26
|
+
if (!autoInject) return null;
|
|
27
|
+
const [cleanId] = id.split("?");
|
|
28
|
+
if (!cleanId || cleanId.includes("node_modules")) return null;
|
|
29
|
+
if (!/\.(t|j)sx?$/.test(cleanId)) return null;
|
|
30
|
+
const filename = path.basename(cleanId);
|
|
31
|
+
if (!filename.startsWith("main.") && !filename.startsWith("index.")) return null;
|
|
32
|
+
if (code.includes("initUpstartEditor")) return null;
|
|
33
|
+
return {
|
|
34
|
+
code: `${[
|
|
35
|
+
`import { initUpstartEditor } from ${JSON.stringify(runtimePath)};`,
|
|
36
|
+
"",
|
|
37
|
+
"if (typeof window !== 'undefined') {",
|
|
38
|
+
" if (document.readyState === 'loading') {",
|
|
39
|
+
" document.addEventListener('DOMContentLoaded', () => initUpstartEditor());",
|
|
40
|
+
" } else {",
|
|
41
|
+
" initUpstartEditor();",
|
|
42
|
+
" }",
|
|
43
|
+
"}",
|
|
44
|
+
""
|
|
45
|
+
].join("\n")}${code}`,
|
|
46
|
+
map: null
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
var plugin_default = upstartEditor.vite;
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { plugin_default as default, upstartEditor };
|
|
55
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["DEFAULT_OPTIONS: Required<UpstartEditorPluginOptions>"],"sources":["../../src/vite-plugin-upstart-editor/plugin.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createUnplugin } from \"unplugin\";\nimport type { UpstartEditorPluginOptions } from \"./runtime/types.js\";\n\nconst DEFAULT_OPTIONS: Required<UpstartEditorPluginOptions> = {\n enabled: false,\n autoInject: true,\n};\n\n/**\n * Upstart Visual Editor Vite plugin (build-time)\n *\n * Injects the editor runtime into the app entry during build.\n */\nexport const upstartEditor = createUnplugin<UpstartEditorPluginOptions>((options = {}) => {\n const { enabled, autoInject } = { ...DEFAULT_OPTIONS, ...options };\n\n if (!enabled) {\n return { name: \"upstart-editor-disabled\" };\n }\n\n const runtimePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), \"./runtime/index\");\n\n return {\n name: \"upstart-editor\",\n enforce: \"pre\",\n\n transform(code, id) {\n if (!autoInject) {\n return null;\n }\n\n const [cleanId] = id.split(\"?\");\n if (!cleanId || cleanId.includes(\"node_modules\")) {\n return null;\n }\n\n if (!/\\.(t|j)sx?$/.test(cleanId)) {\n return null;\n }\n\n const filename = path.basename(cleanId);\n if (!filename.startsWith(\"main.\") && !filename.startsWith(\"index.\")) {\n return null;\n }\n\n if (code.includes(\"initUpstartEditor\")) {\n return null;\n }\n\n const injection = [\n `import { initUpstartEditor } from ${JSON.stringify(runtimePath)};`,\n \"\",\n \"if (typeof window !== 'undefined') {\",\n \" if (document.readyState === 'loading') {\",\n \" document.addEventListener('DOMContentLoaded', () => initUpstartEditor());\",\n \" } else {\",\n \" initUpstartEditor();\",\n \" }\",\n \"}\",\n \"\",\n ].join(\"\\n\");\n\n return {\n code: `${injection}${code}`,\n map: null,\n };\n },\n };\n});\n\nexport default upstartEditor.vite;\n"],"mappings":";;;;;AAKA,MAAMA,kBAAwD;CAC5D,SAAS;CACT,YAAY;CACb;;;;;;AAOD,MAAa,gBAAgB,gBAA4C,UAAU,EAAE,KAAK;CACxF,MAAM,EAAE,SAAS,eAAe;EAAE,GAAG;EAAiB,GAAG;EAAS;AAElE,KAAI,CAAC,QACH,QAAO,EAAE,MAAM,2BAA2B;CAG5C,MAAM,cAAc,KAAK,QAAQ,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EAAE,kBAAkB;AAEjG,QAAO;EACL,MAAM;EACN,SAAS;EAET,UAAU,MAAM,IAAI;AAClB,OAAI,CAAC,WACH,QAAO;GAGT,MAAM,CAAC,WAAW,GAAG,MAAM,IAAI;AAC/B,OAAI,CAAC,WAAW,QAAQ,SAAS,eAAe,CAC9C,QAAO;AAGT,OAAI,CAAC,cAAc,KAAK,QAAQ,CAC9B,QAAO;GAGT,MAAM,WAAW,KAAK,SAAS,QAAQ;AACvC,OAAI,CAAC,SAAS,WAAW,QAAQ,IAAI,CAAC,SAAS,WAAW,SAAS,CACjE,QAAO;AAGT,OAAI,KAAK,SAAS,oBAAoB,CACpC,QAAO;AAgBT,UAAO;IACL,MAAM,GAdU;KAChB,qCAAqC,KAAK,UAAU,YAAY,CAAC;KACjE;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,KAAK,GAGW;IACrB,KAAK;IACN;;EAEJ;EACD;AAEF,qBAAe,cAAc"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/vite-plugin-upstart-editor/runtime/click-handler.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Initialize click handler for className editing.
|
|
4
|
+
*/
|
|
5
|
+
declare function initClickHandler(): void;
|
|
6
|
+
/**
|
|
7
|
+
* Cleanup click handler.
|
|
8
|
+
*/
|
|
9
|
+
declare function cleanupClickHandler(): void;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { cleanupClickHandler, initClickHandler };
|
|
12
|
+
//# sourceMappingURL=click-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"click-handler.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/click-handler.ts"],"sourcesContent":[],"mappings":";;AAQA;AAiBA;iBAjBgB,gBAAA,CAAA;;;;iBAiBA,mBAAA,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { sendToParent } from "./utils.js";
|
|
2
|
+
import { getCurrentMode } from "./index.js";
|
|
3
|
+
|
|
4
|
+
//#region src/vite-plugin-upstart-editor/runtime/click-handler.ts
|
|
5
|
+
let isInitialized = false;
|
|
6
|
+
/**
|
|
7
|
+
* Initialize click handler for className editing.
|
|
8
|
+
*/
|
|
9
|
+
function initClickHandler() {
|
|
10
|
+
if (typeof document === "undefined") return;
|
|
11
|
+
if (isInitialized) return;
|
|
12
|
+
console.log("[Upstart Editor] Initializing click handler...");
|
|
13
|
+
document.addEventListener("click", handleClick, true);
|
|
14
|
+
isInitialized = true;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Cleanup click handler.
|
|
18
|
+
*/
|
|
19
|
+
function cleanupClickHandler() {
|
|
20
|
+
document.removeEventListener("click", handleClick, true);
|
|
21
|
+
isInitialized = false;
|
|
22
|
+
}
|
|
23
|
+
function handleClick(event) {
|
|
24
|
+
if (getCurrentMode() !== "edit") return;
|
|
25
|
+
const target = event.target;
|
|
26
|
+
if (!target) return;
|
|
27
|
+
if (target.closest("[contenteditable='true']")) return;
|
|
28
|
+
const component = target.closest("[data-upstart-component]");
|
|
29
|
+
if (!component) return;
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
event.stopPropagation();
|
|
32
|
+
const hash = component.dataset.upstartHash;
|
|
33
|
+
const componentName = component.dataset.upstartComponent;
|
|
34
|
+
const filePath = component.dataset.upstartFile ?? "";
|
|
35
|
+
if (!hash || !componentName) return;
|
|
36
|
+
const rect = component.getBoundingClientRect();
|
|
37
|
+
sendToParent({
|
|
38
|
+
type: "element-clicked",
|
|
39
|
+
hash,
|
|
40
|
+
componentName,
|
|
41
|
+
filePath,
|
|
42
|
+
currentClassName: component.className,
|
|
43
|
+
bounds: {
|
|
44
|
+
top: rect.top,
|
|
45
|
+
left: rect.left,
|
|
46
|
+
width: rect.width,
|
|
47
|
+
height: rect.height,
|
|
48
|
+
right: rect.right,
|
|
49
|
+
bottom: rect.bottom
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
console.log("[Upstart Editor] Element clicked:", componentName, hash);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { cleanupClickHandler, initClickHandler };
|
|
57
|
+
//# sourceMappingURL=click-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"click-handler.js","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/click-handler.ts"],"sourcesContent":["import { getCurrentMode } from \"./index.js\";\nimport { sendToParent } from \"./utils.js\";\n\nlet isInitialized = false;\n\n/**\n * Initialize click handler for className editing.\n */\nexport function initClickHandler(): void {\n if (typeof document === \"undefined\") {\n return;\n }\n\n if (isInitialized) {\n return;\n }\n\n console.log(\"[Upstart Editor] Initializing click handler...\");\n document.addEventListener(\"click\", handleClick, true);\n isInitialized = true;\n}\n\n/**\n * Cleanup click handler.\n */\nexport function cleanupClickHandler(): void {\n document.removeEventListener(\"click\", handleClick, true);\n isInitialized = false;\n}\n\nfunction handleClick(event: MouseEvent): void {\n if (getCurrentMode() !== \"edit\") {\n return;\n }\n\n const target = event.target as HTMLElement | null;\n if (!target) {\n return;\n }\n\n if (target.closest(\"[contenteditable='true']\")) {\n return;\n }\n\n const component = target.closest<HTMLElement>(\"[data-upstart-component]\");\n if (!component) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n const hash = component.dataset.upstartHash;\n const componentName = component.dataset.upstartComponent;\n const filePath = component.dataset.upstartFile ?? \"\";\n\n if (!hash || !componentName) {\n return;\n }\n\n const rect = component.getBoundingClientRect();\n\n sendToParent({\n type: \"element-clicked\",\n hash,\n componentName,\n filePath,\n currentClassName: component.className,\n bounds: {\n top: rect.top,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n right: rect.right,\n bottom: rect.bottom,\n },\n });\n\n console.log(\"[Upstart Editor] Element clicked:\", componentName, hash);\n}\n"],"mappings":";;;;AAGA,IAAI,gBAAgB;;;;AAKpB,SAAgB,mBAAyB;AACvC,KAAI,OAAO,aAAa,YACtB;AAGF,KAAI,cACF;AAGF,SAAQ,IAAI,iDAAiD;AAC7D,UAAS,iBAAiB,SAAS,aAAa,KAAK;AACrD,iBAAgB;;;;;AAMlB,SAAgB,sBAA4B;AAC1C,UAAS,oBAAoB,SAAS,aAAa,KAAK;AACxD,iBAAgB;;AAGlB,SAAS,YAAY,OAAyB;AAC5C,KAAI,gBAAgB,KAAK,OACvB;CAGF,MAAM,SAAS,MAAM;AACrB,KAAI,CAAC,OACH;AAGF,KAAI,OAAO,QAAQ,2BAA2B,CAC5C;CAGF,MAAM,YAAY,OAAO,QAAqB,2BAA2B;AACzE,KAAI,CAAC,UACH;AAGF,OAAM,gBAAgB;AACtB,OAAM,iBAAiB;CAEvB,MAAM,OAAO,UAAU,QAAQ;CAC/B,MAAM,gBAAgB,UAAU,QAAQ;CACxC,MAAM,WAAW,UAAU,QAAQ,eAAe;AAElD,KAAI,CAAC,QAAQ,CAAC,cACZ;CAGF,MAAM,OAAO,UAAU,uBAAuB;AAE9C,cAAa;EACX,MAAM;EACN;EACA;EACA;EACA,kBAAkB,UAAU;EAC5B,QAAQ;GACN,KAAK,KAAK;GACV,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,QAAQ,KAAK;GACb,OAAO,KAAK;GACZ,QAAQ,KAAK;GACd;EACF,CAAC;AAEF,SAAQ,IAAI,qCAAqC,eAAe,KAAK"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/vite-plugin-upstart-editor/runtime/hover-overlay.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Initialize hover overlay.
|
|
4
|
+
*/
|
|
5
|
+
declare function initHoverOverlay(): void;
|
|
6
|
+
/**
|
|
7
|
+
* Hide all overlays.
|
|
8
|
+
*/
|
|
9
|
+
declare function hideOverlays(): void;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { hideOverlays, initHoverOverlay };
|
|
12
|
+
//# sourceMappingURL=hover-overlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hover-overlay.d.ts","names":[],"sources":["../../../src/vite-plugin-upstart-editor/runtime/hover-overlay.ts"],"sourcesContent":[],"mappings":";;AAWA;AAsBA;iBAtBgB,gBAAA,CAAA;;;;iBAsBA,YAAA,CAAA"}
|