@pilotiq/pilotiq 0.24.1 → 0.24.2
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/CHANGELOG.md +33 -0
- package/boost/guidelines.md +566 -0
- package/boost/skills/pilotiq-fields/SKILL.md +47 -0
- package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
- package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
- package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
- package/boost/skills/pilotiq-relations/SKILL.md +47 -0
- package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
- package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
- package/boost/skills/pilotiq-resource/SKILL.md +61 -0
- package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
- package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
- package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -8
- package/CLAUDE.md +0 -265
- package/src/Cluster.test.ts +0 -283
- package/src/Cluster.ts +0 -83
- package/src/Column.test.ts +0 -199
- package/src/Column.ts +0 -710
- package/src/Global.test.ts +0 -367
- package/src/Global.ts +0 -169
- package/src/Page.test.ts +0 -114
- package/src/Page.ts +0 -208
- package/src/Pilotiq.perf.test.ts +0 -252
- package/src/Pilotiq.test.ts +0 -129
- package/src/Pilotiq.ts +0 -1158
- package/src/PilotiqRegistry.ts +0 -36
- package/src/PilotiqServiceProvider.ts +0 -121
- package/src/RelationManager.test.ts +0 -400
- package/src/RelationManager.ts +0 -527
- package/src/RenderHook.test.ts +0 -252
- package/src/RenderHook.ts +0 -242
- package/src/Resource.test.ts +0 -284
- package/src/Resource.ts +0 -526
- package/src/RightPanel.test.ts +0 -202
- package/src/RightPanel.ts +0 -132
- package/src/Tab.test.ts +0 -91
- package/src/Tab.ts +0 -156
- package/src/UserMenuItem.ts +0 -145
- package/src/actions/Action.test.ts +0 -2526
- package/src/actions/Action.ts +0 -1515
- package/src/actions/ActionGroup.test.ts +0 -112
- package/src/actions/ActionGroup.ts +0 -173
- package/src/actions/attachFactory.ts +0 -172
- package/src/actions/bulkFactories.ts +0 -168
- package/src/actions/crudFactories.ts +0 -220
- package/src/actions/exportFactory.ts +0 -225
- package/src/actions/factoryHelpers.ts +0 -177
- package/src/actions/importFactory.ts +0 -243
- package/src/actions/index.ts +0 -17
- package/src/actions/m2mFactories.ts +0 -193
- package/src/actions/relationFactories.ts +0 -372
- package/src/applyPageHooks.test.ts +0 -463
- package/src/applyPageHooks.ts +0 -330
- package/src/authorization.test.ts +0 -483
- package/src/breadcrumbs.test.ts +0 -238
- package/src/cells/coerce.test.ts +0 -85
- package/src/cells/coerce.ts +0 -84
- package/src/clusterPaths.ts +0 -35
- package/src/columns/BadgeColumn.test.ts +0 -54
- package/src/columns/BadgeColumn.ts +0 -32
- package/src/columns/BooleanColumn.test.ts +0 -41
- package/src/columns/BooleanColumn.ts +0 -18
- package/src/columns/ColorColumn.test.ts +0 -37
- package/src/columns/ColorColumn.ts +0 -38
- package/src/columns/IconColumn.test.ts +0 -54
- package/src/columns/IconColumn.ts +0 -37
- package/src/columns/ImageColumn.test.ts +0 -41
- package/src/columns/ImageColumn.ts +0 -28
- package/src/columns/SelectColumn.ts +0 -98
- package/src/columns/TextColumn.test.ts +0 -190
- package/src/columns/TextColumn.ts +0 -20
- package/src/columns/TextInputColumn.ts +0 -68
- package/src/columns/ToggleColumn.ts +0 -46
- package/src/columns/editableColumns.test.ts +0 -238
- package/src/columns/index.ts +0 -9
- package/src/defaultGlobalPages.ts +0 -95
- package/src/defaultPages.test.ts +0 -634
- package/src/defaultPages.ts +0 -617
- package/src/defaultViewPage.test.ts +0 -147
- package/src/elements/Form.test.ts +0 -223
- package/src/elements/Form.ts +0 -416
- package/src/elements/ListTabs.ts +0 -28
- package/src/elements/Table.test.ts +0 -422
- package/src/elements/Table.ts +0 -850
- package/src/elements/TableGroup.test.ts +0 -260
- package/src/elements/TableGroup.ts +0 -334
- package/src/elements/dispatchAction.test.ts +0 -463
- package/src/elements/dispatchAction.ts +0 -355
- package/src/elements/dispatchForm.test.ts +0 -477
- package/src/elements/dispatchForm.ts +0 -1993
- package/src/elements/dispatchTable.test.ts +0 -1514
- package/src/elements/dispatchTable.ts +0 -745
- package/src/elements/index.ts +0 -21
- package/src/entries/BadgeEntry.ts +0 -39
- package/src/entries/CodeEntry.test.ts +0 -40
- package/src/entries/CodeEntry.ts +0 -52
- package/src/entries/ColorEntry.ts +0 -63
- package/src/entries/ComponentEntry.test.ts +0 -173
- package/src/entries/ComponentEntry.ts +0 -95
- package/src/entries/Entry.ts +0 -304
- package/src/entries/IconEntry.ts +0 -49
- package/src/entries/ImageEntry.ts +0 -61
- package/src/entries/KeyValueEntry.ts +0 -47
- package/src/entries/RepeatableEntry.test.ts +0 -239
- package/src/entries/RepeatableEntry.ts +0 -173
- package/src/entries/TextEntry.test.ts +0 -394
- package/src/entries/TextEntry.ts +0 -60
- package/src/entries/index.ts +0 -12
- package/src/entries/leaves.test.ts +0 -306
- package/src/entries/registry.ts +0 -54
- package/src/fields/BuilderField.test.ts +0 -1188
- package/src/fields/BuilderField.ts +0 -605
- package/src/fields/BuilderRelationship.test.ts +0 -811
- package/src/fields/CheckboxField.test.ts +0 -44
- package/src/fields/CheckboxField.ts +0 -27
- package/src/fields/CheckboxListField.test.ts +0 -99
- package/src/fields/CheckboxListField.ts +0 -66
- package/src/fields/ColorPickerField.test.ts +0 -33
- package/src/fields/ColorPickerField.ts +0 -25
- package/src/fields/DateField.ts +0 -54
- package/src/fields/DateTimeField.test.ts +0 -55
- package/src/fields/EmailField.ts +0 -16
- package/src/fields/Field.test.ts +0 -654
- package/src/fields/Field.ts +0 -817
- package/src/fields/FileUploadField.test.ts +0 -143
- package/src/fields/FileUploadField.ts +0 -159
- package/src/fields/HiddenField.test.ts +0 -27
- package/src/fields/HiddenField.ts +0 -28
- package/src/fields/KeyValueField.test.ts +0 -105
- package/src/fields/KeyValueField.ts +0 -55
- package/src/fields/MarkdownField.test.ts +0 -167
- package/src/fields/MarkdownField.ts +0 -162
- package/src/fields/NumberField.ts +0 -33
- package/src/fields/RadioField.test.ts +0 -94
- package/src/fields/RadioField.ts +0 -67
- package/src/fields/RepeaterField.test.ts +0 -1806
- package/src/fields/RepeaterField.ts +0 -939
- package/src/fields/RepeaterRelationship.test.ts +0 -1923
- package/src/fields/RepeaterSimple.test.ts +0 -248
- package/src/fields/RowButton.test.ts +0 -219
- package/src/fields/RowButton.ts +0 -135
- package/src/fields/SelectField.test.ts +0 -192
- package/src/fields/SelectField.ts +0 -235
- package/src/fields/SliderField.test.ts +0 -50
- package/src/fields/SliderField.ts +0 -53
- package/src/fields/SlugField.ts +0 -24
- package/src/fields/TagsInputField.test.ts +0 -154
- package/src/fields/TagsInputField.ts +0 -133
- package/src/fields/TextField.test.ts +0 -213
- package/src/fields/TextField.ts +0 -177
- package/src/fields/TextareaField.test.ts +0 -58
- package/src/fields/TextareaField.ts +0 -59
- package/src/fields/ToggleButtonsField.test.ts +0 -106
- package/src/fields/ToggleButtonsField.ts +0 -59
- package/src/fields/ToggleField.ts +0 -16
- package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
- package/src/fields/optionsResolver.ts +0 -95
- package/src/fields/resolveField.ts +0 -28
- package/src/filters/BooleanFilter.ts +0 -35
- package/src/filters/DateRangeFilter.test.ts +0 -194
- package/src/filters/DateRangeFilter.ts +0 -148
- package/src/filters/Filter.test.ts +0 -268
- package/src/filters/Filter.ts +0 -184
- package/src/filters/FormFilter.test.ts +0 -238
- package/src/filters/FormFilter.ts +0 -215
- package/src/filters/MultiSelectFilter.test.ts +0 -119
- package/src/filters/MultiSelectFilter.ts +0 -78
- package/src/filters/QueryBuilderFilter.test.ts +0 -662
- package/src/filters/QueryBuilderFilter.ts +0 -398
- package/src/filters/SelectFilter.ts +0 -46
- package/src/filters/TernaryFilter.test.ts +0 -160
- package/src/filters/TernaryFilter.ts +0 -72
- package/src/filters/TrashedFilter.test.ts +0 -149
- package/src/filters/TrashedFilter.ts +0 -55
- package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
- package/src/filters/queryBuilder/Constraint.ts +0 -115
- package/src/filters/queryBuilder/DateConstraint.ts +0 -69
- package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
- package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
- package/src/filters/queryBuilder/TextConstraint.ts +0 -64
- package/src/filters/queryBuilder/index.ts +0 -12
- package/src/icons/index.ts +0 -2
- package/src/icons/lucide.ts +0 -204
- package/src/icons/registry.test.ts +0 -56
- package/src/icons/registry.ts +0 -41
- package/src/icons/types.ts +0 -47
- package/src/index.ts +0 -525
- package/src/io/csv.test.ts +0 -142
- package/src/io/csv.ts +0 -170
- package/src/nestedRelationManagerData.test.ts +0 -547
- package/src/notifications/Notification.test.ts +0 -210
- package/src/notifications/Notification.ts +0 -354
- package/src/notifications/broadcast.test.ts +0 -110
- package/src/notifications/broadcast.ts +0 -95
- package/src/notifications/database.test.ts +0 -383
- package/src/notifications/database.ts +0 -398
- package/src/notifications/databaseNotifications.test.ts +0 -187
- package/src/notifications/dispatchNotificationAction.test.ts +0 -341
- package/src/notifications/dispatchNotificationAction.ts +0 -142
- package/src/notifications/flash.test.ts +0 -89
- package/src/notifications/flash.ts +0 -71
- package/src/notifications/index.ts +0 -45
- package/src/notifications/registerBroadcastAuth.test.ts +0 -134
- package/src/notifications/registerBroadcastAuth.ts +0 -100
- package/src/notifications/resolveSavedNotification.test.ts +0 -82
- package/src/notifications/resolveSavedNotification.ts +0 -59
- package/src/notifications/types.ts +0 -93
- package/src/orm/m2mAccessor.ts +0 -66
- package/src/orm/modelDefaults.test.ts +0 -633
- package/src/orm/modelDefaults.ts +0 -666
- package/src/pageData/breadcrumbs.ts +0 -288
- package/src/pageData/forms.ts +0 -578
- package/src/pageData/helpers.ts +0 -857
- package/src/pageData/misc.ts +0 -347
- package/src/pageData/navigation.ts +0 -842
- package/src/pageData/relationPages.ts +0 -1248
- package/src/pageData/relationTabs.ts +0 -286
- package/src/pageData/resourcePages.ts +0 -609
- package/src/pageData.test.ts +0 -1545
- package/src/pageData.ts +0 -341
- package/src/plugins/index.ts +0 -8
- package/src/plugins/themeEditor.test.ts +0 -36
- package/src/plugins/themeEditor.ts +0 -45
- package/src/react/AppShell.tsx +0 -251
- package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
- package/src/react/CollabRoomContext.ts +0 -98
- package/src/react/CollabTextRendererRegistry.ts +0 -102
- package/src/react/CommandPalette.tsx +0 -375
- package/src/react/CurrentUserContext.tsx +0 -50
- package/src/react/CustomPageWrapperGate.tsx +0 -69
- package/src/react/CustomPageWrapperRegistry.ts +0 -45
- package/src/react/FieldFocusReporterRegistry.ts +0 -37
- package/src/react/FieldLabelSlotRegistry.ts +0 -30
- package/src/react/FieldPresenceRegistry.ts +0 -46
- package/src/react/FormCollabBindingRegistry.ts +0 -242
- package/src/react/FormStateContext.tsx +0 -591
- package/src/react/HeadHooks.tsx +0 -126
- package/src/react/MarkdownEditorRegistry.test.ts +0 -38
- package/src/react/MarkdownEditorRegistry.ts +0 -107
- package/src/react/NotificationActionStrip.tsx +0 -263
- package/src/react/NotificationBell.tsx +0 -426
- package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
- package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
- package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
- package/src/react/PendingSuggestionsContext.tsx +0 -172
- package/src/react/RecordWrapperGate.tsx +0 -58
- package/src/react/RecordWrapperRegistry.ts +0 -39
- package/src/react/RenderHookSlot.tsx +0 -32
- package/src/react/RightSidebar.tsx +0 -257
- package/src/react/RightSidebarContext.tsx +0 -234
- package/src/react/RightSidebarTrigger.tsx +0 -53
- package/src/react/RowCoordsContext.tsx +0 -23
- package/src/react/SchemaRenderer.tsx +0 -549
- package/src/react/SearchTrigger.tsx +0 -46
- package/src/react/ThemeProvider.tsx +0 -93
- package/src/react/ThemeSettingsPage.tsx +0 -579
- package/src/react/ThemeToggle.tsx +0 -20
- package/src/react/Toaster.tsx +0 -158
- package/src/react/UserMenu.tsx +0 -196
- package/src/react/WidgetDataContext.tsx +0 -157
- package/src/react/cells/EditableCell.tsx +0 -389
- package/src/react/component-slots.test.ts +0 -103
- package/src/react/component-slots.ts +0 -116
- package/src/react/fieldJsHandler.test.ts +0 -166
- package/src/react/fieldJsHandler.ts +0 -79
- package/src/react/fields/BuilderInput.tsx +0 -1078
- package/src/react/fields/CheckboxInput.tsx +0 -39
- package/src/react/fields/CheckboxListInput.tsx +0 -102
- package/src/react/fields/ColorInput.tsx +0 -71
- package/src/react/fields/DateFieldInput.tsx +0 -70
- package/src/react/fields/DateTimeInput.tsx +0 -62
- package/src/react/fields/FieldShell.tsx +0 -348
- package/src/react/fields/FileUploadInput.tsx +0 -639
- package/src/react/fields/HiddenInput.tsx +0 -17
- package/src/react/fields/KeyValueInput.tsx +0 -230
- package/src/react/fields/MarkdownInput.tsx +0 -560
- package/src/react/fields/RadioInput.tsx +0 -81
- package/src/react/fields/RepeaterInput.test.ts +0 -116
- package/src/react/fields/RepeaterInput.tsx +0 -1420
- package/src/react/fields/SelectFieldInput.tsx +0 -280
- package/src/react/fields/SliderInput.tsx +0 -81
- package/src/react/fields/TagsInput.tsx +0 -283
- package/src/react/fields/TextLikeInput.tsx +0 -256
- package/src/react/fields/ToggleButtonsInput.tsx +0 -60
- package/src/react/fields/ToggleFieldInput.tsx +0 -56
- package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
- package/src/react/fields/relationshipRenameDispatch.ts +0 -97
- package/src/react/fields/repeaterReconcile.test.ts +0 -114
- package/src/react/fields/repeaterReconcile.ts +0 -104
- package/src/react/fields/rowChromeButton.tsx +0 -336
- package/src/react/fields/rowState.ts +0 -106
- package/src/react/fields/syncRowGates.test.ts +0 -202
- package/src/react/fields/syncRowGates.ts +0 -66
- package/src/react/fields/textInputControls.tsx +0 -238
- package/src/react/fields/useRowReorderDnd.ts +0 -78
- package/src/react/formStateHelpers.test.ts +0 -508
- package/src/react/formStateHelpers.ts +0 -381
- package/src/react/hooks/use-mobile.ts +0 -19
- package/src/react/icon-context.tsx +0 -60
- package/src/react/index.ts +0 -194
- package/src/react/layouts/SidebarLayout.tsx +0 -250
- package/src/react/layouts/TopbarLayout.tsx +0 -258
- package/src/react/navigate.tsx +0 -37
- package/src/react/onProviderSynced.test.ts +0 -90
- package/src/react/parseRecordEditUrl.test.ts +0 -122
- package/src/react/parseRecordEditUrl.ts +0 -94
- package/src/react/persistedState.ts +0 -40
- package/src/react/registry.ts +0 -48
- package/src/react/right-panel-registry.tsx +0 -47
- package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
- package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
- package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
- package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
- package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
- package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
- package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
- package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
- package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
- package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
- package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
- package/src/react/schemaRenderer/action/buttons.tsx +0 -99
- package/src/react/schemaRenderer/action/helpers.ts +0 -140
- package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
- package/src/react/schemaRenderer/columnFormat.ts +0 -65
- package/src/react/schemaRenderer/constants.ts +0 -50
- package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
- package/src/react/schemaRenderer/form/renderField.tsx +0 -511
- package/src/react/schemaRenderer/helpers.tsx +0 -81
- package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
- package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
- package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
- package/src/react/schemaRenderer/table/filters.tsx +0 -1233
- package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
- package/src/react/schemaRenderer/table/links.tsx +0 -112
- package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
- package/src/react/schemaRenderer/table/url.tsx +0 -143
- package/src/react/theme-preview/apply.ts +0 -99
- package/src/react/theme-preview/build-html.ts +0 -436
- package/src/react/ui/button.tsx +0 -51
- package/src/react/ui/calendar.tsx +0 -67
- package/src/react/ui/checkbox.tsx +0 -29
- package/src/react/ui/dialog.tsx +0 -108
- package/src/react/ui/dropdown-menu.tsx +0 -97
- package/src/react/ui/input.tsx +0 -20
- package/src/react/ui/label.tsx +0 -21
- package/src/react/ui/popover.tsx +0 -50
- package/src/react/ui/select.tsx +0 -169
- package/src/react/ui/separator.tsx +0 -25
- package/src/react/ui/sheet.tsx +0 -136
- package/src/react/ui/sidebar.tsx +0 -723
- package/src/react/ui/skeleton.tsx +0 -13
- package/src/react/ui/slider.tsx +0 -34
- package/src/react/ui/switch.tsx +0 -28
- package/src/react/ui/table.tsx +0 -105
- package/src/react/ui/tabs.tsx +0 -63
- package/src/react/ui/textarea.tsx +0 -18
- package/src/react/ui/tooltip.tsx +0 -64
- package/src/react/useResizableWidth.ts +0 -139
- package/src/react/utils.ts +0 -6
- package/src/react/widgetRegistry.test.ts +0 -43
- package/src/react/widgetRegistry.ts +0 -50
- package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
- package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
- package/src/react/widgets/ViewRenderer.tsx +0 -71
- package/src/relationManagerData.test.ts +0 -1595
- package/src/richtext/index.ts +0 -8
- package/src/richtext/registry.ts +0 -89
- package/src/routes/globals.ts +0 -148
- package/src/routes/guard.test.ts +0 -325
- package/src/routes/helpers.ts +0 -704
- package/src/routes/pages.ts +0 -175
- package/src/routes/panel.ts +0 -204
- package/src/routes/relations.ts +0 -1243
- package/src/routes/resources.ts +0 -781
- package/src/routes/theme.ts +0 -91
- package/src/routes-nested-relations.test.ts +0 -676
- package/src/routes-relations.test.ts +0 -972
- package/src/routes.test.ts +0 -2027
- package/src/routes.ts +0 -303
- package/src/schema/Alert.test.ts +0 -109
- package/src/schema/Alert.ts +0 -131
- package/src/schema/Block.ts +0 -169
- package/src/schema/Breadcrumbs.ts +0 -40
- package/src/schema/Card.ts +0 -35
- package/src/schema/Divider.ts +0 -20
- package/src/schema/Element.ts +0 -219
- package/src/schema/EmptyState.test.ts +0 -37
- package/src/schema/EmptyState.ts +0 -63
- package/src/schema/Fieldset.ts +0 -43
- package/src/schema/Grid.ts +0 -43
- package/src/schema/Group.ts +0 -30
- package/src/schema/Heading.ts +0 -39
- package/src/schema/Html.ts +0 -67
- package/src/schema/Icon.ts +0 -54
- package/src/schema/Image.ts +0 -57
- package/src/schema/LinkTag.ts +0 -41
- package/src/schema/Markdown.ts +0 -85
- package/src/schema/MetaTag.ts +0 -41
- package/src/schema/RelationTabs.ts +0 -71
- package/src/schema/ScriptTag.ts +0 -55
- package/src/schema/Section.ts +0 -160
- package/src/schema/ServerDataElement.test.ts +0 -140
- package/src/schema/ServerDataElement.ts +0 -156
- package/src/schema/SlotComponent.test.ts +0 -77
- package/src/schema/SlotComponent.ts +0 -71
- package/src/schema/Split.ts +0 -50
- package/src/schema/Stat.test.ts +0 -118
- package/src/schema/Stat.ts +0 -154
- package/src/schema/StatsOverview.test.ts +0 -141
- package/src/schema/StatsOverview.ts +0 -119
- package/src/schema/StyleTag.ts +0 -35
- package/src/schema/TableWidget.test.ts +0 -297
- package/src/schema/TableWidget.ts +0 -289
- package/src/schema/Tabs.ts +0 -79
- package/src/schema/Text.ts +0 -58
- package/src/schema/UnorderedList.ts +0 -49
- package/src/schema/View.test.ts +0 -111
- package/src/schema/View.ts +0 -127
- package/src/schema/Wizard.ts +0 -220
- package/src/schema/containers.test.ts +0 -564
- package/src/schema/headTags.test.ts +0 -134
- package/src/schema/index.ts +0 -40
- package/src/schema/primes.test.ts +0 -269
- package/src/schema/resolveSchema.test.ts +0 -379
- package/src/schema/resolveSchema.ts +0 -917
- package/src/schema/sanitize.ts +0 -58
- package/src/search.test.ts +0 -446
- package/src/search.ts +0 -178
- package/src/sessionFilters.test.ts +0 -375
- package/src/sessionFilters.ts +0 -143
- package/src/slot-components/index.ts +0 -10
- package/src/slot-components/registry.ts +0 -56
- package/src/styles/file-upload.css +0 -13
- package/src/summarizers/Summarizer.test.ts +0 -84
- package/src/summarizers/Summarizer.ts +0 -123
- package/src/summarizers/index.ts +0 -11
- package/src/theme/base-colors.ts +0 -68
- package/src/theme/chart-colors.ts +0 -50
- package/src/theme/colors.ts +0 -447
- package/src/theme/generate-css.test.ts +0 -139
- package/src/theme/generate-css.ts +0 -44
- package/src/theme/generate-scale.test.ts +0 -106
- package/src/theme/generate-scale.ts +0 -97
- package/src/theme/icon-map.ts +0 -42
- package/src/theme/index.ts +0 -34
- package/src/theme/migrate.test.ts +0 -178
- package/src/theme/migrate.ts +0 -81
- package/src/theme/presets.ts +0 -135
- package/src/theme/radius.ts +0 -18
- package/src/theme/resolve.test.ts +0 -238
- package/src/theme/resolve.ts +0 -96
- package/src/theme/spacing.ts +0 -18
- package/src/theme/storage.test.ts +0 -126
- package/src/theme/storage.ts +0 -106
- package/src/theme/theme-colors.ts +0 -88
- package/src/theme/types.ts +0 -125
- package/src/uploads/UploadAdapter.ts +0 -35
- package/src/uploads/index.ts +0 -2
- package/src/uploads/localUpload.test.ts +0 -70
- package/src/uploads/localUpload.ts +0 -84
- package/src/validation/Validator.ts +0 -49
- package/src/validation/index.ts +0 -28
- package/src/validation/rules.ts +0 -78
- package/src/validation/runValidators.ts +0 -435
- package/src/validation/uniqueValidator.test.ts +0 -196
- package/src/validation/uniqueValidator.ts +0 -133
- package/src/validation/validators.test.ts +0 -268
- package/src/vite.test.ts +0 -184
- package/src/vite.ts +0 -787
- package/src/widgets/index.ts +0 -10
- package/src/widgets/registry.ts +0 -45
- package/src/widgets.test.ts +0 -592
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -4
- package/tsconfig.test.json +0 -10
- package/views/react/Dashboard.tsx +0 -27
- package/views/react/Resources/Form.tsx +0 -102
- package/views/react/Resources/Index.tsx +0 -49
|
@@ -1,560 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
-
import { marked } from 'marked'
|
|
3
|
-
import type { MarkdownEditor as MarkdownEditorComponent } from '../MarkdownEditorRegistry.js'
|
|
4
|
-
import {
|
|
5
|
-
BoldIcon, ItalicIcon, StrikethroughIcon, LinkIcon,
|
|
6
|
-
HeadingIcon, ListIcon, ListOrderedIcon, QuoteIcon,
|
|
7
|
-
CodeIcon, PaperclipIcon, Loader2Icon,
|
|
8
|
-
} from 'lucide-react'
|
|
9
|
-
import { useFieldState } from '../FormStateContext.js'
|
|
10
|
-
import { useCollabRoom } from '../CollabRoomContext.js'
|
|
11
|
-
import { getCollabTextRenderer, type CollabTextRenderer } from '../CollabTextRendererRegistry.js'
|
|
12
|
-
import { getMarkdownEditor } from '../MarkdownEditorRegistry.js'
|
|
13
|
-
import { useRowCoords } from '../RowCoordsContext.js'
|
|
14
|
-
import { parseRowFieldPath } from '../formStateHelpers.js'
|
|
15
|
-
import { useToast } from '../Toaster.js'
|
|
16
|
-
import { Button } from '../ui/button.js'
|
|
17
|
-
|
|
18
|
-
type ToolbarButton =
|
|
19
|
-
| 'bold' | 'italic' | 'strike' | 'link'
|
|
20
|
-
| 'heading' | 'bulletList' | 'orderedList' | 'blockquote'
|
|
21
|
-
| 'codeBlock' | 'attachFiles'
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Plain-markdown editor. Stores the raw string under `name`. Renders a
|
|
25
|
-
* tab switcher (Write / Preview), a configurable formatting toolbar, and
|
|
26
|
-
* a `<textarea>`. The Preview tab parses the current value via `marked`
|
|
27
|
-
* and mounts the result in a `prose`-styled container.
|
|
28
|
-
*
|
|
29
|
-
* `attachFiles` is special-cased — when present in the toolbar AND
|
|
30
|
-
* `uploadUrl` is set, the toolbar button + paste-image handler upload
|
|
31
|
-
* the file and splice an `` reference at the cursor.
|
|
32
|
-
* The server's `MarkdownField.toMeta()` strips the button from the
|
|
33
|
-
* toolbar list when no `UploadAdapter` is registered, so we never see
|
|
34
|
-
* an `attachFiles` entry without a working upload route.
|
|
35
|
-
*/
|
|
36
|
-
export function MarkdownInput({
|
|
37
|
-
name, defaultValue, disabled, placeholder,
|
|
38
|
-
toolbarButtons, minHeight, maxHeight,
|
|
39
|
-
fileAttachmentsDirectory, fileAttachmentsVisibility,
|
|
40
|
-
uploadUrl,
|
|
41
|
-
}: {
|
|
42
|
-
name: string
|
|
43
|
-
defaultValue: unknown
|
|
44
|
-
disabled: boolean
|
|
45
|
-
placeholder: string | undefined
|
|
46
|
-
toolbarButtons: ToolbarButton[]
|
|
47
|
-
minHeight: string | undefined
|
|
48
|
-
maxHeight: string | undefined
|
|
49
|
-
fileAttachmentsDirectory: string | undefined
|
|
50
|
-
fileAttachmentsVisibility: string | undefined
|
|
51
|
-
uploadUrl: string | undefined
|
|
52
|
-
}): React.ReactElement {
|
|
53
|
-
const fs = useFieldState(name)
|
|
54
|
-
const room = useCollabRoom()
|
|
55
|
-
const collabRenderer = getCollabTextRenderer()
|
|
56
|
-
const markdownEditor = getMarkdownEditor()
|
|
57
|
-
const rowCoords = useRowCoords()
|
|
58
|
-
|
|
59
|
-
// Row-leaf fields ride a composite `arrayName.rowId.fieldName` key for
|
|
60
|
-
// collab anchoring so the Y.XmlFragment survives row reorders (the raw
|
|
61
|
-
// dotted `items.<index>.body` would follow the wrong row after a swap).
|
|
62
|
-
// Top-level fields pass through unchanged.
|
|
63
|
-
const fragmentKey: string | null = (() => {
|
|
64
|
-
if (!name.includes('.')) return name
|
|
65
|
-
if (!rowCoords) return null
|
|
66
|
-
const parsed = parseRowFieldPath(name)
|
|
67
|
-
if (!parsed) return null
|
|
68
|
-
if (parsed.arrayName !== rowCoords.arrayName) return null
|
|
69
|
-
if (parsed.index !== rowCoords.rowIndex) return null
|
|
70
|
-
return `${rowCoords.arrayName}.${rowCoords.rowId}.${parsed.fieldName}`
|
|
71
|
-
})()
|
|
72
|
-
|
|
73
|
-
// Plug-supplied WYSIWYG markdown editor (typically `@pilotiq/tiptap`'s
|
|
74
|
-
// Tiptap + tiptap-markdown integration). When registered, it replaces
|
|
75
|
-
// BOTH the legacy non-collab textarea path AND the prior collab plain-text
|
|
76
|
-
// path with a single rich editor that handles WYSIWYG editing, markdown
|
|
77
|
-
// serialization, and collab binding (via its own `useCollabRoom()` read)
|
|
78
|
-
// internally. Row leaves pass the composite key as `fragmentKey` so the
|
|
79
|
-
// editor's collab factory anchors against the stable name while `name`
|
|
80
|
-
// (the dotted positional path) drives the hidden input, form-state, AND
|
|
81
|
-
// AI suggestion routing — AI tool calls reference fields by their
|
|
82
|
-
// FormData name, not their collab-stable composite.
|
|
83
|
-
if (markdownEditor && fragmentKey !== null) {
|
|
84
|
-
return (
|
|
85
|
-
<MarkdownEditorHost
|
|
86
|
-
Editor={markdownEditor}
|
|
87
|
-
name={name}
|
|
88
|
-
{...(fragmentKey !== name ? { fragmentKey } : {})}
|
|
89
|
-
defaultValue={defaultValue}
|
|
90
|
-
disabled={disabled}
|
|
91
|
-
{...(placeholder !== undefined ? { placeholder } : {})}
|
|
92
|
-
toolbarButtons={toolbarButtons}
|
|
93
|
-
{...(minHeight !== undefined ? { minHeight } : {})}
|
|
94
|
-
{...(maxHeight !== undefined ? { maxHeight } : {})}
|
|
95
|
-
{...(fileAttachmentsDirectory !== undefined ? { fileAttachmentsDirectory } : {})}
|
|
96
|
-
{...(fileAttachmentsVisibility !== undefined ? { fileAttachmentsVisibility } : {})}
|
|
97
|
-
{...(uploadUrl !== undefined ? { uploadUrl } : {})}
|
|
98
|
-
/>
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Tiptap-backed plain-text editor for markdown source when collab is on
|
|
103
|
-
// but no WYSIWYG adapter is registered. Same architectural fix as
|
|
104
|
-
// `TextLikeInput`'s `CollabTextField`: y-prosemirror's `RelativePosition`
|
|
105
|
-
// cursor anchoring against a `Y.XmlFragment` replaces whole-string LWW.
|
|
106
|
-
//
|
|
107
|
-
// Tradeoff: the markdown toolbar + Cmd-shortcuts + paste-image upload
|
|
108
|
-
// all operate on a `<textarea>`'s DOM selection — they don't have a
|
|
109
|
-
// way to reach into the Tiptap editor's selection without exposing the
|
|
110
|
-
// editor instance, which would widen the renderer seam. Those features
|
|
111
|
-
// are write-mode-only on the native path; collab users type markdown
|
|
112
|
-
// syntax directly (`**bold**`, `## heading`). The preview tab keeps
|
|
113
|
-
// working since `MarkdownCollabInput` maintains a local mirror.
|
|
114
|
-
if (room && collabRenderer && fragmentKey !== null) {
|
|
115
|
-
return (
|
|
116
|
-
<MarkdownCollabInput
|
|
117
|
-
Renderer={collabRenderer}
|
|
118
|
-
fragmentKey={fragmentKey}
|
|
119
|
-
hiddenInputName={name}
|
|
120
|
-
defaultValue={defaultValue}
|
|
121
|
-
disabled={disabled}
|
|
122
|
-
{...(placeholder !== undefined ? { placeholder } : {})}
|
|
123
|
-
{...(minHeight !== undefined ? { minHeight } : {})}
|
|
124
|
-
{...(maxHeight !== undefined ? { maxHeight } : {})}
|
|
125
|
-
/>
|
|
126
|
-
)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const { notify } = useToast()
|
|
130
|
-
const textareaRef = useRef<HTMLTextAreaElement | null>(null)
|
|
131
|
-
|
|
132
|
-
const initial = useMemo(() => stringValue(defaultValue), [])
|
|
133
|
-
const [localValue, setLocalValue] = useState<string>(initial)
|
|
134
|
-
const [tab, setTab] = useState<'write' | 'preview'>('write')
|
|
135
|
-
const [busy, setBusy] = useState(false)
|
|
136
|
-
|
|
137
|
-
const value = fs.controlled ? stringValue(fs.value) : localValue
|
|
138
|
-
|
|
139
|
-
const setValue = (next: string): void => {
|
|
140
|
-
if (fs.controlled) { fs.setValue(next); fs.triggerLive(next) }
|
|
141
|
-
else { setLocalValue(next); fs.triggerLive(next) }
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const previewHtml = useMemo(
|
|
145
|
-
() => tab === 'preview' ? marked.parse(value, { gfm: true, breaks: false, async: false }) as string : '',
|
|
146
|
-
[tab, value],
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
/** Splice text around the current selection in the textarea. */
|
|
150
|
-
const transform = (fn: (sel: string, before: string, after: string) => {
|
|
151
|
-
next: string
|
|
152
|
-
cursor: { start: number; end: number }
|
|
153
|
-
}): void => {
|
|
154
|
-
const ta = textareaRef.current
|
|
155
|
-
if (!ta) return
|
|
156
|
-
const start = ta.selectionStart
|
|
157
|
-
const end = ta.selectionEnd
|
|
158
|
-
const sel = ta.value.slice(start, end)
|
|
159
|
-
const before = ta.value.slice(0, start)
|
|
160
|
-
const after = ta.value.slice(end)
|
|
161
|
-
const { next, cursor } = fn(sel, before, after)
|
|
162
|
-
setValue(next)
|
|
163
|
-
// Defer cursor restore until React has flushed the new value.
|
|
164
|
-
requestAnimationFrame(() => {
|
|
165
|
-
ta.focus()
|
|
166
|
-
ta.setSelectionRange(cursor.start, cursor.end)
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const wrapSelection = (open: string, close: string = open, placeholderText = ''): void => {
|
|
171
|
-
transform((sel, before, after) => {
|
|
172
|
-
const text = sel || placeholderText
|
|
173
|
-
const next = `${before}${open}${text}${close}${after}`
|
|
174
|
-
const start = before.length + open.length
|
|
175
|
-
const end = start + text.length
|
|
176
|
-
return { next, cursor: { start, end } }
|
|
177
|
-
})
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const prefixLine = (prefix: string): void => {
|
|
181
|
-
transform((sel, before, after) => {
|
|
182
|
-
// Find the start of the line containing selection start.
|
|
183
|
-
const lineStart = before.lastIndexOf('\n') + 1
|
|
184
|
-
const head = before.slice(0, lineStart)
|
|
185
|
-
const lineHead = before.slice(lineStart)
|
|
186
|
-
const next = `${head}${prefix}${lineHead}${sel}${after}`
|
|
187
|
-
const newPos = before.length + prefix.length
|
|
188
|
-
return { next, cursor: { start: newPos, end: newPos + sel.length } }
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const insertOrderedList = (): void => {
|
|
193
|
-
transform((sel, before, after) => {
|
|
194
|
-
const lines = (sel || 'List item').split('\n')
|
|
195
|
-
const numbered = lines.map((l, i) => `${i + 1}. ${l}`).join('\n')
|
|
196
|
-
const next = `${before}${numbered}${after}`
|
|
197
|
-
return { next, cursor: { start: before.length, end: before.length + numbered.length } }
|
|
198
|
-
})
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const insertLink = (): void => {
|
|
202
|
-
const url = window.prompt('URL') ?? ''
|
|
203
|
-
if (!url) return
|
|
204
|
-
transform((sel, before, after) => {
|
|
205
|
-
const text = sel || 'link text'
|
|
206
|
-
const md = `[${text}](${url})`
|
|
207
|
-
return { next: `${before}${md}${after}`, cursor: { start: before.length + 1, end: before.length + 1 + text.length } }
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const insertCodeBlock = (): void => {
|
|
212
|
-
transform((sel, before, after) => {
|
|
213
|
-
const fence = '```'
|
|
214
|
-
const body = sel || 'code'
|
|
215
|
-
const md = `${fence}\n${body}\n${fence}`
|
|
216
|
-
const start = before.length + fence.length + 1
|
|
217
|
-
const end = start + body.length
|
|
218
|
-
return { next: `${before}${md}${after}`, cursor: { start, end } }
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const uploadAndInsert = async (file: File): Promise<void> => {
|
|
223
|
-
if (!uploadUrl) {
|
|
224
|
-
notify({ type: 'error', title: 'Upload URL missing', body: 'Pilotiq panel has no upload route configured.' })
|
|
225
|
-
return
|
|
226
|
-
}
|
|
227
|
-
setBusy(true)
|
|
228
|
-
try {
|
|
229
|
-
const fd = new FormData()
|
|
230
|
-
fd.append('file', file)
|
|
231
|
-
if (fileAttachmentsDirectory) fd.append('directory', fileAttachmentsDirectory)
|
|
232
|
-
if (fileAttachmentsVisibility) fd.append('visibility', fileAttachmentsVisibility)
|
|
233
|
-
fd.append('fieldName', name)
|
|
234
|
-
const res = await fetch(uploadUrl, { method: 'POST', body: fd, headers: { Accept: 'application/json' } })
|
|
235
|
-
const data = await res.json().catch(() => ({} as { ok?: boolean; url?: string; error?: string }))
|
|
236
|
-
if (!res.ok || !data.ok || !data.url) {
|
|
237
|
-
notify({ type: 'error', title: 'Upload failed', body: data.error ?? `Status ${res.status}` })
|
|
238
|
-
return
|
|
239
|
-
}
|
|
240
|
-
const isImage = file.type.startsWith('image/')
|
|
241
|
-
transform((sel, before, after) => {
|
|
242
|
-
const alt = sel || file.name
|
|
243
|
-
const md = isImage ? `` : `[${alt}](${data.url})`
|
|
244
|
-
return { next: `${before}${md}${after}`, cursor: { start: before.length + md.length, end: before.length + md.length } }
|
|
245
|
-
})
|
|
246
|
-
} catch (err) {
|
|
247
|
-
notify({ type: 'error', title: 'Upload failed', body: err instanceof Error ? err.message : String(err) })
|
|
248
|
-
} finally {
|
|
249
|
-
setBusy(false)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const onAttachClick = (): void => {
|
|
254
|
-
const input = document.createElement('input')
|
|
255
|
-
input.type = 'file'
|
|
256
|
-
input.onchange = () => {
|
|
257
|
-
const file = input.files?.[0]
|
|
258
|
-
if (file) void uploadAndInsert(file)
|
|
259
|
-
}
|
|
260
|
-
input.click()
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const onPaste = (e: React.ClipboardEvent<HTMLTextAreaElement>): void => {
|
|
264
|
-
if (!toolbarButtons.includes('attachFiles') || !uploadUrl) return
|
|
265
|
-
const items = Array.from(e.clipboardData?.items ?? [])
|
|
266
|
-
const fileItem = items.find(it => it.kind === 'file' && it.type.startsWith('image/'))
|
|
267
|
-
if (!fileItem) return
|
|
268
|
-
const file = fileItem.getAsFile()
|
|
269
|
-
if (!file) return
|
|
270
|
-
e.preventDefault()
|
|
271
|
-
void uploadAndInsert(file)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>): void => {
|
|
275
|
-
const meta = e.metaKey || e.ctrlKey
|
|
276
|
-
if (!meta) return
|
|
277
|
-
const key = e.key.toLowerCase()
|
|
278
|
-
if (key === 'b') { e.preventDefault(); wrapSelection('**', '**', 'bold text') }
|
|
279
|
-
else if (key === 'i') { e.preventDefault(); wrapSelection('*', '*', 'italic text') }
|
|
280
|
-
else if (key === 'k') { e.preventDefault(); insertLink() }
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const buttonHandler: Record<ToolbarButton, () => void> = {
|
|
284
|
-
bold: () => wrapSelection('**', '**', 'bold text'),
|
|
285
|
-
italic: () => wrapSelection('*', '*', 'italic text'),
|
|
286
|
-
strike: () => wrapSelection('~~', '~~', 'strikethrough'),
|
|
287
|
-
link: insertLink,
|
|
288
|
-
heading: () => prefixLine('## '),
|
|
289
|
-
bulletList: () => prefixLine('- '),
|
|
290
|
-
orderedList: insertOrderedList,
|
|
291
|
-
blockquote: () => prefixLine('> '),
|
|
292
|
-
codeBlock: insertCodeBlock,
|
|
293
|
-
attachFiles: onAttachClick,
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const buttonIcon: Record<ToolbarButton, React.ComponentType<{ className?: string }>> = {
|
|
297
|
-
bold: BoldIcon,
|
|
298
|
-
italic: ItalicIcon,
|
|
299
|
-
strike: StrikethroughIcon,
|
|
300
|
-
link: LinkIcon,
|
|
301
|
-
heading: HeadingIcon,
|
|
302
|
-
bulletList: ListIcon,
|
|
303
|
-
orderedList: ListOrderedIcon,
|
|
304
|
-
blockquote: QuoteIcon,
|
|
305
|
-
codeBlock: CodeIcon,
|
|
306
|
-
attachFiles: PaperclipIcon,
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const buttonLabel: Record<ToolbarButton, string> = {
|
|
310
|
-
bold: 'Bold (⌘B)',
|
|
311
|
-
italic: 'Italic (⌘I)',
|
|
312
|
-
strike: 'Strikethrough',
|
|
313
|
-
link: 'Link (⌘K)',
|
|
314
|
-
heading: 'Heading',
|
|
315
|
-
bulletList: 'Bulleted list',
|
|
316
|
-
orderedList: 'Numbered list',
|
|
317
|
-
blockquote: 'Quote',
|
|
318
|
-
codeBlock: 'Code block',
|
|
319
|
-
attachFiles: 'Attach file',
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const taStyle: React.CSSProperties = {}
|
|
323
|
-
if (minHeight) taStyle.minHeight = minHeight
|
|
324
|
-
if (maxHeight) taStyle.maxHeight = maxHeight
|
|
325
|
-
|
|
326
|
-
return (
|
|
327
|
-
<div className="flex flex-col rounded-md border bg-background">
|
|
328
|
-
<div className="flex items-center justify-between border-b px-2 py-1 gap-2">
|
|
329
|
-
<div className="flex items-center gap-0.5">
|
|
330
|
-
<TabButton active={tab === 'write'} onClick={() => setTab('write')}>Write</TabButton>
|
|
331
|
-
<TabButton active={tab === 'preview'} onClick={() => setTab('preview')}>Preview</TabButton>
|
|
332
|
-
</div>
|
|
333
|
-
{tab === 'write' && toolbarButtons.length > 0 && (
|
|
334
|
-
<div className="flex items-center gap-0.5">
|
|
335
|
-
{toolbarButtons.map((b) => {
|
|
336
|
-
const Icon = buttonIcon[b]
|
|
337
|
-
const isAttach = b === 'attachFiles'
|
|
338
|
-
return (
|
|
339
|
-
<Button
|
|
340
|
-
key={b}
|
|
341
|
-
type="button"
|
|
342
|
-
variant="ghost"
|
|
343
|
-
size="sm"
|
|
344
|
-
className="size-7 p-0"
|
|
345
|
-
onClick={buttonHandler[b]}
|
|
346
|
-
disabled={disabled || (isAttach && busy)}
|
|
347
|
-
title={buttonLabel[b]}
|
|
348
|
-
aria-label={buttonLabel[b]}
|
|
349
|
-
>
|
|
350
|
-
{isAttach && busy
|
|
351
|
-
? <Loader2Icon className="size-4 animate-spin" />
|
|
352
|
-
: <Icon className="size-4" />}
|
|
353
|
-
</Button>
|
|
354
|
-
)
|
|
355
|
-
})}
|
|
356
|
-
</div>
|
|
357
|
-
)}
|
|
358
|
-
</div>
|
|
359
|
-
|
|
360
|
-
{tab === 'write' ? (
|
|
361
|
-
<textarea
|
|
362
|
-
ref={textareaRef}
|
|
363
|
-
name={name}
|
|
364
|
-
id={name}
|
|
365
|
-
className="w-full resize-y bg-transparent px-3 py-2 text-sm font-mono leading-relaxed outline-none disabled:opacity-50"
|
|
366
|
-
style={taStyle}
|
|
367
|
-
placeholder={placeholder}
|
|
368
|
-
disabled={disabled}
|
|
369
|
-
{...(fs.controlled
|
|
370
|
-
? {
|
|
371
|
-
value,
|
|
372
|
-
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value),
|
|
373
|
-
}
|
|
374
|
-
: { defaultValue: initial, onChange: (e) => setLocalValue(e.target.value) })}
|
|
375
|
-
onPaste={onPaste}
|
|
376
|
-
onKeyDown={onKeyDown}
|
|
377
|
-
/>
|
|
378
|
-
) : (
|
|
379
|
-
<>
|
|
380
|
-
<input type="hidden" name={name} value={value} readOnly />
|
|
381
|
-
<div
|
|
382
|
-
className="prose prose-sm dark:prose-invert max-w-none px-3 py-2"
|
|
383
|
-
style={taStyle}
|
|
384
|
-
dangerouslySetInnerHTML={{ __html: previewHtml || '<p class="text-muted-foreground italic">Nothing to preview</p>' }}
|
|
385
|
-
/>
|
|
386
|
-
</>
|
|
387
|
-
)}
|
|
388
|
-
</div>
|
|
389
|
-
)
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function TabButton({ active, onClick, children }: {
|
|
393
|
-
active: boolean
|
|
394
|
-
onClick: () => void
|
|
395
|
-
children: React.ReactNode
|
|
396
|
-
}): React.ReactElement {
|
|
397
|
-
return (
|
|
398
|
-
<button
|
|
399
|
-
type="button"
|
|
400
|
-
className={[
|
|
401
|
-
'px-3 py-1 text-xs font-medium rounded transition-colors',
|
|
402
|
-
active
|
|
403
|
-
? 'bg-accent text-accent-foreground'
|
|
404
|
-
: 'text-muted-foreground hover:text-foreground',
|
|
405
|
-
].join(' ')}
|
|
406
|
-
onClick={onClick}
|
|
407
|
-
>
|
|
408
|
-
{children}
|
|
409
|
-
</button>
|
|
410
|
-
)
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Phase B follow-up — collab-aware markdown editor. Mounts the registered
|
|
415
|
-
* Tiptap-backed plain-text renderer for the Write pane and reuses the
|
|
416
|
-
* existing `marked` pipeline for Preview. No toolbar, no Cmd-shortcuts, no
|
|
417
|
-
* paste-image upload — those features depend on textarea-DOM splicing that
|
|
418
|
-
* doesn't translate to Tiptap's selection model. The cursor-bug fix is the
|
|
419
|
-
* load-bearing change; markdown-syntax authors keep typing as before.
|
|
420
|
-
*/
|
|
421
|
-
function MarkdownCollabInput({
|
|
422
|
-
Renderer, fragmentKey, hiddenInputName, defaultValue, disabled, placeholder, minHeight, maxHeight,
|
|
423
|
-
}: {
|
|
424
|
-
Renderer: CollabTextRenderer
|
|
425
|
-
fragmentKey: string
|
|
426
|
-
hiddenInputName: string
|
|
427
|
-
defaultValue: unknown
|
|
428
|
-
disabled: boolean
|
|
429
|
-
placeholder?: string
|
|
430
|
-
minHeight?: string
|
|
431
|
-
maxHeight?: string
|
|
432
|
-
}): React.ReactElement {
|
|
433
|
-
const fs = useFieldState(hiddenInputName)
|
|
434
|
-
const initial = useMemo(() => stringValue(defaultValue), [])
|
|
435
|
-
const [text, setText] = useState<string>(initial)
|
|
436
|
-
const [tab, setTab] = useState<'write' | 'preview'>('write')
|
|
437
|
-
const textRef = useRef(text)
|
|
438
|
-
useEffect(() => { textRef.current = text }, [text])
|
|
439
|
-
|
|
440
|
-
const handleChange = (next: string): void => {
|
|
441
|
-
setText(next)
|
|
442
|
-
if (fs.controlled) fs.setValue(next)
|
|
443
|
-
fs.triggerLive(next)
|
|
444
|
-
}
|
|
445
|
-
const handleBlur = (): void => { /* fire-and-forget — live trigger already ran on change */ }
|
|
446
|
-
|
|
447
|
-
const previewHtml = useMemo(
|
|
448
|
-
() => tab === 'preview' ? marked.parse(text, { gfm: true, breaks: false, async: false }) as string : '',
|
|
449
|
-
[tab, text],
|
|
450
|
-
)
|
|
451
|
-
|
|
452
|
-
const wrapperStyle: React.CSSProperties = {}
|
|
453
|
-
if (minHeight) wrapperStyle.minHeight = minHeight
|
|
454
|
-
if (maxHeight) wrapperStyle.maxHeight = maxHeight
|
|
455
|
-
|
|
456
|
-
return (
|
|
457
|
-
<div className="flex flex-col rounded-md border bg-background">
|
|
458
|
-
<div className="flex items-center border-b px-2 py-1">
|
|
459
|
-
<TabButton active={tab === 'write'} onClick={() => setTab('write')}>Write</TabButton>
|
|
460
|
-
<TabButton active={tab === 'preview'} onClick={() => setTab('preview')}>Preview</TabButton>
|
|
461
|
-
</div>
|
|
462
|
-
{tab === 'write' ? (
|
|
463
|
-
<div style={wrapperStyle} className="overflow-auto">
|
|
464
|
-
<input type="hidden" name={hiddenInputName} value={text} />
|
|
465
|
-
<Renderer
|
|
466
|
-
name={hiddenInputName}
|
|
467
|
-
{...(fragmentKey !== hiddenInputName ? { fragmentKey } : {})}
|
|
468
|
-
multiline={true}
|
|
469
|
-
defaultValue={initial}
|
|
470
|
-
{...(placeholder !== undefined ? { placeholder } : {})}
|
|
471
|
-
disabled={disabled}
|
|
472
|
-
onChange={handleChange}
|
|
473
|
-
onBlur={handleBlur}
|
|
474
|
-
className="w-full bg-transparent px-3 py-2 text-sm font-mono leading-relaxed outline-none disabled:opacity-50 whitespace-pre-wrap break-words"
|
|
475
|
-
/>
|
|
476
|
-
</div>
|
|
477
|
-
) : (
|
|
478
|
-
<>
|
|
479
|
-
<input type="hidden" name={hiddenInputName} value={text} readOnly />
|
|
480
|
-
<div
|
|
481
|
-
className="prose prose-sm dark:prose-invert max-w-none px-3 py-2"
|
|
482
|
-
style={wrapperStyle}
|
|
483
|
-
dangerouslySetInnerHTML={{ __html: previewHtml || '<p class="text-muted-foreground italic">Nothing to preview</p>' }}
|
|
484
|
-
/>
|
|
485
|
-
</>
|
|
486
|
-
)}
|
|
487
|
-
</div>
|
|
488
|
-
)
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function stringValue(v: unknown): string {
|
|
492
|
-
if (v === undefined || v === null) return ''
|
|
493
|
-
if (typeof v === 'string') return v
|
|
494
|
-
return String(v)
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Bridges the registered adapter editor (`@pilotiq/tiptap`'s `MarkdownEditor`)
|
|
499
|
-
* to pilotiq's form-state + form-submit wire shape. The adapter owns the
|
|
500
|
-
* editor surface (WYSIWYG, toolbar, paste-image, optional source / preview
|
|
501
|
-
* tabs) and its own collab binding via `useCollabRoom()`; this host just
|
|
502
|
-
* threads form-state updates and writes the current markdown to a hidden
|
|
503
|
-
* input so submit picks it up unchanged.
|
|
504
|
-
*/
|
|
505
|
-
function MarkdownEditorHost({
|
|
506
|
-
Editor, name, fragmentKey, defaultValue, disabled, placeholder,
|
|
507
|
-
toolbarButtons, minHeight, maxHeight,
|
|
508
|
-
fileAttachmentsDirectory, fileAttachmentsVisibility, uploadUrl,
|
|
509
|
-
}: {
|
|
510
|
-
Editor: MarkdownEditorComponent
|
|
511
|
-
name: string
|
|
512
|
-
// Distinct from `name` only inside Repeater/Builder rows: the dotted
|
|
513
|
-
// form-input name (`items.0.body`) is unstable across reorders, so the
|
|
514
|
-
// editor binds its `Y.XmlFragment` under this row-id-anchored key
|
|
515
|
-
// instead. AI hooks, hidden input, and the inline-diff banner still
|
|
516
|
-
// use `name` — they route by FormData identity, not collab identity.
|
|
517
|
-
// Defaults to `name` when unset (top-level fields).
|
|
518
|
-
fragmentKey?: string
|
|
519
|
-
defaultValue: unknown
|
|
520
|
-
disabled: boolean
|
|
521
|
-
placeholder?: string
|
|
522
|
-
toolbarButtons: ReadonlyArray<string>
|
|
523
|
-
minHeight?: string
|
|
524
|
-
maxHeight?: string
|
|
525
|
-
fileAttachmentsDirectory?: string
|
|
526
|
-
fileAttachmentsVisibility?: string
|
|
527
|
-
uploadUrl?: string
|
|
528
|
-
}): React.ReactElement {
|
|
529
|
-
const fs = useFieldState(name)
|
|
530
|
-
const initial = useMemo(() => stringValue(defaultValue), [])
|
|
531
|
-
const [text, setText] = useState<string>(initial)
|
|
532
|
-
|
|
533
|
-
const handleChange = (next: string): void => {
|
|
534
|
-
setText(next)
|
|
535
|
-
if (fs.controlled) fs.setValue(next)
|
|
536
|
-
fs.triggerLive(next)
|
|
537
|
-
}
|
|
538
|
-
const handleBlur = (): void => { /* live trigger already ran on change */ }
|
|
539
|
-
|
|
540
|
-
return (
|
|
541
|
-
<>
|
|
542
|
-
<input type="hidden" name={name} value={text} readOnly />
|
|
543
|
-
<Editor
|
|
544
|
-
name={name}
|
|
545
|
-
{...(fragmentKey !== undefined ? { fragmentKey } : {})}
|
|
546
|
-
defaultValue={initial}
|
|
547
|
-
disabled={disabled}
|
|
548
|
-
{...(placeholder !== undefined ? { placeholder } : {})}
|
|
549
|
-
toolbarButtons={toolbarButtons}
|
|
550
|
-
{...(minHeight !== undefined ? { minHeight } : {})}
|
|
551
|
-
{...(maxHeight !== undefined ? { maxHeight } : {})}
|
|
552
|
-
{...(fileAttachmentsDirectory !== undefined ? { fileAttachmentsDirectory } : {})}
|
|
553
|
-
{...(fileAttachmentsVisibility !== undefined ? { fileAttachmentsVisibility } : {})}
|
|
554
|
-
{...(uploadUrl !== undefined ? { uploadUrl } : {})}
|
|
555
|
-
onChange={handleChange}
|
|
556
|
-
onBlur={handleBlur}
|
|
557
|
-
/>
|
|
558
|
-
</>
|
|
559
|
-
)
|
|
560
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { useFieldState, FormIdContext } from '../FormStateContext.js'
|
|
3
|
-
import { registerPendingSuggestionApplier, type PendingSuggestionApplier } from '../PendingSuggestionApplierRegistry.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Single-choice field rendered as a vertical (or `inline:true` horizontal)
|
|
7
|
-
* stack of `<input type="radio">` rows. Controlled when inside a
|
|
8
|
-
* `FormStateProvider`, uncontrolled otherwise.
|
|
9
|
-
*/
|
|
10
|
-
export function RadioInput({
|
|
11
|
-
name, defaultValue, disabled, options, inline,
|
|
12
|
-
}: {
|
|
13
|
-
name: string
|
|
14
|
-
defaultValue: string | undefined
|
|
15
|
-
disabled: boolean
|
|
16
|
-
options: Array<{ value: string; label: string; disabled?: boolean }>
|
|
17
|
-
inline: boolean
|
|
18
|
-
}): React.ReactElement {
|
|
19
|
-
const fs = useFieldState(name)
|
|
20
|
-
const [localValue, setLocalValue] = useState<string>(defaultValue ?? '')
|
|
21
|
-
const value = fs.controlled
|
|
22
|
-
? (fs.value !== undefined && fs.value !== null ? String(fs.value) : '')
|
|
23
|
-
: localValue
|
|
24
|
-
const onChange = (next: string): void => {
|
|
25
|
-
if (fs.controlled) { fs.setValue(next); fs.triggerLive(next) }
|
|
26
|
-
else { setLocalValue(next); fs.triggerLive(next) }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Cross-tree applier — the visible radios live under a
|
|
30
|
-
// `${name}__radio`-named group (separate from the `[name]` hidden
|
|
31
|
-
// mirror), and they're React-controlled via `checked={value === o.value}`.
|
|
32
|
-
// FieldShell skips its generic registration for fieldType === 'radio'.
|
|
33
|
-
const fsRef = useRef(fs)
|
|
34
|
-
useEffect(() => { fsRef.current = fs }, [fs])
|
|
35
|
-
const formId = useContext(FormIdContext) || undefined
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
if (name.includes('.')) return
|
|
38
|
-
const applier: PendingSuggestionApplier = (suggestion) => {
|
|
39
|
-
const v = suggestion.suggestedValue
|
|
40
|
-
const next = v == null ? '' : String(v)
|
|
41
|
-
const cur = fsRef.current
|
|
42
|
-
if (cur.controlled) { cur.setValue(next); cur.triggerLive(next) }
|
|
43
|
-
else { setLocalValue(next); cur.triggerLive(next) }
|
|
44
|
-
}
|
|
45
|
-
return registerPendingSuggestionApplier(formId, name, applier)
|
|
46
|
-
}, [name, formId])
|
|
47
|
-
|
|
48
|
-
const layout = inline ? 'flex flex-row flex-wrap gap-4' : 'flex flex-col gap-2'
|
|
49
|
-
return (
|
|
50
|
-
<div role="radiogroup" className={layout}>
|
|
51
|
-
<input type="hidden" name={name} value={value} />
|
|
52
|
-
{options.map((o) => {
|
|
53
|
-
const id = `${name}-${o.value}`
|
|
54
|
-
const checked = value === o.value
|
|
55
|
-
const optDisabled = disabled || Boolean(o.disabled)
|
|
56
|
-
return (
|
|
57
|
-
<label
|
|
58
|
-
key={o.value}
|
|
59
|
-
htmlFor={id}
|
|
60
|
-
className={
|
|
61
|
-
'flex items-center gap-2 text-sm ' +
|
|
62
|
-
(optDisabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer')
|
|
63
|
-
}
|
|
64
|
-
>
|
|
65
|
-
<input
|
|
66
|
-
type="radio"
|
|
67
|
-
id={id}
|
|
68
|
-
name={`${name}__radio`}
|
|
69
|
-
value={o.value}
|
|
70
|
-
checked={checked}
|
|
71
|
-
onChange={() => onChange(o.value)}
|
|
72
|
-
disabled={optDisabled}
|
|
73
|
-
className="size-4 accent-primary"
|
|
74
|
-
/>
|
|
75
|
-
<span>{o.label}</span>
|
|
76
|
-
</label>
|
|
77
|
-
)
|
|
78
|
-
})}
|
|
79
|
-
</div>
|
|
80
|
-
)
|
|
81
|
-
}
|