@lovision/plugin-dev 1.0.0
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/README.md +49 -0
- package/dist/build.d.ts +16 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +108 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +123 -0
- package/dist/cli.js.map +1 -0
- package/dist/create-plugin.d.ts +19 -0
- package/dist/create-plugin.d.ts.map +1 -0
- package/dist/create-plugin.js +186 -0
- package/dist/create-plugin.js.map +1 -0
- package/dist/dev.d.ts +13 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +206 -0
- package/dist/dev.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/publish.d.ts +15 -0
- package/dist/publish.d.ts.map +1 -0
- package/dist/publish.js +55 -0
- package/dist/publish.js.map +1 -0
- package/dist/shared.d.ts +93 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +436 -0
- package/dist/shared.js.map +1 -0
- package/dist/templates/ai-layout-assistant/README.md.template +24 -0
- package/dist/templates/ai-layout-assistant/eslint.config.mjs +19 -0
- package/dist/templates/ai-layout-assistant/manifest.json.template +38 -0
- package/dist/templates/ai-layout-assistant/package.json.template +18 -0
- package/dist/templates/ai-layout-assistant/src/main.ts.template +345 -0
- package/dist/templates/ai-layout-assistant/tsconfig.json +14 -0
- package/dist/templates/ai-layout-assistant/ui.html.template +114 -0
- package/dist/templates/asset-browser/README.md.template +24 -0
- package/dist/templates/asset-browser/eslint.config.mjs +19 -0
- package/dist/templates/asset-browser/manifest.json.template +29 -0
- package/dist/templates/asset-browser/package.json.template +18 -0
- package/dist/templates/asset-browser/src/main.ts.template +177 -0
- package/dist/templates/asset-browser/tsconfig.json +14 -0
- package/dist/templates/asset-browser/ui.html.template +137 -0
- package/dist/templates/base/README.md.template +34 -0
- package/dist/templates/base/eslint.config.mjs +19 -0
- package/dist/templates/base/manifest.json.template +22 -0
- package/dist/templates/base/package.json.template +18 -0
- package/dist/templates/base/src/main.ts.template +20 -0
- package/dist/templates/base/tsconfig.json +14 -0
- package/dist/templates/batch-layout-organizer/README.md.template +24 -0
- package/dist/templates/batch-layout-organizer/eslint.config.mjs +19 -0
- package/dist/templates/batch-layout-organizer/manifest.json.template +31 -0
- package/dist/templates/batch-layout-organizer/package.json.template +18 -0
- package/dist/templates/batch-layout-organizer/src/main.ts.template +324 -0
- package/dist/templates/batch-layout-organizer/tsconfig.json +14 -0
- package/dist/templates/batch-layout-organizer/ui.html.template +116 -0
- package/dist/templates/data-filler-full/README.md.template +32 -0
- package/dist/templates/data-filler-full/eslint.config.mjs +19 -0
- package/dist/templates/data-filler-full/manifest.json.template +31 -0
- package/dist/templates/data-filler-full/package.json.template +18 -0
- package/dist/templates/data-filler-full/src/main.ts.template +412 -0
- package/dist/templates/data-filler-full/tsconfig.json +14 -0
- package/dist/templates/data-filler-full/ui.html.template +221 -0
- package/dist/templates/data-filler-lite/README.md.template +47 -0
- package/dist/templates/data-filler-lite/eslint.config.mjs +19 -0
- package/dist/templates/data-filler-lite/manifest.json.template +29 -0
- package/dist/templates/data-filler-lite/package.json.template +18 -0
- package/dist/templates/data-filler-lite/src/main.ts.template +222 -0
- package/dist/templates/data-filler-lite/tsconfig.json +14 -0
- package/dist/templates/data-filler-lite/ui.html.template +180 -0
- package/dist/templates/design-lint-panel/README.md.template +33 -0
- package/dist/templates/design-lint-panel/eslint.config.mjs +19 -0
- package/dist/templates/design-lint-panel/manifest.json.template +29 -0
- package/dist/templates/design-lint-panel/package.json.template +18 -0
- package/dist/templates/design-lint-panel/src/main.ts.template +221 -0
- package/dist/templates/design-lint-panel/tsconfig.json +14 -0
- package/dist/templates/design-lint-panel/ui.html.template +172 -0
- package/dist/templates/export-selection/README.md.template +26 -0
- package/dist/templates/export-selection/eslint.config.mjs +19 -0
- package/dist/templates/export-selection/manifest.json.template +31 -0
- package/dist/templates/export-selection/package.json.template +18 -0
- package/dist/templates/export-selection/src/main.ts.template +386 -0
- package/dist/templates/export-selection/tsconfig.json +14 -0
- package/dist/templates/export-selection/ui.html.template +163 -0
- package/dist/templates/review-submitter/README.md.template +24 -0
- package/dist/templates/review-submitter/eslint.config.mjs +19 -0
- package/dist/templates/review-submitter/manifest.json.template +35 -0
- package/dist/templates/review-submitter/package.json.template +18 -0
- package/dist/templates/review-submitter/src/main.ts.template +306 -0
- package/dist/templates/review-submitter/tsconfig.json +14 -0
- package/dist/templates/review-submitter/ui.html.template +114 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +42 -0
- package/dist/validate.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "__PLUGIN_ID__",
|
|
3
|
+
"name": {
|
|
4
|
+
"en": "Data Filler Lite",
|
|
5
|
+
"zh-CN": "Data Filler Lite"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"apiVersion": "1.0",
|
|
9
|
+
"editorType": ["design"],
|
|
10
|
+
"main": "./dist/main.js",
|
|
11
|
+
"ui": "./ui.html",
|
|
12
|
+
"documentAccess": "current-page",
|
|
13
|
+
"permissions": [
|
|
14
|
+
"document:read",
|
|
15
|
+
"document:write.style",
|
|
16
|
+
"notify",
|
|
17
|
+
"selection:read",
|
|
18
|
+
"ui:modal"
|
|
19
|
+
],
|
|
20
|
+
"commands": [
|
|
21
|
+
{
|
|
22
|
+
"id": "run",
|
|
23
|
+
"name": {
|
|
24
|
+
"en": "__COMMAND_NAME_EN__",
|
|
25
|
+
"zh-CN": "__COMMAND_NAME_ZH__"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PACKAGE_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "plugin-dev dev",
|
|
7
|
+
"build": "plugin-dev build",
|
|
8
|
+
"validate": "plugin-dev validate",
|
|
9
|
+
"lint": "eslint src --max-warnings=0"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@lovision/plugin-dev": "__PLUGIN_DEV_SPEC__",
|
|
13
|
+
"@lovision/plugin-sdk": "__PLUGIN_SDK_SPEC__"__LOCAL_SUPPORT_DEPS__,
|
|
14
|
+
"@typescript-eslint/parser": "^8.30.0",
|
|
15
|
+
"eslint": "^9.25.1",
|
|
16
|
+
"typescript": "^5.8.3"
|
|
17
|
+
}__LOCAL_OVERRIDES__
|
|
18
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { definePlugin } from "@lovision/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
type DataRow = Record<string, string>;
|
|
4
|
+
|
|
5
|
+
type ApplyPayload = {
|
|
6
|
+
text: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type UserDecision =
|
|
10
|
+
| {
|
|
11
|
+
kind: "apply";
|
|
12
|
+
payload: unknown;
|
|
13
|
+
}
|
|
14
|
+
| {
|
|
15
|
+
kind: "close";
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
definePlugin({
|
|
19
|
+
apiVersion: "1.0",
|
|
20
|
+
version: "0.1.0",
|
|
21
|
+
command: async (ctx) => {
|
|
22
|
+
const session = await ctx.ui.show({
|
|
23
|
+
entry: "./ui.html",
|
|
24
|
+
title: "Data Filler Lite",
|
|
25
|
+
width: 560,
|
|
26
|
+
height: 520,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
let closed = false;
|
|
30
|
+
const userDecision = new Promise<UserDecision>((resolve) => {
|
|
31
|
+
session.on("close", () => {
|
|
32
|
+
closed = true;
|
|
33
|
+
resolve({ kind: "close" });
|
|
34
|
+
});
|
|
35
|
+
session.on("apply", (payload) => {
|
|
36
|
+
resolve({ kind: "apply", payload });
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await session.postMessage({
|
|
41
|
+
type: "ready",
|
|
42
|
+
payload: {
|
|
43
|
+
message:
|
|
44
|
+
"Paste CSV or JSON. Selection is used first; empty selection falls back to the first top-level node.",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const decision = await userDecision;
|
|
49
|
+
if (decision.kind === "close") {
|
|
50
|
+
await ctx.notify.send("Data Filler Lite closed without applying data.", {
|
|
51
|
+
kind: "info",
|
|
52
|
+
});
|
|
53
|
+
return { applied: 0, closed: true, ok: true };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const payload = readApplyPayload(decision.payload);
|
|
58
|
+
const rows = parseRows(payload.text);
|
|
59
|
+
const targetIds = await resolveTargetNodeIds(ctx);
|
|
60
|
+
if (targetIds.length === 0) {
|
|
61
|
+
throw new Error("No selected or top-level canvas node is available.");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const updates = targetIds.map((id, index) => ({
|
|
65
|
+
id,
|
|
66
|
+
changes: {
|
|
67
|
+
name: nameForRow(rows[index % rows.length], index),
|
|
68
|
+
},
|
|
69
|
+
}));
|
|
70
|
+
const result = await ctx.nodes.update(updates);
|
|
71
|
+
if (!closed) {
|
|
72
|
+
await session.postMessage({
|
|
73
|
+
type: "applied",
|
|
74
|
+
payload: {
|
|
75
|
+
applied: updates.length,
|
|
76
|
+
newVersion: result.newVersion,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
await ctx.notify.send(`Data Filler Lite applied ${updates.length} row(s).`, {
|
|
81
|
+
kind: "success",
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
applied: updates.length,
|
|
85
|
+
newVersion: result.newVersion,
|
|
86
|
+
ok: true,
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
if (!closed) {
|
|
91
|
+
await session.postMessage({
|
|
92
|
+
type: "error",
|
|
93
|
+
payload: { message },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
await ctx.notify.send(`Data Filler Lite failed: ${message}`, {
|
|
97
|
+
kind: "error",
|
|
98
|
+
});
|
|
99
|
+
return { error: message, ok: false };
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function readApplyPayload(payload: unknown): ApplyPayload {
|
|
105
|
+
if (!isRecord(payload) || typeof payload.text !== "string") {
|
|
106
|
+
throw new Error("Apply payload must include a text field.");
|
|
107
|
+
}
|
|
108
|
+
return { text: payload.text };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function resolveTargetNodeIds(ctx: {
|
|
112
|
+
document: { snapshot(): Promise<{ root: { children: Array<{ id: string }> } }> };
|
|
113
|
+
selection: { get(): Promise<string[]> };
|
|
114
|
+
}): Promise<string[]> {
|
|
115
|
+
const selection = await ctx.selection.get();
|
|
116
|
+
if (selection.length > 0) {
|
|
117
|
+
return selection;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const snapshot = await ctx.document.snapshot();
|
|
121
|
+
const firstTopLevel = snapshot.root.children[0];
|
|
122
|
+
return firstTopLevel ? [firstTopLevel.id] : [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseRows(input: string): DataRow[] {
|
|
126
|
+
const text = input.trim();
|
|
127
|
+
if (text.length === 0) {
|
|
128
|
+
throw new Error("Paste CSV or JSON before applying.");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (text.startsWith("{") || text.startsWith("[")) {
|
|
132
|
+
return parseJsonRows(text);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return parseCsvRows(text);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function parseJsonRows(text: string): DataRow[] {
|
|
139
|
+
const parsed = JSON.parse(text) as unknown;
|
|
140
|
+
const values = Array.isArray(parsed) ? parsed : [parsed];
|
|
141
|
+
const rows = values.map((value) => coerceRow(value));
|
|
142
|
+
if (rows.length === 0) {
|
|
143
|
+
throw new Error("JSON input must contain at least one row.");
|
|
144
|
+
}
|
|
145
|
+
return rows;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function parseCsvRows(text: string): DataRow[] {
|
|
149
|
+
const lines = text
|
|
150
|
+
.split(/\r?\n/)
|
|
151
|
+
.map((line) => line.trim())
|
|
152
|
+
.filter((line) => line.length > 0);
|
|
153
|
+
if (lines.length < 2) {
|
|
154
|
+
throw new Error("CSV input needs a header row and at least one data row.");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const headers = parseCsvLine(lines[0]).map((header) => header.trim());
|
|
158
|
+
if (headers.length === 0 || headers.some((header) => header.length === 0)) {
|
|
159
|
+
throw new Error("CSV headers must not be empty.");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return lines.slice(1).map((line) => {
|
|
163
|
+
const values = parseCsvLine(line);
|
|
164
|
+
const row: DataRow = {};
|
|
165
|
+
headers.forEach((header, index) => {
|
|
166
|
+
row[header] = values[index] ?? "";
|
|
167
|
+
});
|
|
168
|
+
return row;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function parseCsvLine(line: string): string[] {
|
|
173
|
+
const values: string[] = [];
|
|
174
|
+
let current = "";
|
|
175
|
+
let quoted = false;
|
|
176
|
+
|
|
177
|
+
for (const char of line) {
|
|
178
|
+
if (char === '"') {
|
|
179
|
+
quoted = !quoted;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (char === "," && !quoted) {
|
|
183
|
+
values.push(current.trim());
|
|
184
|
+
current = "";
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
current += char;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
values.push(current.trim());
|
|
191
|
+
return values;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function coerceRow(value: unknown): DataRow {
|
|
195
|
+
if (!isRecord(value)) {
|
|
196
|
+
throw new Error("Each JSON row must be an object.");
|
|
197
|
+
}
|
|
198
|
+
const row: DataRow = {};
|
|
199
|
+
for (const [key, cell] of Object.entries(value)) {
|
|
200
|
+
row[key] = cell == null ? "" : String(cell);
|
|
201
|
+
}
|
|
202
|
+
return row;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function nameForRow(row: DataRow | undefined, index: number): string {
|
|
206
|
+
if (!row) {
|
|
207
|
+
return `Data Row ${index + 1}`;
|
|
208
|
+
}
|
|
209
|
+
const preferred = row.name ?? row.title ?? row.label;
|
|
210
|
+
if (preferred && preferred.trim().length > 0) {
|
|
211
|
+
return preferred.trim();
|
|
212
|
+
}
|
|
213
|
+
const summary = Object.values(row)
|
|
214
|
+
.map((value) => value.trim())
|
|
215
|
+
.filter((value) => value.length > 0)
|
|
216
|
+
.join(" / ");
|
|
217
|
+
return summary.length > 0 ? summary : `Data Row ${index + 1}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
221
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
222
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["ESNext", "WebWorker"],
|
|
4
|
+
"module": "Preserve",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"target": "ESNext",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"verbatimModuleSyntax": true,
|
|
11
|
+
"noEmit": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Data Filler Lite</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: light dark;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body {
|
|
13
|
+
margin: 0;
|
|
14
|
+
color: CanvasText;
|
|
15
|
+
background: Canvas;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
main {
|
|
19
|
+
display: grid;
|
|
20
|
+
gap: 1rem;
|
|
21
|
+
padding: 1rem;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
textarea {
|
|
25
|
+
box-sizing: border-box;
|
|
26
|
+
min-height: 12rem;
|
|
27
|
+
width: 100%;
|
|
28
|
+
resize: vertical;
|
|
29
|
+
color: FieldText;
|
|
30
|
+
background: Field;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.actions {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-wrap: wrap;
|
|
36
|
+
gap: 0.5rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.status {
|
|
40
|
+
min-height: 1.5rem;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pre {
|
|
44
|
+
max-height: 8rem;
|
|
45
|
+
overflow: auto;
|
|
46
|
+
padding: 0.75rem;
|
|
47
|
+
color: CanvasText;
|
|
48
|
+
background: Canvas;
|
|
49
|
+
border: 1px solid ButtonBorder;
|
|
50
|
+
}
|
|
51
|
+
</style>
|
|
52
|
+
</head>
|
|
53
|
+
<body>
|
|
54
|
+
<main>
|
|
55
|
+
<header>
|
|
56
|
+
<h1>Data Filler Lite</h1>
|
|
57
|
+
<p>
|
|
58
|
+
Paste CSV or JSON, preview the parsed rows, then apply. Selection is
|
|
59
|
+
used first; empty selection falls back to the first top-level node for
|
|
60
|
+
demo verification.
|
|
61
|
+
</p>
|
|
62
|
+
</header>
|
|
63
|
+
|
|
64
|
+
<label>
|
|
65
|
+
Data
|
|
66
|
+
<textarea id="data-input" spellcheck="false">name,role
|
|
67
|
+
Hero Card,Landing
|
|
68
|
+
CTA Button,Marketing</textarea>
|
|
69
|
+
</label>
|
|
70
|
+
|
|
71
|
+
<div class="actions">
|
|
72
|
+
<button id="preview-button" type="button">Preview</button>
|
|
73
|
+
<button id="apply-button" type="button">Apply to nodes</button>
|
|
74
|
+
<button id="reject-button" type="button">Trigger unhandled rejection</button>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<p id="status" class="status" role="status">Waiting for host...</p>
|
|
78
|
+
<pre id="preview" aria-label="Preview"></pre>
|
|
79
|
+
</main>
|
|
80
|
+
|
|
81
|
+
<script>
|
|
82
|
+
const uiId = globalThis.__LOVISION_PLUGIN_UI_ID__;
|
|
83
|
+
const input = document.getElementById("data-input");
|
|
84
|
+
const status = document.getElementById("status");
|
|
85
|
+
const preview = document.getElementById("preview");
|
|
86
|
+
|
|
87
|
+
function post(type, payload) {
|
|
88
|
+
parent.postMessage(
|
|
89
|
+
{
|
|
90
|
+
type: "plugin-ui-message",
|
|
91
|
+
direction: "ui-to-main",
|
|
92
|
+
uiId,
|
|
93
|
+
message: { type, payload },
|
|
94
|
+
},
|
|
95
|
+
"*",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function parseRows(text) {
|
|
100
|
+
const trimmed = text.trim();
|
|
101
|
+
if (!trimmed) {
|
|
102
|
+
throw new Error("Paste CSV or JSON before applying.");
|
|
103
|
+
}
|
|
104
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
105
|
+
const parsed = JSON.parse(trimmed);
|
|
106
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
107
|
+
}
|
|
108
|
+
const lines = trimmed
|
|
109
|
+
.split(/\r?\n/)
|
|
110
|
+
.map((line) => line.trim())
|
|
111
|
+
.filter(Boolean);
|
|
112
|
+
const headers = splitCsvLine(lines[0] || "");
|
|
113
|
+
return lines.slice(1).map((line) => {
|
|
114
|
+
const values = splitCsvLine(line);
|
|
115
|
+
return Object.fromEntries(
|
|
116
|
+
headers.map((header, index) => [header, values[index] || ""]),
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function splitCsvLine(line) {
|
|
122
|
+
const values = [];
|
|
123
|
+
let current = "";
|
|
124
|
+
let quoted = false;
|
|
125
|
+
for (const char of line) {
|
|
126
|
+
if (char === '"') {
|
|
127
|
+
quoted = !quoted;
|
|
128
|
+
} else if (char === "," && !quoted) {
|
|
129
|
+
values.push(current.trim());
|
|
130
|
+
current = "";
|
|
131
|
+
} else {
|
|
132
|
+
current += char;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
values.push(current.trim());
|
|
136
|
+
return values;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
document.getElementById("preview-button").addEventListener("click", () => {
|
|
140
|
+
try {
|
|
141
|
+
const rows = parseRows(input.value);
|
|
142
|
+
preview.textContent = JSON.stringify(rows, null, 2);
|
|
143
|
+
status.textContent = `Previewed ${rows.length} row(s).`;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
status.textContent = error instanceof Error ? error.message : String(error);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
document.getElementById("apply-button").addEventListener("click", () => {
|
|
150
|
+
status.textContent = "Applying...";
|
|
151
|
+
post("apply", { text: input.value });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
document.getElementById("reject-button").addEventListener("click", () => {
|
|
155
|
+
Promise.reject(new Error("Data Filler Lite unhandled rejection demo"));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
window.addEventListener("message", (event) => {
|
|
159
|
+
const envelope = event.data;
|
|
160
|
+
if (
|
|
161
|
+
!envelope ||
|
|
162
|
+
envelope.type !== "plugin-ui-message" ||
|
|
163
|
+
envelope.direction !== "main-to-ui" ||
|
|
164
|
+
envelope.uiId !== uiId
|
|
165
|
+
) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const message = envelope.message;
|
|
170
|
+
if (message.type === "ready") {
|
|
171
|
+
status.textContent = message.payload.message;
|
|
172
|
+
} else if (message.type === "applied") {
|
|
173
|
+
status.textContent = `Applied ${message.payload.applied} node(s).`;
|
|
174
|
+
} else if (message.type === "error") {
|
|
175
|
+
status.textContent = message.payload.message;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
</script>
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Design Lint Panel
|
|
2
|
+
|
|
3
|
+
Design Lint Panel is a panel-first Instinct UI plugin example generated by `plugin-dev create --template design-lint-panel`.
|
|
4
|
+
|
|
5
|
+
It uses a single `ui.html` resource with an inline browser bridge. The iframe UI never calls the Host Facade directly; it only posts `plugin-ui-message` envelopes to the main worker. The main worker owns all `document`, `viewport`, `nodes`, and `notify` calls.
|
|
6
|
+
|
|
7
|
+
## Scripts
|
|
8
|
+
|
|
9
|
+
- `dev` - start the local HTTPS sideload server and print `manifestUrl`
|
|
10
|
+
- `build` - emit a formal-install bundle under `dist/`
|
|
11
|
+
- `validate` - run the same manifest + bundle validation path used by the host
|
|
12
|
+
- `lint` - run the default plugin ESLint setup
|
|
13
|
+
|
|
14
|
+
## First Run
|
|
15
|
+
|
|
16
|
+
1. Install dependencies with your preferred package manager.
|
|
17
|
+
2. Run `dev`.
|
|
18
|
+
3. Open the editor shell Plugin Development panel.
|
|
19
|
+
4. Add by URL with the printed `manifestUrl`.
|
|
20
|
+
5. Run `Open Design Lint Panel`.
|
|
21
|
+
6. Click `Run lint` to scan the current page.
|
|
22
|
+
7. Click `Focus first issue` to focus the first finding.
|
|
23
|
+
8. Click `Fix default names` to prefix default or blank node names with `Lint/`.
|
|
24
|
+
|
|
25
|
+
## Rules
|
|
26
|
+
|
|
27
|
+
- `unnamed-node` - non-root node name is blank or one of `Rectangle`, `Frame`, or `Text`
|
|
28
|
+
- `empty-text-node` - text node content is empty
|
|
29
|
+
- `non-positive-size` - non-root node width or height is not positive
|
|
30
|
+
|
|
31
|
+
## Formal Install
|
|
32
|
+
|
|
33
|
+
Run `build`, then upload the generated `dist/*.bundle.json` as a Formal install. The installed plugin should appear in Installed and MainMenu, and the same panel UI should open from MainMenu.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import pluginSdkConfig from "@lovision/plugin-sdk/eslint-config";
|
|
2
|
+
import tsParser from "@typescript-eslint/parser";
|
|
3
|
+
|
|
4
|
+
const pluginFiles = ["src/**/*.ts"];
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
{
|
|
8
|
+
files: pluginFiles,
|
|
9
|
+
languageOptions: {
|
|
10
|
+
parser: tsParser,
|
|
11
|
+
ecmaVersion: "latest",
|
|
12
|
+
sourceType: "module",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
...pluginSdkConfig.map((entry) => ({
|
|
16
|
+
...entry,
|
|
17
|
+
files: pluginFiles,
|
|
18
|
+
})),
|
|
19
|
+
];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "__PLUGIN_ID__",
|
|
3
|
+
"name": {
|
|
4
|
+
"en": "Design Lint Panel",
|
|
5
|
+
"zh-CN": "Design Lint Panel"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"apiVersion": "1.0",
|
|
9
|
+
"editorType": ["design"],
|
|
10
|
+
"main": "./dist/main.js",
|
|
11
|
+
"ui": "./ui.html",
|
|
12
|
+
"documentAccess": "current-page",
|
|
13
|
+
"permissions": [
|
|
14
|
+
"document:read",
|
|
15
|
+
"document:write.text",
|
|
16
|
+
"notify",
|
|
17
|
+
"ui:panel",
|
|
18
|
+
"viewport:write"
|
|
19
|
+
],
|
|
20
|
+
"commands": [
|
|
21
|
+
{
|
|
22
|
+
"id": "run",
|
|
23
|
+
"name": {
|
|
24
|
+
"en": "__COMMAND_NAME_EN__",
|
|
25
|
+
"zh-CN": "__COMMAND_NAME_ZH__"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PACKAGE_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "plugin-dev dev",
|
|
7
|
+
"build": "plugin-dev build",
|
|
8
|
+
"validate": "plugin-dev validate",
|
|
9
|
+
"lint": "eslint src --max-warnings=0"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@lovision/plugin-dev": "__PLUGIN_DEV_SPEC__",
|
|
13
|
+
"@lovision/plugin-sdk": "__PLUGIN_SDK_SPEC__"__LOCAL_SUPPORT_DEPS__,
|
|
14
|
+
"@typescript-eslint/parser": "^8.30.0",
|
|
15
|
+
"eslint": "^9.25.1",
|
|
16
|
+
"typescript": "^5.8.3"
|
|
17
|
+
}__LOCAL_OVERRIDES__
|
|
18
|
+
}
|