@upstart.gg/vite-plugins 0.0.39 → 0.0.40
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/upstart-editor-api.d.ts +79 -0
- package/dist/upstart-editor-api.d.ts.map +1 -0
- package/dist/upstart-editor-api.js +208 -0
- package/dist/upstart-editor-api.js.map +1 -0
- package/dist/vite-plugin-upstart-attrs.d.ts +3 -3
- package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-attrs.js +227 -25
- package/dist/vite-plugin-upstart-attrs.js.map +1 -1
- package/dist/vite-plugin-upstart-branding/plugin.d.ts +17 -0
- package/dist/vite-plugin-upstart-branding/plugin.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-branding/plugin.js +41 -0
- package/dist/vite-plugin-upstart-branding/plugin.js.map +1 -0
- package/dist/vite-plugin-upstart-branding/runtime.d.ts +10 -0
- package/dist/vite-plugin-upstart-branding/runtime.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-branding/runtime.js +118 -0
- package/dist/vite-plugin-upstart-branding/runtime.js.map +1 -0
- package/dist/vite-plugin-upstart-branding/types.d.ts +14 -0
- package/dist/vite-plugin-upstart-branding/types.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-branding/types.js +1 -0
- package/dist/vite-plugin-upstart-editor/plugin.d.ts +3 -3
- package/dist/vite-plugin-upstart-editor/plugin.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/plugin.js +3 -16
- package/dist/vite-plugin-upstart-editor/plugin.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +25 -11
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/error-handler.d.ts +5 -0
- package/dist/vite-plugin-upstart-editor/runtime/error-handler.d.ts.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/error-handler.js +16 -0
- package/dist/vite-plugin-upstart-editor/runtime/error-handler.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/hover-overlay.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts +2 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.js +42 -7
- package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts +6 -1
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js +423 -129
- package/dist/vite-plugin-upstart-editor/runtime/text-editor.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +18 -10
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-theme.d.ts +3 -3
- package/dist/vite-plugin-upstart-theme.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-theme.js +1 -3
- package/dist/vite-plugin-upstart-theme.js.map +1 -1
- package/package.json +12 -4
- package/src/tests/upstart-editor-api.test.ts +98 -174
- package/src/tests/vite-plugin-upstart-attrs.test.ts +408 -105
- package/src/tests/vite-plugin-upstart-branding.test.ts +90 -0
- package/src/tests/vite-plugin-upstart-editor.test.ts +1 -2
- package/src/upstart-editor-api.ts +90 -29
- package/src/vite-plugin-upstart-attrs.ts +376 -38
- package/src/vite-plugin-upstart-branding/plugin.ts +59 -0
- package/src/vite-plugin-upstart-branding/runtime.ts +128 -0
- package/src/vite-plugin-upstart-branding/types.ts +10 -0
- package/src/vite-plugin-upstart-editor/plugin.ts +4 -19
- package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +25 -12
- package/src/vite-plugin-upstart-editor/runtime/error-handler.ts +12 -0
- package/src/vite-plugin-upstart-editor/runtime/hover-overlay.ts +1 -1
- package/src/vite-plugin-upstart-editor/runtime/index.ts +39 -5
- package/src/vite-plugin-upstart-editor/runtime/text-editor.ts +518 -141
- package/src/vite-plugin-upstart-editor/runtime/types.ts +18 -4
- package/src/vite-plugin-upstart-theme.ts +0 -3
- package/src/vite-plugin-upstart-editor/PLAN.md +0 -1391
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { EditableEntry } from "./vite-plugin-upstart-attrs.js";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/upstart-editor-api.d.ts
|
|
5
|
+
declare const payloadEditText: z.ZodObject<{
|
|
6
|
+
action: z.ZodLiteral<"editText">;
|
|
7
|
+
language: z.ZodString;
|
|
8
|
+
namespace: z.ZodString;
|
|
9
|
+
key: z.ZodString;
|
|
10
|
+
content: z.ZodString;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
type PayloadEditText = z.infer<typeof payloadEditText>;
|
|
13
|
+
declare const payloadEditClassName: z.ZodObject<{
|
|
14
|
+
action: z.ZodLiteral<"editClassName">;
|
|
15
|
+
id: z.ZodString;
|
|
16
|
+
className: z.ZodString;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
type PayloadEditClassName = z.infer<typeof payloadEditClassName>;
|
|
19
|
+
interface EditableRegistry {
|
|
20
|
+
version: number;
|
|
21
|
+
generatedAt: string;
|
|
22
|
+
elements: Record<string, EditableEntry>;
|
|
23
|
+
}
|
|
24
|
+
type EditResult = {
|
|
25
|
+
success: true;
|
|
26
|
+
error?: never;
|
|
27
|
+
filePath: string;
|
|
28
|
+
} | {
|
|
29
|
+
success: false;
|
|
30
|
+
error: string;
|
|
31
|
+
filePath?: never;
|
|
32
|
+
};
|
|
33
|
+
declare class UpstartEditorAPI {
|
|
34
|
+
private registry;
|
|
35
|
+
private projectRoot;
|
|
36
|
+
private registryPath;
|
|
37
|
+
constructor(projectRoot: string, registryPath: string);
|
|
38
|
+
/**
|
|
39
|
+
* Load the registry from disk
|
|
40
|
+
*/
|
|
41
|
+
loadRegistry(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Get the current registry (for testing/debugging)
|
|
44
|
+
*/
|
|
45
|
+
getRegistry(): EditableRegistry | null;
|
|
46
|
+
/**
|
|
47
|
+
* Set the registry directly (for testing)
|
|
48
|
+
*/
|
|
49
|
+
setRegistry(registry: EditableRegistry): void;
|
|
50
|
+
/**
|
|
51
|
+
* Edit a translation value in an i18next locale file.
|
|
52
|
+
* Auto-detects flat keys (e.g. "nav.home" as literal key) vs nested keys (e.g. nav -> home).
|
|
53
|
+
* Only updates existing keys — returns an error if the key is not found.
|
|
54
|
+
*/
|
|
55
|
+
editText(params: PayloadEditText): Promise<EditResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Edit the className of an element
|
|
58
|
+
*/
|
|
59
|
+
editClassName(params: PayloadEditClassName): Promise<EditResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Apply an edit to a source file
|
|
62
|
+
*/
|
|
63
|
+
private applyEdit;
|
|
64
|
+
/**
|
|
65
|
+
* Get element info by ID
|
|
66
|
+
*/
|
|
67
|
+
getElement(id: string): EditableEntry | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Get all elements of a specific type
|
|
70
|
+
*/
|
|
71
|
+
getElementsByType(type: "text" | "className"): Record<string, EditableEntry>;
|
|
72
|
+
/**
|
|
73
|
+
* Get all elements in a specific file
|
|
74
|
+
*/
|
|
75
|
+
getElementsByFile(file: string): Record<string, EditableEntry>;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { EditResult, EditableRegistry, PayloadEditClassName, PayloadEditText, UpstartEditorAPI, payloadEditClassName, payloadEditText };
|
|
79
|
+
//# sourceMappingURL=upstart-editor-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upstart-editor-api.d.ts","names":[],"sources":["../src/upstart-editor-api.ts"],"sourcesContent":[],"mappings":";;;;cAMa,iBAAe,CAAA,CAAA;;EAAf,QAAA,aASX;;;;;KAEU,eAAA,GAAkB,CAAA,CAAE,aAAa;cAEhC,sBAAoB,CAAA,CAAA;EAbL,MAAA,cAAA,CAAA,eAAA,CAAA;EAAA,EAAA,aAAA;EAWhB,SAAA,aAAe;AAE3B,CAAA,eAAa,CAAA;KAMD,oBAAA,GAAuB,CAAA,CAAE,aAAa;UAEjC,gBAAA;;;EARgB,QAAA,EAWrB,MAXqB,CAAA,MAAA,EAWN,aAXM,CAAA;;AAMrB,KAQA,UAAA,GARA;EAEK,OAAA,EAAA,IAAA;EAML,KAAA,CAAA,EAAA,KAAU;EAYT,QAAA,EAAA,MAAA;CAaW,GAAA;EAQP,OAAA,EAAA,KAAA;EAOO,KAAA,EAAA,MAAA;EASC,QAAA,CAAA,EAAA,KAAA;CAA0B;AAAR,cArC9B,gBAAA,CAqC8B;EAwDb,QAAA,QAAA;EAA+B,QAAA,WAAA;EAAR,QAAA,YAAA;EAyF3B,WAAA,CAAA,WAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA;EAOsC;;;EAiB7B,YAAA,CAAA,CAAA,EAjMX,OAiMW,CAAA,IAAA,CAAA;EAAM;;;iBAzLxB;;;;wBAOO;;;;;;mBASC,kBAAkB,QAAQ;;;;wBAwDrB,uBAAuB,QAAQ;;;;;;;;0BAyFnC;;;;iDAOuB,eAAe;;;;mCAiB7B,eAAe"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import MagicString from "magic-string";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import z from "zod";
|
|
5
|
+
|
|
6
|
+
//#region src/upstart-editor-api.ts
|
|
7
|
+
const payloadEditText = z.object({
|
|
8
|
+
action: z.literal("editText"),
|
|
9
|
+
language: z.string().length(2).regex(/^[a-z]{2}$/),
|
|
10
|
+
namespace: z.string().regex(/^[a-z0-9_-]+$/),
|
|
11
|
+
key: z.string().regex(/^[a-zA-Z0-9_.-]+$/),
|
|
12
|
+
content: z.string()
|
|
13
|
+
});
|
|
14
|
+
const payloadEditClassName = z.object({
|
|
15
|
+
action: z.literal("editClassName"),
|
|
16
|
+
id: z.string().min(1),
|
|
17
|
+
className: z.string()
|
|
18
|
+
});
|
|
19
|
+
var UpstartEditorAPI = class {
|
|
20
|
+
registry = null;
|
|
21
|
+
projectRoot;
|
|
22
|
+
registryPath;
|
|
23
|
+
constructor(projectRoot, registryPath) {
|
|
24
|
+
this.projectRoot = projectRoot;
|
|
25
|
+
this.registryPath = registryPath;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load the registry from disk
|
|
29
|
+
*/
|
|
30
|
+
async loadRegistry() {
|
|
31
|
+
const content = await fs.readFile(this.registryPath, "utf-8");
|
|
32
|
+
this.registry = JSON.parse(content);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the current registry (for testing/debugging)
|
|
36
|
+
*/
|
|
37
|
+
getRegistry() {
|
|
38
|
+
return this.registry;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Set the registry directly (for testing)
|
|
42
|
+
*/
|
|
43
|
+
setRegistry(registry) {
|
|
44
|
+
this.registry = registry;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Edit a translation value in an i18next locale file.
|
|
48
|
+
* Auto-detects flat keys (e.g. "nav.home" as literal key) vs nested keys (e.g. nav -> home).
|
|
49
|
+
* Only updates existing keys — returns an error if the key is not found.
|
|
50
|
+
*/
|
|
51
|
+
async editText(params) {
|
|
52
|
+
const parsed = payloadEditText.safeParse(params);
|
|
53
|
+
if (!parsed.success) return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: `Invalid payload: ${parsed.error.message}`
|
|
56
|
+
};
|
|
57
|
+
const { language, namespace, key, content: newContent } = parsed.data;
|
|
58
|
+
const filePath = path.join(this.projectRoot, "app", "locales", language, `${namespace}.json`);
|
|
59
|
+
let raw;
|
|
60
|
+
try {
|
|
61
|
+
raw = await fs.readFile(filePath, "utf-8");
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: `Failed to read locale file: ${filePath}`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
let data;
|
|
69
|
+
try {
|
|
70
|
+
data = JSON.parse(raw);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
error: `Failed to parse locale file: ${filePath}`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (key in data && typeof data[key] === "string") data[key] = newContent;
|
|
78
|
+
else {
|
|
79
|
+
const parts = key.split(".");
|
|
80
|
+
let current = data;
|
|
81
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
82
|
+
const part = parts[i];
|
|
83
|
+
if (current[part] == null || typeof current[part] !== "object") return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: `Key "${key}" not found in locale file ${filePath}`
|
|
86
|
+
};
|
|
87
|
+
current = current[part];
|
|
88
|
+
}
|
|
89
|
+
const leafKey = parts[parts.length - 1];
|
|
90
|
+
if (!(leafKey in current) || typeof current[leafKey] !== "string") return {
|
|
91
|
+
success: false,
|
|
92
|
+
error: `Key "${key}" not found in locale file ${filePath}`
|
|
93
|
+
};
|
|
94
|
+
current[leafKey] = newContent;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await fs.writeFile(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
98
|
+
} catch (err) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: `Failed to write locale file: ${filePath}`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
filePath
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Edit the className of an element
|
|
111
|
+
*/
|
|
112
|
+
async editClassName(params) {
|
|
113
|
+
const parsed = payloadEditClassName.safeParse(params);
|
|
114
|
+
if (!parsed.success) return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: `Invalid payload: ${parsed.error.message}`
|
|
117
|
+
};
|
|
118
|
+
const { id, className: newClassName } = parsed.data;
|
|
119
|
+
if (!this.registry) try {
|
|
120
|
+
await this.loadRegistry();
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: `Failed to load registry: ${err}`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const entry = this.registry.elements[id];
|
|
128
|
+
if (!entry) return {
|
|
129
|
+
success: false,
|
|
130
|
+
error: `Element ${id} not found in registry`
|
|
131
|
+
};
|
|
132
|
+
if (entry.type !== "className") return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: `Element ${id} is not a className element (type: ${entry.type})`
|
|
135
|
+
};
|
|
136
|
+
return this.applyEdit(id, entry, newClassName);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Apply an edit to a source file
|
|
140
|
+
*/
|
|
141
|
+
async applyEdit(id, entry, newContent) {
|
|
142
|
+
const filePath = path.join(this.projectRoot, entry.file);
|
|
143
|
+
try {
|
|
144
|
+
const code = await fs.readFile(filePath, "utf-8");
|
|
145
|
+
const currentContent = code.slice(entry.startOffset, entry.endOffset);
|
|
146
|
+
let actualStart = entry.startOffset;
|
|
147
|
+
let actualEnd = entry.endOffset;
|
|
148
|
+
if (currentContent !== entry.originalContent) {
|
|
149
|
+
const searchIndex = code.indexOf(entry.originalContent);
|
|
150
|
+
if (searchIndex === -1) return {
|
|
151
|
+
success: false,
|
|
152
|
+
error: `Original content "${entry.originalContent}" not found in file ${entry.file}. The file may have been modified.`
|
|
153
|
+
};
|
|
154
|
+
actualStart = searchIndex;
|
|
155
|
+
actualEnd = searchIndex + entry.originalContent.length;
|
|
156
|
+
}
|
|
157
|
+
const s = new MagicString(code);
|
|
158
|
+
s.overwrite(actualStart, actualEnd, newContent);
|
|
159
|
+
await fs.writeFile(filePath, s.toString());
|
|
160
|
+
const lengthDiff = newContent.length - entry.originalContent.length;
|
|
161
|
+
entry.startOffset = actualStart;
|
|
162
|
+
entry.endOffset = actualStart + newContent.length;
|
|
163
|
+
entry.originalContent = newContent;
|
|
164
|
+
for (const [otherId, otherEntry] of Object.entries(this.registry.elements)) if (otherId !== id && otherEntry.file === entry.file && otherEntry.startOffset > actualStart) {
|
|
165
|
+
otherEntry.startOffset += lengthDiff;
|
|
166
|
+
otherEntry.endOffset += lengthDiff;
|
|
167
|
+
}
|
|
168
|
+
await fs.writeFile(this.registryPath, JSON.stringify(this.registry, null, 2));
|
|
169
|
+
return {
|
|
170
|
+
success: true,
|
|
171
|
+
filePath
|
|
172
|
+
};
|
|
173
|
+
} catch (err) {
|
|
174
|
+
return {
|
|
175
|
+
success: false,
|
|
176
|
+
error: String(err)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get element info by ID
|
|
182
|
+
*/
|
|
183
|
+
getElement(id) {
|
|
184
|
+
return this.registry?.elements[id];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get all elements of a specific type
|
|
188
|
+
*/
|
|
189
|
+
getElementsByType(type) {
|
|
190
|
+
if (!this.registry) return {};
|
|
191
|
+
const result = {};
|
|
192
|
+
for (const [id, entry] of Object.entries(this.registry.elements)) if (entry.type === type) result[id] = entry;
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get all elements in a specific file
|
|
197
|
+
*/
|
|
198
|
+
getElementsByFile(file) {
|
|
199
|
+
if (!this.registry) return {};
|
|
200
|
+
const result = {};
|
|
201
|
+
for (const [id, entry] of Object.entries(this.registry.elements)) if (entry.file === file) result[id] = entry;
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
//#endregion
|
|
207
|
+
export { UpstartEditorAPI, payloadEditClassName, payloadEditText };
|
|
208
|
+
//# sourceMappingURL=upstart-editor-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upstart-editor-api.js","names":["raw: string","data: Record<string, unknown>","current: Record<string, unknown>","result: Record<string, EditableEntry>"],"sources":["../src/upstart-editor-api.ts"],"sourcesContent":["import MagicString from \"magic-string\";\nimport fs from \"fs/promises\";\nimport path from \"path\";\nimport z from \"zod\";\nimport type { EditableEntry } from \"./vite-plugin-upstart-attrs\";\n\nexport const payloadEditText = z.object({\n action: z.literal(\"editText\"),\n language: z\n .string()\n .length(2)\n .regex(/^[a-z]{2}$/),\n namespace: z.string().regex(/^[a-z0-9_-]+$/),\n key: z.string().regex(/^[a-zA-Z0-9_.-]+$/),\n content: z.string(),\n});\n\nexport type PayloadEditText = z.infer<typeof payloadEditText>;\n\nexport const payloadEditClassName = z.object({\n action: z.literal(\"editClassName\"),\n id: z.string().min(1),\n className: z.string(),\n});\n\nexport type PayloadEditClassName = z.infer<typeof payloadEditClassName>;\n\nexport interface EditableRegistry {\n version: number;\n generatedAt: string;\n elements: Record<string, EditableEntry>;\n}\n\nexport type EditResult =\n | {\n success: true;\n error?: never;\n filePath: string;\n }\n | {\n success: false;\n error: string;\n filePath?: never;\n };\n\nexport class UpstartEditorAPI {\n private registry: EditableRegistry | null = null;\n private projectRoot: string;\n private registryPath: string;\n\n constructor(projectRoot: string, registryPath: string) {\n this.projectRoot = projectRoot;\n this.registryPath = registryPath;\n }\n\n /**\n * Load the registry from disk\n */\n async loadRegistry(): Promise<void> {\n const content = await fs.readFile(this.registryPath, \"utf-8\");\n this.registry = JSON.parse(content);\n }\n\n /**\n * Get the current registry (for testing/debugging)\n */\n getRegistry(): EditableRegistry | null {\n return this.registry;\n }\n\n /**\n * Set the registry directly (for testing)\n */\n setRegistry(registry: EditableRegistry): void {\n this.registry = registry;\n }\n\n /**\n * Edit a translation value in an i18next locale file.\n * Auto-detects flat keys (e.g. \"nav.home\" as literal key) vs nested keys (e.g. nav -> home).\n * Only updates existing keys — returns an error if the key is not found.\n */\n async editText(params: PayloadEditText): Promise<EditResult> {\n const parsed = payloadEditText.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { language, namespace, key, content: newContent } = parsed.data;\n const filePath = path.join(this.projectRoot, \"app\", \"locales\", language, `${namespace}.json`);\n\n let raw: string;\n try {\n raw = await fs.readFile(filePath, \"utf-8\");\n } catch (err) {\n return { success: false, error: `Failed to read locale file: ${filePath}` };\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(raw);\n } catch (err) {\n return { success: false, error: `Failed to parse locale file: ${filePath}` };\n }\n\n // Strategy 1: check for flat/literal key at top level\n if (key in data && typeof data[key] === \"string\") {\n data[key] = newContent;\n } else {\n // Strategy 2: nested traversal via dot notation\n const parts = key.split(\".\");\n let current: Record<string, unknown> = data;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (current[part] == null || typeof current[part] !== \"object\") {\n return { success: false, error: `Key \"${key}\" not found in locale file ${filePath}` };\n }\n current = current[part] as Record<string, unknown>;\n }\n\n const leafKey = parts[parts.length - 1];\n if (!(leafKey in current) || typeof current[leafKey] !== \"string\") {\n return { success: false, error: `Key \"${key}\" not found in locale file ${filePath}` };\n }\n current[leafKey] = newContent;\n }\n\n try {\n await fs.writeFile(filePath, JSON.stringify(data, null, 2) + \"\\n\");\n } catch (err) {\n return { success: false, error: `Failed to write locale file: ${filePath}` };\n }\n\n return { success: true, filePath };\n }\n\n /**\n * Edit the className of an element\n */\n async editClassName(params: PayloadEditClassName): Promise<EditResult> {\n const parsed = payloadEditClassName.safeParse(params);\n if (!parsed.success) {\n return { success: false, error: `Invalid payload: ${parsed.error.message}` };\n }\n const { id, className: newClassName } = parsed.data;\n if (!this.registry) {\n try {\n await this.loadRegistry();\n } catch (err) {\n return { success: false, error: `Failed to load registry: ${err}` };\n }\n }\n\n const entry = this.registry!.elements[id];\n if (!entry) {\n return { success: false, error: `Element ${id} not found in registry` };\n }\n\n if (entry.type !== \"className\") {\n return { success: false, error: `Element ${id} is not a className element (type: ${entry.type})` };\n }\n\n return this.applyEdit(id, entry, newClassName);\n }\n\n /**\n * Apply an edit to a source file\n */\n private async applyEdit(id: string, entry: EditableEntry, newContent: string): Promise<EditResult> {\n const filePath = path.join(this.projectRoot, entry.file);\n\n try {\n const code = await fs.readFile(filePath, \"utf-8\");\n\n // Verify content at expected location\n const currentContent = code.slice(entry.startOffset, entry.endOffset);\n\n let actualStart = entry.startOffset;\n let actualEnd = entry.endOffset;\n\n if (currentContent !== entry.originalContent) {\n // Content has shifted - try to find it by searching\n const searchIndex = code.indexOf(entry.originalContent);\n if (searchIndex === -1) {\n return {\n success: false,\n error: `Original content \"${entry.originalContent}\" not found in file ${entry.file}. The file may have been modified.`,\n };\n }\n actualStart = searchIndex;\n actualEnd = searchIndex + entry.originalContent.length;\n }\n\n // Apply the edit using MagicString\n const s = new MagicString(code);\n s.overwrite(actualStart, actualEnd, newContent);\n\n // Write the modified file\n await fs.writeFile(filePath, s.toString());\n\n // Calculate the length difference for offset adjustments\n const lengthDiff = newContent.length - entry.originalContent.length;\n\n // Update the registry entry\n entry.startOffset = actualStart;\n entry.endOffset = actualStart + newContent.length;\n entry.originalContent = newContent;\n\n // Shift all subsequent entries in the same file\n for (const [otherId, otherEntry] of Object.entries(this.registry!.elements)) {\n if (otherId !== id && otherEntry.file === entry.file && otherEntry.startOffset > actualStart) {\n otherEntry.startOffset += lengthDiff;\n otherEntry.endOffset += lengthDiff;\n }\n }\n\n // Save the updated registry\n await fs.writeFile(this.registryPath, JSON.stringify(this.registry, null, 2));\n\n return { success: true, filePath };\n } catch (err) {\n return { success: false, error: String(err) };\n }\n }\n\n /**\n * Get element info by ID\n */\n getElement(id: string): EditableEntry | undefined {\n return this.registry?.elements[id];\n }\n\n /**\n * Get all elements of a specific type\n */\n getElementsByType(type: \"text\" | \"className\"): Record<string, EditableEntry> {\n if (!this.registry) {\n return {};\n }\n\n const result: Record<string, EditableEntry> = {};\n for (const [id, entry] of Object.entries(this.registry.elements)) {\n if (entry.type === type) {\n result[id] = entry;\n }\n }\n return result;\n }\n\n /**\n * Get all elements in a specific file\n */\n getElementsByFile(file: string): Record<string, EditableEntry> {\n if (!this.registry) {\n return {};\n }\n\n const result: Record<string, EditableEntry> = {};\n for (const [id, entry] of Object.entries(this.registry.elements)) {\n if (entry.file === file) {\n result[id] = entry;\n }\n }\n return result;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as unplugin5 from "unplugin";
|
|
2
1
|
import * as magic_string0 from "magic-string";
|
|
2
|
+
import * as unplugin0 from "unplugin";
|
|
3
3
|
|
|
4
4
|
//#region src/vite-plugin-upstart-attrs.d.ts
|
|
5
5
|
interface Options {
|
|
@@ -18,12 +18,12 @@ interface EditableEntry {
|
|
|
18
18
|
}
|
|
19
19
|
declare function getRegistry(): Record<string, EditableEntry>;
|
|
20
20
|
declare function clearRegistry(): void;
|
|
21
|
-
declare const upstartEditor:
|
|
21
|
+
declare const upstartEditor: unplugin0.UnpluginInstance<Options, boolean>;
|
|
22
22
|
declare function transformWithOxc(code: string, filePath: string): {
|
|
23
23
|
code: string;
|
|
24
24
|
map: magic_string0.SourceMap;
|
|
25
25
|
} | null;
|
|
26
|
-
declare const _default: (options: Options) =>
|
|
26
|
+
declare const _default: (options: Options) => unplugin0.VitePlugin<any> | unplugin0.VitePlugin<any>[];
|
|
27
27
|
//#endregion
|
|
28
28
|
export { EditableEntry, clearRegistry, _default as default, getRegistry, transformWithOxc, upstartEditor };
|
|
29
29
|
//# sourceMappingURL=vite-plugin-upstart-attrs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin-upstart-attrs.d.ts","names":[],"sources":["../src/vite-plugin-upstart-attrs.ts"],"sourcesContent":[],"mappings":";;;;UAgBU,OAAA;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"vite-plugin-upstart-attrs.d.ts","names":[],"sources":["../src/vite-plugin-upstart-attrs.ts"],"sourcesContent":[],"mappings":";;;;UAgBU,OAAA;;;;AAAA,UA0CO,aAAA,CA1CA;EA0CA,IAAA,EAAA,MAAA;EAoBD,IAAA,EAAA,MAAA,GAAW,WAAmB;EAK9B,WAAA,EAAA,MAAa;EAehB,SAAA,EAAA,MAkEX;EAEc,eAAA,EAAA,MAAgB;EA0P/B,OAAA,EAAA;;;;iBAlVe,WAAA,CAAA,GAAe,eAAe;iBAK9B,aAAA,CAAA;cAeH,eAAa,SAAA,CAAA,iBAAA;iBAoEV,gBAAA;;OAAgB,aAAA,CAAA;;cA0P/B"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import MagicString from "magic-string";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { createUnplugin } from "unplugin";
|
|
2
4
|
import { parseSync } from "oxc-parser";
|
|
3
5
|
import { walk } from "zimmerframe";
|
|
4
|
-
import MagicString from "magic-string";
|
|
5
|
-
import path from "node:path";
|
|
6
6
|
|
|
7
7
|
//#region src/vite-plugin-upstart-attrs.ts
|
|
8
8
|
function hasRange(node) {
|
|
@@ -76,11 +76,16 @@ function transformWithOxc(code, filePath) {
|
|
|
76
76
|
filePath,
|
|
77
77
|
code,
|
|
78
78
|
s,
|
|
79
|
-
loopStack: []
|
|
79
|
+
loopStack: [],
|
|
80
|
+
constants: /* @__PURE__ */ new Map(),
|
|
81
|
+
tFunctions: /* @__PURE__ */ new Map()
|
|
80
82
|
};
|
|
81
83
|
let modified = false;
|
|
82
84
|
walk(ast.program, state, { _(node, { state: state$1, next }) {
|
|
83
|
-
if (node.type === "VariableDeclaration")
|
|
85
|
+
if (node.type === "VariableDeclaration") {
|
|
86
|
+
trackUseTranslation(node, state$1);
|
|
87
|
+
for (const decl of node.declarations) if (decl.type === "VariableDeclarator" && decl.id?.type === "Identifier" && decl.init?.type === "Literal" && typeof decl.init.value === "string") state$1.constants.set(decl.id.name, decl.init.value);
|
|
88
|
+
}
|
|
84
89
|
if (node.type === "CallExpression") {
|
|
85
90
|
const loopInfo = detectMapCall(node, state$1.code);
|
|
86
91
|
if (loopInfo) {
|
|
@@ -94,14 +99,38 @@ function transformWithOxc(code, filePath) {
|
|
|
94
99
|
const jsxNode = node;
|
|
95
100
|
const opening = jsxNode.openingElement;
|
|
96
101
|
const tagName = getJSXElementName(opening);
|
|
102
|
+
if (tagName === "Trans") {
|
|
103
|
+
next();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
97
106
|
const insertPos = getAttributeInsertPosition(opening, state$1.code);
|
|
98
107
|
const attributes = [];
|
|
99
108
|
if (hasRange(jsxNode)) {
|
|
100
109
|
const hash = hashContent(state$1.code.slice(jsxNode.start, jsxNode.end));
|
|
101
|
-
|
|
110
|
+
const loopIndices = state$1.loopStack.map((l) => l.indexName).filter((n) => n !== null);
|
|
111
|
+
if (loopIndices.length > 0) {
|
|
112
|
+
const suffix = loopIndices.map((n) => `\${${n}}`).join("-");
|
|
113
|
+
attributes.push(`data-upstart-hash={\`${hash}-${suffix}\`}`);
|
|
114
|
+
} else attributes.push(`data-upstart-hash="${hash}"`);
|
|
102
115
|
}
|
|
103
|
-
|
|
116
|
+
const transChildren = findTransInChildren(jsxNode, state$1.code, state$1.constants);
|
|
117
|
+
const tCallChildren = findTCallsInChildren(jsxNode, state$1.code, state$1);
|
|
118
|
+
const allI18nKeys = [...transChildren, ...tCallChildren];
|
|
119
|
+
const hasI18n = allI18nKeys.length > 0;
|
|
120
|
+
if (hasI18n) {
|
|
104
121
|
attributes.push("data-upstart-editable-text=\"true\"");
|
|
122
|
+
if (allI18nKeys.some((t) => t.keyExpr || t.nsExpr)) {
|
|
123
|
+
const parts = allI18nKeys.map((t) => {
|
|
124
|
+
return `${t.nsExpr ? `\${${t.nsExpr}}` : t.namespace}:${t.keyExpr ? `\${${t.keyExpr}}` : t.key}`;
|
|
125
|
+
});
|
|
126
|
+
attributes.push(`data-upstart-i18n={\`${parts.join(",")}\`}`);
|
|
127
|
+
} else {
|
|
128
|
+
const keys = allI18nKeys.map((t) => escapeProp(t.fullKey)).join(",");
|
|
129
|
+
attributes.push(`data-upstart-i18n="${keys}"`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (isTextLeafElement(jsxNode)) {
|
|
133
|
+
if (!hasI18n) attributes.push("data-upstart-editable-text=\"false\"");
|
|
105
134
|
const textChild = jsxNode.children.find((c) => c.type === "JSXText" && c.value?.trim());
|
|
106
135
|
if (textChild && hasRange(textChild)) {
|
|
107
136
|
const id = generateId(state$1.filePath, textChild);
|
|
@@ -118,12 +147,7 @@ function transformWithOxc(code, filePath) {
|
|
|
118
147
|
});
|
|
119
148
|
attributes.push(`data-upstart-id="${id}"`);
|
|
120
149
|
}
|
|
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
|
-
}
|
|
150
|
+
} else if (!hasI18n && hasVisibleTextContent(jsxNode)) attributes.push("data-upstart-editable-text=\"false\"");
|
|
127
151
|
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
152
|
if (classNameAttr && classNameAttr.value && hasRange(classNameAttr.value)) {
|
|
129
153
|
const id = generateId(state$1.filePath, classNameAttr.value);
|
|
@@ -254,10 +278,10 @@ function extractDatasource(expr) {
|
|
|
254
278
|
}
|
|
255
279
|
function extractRecordId(expr) {
|
|
256
280
|
const obj = expr.object;
|
|
257
|
-
if (obj.type === "Identifier") return `${obj.name}
|
|
281
|
+
if (obj.type === "Identifier") return `${obj.name}.$id`;
|
|
258
282
|
if (obj.type === "MemberExpression") {
|
|
259
283
|
const datasource = extractDatasource(obj);
|
|
260
|
-
if (datasource) return `${datasource}
|
|
284
|
+
if (datasource) return `${datasource}.$id`;
|
|
261
285
|
}
|
|
262
286
|
}
|
|
263
287
|
function detectMapCall(node, code) {
|
|
@@ -278,37 +302,201 @@ function detectMapCall(node, code) {
|
|
|
278
302
|
function escapeProp(value) {
|
|
279
303
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
280
304
|
}
|
|
281
|
-
function
|
|
305
|
+
function trackUseTranslation(node, state) {
|
|
282
306
|
if (node.type !== "VariableDeclaration") return;
|
|
283
307
|
const decl = node;
|
|
284
308
|
for (const declarator of decl.declarations) {
|
|
285
309
|
if (declarator.id?.type !== "ObjectPattern" || !declarator.init || declarator.init.type !== "CallExpression") continue;
|
|
286
310
|
const callExpr = declarator.init;
|
|
287
311
|
if (callExpr.callee?.type !== "Identifier" || callExpr.callee.name !== "useTranslation") continue;
|
|
312
|
+
const namespace = extractUseTranslationNamespace(callExpr);
|
|
288
313
|
for (const prop of declarator.id.properties) {
|
|
289
314
|
if (prop.type !== "Property") continue;
|
|
290
|
-
if (prop.key?.type
|
|
315
|
+
if (prop.key?.type !== "Identifier" || prop.key.name !== "t") continue;
|
|
316
|
+
const localName = prop.value?.type === "Identifier" ? prop.value.name : "t";
|
|
317
|
+
state.tFunctions.set(localName, namespace);
|
|
291
318
|
}
|
|
292
319
|
}
|
|
293
320
|
}
|
|
294
|
-
function
|
|
321
|
+
function extractUseTranslationNamespace(callExpr) {
|
|
322
|
+
const firstArg = callExpr.arguments?.[0];
|
|
323
|
+
if (!firstArg) return "translation";
|
|
324
|
+
if (firstArg.type === "Literal" && typeof firstArg.value === "string") return firstArg.value;
|
|
325
|
+
if (firstArg.type === "ArrayExpression" && firstArg.elements?.length > 0) {
|
|
326
|
+
const first = firstArg.elements[0];
|
|
327
|
+
if (first?.type === "Literal" && typeof first.value === "string") return first.value;
|
|
328
|
+
}
|
|
329
|
+
return "translation";
|
|
330
|
+
}
|
|
331
|
+
function resolveJSXAttrValue(value, code, constants) {
|
|
332
|
+
if (!value) return null;
|
|
333
|
+
if (value.type === "Literal" && typeof value.value === "string") return {
|
|
334
|
+
value: value.value,
|
|
335
|
+
expr: null
|
|
336
|
+
};
|
|
337
|
+
if (value.type === "JSXExpressionContainer") {
|
|
338
|
+
const expression = value.expression;
|
|
339
|
+
if (!expression) return null;
|
|
340
|
+
if (expression.type === "Identifier" && constants.has(expression.name)) return {
|
|
341
|
+
value: constants.get(expression.name),
|
|
342
|
+
expr: null
|
|
343
|
+
};
|
|
344
|
+
if (hasRange(expression)) {
|
|
345
|
+
const src = code.slice(expression.start, expression.end);
|
|
346
|
+
return {
|
|
347
|
+
value: src,
|
|
348
|
+
expr: src
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
function detectTransComponent(jsxElement, code, constants) {
|
|
295
355
|
const opening = jsxElement.openingElement;
|
|
296
356
|
if (getJSXElementName(opening) !== "Trans") return null;
|
|
297
|
-
let
|
|
298
|
-
let
|
|
357
|
+
let keyResult = null;
|
|
358
|
+
let nsResult = null;
|
|
299
359
|
for (const attr of opening.attributes) {
|
|
300
360
|
if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier") continue;
|
|
301
361
|
const attrName = attr.name.name;
|
|
302
|
-
if (attrName === "i18nKey"
|
|
303
|
-
if (attrName === "ns"
|
|
362
|
+
if (attrName === "i18nKey") keyResult = resolveJSXAttrValue(attr.value, code, constants);
|
|
363
|
+
if (attrName === "ns") nsResult = resolveJSXAttrValue(attr.value, code, constants);
|
|
304
364
|
}
|
|
305
|
-
if (!
|
|
365
|
+
if (!keyResult) return null;
|
|
366
|
+
const key = keyResult.value;
|
|
367
|
+
const namespace = nsResult?.value ?? "translation";
|
|
368
|
+
const keyExpr = keyResult.expr;
|
|
369
|
+
const nsExpr = nsResult?.expr ?? null;
|
|
306
370
|
return {
|
|
307
|
-
fullKey: `${namespace}:${
|
|
308
|
-
key
|
|
309
|
-
namespace
|
|
371
|
+
fullKey: `${namespace}:${key}`,
|
|
372
|
+
key,
|
|
373
|
+
namespace,
|
|
374
|
+
keyExpr,
|
|
375
|
+
nsExpr
|
|
310
376
|
};
|
|
311
377
|
}
|
|
378
|
+
function findTransInChildren(jsxElement, code, constants) {
|
|
379
|
+
const results = [];
|
|
380
|
+
for (const child of jsxElement.children) {
|
|
381
|
+
if (child.type === "JSXElement") {
|
|
382
|
+
const info = detectTransComponent(child, code, constants);
|
|
383
|
+
if (info) results.push(info);
|
|
384
|
+
}
|
|
385
|
+
if (child.type === "JSXExpressionContainer") {
|
|
386
|
+
const expr = child.expression;
|
|
387
|
+
if (expr && expr.type !== "JSXEmptyExpression") findTransInExpression(expr, code, results, constants);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return results;
|
|
391
|
+
}
|
|
392
|
+
function findTransInExpression(expr, code, results, constants) {
|
|
393
|
+
if (!expr || !expr.type) return;
|
|
394
|
+
if (expr.type === "JSXElement") {
|
|
395
|
+
const info = detectTransComponent(expr, code, constants);
|
|
396
|
+
if (info) results.push(info);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (expr.type === "LogicalExpression") {
|
|
400
|
+
findTransInExpression(expr.left, code, results, constants);
|
|
401
|
+
findTransInExpression(expr.right, code, results, constants);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (expr.type === "ConditionalExpression") {
|
|
405
|
+
findTransInExpression(expr.consequent, code, results, constants);
|
|
406
|
+
findTransInExpression(expr.alternate, code, results, constants);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (expr.type === "CallExpression") {
|
|
410
|
+
for (const arg of expr.arguments) findTransInExpression(arg, code, results, constants);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (expr.type === "ArrowFunctionExpression" || expr.type === "FunctionExpression") {
|
|
414
|
+
findTransInExpression(expr.body, code, results, constants);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (expr.type === "BlockStatement") {
|
|
418
|
+
for (const stmt of expr.body) if (stmt.type === "ReturnStatement" && stmt.argument) findTransInExpression(stmt.argument, code, results, constants);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function findTCallsInChildren(jsxElement, code, state) {
|
|
423
|
+
if (state.tFunctions.size === 0) return [];
|
|
424
|
+
const results = [];
|
|
425
|
+
for (const child of jsxElement.children) if (child.type === "JSXExpressionContainer") {
|
|
426
|
+
const expr = child.expression;
|
|
427
|
+
if (expr && expr.type !== "JSXEmptyExpression") findTCallInExpression(expr, code, results, state);
|
|
428
|
+
}
|
|
429
|
+
return results;
|
|
430
|
+
}
|
|
431
|
+
function findTCallInExpression(expr, code, results, state) {
|
|
432
|
+
if (!expr || !expr.type) return;
|
|
433
|
+
if (expr.type === "CallExpression") {
|
|
434
|
+
const info = detectTCall(expr, code, state);
|
|
435
|
+
if (info) {
|
|
436
|
+
results.push(info);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
for (const arg of expr.arguments) findTCallInExpression(arg, code, results, state);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (expr.type === "LogicalExpression") {
|
|
443
|
+
findTCallInExpression(expr.left, code, results, state);
|
|
444
|
+
findTCallInExpression(expr.right, code, results, state);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (expr.type === "ConditionalExpression") {
|
|
448
|
+
findTCallInExpression(expr.consequent, code, results, state);
|
|
449
|
+
findTCallInExpression(expr.alternate, code, results, state);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (expr.type === "ArrowFunctionExpression" || expr.type === "FunctionExpression") {
|
|
453
|
+
findTCallInExpression(expr.body, code, results, state);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (expr.type === "BlockStatement") {
|
|
457
|
+
for (const stmt of expr.body) if (stmt.type === "ReturnStatement" && stmt.argument) findTCallInExpression(stmt.argument, code, results, state);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function detectTCall(callExpr, code, state) {
|
|
462
|
+
if (callExpr.callee?.type !== "Identifier") return null;
|
|
463
|
+
const calleeName = callExpr.callee.name;
|
|
464
|
+
const namespace = state.tFunctions.get(calleeName);
|
|
465
|
+
if (namespace === void 0) return null;
|
|
466
|
+
const firstArg = callExpr.arguments?.[0];
|
|
467
|
+
if (!firstArg) return null;
|
|
468
|
+
if (firstArg.type === "Literal" && typeof firstArg.value === "string") {
|
|
469
|
+
const key = firstArg.value;
|
|
470
|
+
return {
|
|
471
|
+
fullKey: `${namespace}:${key}`,
|
|
472
|
+
key,
|
|
473
|
+
namespace,
|
|
474
|
+
keyExpr: null,
|
|
475
|
+
nsExpr: null
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (hasRange(firstArg)) {
|
|
479
|
+
const exprSrc = code.slice(firstArg.start, firstArg.end);
|
|
480
|
+
if (firstArg.type === "Identifier" && state.constants.has(firstArg.name)) {
|
|
481
|
+
const key = state.constants.get(firstArg.name);
|
|
482
|
+
return {
|
|
483
|
+
fullKey: `${namespace}:${key}`,
|
|
484
|
+
key,
|
|
485
|
+
namespace,
|
|
486
|
+
keyExpr: null,
|
|
487
|
+
nsExpr: null
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
fullKey: `${namespace}:${exprSrc}`,
|
|
492
|
+
key: exprSrc,
|
|
493
|
+
namespace,
|
|
494
|
+
keyExpr: exprSrc,
|
|
495
|
+
nsExpr: null
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
312
500
|
function isTextLeafElement(jsxElement) {
|
|
313
501
|
let hasText = false;
|
|
314
502
|
for (const child of jsxElement.children) if (child.type === "JSXText") {
|
|
@@ -316,6 +504,20 @@ function isTextLeafElement(jsxElement) {
|
|
|
316
504
|
} else if (child.type === "JSXElement" || child.type === "JSXFragment" || child.type === "JSXExpressionContainer" || child.type === "JSXSpreadChild") return false;
|
|
317
505
|
return hasText;
|
|
318
506
|
}
|
|
507
|
+
function hasVisibleTextContent(jsxElement) {
|
|
508
|
+
for (const child of jsxElement.children) {
|
|
509
|
+
if (child.type === "JSXText") {
|
|
510
|
+
if (child.value?.trim()) return true;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (child.type === "JSXExpressionContainer") {
|
|
514
|
+
const expr = child.expression;
|
|
515
|
+
if (!expr || expr.type === "JSXEmptyExpression") continue;
|
|
516
|
+
if (expr.type === "Identifier" || expr.type === "MemberExpression" || expr.type === "Literal" || expr.type === "TemplateLiteral") return true;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
319
521
|
var vite_plugin_upstart_attrs_default = upstartEditor.vite;
|
|
320
522
|
|
|
321
523
|
//#endregion
|