@upstart.gg/vite-plugins 0.1.84 → 0.1.85
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 +58 -1
- package/dist/upstart-editor-api.d.ts.map +1 -1
- package/dist/upstart-editor-api.js +224 -1
- package/dist/upstart-editor-api.js.map +1 -1
- package/dist/vite-plugin-upstart-attrs.d.ts +1 -1
- package/dist/vite-plugin-upstart-attrs.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-attrs.js +65 -2
- package/dist/vite-plugin-upstart-attrs.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/array-controls.js +2 -24
- package/dist/vite-plugin-upstart-editor/runtime/array-controls.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js +7 -1
- package/dist/vite-plugin-upstart-editor/runtime/click-handler.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/controls-ui.js +33 -0
- package/dist/vite-plugin-upstart-editor/runtime/controls-ui.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/index.js +23 -0
- package/dist/vite-plugin-upstart-editor/runtime/index.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/labels.d.ts +5 -0
- package/dist/vite-plugin-upstart-editor/runtime/labels.d.ts.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/labels.js +5 -0
- package/dist/vite-plugin-upstart-editor/runtime/labels.js.map +1 -1
- package/dist/vite-plugin-upstart-editor/runtime/move-controls.js +434 -0
- package/dist/vite-plugin-upstart-editor/runtime/move-controls.js.map +1 -0
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts +6 -3
- package/dist/vite-plugin-upstart-editor/runtime/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/tests/move-controls.test.ts +86 -0
- package/src/tests/upstart-editor-api-move-element.test.ts +220 -0
- package/src/tests/vite-plugin-upstart-attrs.test.ts +165 -4
- package/src/upstart-editor-api.ts +263 -0
- package/src/vite-plugin-upstart-attrs.ts +93 -2
- package/src/vite-plugin-upstart-editor/runtime/array-controls.ts +2 -40
- package/src/vite-plugin-upstart-editor/runtime/click-handler.ts +17 -3
- package/src/vite-plugin-upstart-editor/runtime/controls-ui.ts +48 -0
- package/src/vite-plugin-upstart-editor/runtime/index.ts +31 -0
- package/src/vite-plugin-upstart-editor/runtime/labels.ts +10 -0
- package/src/vite-plugin-upstart-editor/runtime/move-controls.ts +536 -0
- package/src/vite-plugin-upstart-editor/runtime/types.ts +7 -2
|
@@ -17,6 +17,38 @@ declare function escapeStringLiteralBody(value: string, quote: string): string;
|
|
|
17
17
|
declare function readLocaleValue(data: Record<string, unknown>, key: string): string | null;
|
|
18
18
|
/** Write a key, keeping the shape it already has; new keys are created flat. */
|
|
19
19
|
declare function writeLocaleValue(data: Record<string, unknown>, key: string, value: string): void;
|
|
20
|
+
interface SourceRange {
|
|
21
|
+
start: number;
|
|
22
|
+
end: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The result of moving a range: the new code, plus what changed so the offsets of other
|
|
26
|
+
* elements can be mapped onto it (see `mapOffset`). Offsets are in the ORIGINAL code, except
|
|
27
|
+
* `moved`, which is the new range of the moved element.
|
|
28
|
+
*/
|
|
29
|
+
interface MoveEdit {
|
|
30
|
+
code: string;
|
|
31
|
+
/** Removed source range. */
|
|
32
|
+
removeStart: number;
|
|
33
|
+
removeEnd: number;
|
|
34
|
+
/** Where the moved text was inserted, and its length. */
|
|
35
|
+
insertAt: number;
|
|
36
|
+
insertLength: number;
|
|
37
|
+
moved: SourceRange;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Move the `src` range before or after the `tgt` range (`tgt` must not be inside `src`).
|
|
41
|
+
* Elements alone on their lines are moved as whole lines, re-indented to the target's level;
|
|
42
|
+
* inline elements are moved as-is. Inserting at the far edge of the target keeps the target's
|
|
43
|
+
* own range untouched even when the source sits inside it (e.g. a <span> out of its <p>).
|
|
44
|
+
*/
|
|
45
|
+
declare function moveRange(code: string, src: SourceRange, tgt: SourceRange, position: "before" | "after"): MoveEdit;
|
|
46
|
+
/**
|
|
47
|
+
* Where an offset of the original code (outside the removed range) lands after `edit`.
|
|
48
|
+
* A start sitting exactly at the insertion point is pushed after the inserted text, an end
|
|
49
|
+
* sitting there stays before it.
|
|
50
|
+
*/
|
|
51
|
+
declare function mapOffset(edit: MoveEdit, offset: number, isEnd: boolean): number;
|
|
20
52
|
declare const payloadEditText: z.ZodObject<{
|
|
21
53
|
action: z.ZodLiteral<"editText">;
|
|
22
54
|
language: z.ZodString;
|
|
@@ -62,6 +94,25 @@ declare const payloadArraySet: z.ZodObject<{
|
|
|
62
94
|
items: z.ZodArray<z.ZodString>;
|
|
63
95
|
}, z.core.$strip>;
|
|
64
96
|
type PayloadArraySet = z.infer<typeof payloadArraySet>;
|
|
97
|
+
declare const payloadMoveElements: z.ZodObject<{
|
|
98
|
+
action: z.ZodLiteral<"moveElements">;
|
|
99
|
+
moves: z.ZodArray<z.ZodObject<{
|
|
100
|
+
id: z.ZodString;
|
|
101
|
+
targetId: z.ZodString;
|
|
102
|
+
position: z.ZodEnum<{
|
|
103
|
+
after: "after";
|
|
104
|
+
before: "before";
|
|
105
|
+
}>;
|
|
106
|
+
}, z.core.$strip>>;
|
|
107
|
+
}, z.core.$strip>;
|
|
108
|
+
type PayloadMoveElements = z.infer<typeof payloadMoveElements>;
|
|
109
|
+
type MoveElementsResult = {
|
|
110
|
+
success: true;
|
|
111
|
+
filePaths: string[];
|
|
112
|
+
} | {
|
|
113
|
+
success: false;
|
|
114
|
+
error: string;
|
|
115
|
+
};
|
|
65
116
|
declare const payloadGetPageMeta: z.ZodObject<{
|
|
66
117
|
action: z.ZodLiteral<"getPageMeta">;
|
|
67
118
|
routeId: z.ZodString;
|
|
@@ -219,6 +270,12 @@ declare class UpstartEditorAPI {
|
|
|
219
270
|
* batched ✓ apply so a whole add/delete session is a single edit + rebuild.
|
|
220
271
|
*/
|
|
221
272
|
arraySet(params: PayloadArraySet): Promise<EditResult>;
|
|
273
|
+
/**
|
|
274
|
+
* Apply a batch of element moves (editor drag & drop), in order. Every element involved is
|
|
275
|
+
* tracked across the moves, so a later move finds elements an earlier one shifted. Files are
|
|
276
|
+
* only written when every move succeeded and every result still parses.
|
|
277
|
+
*/
|
|
278
|
+
moveElements(params: PayloadMoveElements): Promise<MoveElementsResult>;
|
|
222
279
|
/**
|
|
223
280
|
* Read the page metadata shown in the browser tab and in search results.
|
|
224
281
|
*
|
|
@@ -283,5 +340,5 @@ declare class UpstartEditorAPI {
|
|
|
283
340
|
getElementsByFile(file: string): Record<string, EditableEntry>;
|
|
284
341
|
}
|
|
285
342
|
//#endregion
|
|
286
|
-
export { EDITABLE_THEME_COLORS, EditResult, EditableRegistry, type EditableThemeColor, GetPageMetaResult, GetSiteMetaResult, type GetThemeResult, PageMetaField, PayloadArrayItemAdd, PayloadArrayItemDelete, PayloadArraySet, PayloadEditClassName, PayloadEditImage, PayloadEditText, PayloadEditTextDirect, PayloadGetPageMeta, PayloadGetSiteMeta, type PayloadGetTheme, PayloadSetPageMeta, PayloadSetSiteMeta, type PayloadSetTheme, SetPageMetaResult, SetSiteMetaResult, type SetThemeResult, type ThemeColors, type ThemePalette, UpstartEditorAPI, escapeStringLiteralBody, payloadArrayItemAdd, payloadArrayItemDelete, payloadArraySet, payloadEditClassName, payloadEditImage, payloadEditText, payloadEditTextDirect, payloadGetPageMeta, payloadGetSiteMeta, payloadGetTheme, payloadSetPageMeta, payloadSetSiteMeta, payloadSetTheme, readLocaleValue, writeLocaleValue };
|
|
343
|
+
export { EDITABLE_THEME_COLORS, EditResult, EditableRegistry, type EditableThemeColor, GetPageMetaResult, GetSiteMetaResult, type GetThemeResult, MoveEdit, MoveElementsResult, PageMetaField, PayloadArrayItemAdd, PayloadArrayItemDelete, PayloadArraySet, PayloadEditClassName, PayloadEditImage, PayloadEditText, PayloadEditTextDirect, PayloadGetPageMeta, PayloadGetSiteMeta, type PayloadGetTheme, PayloadMoveElements, PayloadSetPageMeta, PayloadSetSiteMeta, type PayloadSetTheme, SetPageMetaResult, SetSiteMetaResult, type SetThemeResult, type ThemeColors, type ThemePalette, UpstartEditorAPI, escapeStringLiteralBody, mapOffset, moveRange, payloadArrayItemAdd, payloadArrayItemDelete, payloadArraySet, payloadEditClassName, payloadEditImage, payloadEditText, payloadEditTextDirect, payloadGetPageMeta, payloadGetSiteMeta, payloadGetTheme, payloadMoveElements, payloadSetPageMeta, payloadSetSiteMeta, payloadSetTheme, readLocaleValue, writeLocaleValue };
|
|
287
344
|
//# sourceMappingURL=upstart-editor-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upstart-editor-api.d.ts","names":[],"sources":["../src/upstart-editor-api.ts"],"mappings":";;;;;;AAoCA;;;;;iBAAgB,uBAAA,CAAwB,KAAA,UAAe,KAAA;;;;;iBAgQvC,eAAA,CAAgB,IAAA,EAAM,MAAA,mBAAyB,GAAA;;iBAY/C,gBAAA,CAAiB,IAAA,EAAM,MAAA,mBAAyB,GAAA,UAAa,KAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"upstart-editor-api.d.ts","names":[],"sources":["../src/upstart-editor-api.ts"],"mappings":"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"}
|
|
@@ -261,6 +261,109 @@ function printMetaTag(fieldName, value) {
|
|
|
261
261
|
if (fieldName === "title") return !/[{}<>&\n]/.test(value) ? `<title>${value}</title>` : `<title>{"${escapeStringLiteralBody(value, "\"")}"}</title>`;
|
|
262
262
|
return `<meta name="${fieldName}" ${/["\n]/.test(value) ? `content={"${escapeStringLiteralBody(value, "\"")}"}` : `content="${value}"`} />`;
|
|
263
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Current range of an element in `code`: `hint` when the source still matches there, otherwise
|
|
266
|
+
* the occurrence of its source nearest to `hint` (an earlier edit shifted it), and as a last
|
|
267
|
+
* resort a whitespace-insensitive match (a move re-indented it).
|
|
268
|
+
*/
|
|
269
|
+
function locateElement(code, hint) {
|
|
270
|
+
const { start, end, content } = hint;
|
|
271
|
+
if (code.slice(start, end) === content) return hint;
|
|
272
|
+
if (content.trim() === "") return null;
|
|
273
|
+
const nearest = (a, b) => Math.abs(a - start) < Math.abs(b - start);
|
|
274
|
+
let best = -1;
|
|
275
|
+
for (let i = code.indexOf(content); i !== -1; i = code.indexOf(content, i + 1)) if (best === -1 || nearest(i, best)) best = i;
|
|
276
|
+
if (best !== -1) return {
|
|
277
|
+
start: best,
|
|
278
|
+
end: best + content.length,
|
|
279
|
+
content
|
|
280
|
+
};
|
|
281
|
+
const pattern = content.trim().split(/\s+/).map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
|
|
282
|
+
let found = null;
|
|
283
|
+
for (const m of code.matchAll(new RegExp(pattern, "g"))) if (!found || nearest(m.index, found.start)) found = {
|
|
284
|
+
start: m.index,
|
|
285
|
+
end: m.index + m[0].length,
|
|
286
|
+
content: m[0]
|
|
287
|
+
};
|
|
288
|
+
return found;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The full lines a range occupies (from the line start to just after its trailing newline)
|
|
292
|
+
* when nothing else than whitespace shares those lines, plus the indentation of the first one.
|
|
293
|
+
*/
|
|
294
|
+
function ownLines(code, range) {
|
|
295
|
+
const lineStart = code.lastIndexOf("\n", range.start - 1) + 1;
|
|
296
|
+
const indent = code.slice(lineStart, range.start);
|
|
297
|
+
if (indent.trim() !== "") return null;
|
|
298
|
+
const newline = code.indexOf("\n", range.end);
|
|
299
|
+
const lineEnd = newline === -1 ? code.length : newline;
|
|
300
|
+
if (code.slice(range.end, lineEnd).trim() !== "") return null;
|
|
301
|
+
return {
|
|
302
|
+
start: lineStart,
|
|
303
|
+
end: newline === -1 ? code.length : newline + 1,
|
|
304
|
+
indent
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Move the `src` range before or after the `tgt` range (`tgt` must not be inside `src`).
|
|
309
|
+
* Elements alone on their lines are moved as whole lines, re-indented to the target's level;
|
|
310
|
+
* inline elements are moved as-is. Inserting at the far edge of the target keeps the target's
|
|
311
|
+
* own range untouched even when the source sits inside it (e.g. a <span> out of its <p>).
|
|
312
|
+
*/
|
|
313
|
+
function moveRange(code, src, tgt, position) {
|
|
314
|
+
const srcLines = ownLines(code, src);
|
|
315
|
+
const tgtLines = ownLines(code, tgt);
|
|
316
|
+
let removeStart = src.start;
|
|
317
|
+
let removeEnd = src.end;
|
|
318
|
+
let insertAt = position === "before" ? tgt.start : tgt.end;
|
|
319
|
+
let inserted = code.slice(src.start, src.end);
|
|
320
|
+
let elementOffset = 0;
|
|
321
|
+
let elementLength = inserted.length;
|
|
322
|
+
if (srcLines && tgtLines) {
|
|
323
|
+
const body = inserted.split("\n").map((line, i) => {
|
|
324
|
+
if (i === 0) return line;
|
|
325
|
+
return line.startsWith(srcLines.indent) ? tgtLines.indent + line.slice(srcLines.indent.length) : line;
|
|
326
|
+
}).join("\n");
|
|
327
|
+
removeStart = srcLines.start;
|
|
328
|
+
removeEnd = srcLines.end;
|
|
329
|
+
elementOffset = tgtLines.indent.length;
|
|
330
|
+
elementLength = body.length;
|
|
331
|
+
if (position === "before") {
|
|
332
|
+
insertAt = tgtLines.start;
|
|
333
|
+
inserted = `${tgtLines.indent}${body}\n`;
|
|
334
|
+
} else {
|
|
335
|
+
insertAt = tgtLines.end;
|
|
336
|
+
const atEofWithoutNewline = tgtLines.end === code.length && !code.endsWith("\n");
|
|
337
|
+
inserted = atEofWithoutNewline ? `\n${tgtLines.indent}${body}` : `${tgtLines.indent}${body}\n`;
|
|
338
|
+
if (atEofWithoutNewline) elementOffset += 1;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const removed = removeEnd - removeStart;
|
|
342
|
+
const next = insertAt <= removeStart ? code.slice(0, insertAt) + inserted + code.slice(insertAt, removeStart) + code.slice(removeEnd) : code.slice(0, removeStart) + code.slice(removeEnd, insertAt) + inserted + code.slice(insertAt);
|
|
343
|
+
const movedStart = (insertAt <= removeStart ? insertAt : insertAt - removed) + elementOffset;
|
|
344
|
+
return {
|
|
345
|
+
code: next,
|
|
346
|
+
removeStart,
|
|
347
|
+
removeEnd,
|
|
348
|
+
insertAt,
|
|
349
|
+
insertLength: inserted.length,
|
|
350
|
+
moved: {
|
|
351
|
+
start: movedStart,
|
|
352
|
+
end: movedStart + elementLength
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Where an offset of the original code (outside the removed range) lands after `edit`.
|
|
358
|
+
* A start sitting exactly at the insertion point is pushed after the inserted text, an end
|
|
359
|
+
* sitting there stays before it.
|
|
360
|
+
*/
|
|
361
|
+
function mapOffset(edit, offset, isEnd) {
|
|
362
|
+
let mapped = offset;
|
|
363
|
+
if (offset >= edit.removeEnd) mapped -= edit.removeEnd - edit.removeStart;
|
|
364
|
+
if (isEnd ? offset > edit.insertAt : offset >= edit.insertAt) mapped += edit.insertLength;
|
|
365
|
+
return mapped;
|
|
366
|
+
}
|
|
264
367
|
/** Start of the line `offset` sits on, so a removed tag doesn't leave a blank line behind. */
|
|
265
368
|
function lineRangeStart(code, offset) {
|
|
266
369
|
const lineStart = code.lastIndexOf("\n", offset);
|
|
@@ -311,6 +414,14 @@ const payloadArraySet = z.object({
|
|
|
311
414
|
arrayId: z.string().min(1),
|
|
312
415
|
items: z.array(z.string()).min(1)
|
|
313
416
|
});
|
|
417
|
+
const payloadMoveElements = z.object({
|
|
418
|
+
action: z.literal("moveElements"),
|
|
419
|
+
moves: z.array(z.object({
|
|
420
|
+
id: z.string().min(1),
|
|
421
|
+
targetId: z.string().min(1),
|
|
422
|
+
position: z.enum(["before", "after"])
|
|
423
|
+
})).min(1).max(200)
|
|
424
|
+
});
|
|
314
425
|
const routeIdSchema = z.string().min(1).regex(/^[a-zA-Z0-9._$/()[\]+~-]+$/).refine((id) => !id.split("/").includes("..") && !id.startsWith("/"), "Invalid route id");
|
|
315
426
|
const payloadGetPageMeta = z.object({
|
|
316
427
|
action: z.literal("getPageMeta"),
|
|
@@ -717,6 +828,118 @@ var UpstartEditorAPI = class {
|
|
|
717
828
|
}
|
|
718
829
|
}
|
|
719
830
|
/**
|
|
831
|
+
* Apply a batch of element moves (editor drag & drop), in order. Every element involved is
|
|
832
|
+
* tracked across the moves, so a later move finds elements an earlier one shifted. Files are
|
|
833
|
+
* only written when every move succeeded and every result still parses.
|
|
834
|
+
*/
|
|
835
|
+
async moveElements(params) {
|
|
836
|
+
const parsed = payloadMoveElements.safeParse(params);
|
|
837
|
+
if (!parsed.success) return {
|
|
838
|
+
success: false,
|
|
839
|
+
error: `Invalid payload: ${parsed.error.message}`
|
|
840
|
+
};
|
|
841
|
+
if (!this.registry) try {
|
|
842
|
+
await this.loadRegistry();
|
|
843
|
+
} catch (err) {
|
|
844
|
+
return {
|
|
845
|
+
success: false,
|
|
846
|
+
error: `Failed to load registry: ${err}`
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
const elements = this.registry.elements;
|
|
850
|
+
const codes = /* @__PURE__ */ new Map();
|
|
851
|
+
const tracked = /* @__PURE__ */ new Map();
|
|
852
|
+
try {
|
|
853
|
+
for (const { id, targetId, position } of parsed.data.moves) {
|
|
854
|
+
if (id === targetId) return {
|
|
855
|
+
success: false,
|
|
856
|
+
error: "Cannot move an element onto itself"
|
|
857
|
+
};
|
|
858
|
+
const source = elements[id];
|
|
859
|
+
const target = elements[targetId];
|
|
860
|
+
if (source?.type !== "element") return {
|
|
861
|
+
success: false,
|
|
862
|
+
error: `Element ${id} not found in registry`
|
|
863
|
+
};
|
|
864
|
+
if (target?.type !== "element") return {
|
|
865
|
+
success: false,
|
|
866
|
+
error: `Element ${targetId} not found in registry`
|
|
867
|
+
};
|
|
868
|
+
if (source.file !== target.file) return {
|
|
869
|
+
success: false,
|
|
870
|
+
error: "Elements can only be moved within the same file"
|
|
871
|
+
};
|
|
872
|
+
const file = source.file;
|
|
873
|
+
let code = codes.get(file);
|
|
874
|
+
if (code === void 0) {
|
|
875
|
+
code = await fs.readFile(path.join(this.projectRoot, file), "utf-8");
|
|
876
|
+
codes.set(file, code);
|
|
877
|
+
for (const [elementId, entry] of Object.entries(elements)) {
|
|
878
|
+
if (entry.type !== "element" || entry.file !== file) continue;
|
|
879
|
+
tracked.set(elementId, {
|
|
880
|
+
start: entry.startOffset,
|
|
881
|
+
end: entry.endOffset,
|
|
882
|
+
content: entry.originalContent
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
const src = locateElement(code, tracked.get(id));
|
|
887
|
+
const tgt = locateElement(code, tracked.get(targetId));
|
|
888
|
+
if (!src || !tgt) return {
|
|
889
|
+
success: false,
|
|
890
|
+
error: `Element not found in ${file}. The file may have been modified.`
|
|
891
|
+
};
|
|
892
|
+
if (tgt.start >= src.start && tgt.end <= src.end) return {
|
|
893
|
+
success: false,
|
|
894
|
+
error: "Cannot move an element inside itself"
|
|
895
|
+
};
|
|
896
|
+
const edit = moveRange(code, src, tgt, position);
|
|
897
|
+
codes.set(file, edit.code);
|
|
898
|
+
for (const [elementId, el] of tracked) {
|
|
899
|
+
if (elementId === id) continue;
|
|
900
|
+
if (elements[elementId].file !== file) continue;
|
|
901
|
+
if (el.start >= src.start && el.end <= src.end) {
|
|
902
|
+
const delta = edit.moved.start - src.start;
|
|
903
|
+
tracked.set(elementId, {
|
|
904
|
+
...el,
|
|
905
|
+
start: el.start + delta,
|
|
906
|
+
end: el.end + delta
|
|
907
|
+
});
|
|
908
|
+
} else tracked.set(elementId, {
|
|
909
|
+
...el,
|
|
910
|
+
start: mapOffset(edit, el.start, false),
|
|
911
|
+
end: mapOffset(edit, el.end, true)
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
tracked.set(id, {
|
|
915
|
+
...edit.moved,
|
|
916
|
+
content: edit.code.slice(edit.moved.start, edit.moved.end)
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
const changed = [];
|
|
920
|
+
for (const [file, code] of codes) {
|
|
921
|
+
const filePath = path.join(this.projectRoot, file);
|
|
922
|
+
if (code === await fs.readFile(filePath, "utf-8")) continue;
|
|
923
|
+
if (parseSync(file, code, { sourceType: "module" }).errors.length > 0) return {
|
|
924
|
+
success: false,
|
|
925
|
+
error: `Moving the elements would produce invalid code in ${file}`
|
|
926
|
+
};
|
|
927
|
+
changed.push([filePath, code]);
|
|
928
|
+
}
|
|
929
|
+
for (const [filePath, code] of changed) await fs.writeFile(filePath, code);
|
|
930
|
+
if (changed.length > 0) this.registry = null;
|
|
931
|
+
return {
|
|
932
|
+
success: true,
|
|
933
|
+
filePaths: changed.map(([filePath]) => filePath)
|
|
934
|
+
};
|
|
935
|
+
} catch (err) {
|
|
936
|
+
return {
|
|
937
|
+
success: false,
|
|
938
|
+
error: String(err)
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
720
943
|
* Read the page metadata shown in the browser tab and in search results.
|
|
721
944
|
*
|
|
722
945
|
* Two shapes are supported, in this order: React-rendered tags (`<title>{title}</title>`
|
|
@@ -1351,6 +1574,6 @@ var UpstartEditorAPI = class {
|
|
|
1351
1574
|
}
|
|
1352
1575
|
};
|
|
1353
1576
|
//#endregion
|
|
1354
|
-
export { EDITABLE_THEME_COLORS, UpstartEditorAPI, escapeStringLiteralBody, payloadArrayItemAdd, payloadArrayItemDelete, payloadArraySet, payloadEditClassName, payloadEditImage, payloadEditText, payloadEditTextDirect, payloadGetPageMeta, payloadGetSiteMeta, payloadGetTheme, payloadSetPageMeta, payloadSetSiteMeta, payloadSetTheme, readLocaleValue, writeLocaleValue };
|
|
1577
|
+
export { EDITABLE_THEME_COLORS, UpstartEditorAPI, escapeStringLiteralBody, mapOffset, moveRange, payloadArrayItemAdd, payloadArrayItemDelete, payloadArraySet, payloadEditClassName, payloadEditImage, payloadEditText, payloadEditTextDirect, payloadGetPageMeta, payloadGetSiteMeta, payloadGetTheme, payloadMoveElements, payloadSetPageMeta, payloadSetSiteMeta, payloadSetTheme, readLocaleValue, writeLocaleValue };
|
|
1355
1578
|
|
|
1356
1579
|
//# sourceMappingURL=upstart-editor-api.js.map
|