@alpaca-editor/core 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.
Files changed (239) hide show
  1. package/.prettierrc +3 -0
  2. package/eslint.config.mjs +4 -0
  3. package/images/bg-shape-black.webp +0 -0
  4. package/package.json +52 -0
  5. package/src/client-components/api.ts +6 -0
  6. package/src/client-components/index.ts +19 -0
  7. package/src/components/ActionButton.tsx +43 -0
  8. package/src/components/Error.tsx +57 -0
  9. package/src/config/config.tsx +737 -0
  10. package/src/config/types.ts +263 -0
  11. package/src/editor/ComponentInfo.tsx +77 -0
  12. package/src/editor/ConfirmationDialog.tsx +103 -0
  13. package/src/editor/ContentTree.tsx +654 -0
  14. package/src/editor/ContextMenu.tsx +155 -0
  15. package/src/editor/Editor.tsx +91 -0
  16. package/src/editor/EditorWarning.tsx +34 -0
  17. package/src/editor/EditorWarnings.tsx +33 -0
  18. package/src/editor/FieldEditorPopup.tsx +65 -0
  19. package/src/editor/FieldHistory.tsx +74 -0
  20. package/src/editor/FieldList.tsx +190 -0
  21. package/src/editor/FieldListField.tsx +387 -0
  22. package/src/editor/FieldListFieldWithFallbacks.tsx +211 -0
  23. package/src/editor/FloatingToolbar.tsx +163 -0
  24. package/src/editor/ImageEditor.tsx +129 -0
  25. package/src/editor/InsertMenu.tsx +332 -0
  26. package/src/editor/ItemInfo.tsx +90 -0
  27. package/src/editor/LinkEditorDialog.tsx +192 -0
  28. package/src/editor/MainLayout.tsx +94 -0
  29. package/src/editor/NewEditorClient.tsx +11 -0
  30. package/src/editor/PictureCropper.tsx +505 -0
  31. package/src/editor/PictureEditor.tsx +206 -0
  32. package/src/editor/PictureEditorDialog.tsx +381 -0
  33. package/src/editor/PublishDialog.ignore +74 -0
  34. package/src/editor/ScrollingContentTree.tsx +47 -0
  35. package/src/editor/Terminal.tsx +215 -0
  36. package/src/editor/Titlebar.tsx +23 -0
  37. package/src/editor/ai/AiPopup.tsx +59 -0
  38. package/src/editor/ai/AiResponseMessage.tsx +82 -0
  39. package/src/editor/ai/AiTerminal.tsx +450 -0
  40. package/src/editor/ai/AiToolCall.tsx +46 -0
  41. package/src/editor/ai/EditorAiTerminal.tsx +20 -0
  42. package/src/editor/ai/editorAiContext.ts +18 -0
  43. package/src/editor/client/DialogContext.tsx +49 -0
  44. package/src/editor/client/EditorClient.tsx +1831 -0
  45. package/src/editor/client/GenericDialog.tsx +50 -0
  46. package/src/editor/client/editContext.ts +330 -0
  47. package/src/editor/client/helpers.ts +44 -0
  48. package/src/editor/client/itemsRepository.ts +391 -0
  49. package/src/editor/client/operations.ts +610 -0
  50. package/src/editor/client/pageModelBuilder.ts +182 -0
  51. package/src/editor/commands/commands.ts +23 -0
  52. package/src/editor/commands/componentCommands.tsx +408 -0
  53. package/src/editor/commands/createVersionCommand.ts +33 -0
  54. package/src/editor/commands/deleteVersionCommand.ts +71 -0
  55. package/src/editor/commands/itemCommands.tsx +186 -0
  56. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
  57. package/src/editor/commands/undo.ts +39 -0
  58. package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
  59. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
  60. package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
  61. package/src/editor/component-designer/ComponentEditor.tsx +97 -0
  62. package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
  63. package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
  64. package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
  65. package/src/editor/component-designer/PlaceholdersEditor.tsx +183 -0
  66. package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
  67. package/src/editor/component-designer/TemplateEditor.tsx +236 -0
  68. package/src/editor/component-designer/aiContext.ts +23 -0
  69. package/src/editor/componentTreeHelper.tsx +114 -0
  70. package/src/editor/control-center/ControlCenterMenu.tsx +71 -0
  71. package/src/editor/control-center/IndexOverview.tsx +50 -0
  72. package/src/editor/control-center/IndexSettings.tsx +266 -0
  73. package/src/editor/control-center/Status.tsx +7 -0
  74. package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
  75. package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
  76. package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
  77. package/src/editor/editor-warnings/NoWriteAccess.tsx +15 -0
  78. package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
  79. package/src/editor/field-types/AttachmentEditor.tsx +9 -0
  80. package/src/editor/field-types/CheckboxEditor.tsx +47 -0
  81. package/src/editor/field-types/DropLinkEditor.tsx +75 -0
  82. package/src/editor/field-types/DropListEditor.tsx +84 -0
  83. package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
  84. package/src/editor/field-types/InternalLinkFieldEditor.tsx +112 -0
  85. package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
  86. package/src/editor/field-types/MultiLineText.tsx +63 -0
  87. package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
  88. package/src/editor/field-types/RawEditor.tsx +53 -0
  89. package/src/editor/field-types/ReactQuill.tsx +580 -0
  90. package/src/editor/field-types/RichTextEditor.tsx +22 -0
  91. package/src/editor/field-types/RichTextEditorComponent.tsx +108 -0
  92. package/src/editor/field-types/SingleLineText.tsx +150 -0
  93. package/src/editor/field-types/TreeListEditor.tsx +261 -0
  94. package/src/editor/fieldTypes.ts +140 -0
  95. package/src/editor/media-selector/AiImageSearch.tsx +186 -0
  96. package/src/editor/media-selector/AiImageSearchPrompt.tsx +95 -0
  97. package/src/editor/media-selector/MediaSelector.tsx +42 -0
  98. package/src/editor/media-selector/Preview.tsx +14 -0
  99. package/src/editor/media-selector/Thumbnails.tsx +48 -0
  100. package/src/editor/media-selector/TreeSelector.tsx +292 -0
  101. package/src/editor/media-selector/UploadZone.tsx +137 -0
  102. package/src/editor/menubar/ActionsMenu.tsx +47 -0
  103. package/src/editor/menubar/ActiveUsers.tsx +17 -0
  104. package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
  105. package/src/editor/menubar/BrowseHistory.tsx +37 -0
  106. package/src/editor/menubar/ItemLanguageVersion.tsx +52 -0
  107. package/src/editor/menubar/LanguageSelector.tsx +152 -0
  108. package/src/editor/menubar/Menu.tsx +83 -0
  109. package/src/editor/menubar/NavButtons.tsx +74 -0
  110. package/src/editor/menubar/PageSelector.tsx +139 -0
  111. package/src/editor/menubar/PageViewerControls.tsx +99 -0
  112. package/src/editor/menubar/Separator.tsx +12 -0
  113. package/src/editor/menubar/SiteInfo.tsx +53 -0
  114. package/src/editor/menubar/User.tsx +27 -0
  115. package/src/editor/menubar/VersionSelector.tsx +143 -0
  116. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +287 -0
  117. package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
  118. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +44 -0
  119. package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
  120. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
  121. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
  122. package/src/editor/page-editor-chrome/FrameMenu.tsx +263 -0
  123. package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
  124. package/src/editor/page-editor-chrome/InlineEditor.tsx +147 -0
  125. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
  126. package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
  127. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +119 -0
  128. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +154 -0
  129. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +171 -0
  130. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +233 -0
  131. package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
  132. package/src/editor/page-viewer/EditorForm.tsx +247 -0
  133. package/src/editor/page-viewer/MiniMap.tsx +351 -0
  134. package/src/editor/page-viewer/PageViewer.tsx +127 -0
  135. package/src/editor/page-viewer/PageViewerFrame.tsx +1030 -0
  136. package/src/editor/page-viewer/pageViewContext.ts +186 -0
  137. package/src/editor/pageModel.ts +191 -0
  138. package/src/editor/picture-shared.tsx +53 -0
  139. package/src/editor/reviews/Comment.tsx +265 -0
  140. package/src/editor/reviews/Comments.tsx +50 -0
  141. package/src/editor/reviews/PreviewInfo.tsx +35 -0
  142. package/src/editor/reviews/Reviews.tsx +280 -0
  143. package/src/editor/reviews/reviewCommands.tsx +47 -0
  144. package/src/editor/reviews/useReviews.tsx +70 -0
  145. package/src/editor/services/aiService.ts +155 -0
  146. package/src/editor/services/componentDesignerService.ts +151 -0
  147. package/src/editor/services/contentService.ts +159 -0
  148. package/src/editor/services/editService.ts +462 -0
  149. package/src/editor/services/indexService.ts +24 -0
  150. package/src/editor/services/reviewsService.ts +45 -0
  151. package/src/editor/services/serviceHelper.ts +95 -0
  152. package/src/editor/services/systemService.ts +5 -0
  153. package/src/editor/services/translationService.ts +21 -0
  154. package/src/editor/services-server/api.ts +150 -0
  155. package/src/editor/services-server/graphQL.ts +106 -0
  156. package/src/editor/sidebar/ComponentPalette.tsx +146 -0
  157. package/src/editor/sidebar/ComponentTree.tsx +512 -0
  158. package/src/editor/sidebar/ComponentTree2.tsxx +490 -0
  159. package/src/editor/sidebar/Debug.tsx +105 -0
  160. package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
  161. package/src/editor/sidebar/EditHistory.tsx +134 -0
  162. package/src/editor/sidebar/GraphQL.tsx +164 -0
  163. package/src/editor/sidebar/Insert.tsx +35 -0
  164. package/src/editor/sidebar/MainContentTree.tsx +95 -0
  165. package/src/editor/sidebar/Performance.tsx +53 -0
  166. package/src/editor/sidebar/Sessions.tsx +35 -0
  167. package/src/editor/sidebar/Sidebar.tsx +20 -0
  168. package/src/editor/sidebar/SidebarView.tsx +150 -0
  169. package/src/editor/sidebar/Translations.tsx +276 -0
  170. package/src/editor/sidebar/Validation.tsx +102 -0
  171. package/src/editor/sidebar/ViewSelector.tsx +49 -0
  172. package/src/editor/sidebar/Workbox.tsx +209 -0
  173. package/src/editor/ui/CenteredMessage.tsx +7 -0
  174. package/src/editor/ui/CopyToClipboardButton.tsx +23 -0
  175. package/src/editor/ui/DialogButtons.tsx +11 -0
  176. package/src/editor/ui/Icons.tsx +585 -0
  177. package/src/editor/ui/ItemNameDialog.tsx +94 -0
  178. package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
  179. package/src/editor/ui/ItemSearch.tsx +173 -0
  180. package/src/editor/ui/PerfectTree.tsx +550 -0
  181. package/src/editor/ui/Section.tsx +35 -0
  182. package/src/editor/ui/SimpleIconButton.tsx +43 -0
  183. package/src/editor/ui/SimpleMenu.tsx +48 -0
  184. package/src/editor/ui/SimpleTable.tsx +63 -0
  185. package/src/editor/ui/SimpleTabs.tsx +55 -0
  186. package/src/editor/ui/SimpleToolbar.tsx +7 -0
  187. package/src/editor/ui/Spinner.tsx +7 -0
  188. package/src/editor/ui/Splitter.tsx +247 -0
  189. package/src/editor/ui/StackedPanels.tsx +134 -0
  190. package/src/editor/ui/Toolbar.tsx +7 -0
  191. package/src/editor/utils/id-helper.ts +3 -0
  192. package/src/editor/utils/insertOptions.ts +69 -0
  193. package/src/editor/utils/itemutils.ts +29 -0
  194. package/src/editor/utils/useMemoDebug.ts +28 -0
  195. package/src/editor/utils.ts +435 -0
  196. package/src/editor/views/CompareView.tsx +256 -0
  197. package/src/editor/views/EditView.tsx +27 -0
  198. package/src/editor/views/ItemEditor.tsx +58 -0
  199. package/src/editor/views/SingleEditView.tsx +44 -0
  200. package/src/fonts/Geist-Black.woff2 +0 -0
  201. package/src/fonts/Geist-Bold.woff2 +0 -0
  202. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  203. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  204. package/src/fonts/Geist-Light.woff2 +0 -0
  205. package/src/fonts/Geist-Medium.woff2 +0 -0
  206. package/src/fonts/Geist-Regular.woff2 +0 -0
  207. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  208. package/src/fonts/Geist-Thin.woff2 +0 -0
  209. package/src/fonts/Geist[wght].woff2 +0 -0
  210. package/src/index.ts +7 -0
  211. package/src/page-wizard/PageWizard.tsx +163 -0
  212. package/src/page-wizard/SelectWizard.tsx +109 -0
  213. package/src/page-wizard/WizardSteps.tsx +207 -0
  214. package/src/page-wizard/service.ts +35 -0
  215. package/src/page-wizard/startPageWizardCommand.ts +27 -0
  216. package/src/page-wizard/steps/BuildPageStep.tsx +266 -0
  217. package/src/page-wizard/steps/CollectStep.tsx +233 -0
  218. package/src/page-wizard/steps/ComponentTypesSelector.tsx +443 -0
  219. package/src/page-wizard/steps/Components.tsx +193 -0
  220. package/src/page-wizard/steps/CreatePage.tsx +285 -0
  221. package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +384 -0
  222. package/src/page-wizard/steps/EditButton.tsx +34 -0
  223. package/src/page-wizard/steps/FieldEditor.tsx +102 -0
  224. package/src/page-wizard/steps/Generate.tsx +32 -0
  225. package/src/page-wizard/steps/ImagesStep.tsx +318 -0
  226. package/src/page-wizard/steps/LayoutStep.tsx +228 -0
  227. package/src/page-wizard/steps/SelectStep.tsx +256 -0
  228. package/src/page-wizard/steps/schema.ts +180 -0
  229. package/src/page-wizard/steps/usePageCreator.ts +279 -0
  230. package/src/splash-screen/NewPage.tsx +232 -0
  231. package/src/splash-screen/SectionHeadline.tsx +21 -0
  232. package/src/splash-screen/SplashScreen.tsx +156 -0
  233. package/src/tour/Tour.tsx +558 -0
  234. package/src/tour/default-tour.tsx +300 -0
  235. package/src/tour/preview-tour.tsx +127 -0
  236. package/src/types.ts +302 -0
  237. package/styles.css +476 -0
  238. package/tsconfig.build.json +21 -0
  239. package/tsconfig.json +11 -0
@@ -0,0 +1,266 @@
1
+ import { Splitter, SplitterPanel } from "../../editor/ui/Splitter";
2
+
3
+ import { FullItem, ItemDescriptor } from "../../editor/pageModel";
4
+ import { useEditContext } from "../../editor/client/editContext";
5
+
6
+ import { useEffect, useState } from "react";
7
+ import { StepComponentProps } from "../../config/types";
8
+ import { PageViewer } from "../../editor/page-viewer/PageViewer";
9
+
10
+ import { useDebouncedCallback } from "use-debounce";
11
+ import { getChildren } from "../../editor/services/contentService";
12
+ import { ActionButton } from "../../components/ActionButton";
13
+ import Generate from "./Generate";
14
+ import { usePageCreator } from "./usePageCreator";
15
+
16
+ export function BuildPageStep({
17
+ data,
18
+ setData,
19
+ parentItem,
20
+ wizard,
21
+ setStepCompleted,
22
+ }: StepComponentProps) {
23
+ const editContext = useEditContext();
24
+
25
+ const [createdPageDescriptor, setCreatedPageDescriptor] =
26
+ useState<ItemDescriptor>();
27
+ const [pageLoaded, setPageLoaded] = useState(false);
28
+
29
+ const [fullParentItem, setFullParentItem] = useState<FullItem>();
30
+ const [isBuilding, setIsBuilding] = useState(false);
31
+
32
+ const [validationMessage, setValidationMessage] = useState<string>();
33
+
34
+ useEffect(() => {
35
+ if (
36
+ createdPageDescriptor &&
37
+ createdPageDescriptor.id === editContext?.page?.item?.descriptor.id
38
+ )
39
+ setPageLoaded(true);
40
+ }, [editContext?.page]);
41
+
42
+ useEffect(() => {
43
+ const loadParentItem = async () => {
44
+ if (!parentItem) return;
45
+ const item = await editContext?.itemsRepository.getItem(parentItem);
46
+ setFullParentItem(item);
47
+ };
48
+ loadParentItem();
49
+ }, [parentItem]);
50
+
51
+ useEffect(() => {
52
+ const buildComponents = async () => {
53
+ try {
54
+ console.log(
55
+ "Building components",
56
+ createdPageDescriptor,
57
+ editContext?.page
58
+ );
59
+ // Recursively create components from page model
60
+ if (pageModel.components && pageModel.components.length > 0) {
61
+ await pageCreator.createComponentsRecursively(
62
+ pageModel.components,
63
+ "root"
64
+ );
65
+ }
66
+ editContext?.requestRefresh();
67
+ console.log(
68
+ "Components built",
69
+ createdPageDescriptor,
70
+ editContext?.page
71
+ );
72
+ } finally {
73
+ setIsBuilding(false);
74
+ }
75
+ };
76
+ if (pageLoaded) {
77
+ buildComponents();
78
+ }
79
+ }, [pageLoaded]);
80
+
81
+ // Initialize pageModel if it doesn't exist
82
+ const pageModel = data.pageModel || {
83
+ components: [],
84
+ name: "",
85
+ metaDescription: "",
86
+ };
87
+
88
+ // Handle input changes
89
+ const handleInputChange = (field: keyof typeof pageModel, value: string) => {
90
+ const updatedPageModel = {
91
+ ...pageModel,
92
+ [field]: value,
93
+ };
94
+
95
+ setData({
96
+ ...data,
97
+ pageModel: updatedPageModel,
98
+ });
99
+ };
100
+
101
+ const checkName = async () => {
102
+ if (!parentItem) return;
103
+ let valid: boolean =
104
+ !!wizard.templateId && pageModel.name.trim().length >= 3;
105
+ if (valid) {
106
+ const children = await getChildren(
107
+ parentItem.id,
108
+ editContext?.sessionId ?? "",
109
+ [],
110
+ false,
111
+ editContext?.contentEditorItem?.language || "en"
112
+ );
113
+ if (
114
+ children.find(
115
+ (x) =>
116
+ x.name.toLocaleLowerCase() ===
117
+ pageModel.name.trim().toLocaleLowerCase()
118
+ )
119
+ ) {
120
+ valid = false;
121
+ setValidationMessage("A page with this name already exists.");
122
+ }
123
+ } else {
124
+ if (pageModel.name.trim().length > 0 && pageModel.name.trim().length < 3)
125
+ setValidationMessage("Name is too short.");
126
+ else setValidationMessage(undefined);
127
+ }
128
+
129
+ if (valid) setValidationMessage(undefined);
130
+ return valid;
131
+ };
132
+
133
+ const checkNameValidDebounced = useDebouncedCallback(
134
+ async () => checkName(),
135
+ 500
136
+ );
137
+
138
+ useEffect(() => {
139
+ checkNameValidDebounced();
140
+ }, [pageModel.name]);
141
+
142
+ const createPage = () => {
143
+ setTimeout(async () => {
144
+ if (!editContext || !parentItem) return;
145
+ try {
146
+ if (!(await checkName())) return;
147
+ setIsBuilding(true);
148
+ const result = await editContext.operations.createItem(
149
+ parentItem,
150
+ wizard.templateId,
151
+ pageModel.name
152
+ );
153
+ if (!result) return;
154
+ editContext?.loadItem(result, { addToBrowseHistory: true });
155
+
156
+ setCreatedPageDescriptor(result);
157
+
158
+ console.log("Page created", result, "Page model", pageModel);
159
+ setStepCompleted(true);
160
+ } catch (error) {
161
+ console.error("Error creating page", error);
162
+ setIsBuilding(false);
163
+ }
164
+ }, 1);
165
+ };
166
+
167
+ const pageCreator = usePageCreator(
168
+ createdPageDescriptor,
169
+ wizard,
170
+ (pageModel) => setData({ ...data, pageModel })
171
+ );
172
+
173
+ const settingsPanel: SplitterPanel = {
174
+ name: "settings",
175
+ defaultSize: 450,
176
+ collapsible: false,
177
+ content: (
178
+ <div className="pr-6">
179
+ <div className="mb-4">
180
+ <div className="text-sm font-medium mb-1">
181
+ Target Parent Item
182
+ </div>
183
+ <div className="mb-4 text-xs break-after-all">
184
+ {fullParentItem?.path}
185
+ </div>
186
+ </div>
187
+ <div className="mb-4">
188
+ <label
189
+ htmlFor="pageName"
190
+ className="block text-sm font-medium mb-1"
191
+ >
192
+ Page Name
193
+ </label>
194
+ <input
195
+ id="pageName"
196
+ type="text"
197
+ className="w-full p-2 border rounded text-sm"
198
+ value={pageModel.name}
199
+ onChange={(e) => handleInputChange("name", e.target.value)}
200
+ placeholder="Enter page name"
201
+ />
202
+ {validationMessage && (
203
+ <div className="mt-2 text-sm text-red-500">
204
+ {validationMessage}
205
+ </div>
206
+ )}
207
+ </div>
208
+
209
+ <div className="mb-4">
210
+ <label
211
+ htmlFor="metaDescription"
212
+ className="block text-sm font-medium mb-1"
213
+ >
214
+ Meta Description
215
+ </label>
216
+ <textarea
217
+ id="metaDescription"
218
+ className="w-full p-2 border rounded min-h-[100px] text-sm"
219
+ value={pageModel.metaDescription}
220
+ onChange={(e) =>
221
+ handleInputChange("metaDescription", e.target.value)
222
+ }
223
+ placeholder="Enter meta description"
224
+ />
225
+ </div>
226
+ <ActionButton
227
+ disabled={isBuilding || !!validationMessage}
228
+ className="flex-1 w-full"
229
+ onClick={() => {
230
+ createPage();
231
+ }}
232
+ isLoading={isBuilding}
233
+ loadingText="Working"
234
+ >
235
+ Create Page
236
+ </ActionButton>
237
+ </div>
238
+ ),
239
+ };
240
+
241
+ const contentPanel: SplitterPanel = {
242
+ name: "content",
243
+ defaultSize: "auto",
244
+ collapsible: false,
245
+ content: (
246
+ <>
247
+ <div className={pageLoaded ? "h-full" : "h-0"}>
248
+ <PageViewer
249
+ name="single"
250
+ mode={editContext?.previewMode ? "view" : "edit"}
251
+ showFormEditor={false}
252
+ followEditsDefault={true}
253
+ pageViewContext={editContext!.pageView}
254
+ />
255
+ </div>
256
+ {isBuilding && !pageLoaded && (
257
+ <div className="flex items-center justify-center h-full">
258
+ <Generate title="Building page..." />
259
+ </div>
260
+ )}
261
+ </>
262
+ ),
263
+ };
264
+
265
+ return <Splitter panels={[settingsPanel, contentPanel]}></Splitter>;
266
+ }
@@ -0,0 +1,233 @@
1
+ import { InputText } from "primereact/inputtext";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { WizardData } from "../PageWizard";
4
+ import { Splitter, SplitterPanel } from "../../editor/ui/Splitter";
5
+ import MDEditor from "@uiw/react-md-editor";
6
+ import { StepComponentProps } from "../../config/types";
7
+ import { ActionButton } from "../../components/ActionButton";
8
+ import { UploadCloudIcon } from "../../editor/ui/Icons";
9
+ import { Spinner } from "../../editor/ui/Spinner";
10
+
11
+ export function CollectStep({
12
+ data,
13
+ setData,
14
+ setStepCompleted,
15
+ }: StepComponentProps) {
16
+ const [isUploading, setIsUploading] = useState(false);
17
+ const [isScraping, setIsScraping] = useState(false);
18
+ const [error, setError] = useState<string | null>(null);
19
+ const [scrapeError, setScrapeError] = useState<string | null>(null);
20
+ const [scrapeUrl, setScrapeUrl] = useState<string | null>(null);
21
+ const fileInputRef = useRef<HTMLInputElement>(null);
22
+
23
+ const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
24
+ e.preventDefault();
25
+ e.stopPropagation();
26
+
27
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
28
+ uploadFile(e.dataTransfer.files[0]!);
29
+ setError(null);
30
+ }
31
+ };
32
+
33
+ useEffect(() => {
34
+ if (data.htmlContent) {
35
+ setStepCompleted(true);
36
+ }
37
+ }, [data.htmlContent]);
38
+
39
+ const uploadFile = async (file: File) => {
40
+ if (!file) {
41
+ setError("Please select a file first");
42
+ return;
43
+ }
44
+
45
+ try {
46
+ setIsUploading(true);
47
+ setError(null);
48
+
49
+ const formData = new FormData();
50
+ formData.append("uploadedFile", file);
51
+
52
+ const response = await fetch("/alpaca/editor/page-wizard/convertFile", {
53
+ method: "POST",
54
+ body: formData,
55
+ });
56
+
57
+ if (!response.ok) {
58
+ throw new Error(
59
+ `Upload failed: ${response.status} ${response.statusText}`
60
+ );
61
+ }
62
+
63
+ const htmlContent = await response.text();
64
+ setData((prev: WizardData) => ({
65
+ ...prev,
66
+ htmlContent: htmlContent,
67
+ }));
68
+ } catch (err) {
69
+ setError(err instanceof Error ? err.message : "Unknown error occurred");
70
+ } finally {
71
+ setIsUploading(false);
72
+ }
73
+ };
74
+
75
+ const triggerFileInput = () => {
76
+ fileInputRef.current ? (fileInputRef.current.value = "") : "";
77
+
78
+ fileInputRef.current?.click();
79
+ };
80
+
81
+ const handleScrape = async (e: React.FormEvent) => {
82
+ e.preventDefault();
83
+
84
+ try {
85
+ setScrapeError(null);
86
+
87
+ if (!scrapeUrl) {
88
+ setScrapeError("Please enter a URL first");
89
+ return;
90
+ }
91
+
92
+ setIsScraping(true);
93
+ const response = await fetch("/alpaca/editor/page-wizard/scrape", {
94
+ method: "POST",
95
+ body: JSON.stringify({ url: scrapeUrl }),
96
+ headers: {
97
+ "Content-Type": "application/json",
98
+ },
99
+ });
100
+
101
+ if (!response.ok) {
102
+ throw new Error(
103
+ `Scraping failed: ${response.status} ${response.statusText}`
104
+ );
105
+ }
106
+
107
+ const htmlContent = await response.text();
108
+
109
+ setData((prev: WizardData) => ({
110
+ ...prev,
111
+ htmlContent: htmlContent,
112
+ }));
113
+ } catch (err) {
114
+ setScrapeError(
115
+ err instanceof Error ? err.message : "Unknown error occurred"
116
+ );
117
+ } finally {
118
+ setIsScraping(false);
119
+ }
120
+ };
121
+
122
+ const uploadPanel: SplitterPanel = {
123
+ name: "upload",
124
+ defaultSize: 300,
125
+ collapsible: false,
126
+ content: (
127
+ <div className="flex flex-col gap-6 items-stretch p-2 6">
128
+ <div className="flex flex-col gap-2">
129
+ <div className="text-sm font-semibold text-gray-700">
130
+ Upload a file
131
+ </div>
132
+
133
+ <div
134
+ className={`h-40 w-40 border border-dashed border-gray-300 flex flex-col justify-center items-center p-5 ${
135
+ isUploading ? "bg-gray-200" : "bg-white"
136
+ }`}
137
+ onDrop={handleDrop}
138
+ onDragOver={(e) => e.preventDefault()}
139
+ >
140
+ <div className="text-sm font-medium flex flex-col justify-center items-center text-center gap-1">
141
+ {!isUploading && <UploadCloudIcon />}
142
+ {isUploading && <Spinner />}
143
+ <input
144
+ ref={fileInputRef}
145
+ type="file"
146
+ multiple
147
+ accept=".pdf,.doc,.docx"
148
+ style={{ display: "none" }}
149
+ onChange={(e) => {
150
+ const file = e.target.files?.[0];
151
+ if (file) {
152
+ uploadFile(file);
153
+ }
154
+ }}
155
+ onDrop={handleDrop}
156
+ disabled={isUploading}
157
+ />
158
+ Drag & Drop to upload or
159
+ <span
160
+ className={`text-sm font-medium ${
161
+ isUploading
162
+ ? "text-gray-500"
163
+ : "text-canvas-pink underline cursor-pointer"
164
+ } `}
165
+ onClick={triggerFileInput}
166
+ >
167
+ browse
168
+ </span>
169
+ </div>
170
+ </div>
171
+ </div>
172
+
173
+ {error && (
174
+ <div className="text-red-500 text-sm mt-2">{error}</div>
175
+ )}
176
+
177
+ <div className="flex flex-col gap-2">
178
+ <div className="text-sm font-semibold text-gray-700">
179
+ Scrape page from URL
180
+ </div>
181
+ <InputText
182
+ type="text"
183
+ className="border border-gray-300 rounded-md px-3 py-2"
184
+ onChange={(e) => setScrapeUrl(e.target.value)}
185
+ />
186
+ {scrapeError && (
187
+ <div className="text-red-500 text-sm mt-2">{scrapeError}</div>
188
+ )}
189
+ <div className="flex gap-2">
190
+ <ActionButton
191
+ type="submit"
192
+ isLoading={isScraping}
193
+ onClick={handleScrape}
194
+ loadingText="Scraping..."
195
+ >
196
+ Scrape
197
+ </ActionButton>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ ),
202
+ };
203
+
204
+ const resultPanel: SplitterPanel = {
205
+ name: "result",
206
+ defaultSize: "auto",
207
+ collapsible: false,
208
+ content: (
209
+ <div className="absolute inset-0 w-full h-full px-4 pb-4">
210
+ <MDEditor
211
+ height="100%"
212
+ className="max-h-full"
213
+ value={data.htmlContent}
214
+ preview="edit"
215
+ enableScroll={true}
216
+ onChange={(val) =>
217
+ setData((prev: WizardData) => ({
218
+ ...prev,
219
+ htmlContent: val || "",
220
+ }))
221
+ }
222
+ />
223
+ </div>
224
+ ),
225
+ };
226
+
227
+ return (
228
+ <Splitter
229
+ localStorageKey="editor.page-wizard.panels"
230
+ panels={[uploadPanel, resultPanel]}
231
+ />
232
+ );
233
+ }