southpaw 0.2.7
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 +51 -0
- package/api/client.ts +83 -0
- package/api/helpers/fs.ts +98 -0
- package/api/helpers/stream.ts +6 -0
- package/api/index.ts +50 -0
- package/api/server.ts +566 -0
- package/constants/fonts.ts +1583 -0
- package/constants/icons.ts +2124 -0
- package/constants/langs.ts +2592 -0
- package/constants/ssr-loading.tsx +17 -0
- package/contexts/dev.tsx +124 -0
- package/hooks/useDebounceCb.ts +21 -0
- package/modules/app.tsx +102 -0
- package/modules/behaviors.ts +161 -0
- package/modules/component.tsx +483 -0
- package/modules/config/index.ts +8 -0
- package/modules/database.ts +25 -0
- package/modules/document.tsx +23 -0
- package/modules/error.tsx +16 -0
- package/modules/page/autozoom.tsx +92 -0
- package/modules/page/browser.tsx +166 -0
- package/modules/page/index.tsx +327 -0
- package/modules/page/insert-modal.tsx +225 -0
- package/modules/page/inspect/index.tsx +237 -0
- package/modules/page/inspect/wrapper.tsx +88 -0
- package/modules/page/knobs/action.tsx +58 -0
- package/modules/page/knobs/color.tsx +78 -0
- package/modules/page/knobs/helpers.ts +71 -0
- package/modules/page/knobs/icon.tsx +85 -0
- package/modules/page/knobs/linker.tsx +188 -0
- package/modules/page/knobs/list-json.tsx +149 -0
- package/modules/page/knobs/list-text.tsx +129 -0
- package/modules/page/knobs/number.tsx +84 -0
- package/modules/page/knobs/segmented.tsx +81 -0
- package/modules/page/knobs/select.tsx +89 -0
- package/modules/page/knobs/size.tsx +106 -0
- package/modules/page/knobs/switch.tsx +75 -0
- package/modules/page/knobs/text.tsx +93 -0
- package/modules/page/left-tabs.tsx +36 -0
- package/modules/page/right-tabs.tsx +31 -0
- package/modules/page/seo.tsx +69 -0
- package/modules/page/tree.tsx +860 -0
- package/modules/state.ts +787 -0
- package/modules/store.ts +37 -0
- package/modules/theme.ts +105 -0
- package/modules/translations.ts +15 -0
- package/package.json +68 -0
- package/scripts/bin.js +607 -0
- package/scripts/test.js +145 -0
- package/themes/base.tsx +8962 -0
- package/themes/index.ts +4 -0
- package/themes/m2.ts +39 -0
- package/themes/newspaper.ts +39 -0
- package/types/api.ts +57 -0
- package/types/fiber_node.ts +69 -0
- package/types/react_element.ts +14 -0
- package/ui/accordion.tsx +70 -0
- package/ui/actionicon.tsx +41 -0
- package/ui/affix.tsx +14 -0
- package/ui/alert.tsx +37 -0
- package/ui/anchor.tsx +52 -0
- package/ui/arrow.tsx +116 -0
- package/ui/aspectratio.tsx +21 -0
- package/ui/autocomplete.tsx +34 -0
- package/ui/avatar.tsx +36 -0
- package/ui/backgroundimage.tsx +16 -0
- package/ui/badge.tsx +28 -0
- package/ui/blockquote.tsx +15 -0
- package/ui/blocks/faq.tsx +145 -0
- package/ui/blocks/features.tsx +167 -0
- package/ui/blocks/footer.tsx +182 -0
- package/ui/blocks/herobanner.tsx +391 -0
- package/ui/blocks/index.ts +44 -0
- package/ui/blocks/leftnav.tsx +141 -0
- package/ui/blocks/pricing.tsx +210 -0
- package/ui/blocks/testimonials.tsx +149 -0
- package/ui/blocks/uidocs.tsx +1429 -0
- package/ui/box.tsx +27 -0
- package/ui/breadcrumbs.tsx +33 -0
- package/ui/burger.tsx +23 -0
- package/ui/button.tsx +85 -0
- package/ui/card.tsx +28 -0
- package/ui/center.tsx +26 -0
- package/ui/checkbox.tsx +35 -0
- package/ui/chip.tsx +32 -0
- package/ui/closebutton.tsx +15 -0
- package/ui/code.tsx +104 -0
- package/ui/collapse.tsx +16 -0
- package/ui/colorinput.tsx +32 -0
- package/ui/colorpicker.tsx +26 -0
- package/ui/colorswatch.tsx +17 -0
- package/ui/combobox.tsx +80 -0
- package/ui/container.tsx +27 -0
- package/ui/dialog.tsx +15 -0
- package/ui/divider.tsx +58 -0
- package/ui/drawer.tsx +28 -0
- package/ui/fieldset.tsx +15 -0
- package/ui/filebutton.tsx +23 -0
- package/ui/fileinput.tsx +36 -0
- package/ui/flex.tsx +139 -0
- package/ui/focustrap.tsx +15 -0
- package/ui/form.tsx +26 -0
- package/ui/grid.tsx +69 -0
- package/ui/highlight.tsx +15 -0
- package/ui/hoverable.tsx +66 -0
- package/ui/hovercard.tsx +45 -0
- package/ui/icon.tsx +76 -0
- package/ui/image.tsx +32 -0
- package/ui/index.ts +1355 -0
- package/ui/indicator.tsx +42 -0
- package/ui/infobox.tsx +200 -0
- package/ui/input.tsx +18 -0
- package/ui/inviewport.tsx +23 -0
- package/ui/jsoninput.tsx +35 -0
- package/ui/kbd.tsx +20 -0
- package/ui/list.tsx +33 -0
- package/ui/loader.tsx +26 -0
- package/ui/loadingoverlay.tsx +22 -0
- package/ui/mark.tsx +15 -0
- package/ui/menu.tsx +89 -0
- package/ui/modal.tsx +27 -0
- package/ui/multiselect.tsx +42 -0
- package/ui/nativeselect.tsx +17 -0
- package/ui/navlink.tsx +40 -0
- package/ui/notification.tsx +25 -0
- package/ui/numberformatter.tsx +15 -0
- package/ui/numberinput.tsx +37 -0
- package/ui/overlay.tsx +15 -0
- package/ui/pagination.tsx +21 -0
- package/ui/paper.tsx +33 -0
- package/ui/passwordinput.tsx +36 -0
- package/ui/pill.tsx +17 -0
- package/ui/pillsinput.tsx +37 -0
- package/ui/pininput.tsx +30 -0
- package/ui/popover.tsx +43 -0
- package/ui/portal.tsx +15 -0
- package/ui/progress.tsx +49 -0
- package/ui/props.ts +91 -0
- package/ui/propsDef.ts +38 -0
- package/ui/radio.tsx +30 -0
- package/ui/rating.tsx +25 -0
- package/ui/ringprogress.tsx +17 -0
- package/ui/scrollarea.tsx +27 -0
- package/ui/segmentedcontrol.tsx +38 -0
- package/ui/select.tsx +43 -0
- package/ui/simplegrid.tsx +23 -0
- package/ui/skeleton.tsx +15 -0
- package/ui/slider.tsx +31 -0
- package/ui/space.tsx +19 -0
- package/ui/spoiler.tsx +23 -0
- package/ui/stepper.tsx +63 -0
- package/ui/switch.tsx +29 -0
- package/ui/table.tsx +93 -0
- package/ui/tabs.tsx +80 -0
- package/ui/tagsinput.tsx +19 -0
- package/ui/terminal.tsx +44 -0
- package/ui/text.tsx +168 -0
- package/ui/textarea.tsx +36 -0
- package/ui/textinput.tsx +34 -0
- package/ui/timeline.tsx +30 -0
- package/ui/title.tsx +42 -0
- package/ui/tooltip.tsx +32 -0
- package/ui/transition.tsx +21 -0
- package/ui/tree.tsx +42 -0
- package/ui/typographystylesprovider.tsx +20 -0
- package/ui/unstyledbutton.tsx +18 -0
- package/ui/video.tsx +16 -0
- package/ui/visuallyhidden.tsx +15 -0
- package/utils/index.ts +76 -0
package/api/server.ts
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JsxAttributeStructure,
|
|
3
|
+
JsxElement,
|
|
4
|
+
JsxElementStructure,
|
|
5
|
+
JsxFragment,
|
|
6
|
+
JsxSelfClosingElement,
|
|
7
|
+
JsxSelfClosingElementStructure,
|
|
8
|
+
Project,
|
|
9
|
+
SourceFile,
|
|
10
|
+
StructureKind,
|
|
11
|
+
ts,
|
|
12
|
+
} from "ts-morph";
|
|
13
|
+
import PATH from "path";
|
|
14
|
+
import prettier from "prettier";
|
|
15
|
+
import { ApiType } from "../types/api";
|
|
16
|
+
import { FiberNodeType } from "../types/fiber_node";
|
|
17
|
+
|
|
18
|
+
const project = new Project();
|
|
19
|
+
project.addSourceFilesAtPaths(["src/**/*{.tsx,.ts}", "package.json"]);
|
|
20
|
+
|
|
21
|
+
const getSourceFile = (filename: string) => {
|
|
22
|
+
if (filename.startsWith("[project]")) {
|
|
23
|
+
const url = PATH.join(
|
|
24
|
+
process.cwd(),
|
|
25
|
+
PATH.normalize(filename.split("[project]")[1]),
|
|
26
|
+
);
|
|
27
|
+
const sourceFile = project.getSourceFile(url);
|
|
28
|
+
sourceFile?.refreshFromFileSystemSync();
|
|
29
|
+
if (!sourceFile) {
|
|
30
|
+
throw `Source file not found for ${filename}`;
|
|
31
|
+
}
|
|
32
|
+
return sourceFile;
|
|
33
|
+
} else {
|
|
34
|
+
const url = PATH.normalize(filename);
|
|
35
|
+
const sourceFile = project.getSourceFile(url);
|
|
36
|
+
sourceFile?.refreshFromFileSystemSync();
|
|
37
|
+
if (!sourceFile) {
|
|
38
|
+
throw `Source file not found for ${filename}`;
|
|
39
|
+
}
|
|
40
|
+
return sourceFile;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const getNodeAtPos = (
|
|
44
|
+
sourcefile: SourceFile,
|
|
45
|
+
location: FiberNodeType["_debugSource"],
|
|
46
|
+
) => {
|
|
47
|
+
const node =
|
|
48
|
+
sourcefile.getDescendants().find((node) => {
|
|
49
|
+
const lineNumber = node.getStartLineNumber();
|
|
50
|
+
const columnNumber = node.getStart() - node.getStartLinePos();
|
|
51
|
+
const found =
|
|
52
|
+
location.lineNumber === lineNumber &&
|
|
53
|
+
location.columnNumber === columnNumber;
|
|
54
|
+
return found;
|
|
55
|
+
}) || null;
|
|
56
|
+
if (!node) {
|
|
57
|
+
throw new Error(`Node at ${JSON.stringify(location)} not found`);
|
|
58
|
+
}
|
|
59
|
+
return node;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const saveSourceFile = async (sourceFile: SourceFile) => {
|
|
63
|
+
sourceFile.organizeImports();
|
|
64
|
+
|
|
65
|
+
const fullPath = sourceFile.getFilePath();
|
|
66
|
+
const content = sourceFile.getFullText();
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const formatted = await prettier.format(content, {
|
|
70
|
+
filepath: fullPath,
|
|
71
|
+
parser: "typescript",
|
|
72
|
+
semi: true,
|
|
73
|
+
singleQuote: false,
|
|
74
|
+
tabWidth: 2,
|
|
75
|
+
trailingComma: "all",
|
|
76
|
+
});
|
|
77
|
+
sourceFile.replaceWithText(formatted);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("Prettier formatting failed:", error);
|
|
80
|
+
// If Prettier fails, we still save the unformatted content to avoid data loss
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await sourceFile.save();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const ensureImports = (sourceFile: SourceFile, code: string) => {
|
|
87
|
+
const hasUI = code.includes("UI.");
|
|
88
|
+
const hasBlocks = code.includes("Blocks.");
|
|
89
|
+
|
|
90
|
+
if (hasUI) {
|
|
91
|
+
const uiImport = sourceFile.getImportDeclaration("southpaw/ui");
|
|
92
|
+
if (!uiImport) {
|
|
93
|
+
sourceFile.addImportDeclaration({
|
|
94
|
+
namedImports: ["UI"],
|
|
95
|
+
moduleSpecifier: "southpaw/ui",
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
if (!uiImport.getNamedImports().some((ni) => ni.getName() === "UI")) {
|
|
99
|
+
uiImport.addNamedImport("UI");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (hasBlocks) {
|
|
105
|
+
const blocksImport = sourceFile.getImportDeclaration("southpaw/ui/blocks");
|
|
106
|
+
if (!blocksImport) {
|
|
107
|
+
sourceFile.addImportDeclaration({
|
|
108
|
+
namedImports: ["Blocks"],
|
|
109
|
+
moduleSpecifier: "southpaw/ui/blocks",
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
if (!blocksImport.getNamedImports().some((ni) => ni.getName() === "Blocks")) {
|
|
113
|
+
blocksImport.addNamedImport("Blocks");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
export const API: ApiType = {
|
|
119
|
+
application: {},
|
|
120
|
+
page: {
|
|
121
|
+
async writeSEO(args) {
|
|
122
|
+
try {
|
|
123
|
+
const source = args.source;
|
|
124
|
+
const seo = args.seo;
|
|
125
|
+
const sourceFile = getSourceFile(source.fileName);
|
|
126
|
+
|
|
127
|
+
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
128
|
+
ts.SyntaxKind.CallExpression,
|
|
129
|
+
);
|
|
130
|
+
const createPageCallExp = callExpressions.find(
|
|
131
|
+
(callExp) => callExp.getExpression().getText() === "create_page",
|
|
132
|
+
);
|
|
133
|
+
if (!createPageCallExp) {
|
|
134
|
+
throw `create_page call expression not found in ${source.fileName}`;
|
|
135
|
+
}
|
|
136
|
+
const paramsObject = createPageCallExp
|
|
137
|
+
.getArguments()[0]
|
|
138
|
+
.asKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression);
|
|
139
|
+
const seoObject = paramsObject
|
|
140
|
+
.getPropertyOrThrow("seo")
|
|
141
|
+
.asKindOrThrow(ts.SyntaxKind.PropertyAssignment);
|
|
142
|
+
seoObject.setInitializer(JSON.stringify(seo, null, " "));
|
|
143
|
+
await saveSourceFile(sourceFile);
|
|
144
|
+
return Promise.resolve({
|
|
145
|
+
type: "success",
|
|
146
|
+
message: `Seo title written successfully`,
|
|
147
|
+
payload: {},
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.log({ error });
|
|
151
|
+
return Promise.resolve({
|
|
152
|
+
type: "error",
|
|
153
|
+
message: (error as Error).message,
|
|
154
|
+
payload: {},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
async insertNode(args) {
|
|
159
|
+
try {
|
|
160
|
+
const source = args.source;
|
|
161
|
+
const code = args.code;
|
|
162
|
+
const insertMode = args.insertMode;
|
|
163
|
+
const sourceFile = getSourceFile(source.fileName);
|
|
164
|
+
const node = getNodeAtPos(sourceFile, source);
|
|
165
|
+
|
|
166
|
+
ensureImports(sourceFile, code);
|
|
167
|
+
|
|
168
|
+
const jsxElement =
|
|
169
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxSelfClosingElement) ||
|
|
170
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxElement) ||
|
|
171
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxFragment);
|
|
172
|
+
if (!jsxElement) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`No jsx element found at source ${JSON.stringify(source)}`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
switch (true) {
|
|
178
|
+
case jsxElement instanceof JsxElement:
|
|
179
|
+
switch (insertMode) {
|
|
180
|
+
case "before":
|
|
181
|
+
jsxElement.replaceWithText(`${code}\n${jsxElement.getText()}`);
|
|
182
|
+
break;
|
|
183
|
+
case "after":
|
|
184
|
+
jsxElement.replaceWithText(`${jsxElement.getText()}\n${code}`);
|
|
185
|
+
break;
|
|
186
|
+
case "within":
|
|
187
|
+
const jsxChildren = (jsxElement as JsxElement).getJsxChildren();
|
|
188
|
+
const lastChild = (jsxElement as JsxElement).getJsxChildren()[
|
|
189
|
+
jsxChildren.length - 1
|
|
190
|
+
];
|
|
191
|
+
if (lastChild) {
|
|
192
|
+
lastChild.replaceWithText(`${lastChild.getText()}\n${code}`);
|
|
193
|
+
} else {
|
|
194
|
+
(jsxElement as JsxElement).setBodyText(code);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
case jsxElement instanceof JsxSelfClosingElement:
|
|
201
|
+
switch (insertMode) {
|
|
202
|
+
case "before":
|
|
203
|
+
jsxElement.replaceWithText(`${code}\n${jsxElement.getText()}`);
|
|
204
|
+
break;
|
|
205
|
+
case "after":
|
|
206
|
+
jsxElement.replaceWithText(`${jsxElement.getText()}\n${code}`);
|
|
207
|
+
break;
|
|
208
|
+
case "within":
|
|
209
|
+
const tagName = (jsxElement as JsxSelfClosingElement)
|
|
210
|
+
.getTagNameNode()
|
|
211
|
+
.getText();
|
|
212
|
+
jsxElement.replaceWithText(
|
|
213
|
+
`${jsxElement
|
|
214
|
+
.getText()
|
|
215
|
+
.replace("/>", ">")}\n${code}\n</${tagName}>`,
|
|
216
|
+
);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
case jsxElement instanceof JsxFragment:
|
|
221
|
+
switch (insertMode) {
|
|
222
|
+
case "before":
|
|
223
|
+
jsxElement.replaceWithText(`${code}\n${jsxElement.getText()}`);
|
|
224
|
+
break;
|
|
225
|
+
case "after":
|
|
226
|
+
jsxElement.replaceWithText(`${jsxElement.getText()}\n${code}`);
|
|
227
|
+
break;
|
|
228
|
+
case "within":
|
|
229
|
+
const jsxChildren = (jsxElement as JsxFragment).getJsxChildren();
|
|
230
|
+
const lastChild = jsxChildren[jsxChildren.length - 1];
|
|
231
|
+
if (lastChild) {
|
|
232
|
+
lastChild.replaceWithText(`${lastChild.getText()}\n${code}`);
|
|
233
|
+
} else {
|
|
234
|
+
const opening = (jsxElement as JsxFragment)
|
|
235
|
+
.getOpeningFragment()
|
|
236
|
+
.getText();
|
|
237
|
+
const closing = (jsxElement as JsxFragment)
|
|
238
|
+
.getClosingFragment()
|
|
239
|
+
.getText();
|
|
240
|
+
jsxElement.replaceWithText(`${opening}\n${code}\n${closing}`);
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
await saveSourceFile(sourceFile);
|
|
247
|
+
return Promise.resolve({
|
|
248
|
+
type: "success",
|
|
249
|
+
message: `Code inserted successfully`,
|
|
250
|
+
payload: {},
|
|
251
|
+
});
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.log({ error });
|
|
254
|
+
return Promise.resolve({
|
|
255
|
+
type: "error",
|
|
256
|
+
message: (error as Error).message,
|
|
257
|
+
payload: {},
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
async deleteNode(args) {
|
|
262
|
+
try {
|
|
263
|
+
const source = args.source;
|
|
264
|
+
const sourceFile = getSourceFile(source.fileName);
|
|
265
|
+
|
|
266
|
+
const node = getNodeAtPos(sourceFile, source);
|
|
267
|
+
|
|
268
|
+
const jsxElement =
|
|
269
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxSelfClosingElement) ||
|
|
270
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxElement) ||
|
|
271
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxFragment);
|
|
272
|
+
jsxElement?.replaceWithText("");
|
|
273
|
+
await saveSourceFile(sourceFile);
|
|
274
|
+
return Promise.resolve({
|
|
275
|
+
type: "success",
|
|
276
|
+
message: `Code inserted successfully`,
|
|
277
|
+
payload: {},
|
|
278
|
+
});
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.log({ error });
|
|
281
|
+
return Promise.resolve({
|
|
282
|
+
type: "error",
|
|
283
|
+
message: (error as Error).message,
|
|
284
|
+
payload: {},
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
async duplicateNode(args) {
|
|
289
|
+
try {
|
|
290
|
+
const source = args.source;
|
|
291
|
+
const sourceFile = getSourceFile(source.fileName);
|
|
292
|
+
|
|
293
|
+
const node = getNodeAtPos(sourceFile, source);
|
|
294
|
+
|
|
295
|
+
const jsxElement =
|
|
296
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxSelfClosingElement) ||
|
|
297
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxElement) ||
|
|
298
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxFragment);
|
|
299
|
+
jsxElement?.replaceWithText(
|
|
300
|
+
`${jsxElement.getText()}\n${jsxElement.getText()}`,
|
|
301
|
+
);
|
|
302
|
+
await saveSourceFile(sourceFile);
|
|
303
|
+
return Promise.resolve({
|
|
304
|
+
type: "success",
|
|
305
|
+
message: `Code duplicated successfully`,
|
|
306
|
+
payload: {},
|
|
307
|
+
});
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.log({ error });
|
|
310
|
+
return Promise.resolve({
|
|
311
|
+
type: "error",
|
|
312
|
+
message: (error as Error).message,
|
|
313
|
+
payload: {},
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
async moveUpNode(args) {
|
|
318
|
+
try {
|
|
319
|
+
const source = args.source;
|
|
320
|
+
const sourceFile = getSourceFile(source.fileName);
|
|
321
|
+
|
|
322
|
+
const node = getNodeAtPos(sourceFile, source);
|
|
323
|
+
|
|
324
|
+
const jsxElement =
|
|
325
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxSelfClosingElement) ||
|
|
326
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxElement) ||
|
|
327
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxText) ||
|
|
328
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxFragment);
|
|
329
|
+
if (jsxElement) {
|
|
330
|
+
const selfText = jsxElement.getText();
|
|
331
|
+
const prevNode = jsxElement
|
|
332
|
+
.getPreviousSiblings()
|
|
333
|
+
.filter((sib) => sib.getText())[0];
|
|
334
|
+
if (prevNode) {
|
|
335
|
+
const prevNodeText = prevNode.getText();
|
|
336
|
+
prevNode.replaceWithText(selfText);
|
|
337
|
+
jsxElement.replaceWithText(prevNodeText);
|
|
338
|
+
await saveSourceFile(sourceFile);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return Promise.resolve({
|
|
342
|
+
type: "success",
|
|
343
|
+
message: `Code moved up successfully`,
|
|
344
|
+
payload: {},
|
|
345
|
+
});
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.log({ error });
|
|
348
|
+
return Promise.resolve({
|
|
349
|
+
type: "error",
|
|
350
|
+
message: (error as Error).message,
|
|
351
|
+
payload: {},
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
async moveDownNode(args) {
|
|
356
|
+
try {
|
|
357
|
+
const source = args.source;
|
|
358
|
+
const sourceFile = getSourceFile(source.fileName);
|
|
359
|
+
|
|
360
|
+
const node = getNodeAtPos(sourceFile, source);
|
|
361
|
+
|
|
362
|
+
const jsxElement =
|
|
363
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxSelfClosingElement) ||
|
|
364
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxText) ||
|
|
365
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxElement) ||
|
|
366
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxFragment);
|
|
367
|
+
if (jsxElement) {
|
|
368
|
+
const selfText = jsxElement.getText();
|
|
369
|
+
const nextNode = jsxElement
|
|
370
|
+
.getNextSiblings()
|
|
371
|
+
.filter((sib) => sib.getText())[0];
|
|
372
|
+
|
|
373
|
+
if (nextNode) {
|
|
374
|
+
const nextNodeText = nextNode.getText();
|
|
375
|
+
nextNode.replaceWithText(selfText);
|
|
376
|
+
jsxElement.replaceWithText(nextNodeText);
|
|
377
|
+
await saveSourceFile(sourceFile);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return Promise.resolve({
|
|
381
|
+
type: "success",
|
|
382
|
+
message: `Code moved down successfully`,
|
|
383
|
+
payload: {},
|
|
384
|
+
});
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.log({ error });
|
|
387
|
+
return Promise.resolve({
|
|
388
|
+
type: "error",
|
|
389
|
+
message: (error as Error).message,
|
|
390
|
+
payload: {},
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
async moveNode(args) {
|
|
395
|
+
try {
|
|
396
|
+
const source = args.source;
|
|
397
|
+
const target = args.target;
|
|
398
|
+
const position = args.position;
|
|
399
|
+
const sourceFile = getSourceFile(source.fileName);
|
|
400
|
+
const sourceNode = getNodeAtPos(sourceFile, source);
|
|
401
|
+
const targetNode = getNodeAtPos(sourceFile, target);
|
|
402
|
+
|
|
403
|
+
const sourceJsx =
|
|
404
|
+
sourceNode.getFirstAncestorByKind(ts.SyntaxKind.JsxSelfClosingElement) ||
|
|
405
|
+
sourceNode.getFirstAncestorByKind(ts.SyntaxKind.JsxElement) ||
|
|
406
|
+
sourceNode.getFirstAncestorByKind(ts.SyntaxKind.JsxFragment);
|
|
407
|
+
|
|
408
|
+
const targetJsx =
|
|
409
|
+
targetNode.getFirstAncestorByKind(ts.SyntaxKind.JsxSelfClosingElement) ||
|
|
410
|
+
targetNode.getFirstAncestorByKind(ts.SyntaxKind.JsxElement) ||
|
|
411
|
+
targetNode.getFirstAncestorByKind(ts.SyntaxKind.JsxFragment);
|
|
412
|
+
|
|
413
|
+
if (!sourceJsx || !targetJsx) {
|
|
414
|
+
throw new Error("Source or target JSX element not found");
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const sourceText = sourceJsx.getText();
|
|
418
|
+
sourceJsx.replaceWithText("");
|
|
419
|
+
|
|
420
|
+
if (position === "before") {
|
|
421
|
+
targetJsx.replaceWithText(`${sourceText}\n${targetJsx.getText()}`);
|
|
422
|
+
} else if (position === "after") {
|
|
423
|
+
targetJsx.replaceWithText(`${targetJsx.getText()}\n${sourceText}`);
|
|
424
|
+
} else if (position === "within") {
|
|
425
|
+
if (targetJsx instanceof JsxSelfClosingElement) {
|
|
426
|
+
const tagName = targetJsx.getTagNameNode().getText();
|
|
427
|
+
const attributes = targetJsx.getAttributes()
|
|
428
|
+
.map((a) => a.getText())
|
|
429
|
+
.join(" ");
|
|
430
|
+
targetJsx.replaceWithText(
|
|
431
|
+
`<${tagName} ${attributes}>\n${sourceText}\n</${tagName}>`,
|
|
432
|
+
);
|
|
433
|
+
} else if (targetJsx instanceof JsxElement) {
|
|
434
|
+
const childrenText = targetJsx
|
|
435
|
+
.getJsxChildren()
|
|
436
|
+
.map((c) => c.getText())
|
|
437
|
+
.join("");
|
|
438
|
+
targetJsx.setBodyText(`${childrenText}\n${sourceText}`);
|
|
439
|
+
} else if (targetJsx instanceof JsxFragment) {
|
|
440
|
+
const opening = targetJsx.getOpeningFragment().getText();
|
|
441
|
+
const childrenText = targetJsx
|
|
442
|
+
.getJsxChildren()
|
|
443
|
+
.map((c) => c.getText())
|
|
444
|
+
.join("");
|
|
445
|
+
const closing = targetJsx.getClosingFragment().getText();
|
|
446
|
+
targetJsx.replaceWithText(
|
|
447
|
+
`${opening}${childrenText}\n${sourceText}${closing}`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
await saveSourceFile(sourceFile);
|
|
453
|
+
return Promise.resolve({
|
|
454
|
+
type: "success",
|
|
455
|
+
message: `Node moved successfully`,
|
|
456
|
+
payload: {},
|
|
457
|
+
});
|
|
458
|
+
} catch (error) {
|
|
459
|
+
console.log({ error });
|
|
460
|
+
return Promise.resolve({
|
|
461
|
+
type: "error",
|
|
462
|
+
message: (error as Error).message,
|
|
463
|
+
payload: {},
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
async setPropNode(args) {
|
|
468
|
+
try {
|
|
469
|
+
const source = args.source;
|
|
470
|
+
const sourceFile = getSourceFile(source.fileName);
|
|
471
|
+
|
|
472
|
+
const node = getNodeAtPos(sourceFile, source);
|
|
473
|
+
|
|
474
|
+
const jsxElement =
|
|
475
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxSelfClosingElement) ||
|
|
476
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxElement);
|
|
477
|
+
if (jsxElement) {
|
|
478
|
+
const structure =
|
|
479
|
+
jsxElement.getStructure() as Partial<JsxSelfClosingElementStructure> &
|
|
480
|
+
Partial<JsxElementStructure>;
|
|
481
|
+
args.attrs.forEach((attr) => {
|
|
482
|
+
if (attr.newValue) ensureImports(sourceFile, attr.newValue);
|
|
483
|
+
const attribute = structure.attributes!.find(
|
|
484
|
+
(a) => (a as JsxAttributeStructure).name === attr.name,
|
|
485
|
+
) as JsxAttributeStructure | undefined;
|
|
486
|
+
|
|
487
|
+
if (typeof attr.newValue === "undefined") {
|
|
488
|
+
structure.attributes = structure.attributes?.filter(
|
|
489
|
+
(a) => (a as JsxAttributeStructure).name !== attr.name,
|
|
490
|
+
);
|
|
491
|
+
} else if (attribute) {
|
|
492
|
+
attribute.initializer = attr.newValue;
|
|
493
|
+
} else {
|
|
494
|
+
structure.attributes!.push({
|
|
495
|
+
name: attr.name,
|
|
496
|
+
initializer: attr.newValue,
|
|
497
|
+
kind: StructureKind.JsxAttribute,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
jsxElement.set(structure);
|
|
502
|
+
await saveSourceFile(sourceFile);
|
|
503
|
+
}
|
|
504
|
+
return Promise.resolve({
|
|
505
|
+
type: "success",
|
|
506
|
+
message: `Prop set successfully`,
|
|
507
|
+
payload: {},
|
|
508
|
+
});
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.log({ error });
|
|
511
|
+
return Promise.resolve({
|
|
512
|
+
type: "error",
|
|
513
|
+
message: (error as Error).message,
|
|
514
|
+
payload: {},
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
async getRawPropsNode(args) {
|
|
519
|
+
try {
|
|
520
|
+
const source = args.source;
|
|
521
|
+
const sourceFile = getSourceFile(source.fileName);
|
|
522
|
+
|
|
523
|
+
const node = getNodeAtPos(sourceFile, source);
|
|
524
|
+
|
|
525
|
+
const jsxElement =
|
|
526
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxSelfClosingElement) ||
|
|
527
|
+
node.getFirstAncestorByKind(ts.SyntaxKind.JsxElement);
|
|
528
|
+
if (!jsxElement) {
|
|
529
|
+
return Promise.resolve({
|
|
530
|
+
type: "error",
|
|
531
|
+
message: "No jsx element found",
|
|
532
|
+
payload: { rawProps: {} },
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
const structure = jsxElement.getStructure();
|
|
536
|
+
const attributes = structure.attributes || [];
|
|
537
|
+
const rawProps = attributes.reduce(
|
|
538
|
+
(acc, curr) => {
|
|
539
|
+
if (
|
|
540
|
+
curr.kind === StructureKind.JsxAttribute &&
|
|
541
|
+
typeof curr.name === "string"
|
|
542
|
+
) {
|
|
543
|
+
acc[curr.name] = curr.initializer || "";
|
|
544
|
+
}
|
|
545
|
+
return acc;
|
|
546
|
+
},
|
|
547
|
+
{} as Record<string, string>,
|
|
548
|
+
);
|
|
549
|
+
return Promise.resolve({
|
|
550
|
+
type: "success",
|
|
551
|
+
message: `Get raw props successfully`,
|
|
552
|
+
payload: {
|
|
553
|
+
rawProps,
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
} catch (error) {
|
|
557
|
+
console.log({ error });
|
|
558
|
+
return Promise.resolve({
|
|
559
|
+
type: "error",
|
|
560
|
+
message: (error as Error).message,
|
|
561
|
+
payload: { rawProps: {} },
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
};
|