@rebasepro/cms 0.17.3 → 0.18.1
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 +4 -0
- package/dist/{CollectionEditorDialog-B-akreOU.js → CollectionEditorDialog-BeX4b92r.js} +13 -7
- package/dist/{CollectionEditorDialog-B-akreOU.js.map → CollectionEditorDialog-BeX4b92r.js.map} +1 -1
- package/dist/{PropertyEditView-CiY84wkR.js → PropertyEditView-C7thfj4L.js} +3 -3
- package/dist/{PropertyEditView-CiY84wkR.js.map → PropertyEditView-C7thfj4L.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-DkAWda4N.js → RouterCollectionsStudioView-BtjzvB6E.js} +4 -4
- package/dist/{RouterCollectionsStudioView-DkAWda4N.js.map → RouterCollectionsStudioView-BtjzvB6E.js.map} +1 -1
- package/dist/{SchemaChangeDialog-DceT8QFX.js → SchemaChangeDialog-CkqK6KOC.js} +4 -2
- package/dist/{SchemaChangeDialog-DceT8QFX.js.map → SchemaChangeDialog-CkqK6KOC.js.map} +1 -1
- package/dist/collection_editor/api/generateCollectionApi.d.ts +1 -2
- package/dist/collection_editor_ui.d.ts +2 -0
- package/dist/collection_editor_ui.js +6 -5
- package/dist/collection_editor_ui.js.map +1 -1
- package/dist/components/DeleteEntityDialog.d.ts +2 -3
- package/dist/components/RebaseLayout.d.ts +11 -4
- package/dist/components/field_configs.d.ts +1 -1
- package/dist/editor/sanitize-html.d.ts +15 -0
- package/dist/editor.js +195 -10
- package/dist/editor.js.map +1 -1
- package/dist/{export-Ben8E355.js → export--arni_VY.js} +2 -2
- package/dist/{export-Ben8E355.js.map → export--arni_VY.js.map} +1 -1
- package/dist/form/components/LabelWithIcon.d.ts +10 -0
- package/dist/{history-DIEVd26f.js → history-C2TpoQwi.js} +2 -2
- package/dist/{history-DIEVd26f.js.map → history-C2TpoQwi.js.map} +1 -1
- package/dist/hooks/navigation/useBuildNavigationStateController.d.ts +1 -1
- package/dist/hooks/navigation/useResolvedViews.d.ts +1 -1
- package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
- package/dist/{import-D25x7ko-.js → import-DTkXNoBg.js} +2 -2
- package/dist/{import-D25x7ko-.js.map → import-DTkXNoBg.js.map} +1 -1
- package/dist/index.js +78 -33
- package/dist/index.js.map +1 -1
- package/dist/util/propertyConfigMapper.d.ts +1 -1
- package/dist/util-CfLFggP7.js +57 -0
- package/dist/util-CfLFggP7.js.map +1 -0
- package/dist/{util-DVqnqlLn.js → util-DE1WQW6h.js} +134 -112
- package/dist/util-DE1WQW6h.js.map +1 -0
- package/package.json +42 -34
- package/dist/util-DVqnqlLn.js.map +0 -1
- package/src/collection_editor/CollectionEditorDialogsContext.tsx +0 -25
- package/src/collection_editor/ConfigControllerProvider.tsx +0 -395
- package/src/collection_editor/_cms_internals.ts +0 -41
- package/src/collection_editor/api/generateCollectionApi.ts +0 -126
- package/src/collection_editor/api/index.ts +0 -1
- package/src/collection_editor/extensibility_types.ts +0 -168
- package/src/collection_editor/index.ts +0 -108
- package/src/collection_editor/liveSchemaClient.ts +0 -285
- package/src/collection_editor/relation_kinds.ts +0 -44
- package/src/collection_editor/serializable_types.ts +0 -535
- package/src/collection_editor/serializable_utils.ts +0 -729
- package/src/collection_editor/types/collection_editor_controller.tsx +0 -62
- package/src/collection_editor/types/collection_inference.ts +0 -18
- package/src/collection_editor/types/config_controller.tsx +0 -121
- package/src/collection_editor/ui/AddKanbanColumnAction.tsx +0 -199
- package/src/collection_editor/ui/CollectionViewHeaderAction.tsx +0 -51
- package/src/collection_editor/ui/EditorCollectionAction.tsx +0 -54
- package/src/collection_editor/ui/EditorCollectionActionStart.tsx +0 -90
- package/src/collection_editor/ui/EditorEntityAction.tsx +0 -50
- package/src/collection_editor/ui/HomePageEditorCollectionAction.tsx +0 -114
- package/src/collection_editor/ui/KanbanSetupAction.tsx +0 -43
- package/src/collection_editor/ui/MissingReferenceWidget.tsx +0 -35
- package/src/collection_editor/ui/NewCollectionButton.tsx +0 -27
- package/src/collection_editor/ui/NewCollectionCard.tsx +0 -48
- package/src/collection_editor/ui/PropertyAddColumnComponent.tsx +0 -49
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +0 -260
- package/src/collection_editor/ui/collection_editor/AIModifiedPathsContext.tsx +0 -90
- package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +0 -388
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +0 -1098
- package/src/collection_editor/ui/collection_editor/CollectionEditorWelcomeView.tsx +0 -301
- package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +0 -172
- package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +0 -620
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +0 -530
- package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +0 -462
- package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +0 -104
- package/src/collection_editor/ui/collection_editor/CollectionYupValidation.tsx +0 -9
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +0 -199
- package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +0 -315
- package/src/collection_editor/ui/collection_editor/EntityActionsEditTab.tsx +0 -164
- package/src/collection_editor/ui/collection_editor/EntityActionsSelectDialog.tsx +0 -40
- package/src/collection_editor/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +0 -39
- package/src/collection_editor/ui/collection_editor/EnumForm.tsx +0 -373
- package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +0 -288
- package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +0 -141
- package/src/collection_editor/ui/collection_editor/KanbanConfigSection.tsx +0 -194
- package/src/collection_editor/ui/collection_editor/LayoutModeSwitch.tsx +0 -53
- package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +0 -980
- package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +0 -164
- package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +0 -364
- package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +0 -44
- package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +0 -41
- package/src/collection_editor/ui/collection_editor/SchemaChangeDialog.tsx +0 -470
- package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +0 -256
- package/src/collection_editor/ui/collection_editor/SwitchControl.tsx +0 -39
- package/src/collection_editor/ui/collection_editor/ViewModeSwitch.tsx +0 -68
- package/src/collection_editor/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +0 -67
- package/src/collection_editor/ui/collection_editor/import/CollectionEditorImportMapping.tsx +0 -296
- package/src/collection_editor/ui/collection_editor/import/clean_import_data.ts +0 -57
- package/src/collection_editor/ui/collection_editor/properties/BlockPropertyField.tsx +0 -147
- package/src/collection_editor/ui/collection_editor/properties/BooleanPropertyField.tsx +0 -41
- package/src/collection_editor/ui/collection_editor/properties/CommonPropertyFields.tsx +0 -113
- package/src/collection_editor/ui/collection_editor/properties/DateTimePropertyField.tsx +0 -250
- package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +0 -126
- package/src/collection_editor/ui/collection_editor/properties/KeyValuePropertyField.tsx +0 -20
- package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +0 -153
- package/src/collection_editor/ui/collection_editor/properties/MarkdownPropertyField.tsx +0 -217
- package/src/collection_editor/ui/collection_editor/properties/NumberPropertyField.tsx +0 -117
- package/src/collection_editor/ui/collection_editor/properties/ReferencePropertyField.tsx +0 -129
- package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +0 -335
- package/src/collection_editor/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -107
- package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +0 -403
- package/src/collection_editor/ui/collection_editor/properties/StringPropertyField.tsx +0 -148
- package/src/collection_editor/ui/collection_editor/properties/UrlPropertyField.tsx +0 -91
- package/src/collection_editor/ui/collection_editor/properties/VectorPropertyField.tsx +0 -34
- package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +0 -49
- package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +0 -925
- package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsPanel.tsx +0 -28
- package/src/collection_editor/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +0 -626
- package/src/collection_editor/ui/collection_editor/properties/conditions/index.ts +0 -6
- package/src/collection_editor/ui/collection_editor/properties/conditions/property_paths.ts +0 -93
- package/src/collection_editor/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +0 -52
- package/src/collection_editor/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +0 -63
- package/src/collection_editor/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +0 -114
- package/src/collection_editor/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +0 -154
- package/src/collection_editor/ui/collection_editor/properties/validation/ValidationPanel.tsx +0 -28
- package/src/collection_editor/ui/collection_editor/templates/blog_template.ts +0 -112
- package/src/collection_editor/ui/collection_editor/templates/pages_template.ts +0 -183
- package/src/collection_editor/ui/collection_editor/templates/products_template.ts +0 -88
- package/src/collection_editor/ui/collection_editor/templates/users_template.ts +0 -42
- package/src/collection_editor/ui/collection_editor/util.ts +0 -28
- package/src/collection_editor/ui/collection_editor/utils/strings.ts +0 -1
- package/src/collection_editor/ui/collection_editor/utils/supported_fields.tsx +0 -39
- package/src/collection_editor/ui/collection_editor/utils/update_property_for_widget.ts +0 -276
- package/src/collection_editor/useCollectionEditorController.tsx +0 -9
- package/src/collection_editor/useCollectionsConfigController.tsx +0 -9
- package/src/collection_editor/useJsonCollectionsConfigController.tsx +0 -297
- package/src/collection_editor/useLiveSchemaEditing.tsx +0 -315
- package/src/collection_editor/useLocalCollectionsConfigController.tsx +0 -341
- package/src/collection_editor/useSafeSnackbarController.ts +0 -9
- package/src/collection_editor/validateCollectionJson.ts +0 -440
- package/src/collection_editor_ui.ts +0 -21
- package/src/components/AdditionalFieldValue.tsx +0 -68
- package/src/components/AdminModeSyncer.tsx +0 -56
- package/src/components/ArrayContainer.tsx +0 -542
- package/src/components/ClearFilterSortButton.tsx +0 -56
- package/src/components/CollectionEditorDialogs.tsx +0 -69
- package/src/components/CollectionPanel.tsx +0 -179
- package/src/components/CollectionTableBinding/CollectionRowActions.tsx +0 -244
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +0 -394
- package/src/components/CollectionTableBinding/CollectionTableBindingProps.tsx +0 -172
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +0 -350
- package/src/components/CollectionTableBinding/column_utils.tsx +0 -155
- package/src/components/CollectionTableBinding/fields/TableMultipleRelationField.tsx +0 -131
- package/src/components/CollectionTableBinding/fields/TableReferenceField.tsx +0 -193
- package/src/components/CollectionTableBinding/fields/TableRelationField.tsx +0 -192
- package/src/components/CollectionTableBinding/fields/TableRelationSelectorField.tsx +0 -42
- package/src/components/CollectionTableBinding/fields/TableStorageUpload.tsx +0 -324
- package/src/components/CollectionTableBinding/fields/VirtualTableSelect.tsx +0 -132
- package/src/components/CollectionTableBinding/fields/VirtualTableUserSelect.tsx +0 -41
- package/src/components/CollectionTableBinding/index.tsx +0 -14
- package/src/components/CollectionTableBinding/internal/CollectionTableToolbar.tsx +0 -96
- package/src/components/CollectionTableBinding/internal/EntityTableCell.tsx +0 -309
- package/src/components/CollectionTableBinding/internal/EntityTableCellActions.tsx +0 -80
- package/src/components/CollectionTableBinding/internal/common.tsx +0 -74
- package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +0 -422
- package/src/components/CollectionTableBinding/internal/popup_field/useDraggable.tsx +0 -99
- package/src/components/CollectionTableBinding/internal/popup_field/useWindowSize.tsx +0 -22
- package/src/components/CollectionTableBinding/table_bindings.tsx +0 -350
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +0 -263
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +0 -662
- package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +0 -159
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +0 -1430
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +0 -227
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +0 -1603
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +0 -240
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +0 -239
- package/src/components/CollectionViewBinding/FilterPresetsButton.tsx +0 -298
- package/src/components/CollectionViewBinding/FiltersDialog.tsx +0 -218
- package/src/components/CollectionViewBinding/SearchExplanation.tsx +0 -106
- package/src/components/CollectionViewBinding/SearchHighlight.tsx +0 -311
- package/src/components/CollectionViewBinding/SlotValue.tsx +0 -156
- package/src/components/CollectionViewBinding/SortButton.tsx +0 -354
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +0 -49
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +0 -28
- package/src/components/CollectionViewBinding/SplitListView.tsx +0 -488
- package/src/components/CollectionViewBinding/SplitViewContext.tsx +0 -34
- package/src/components/CollectionViewBinding/ViewModeToggle.tsx +0 -250
- package/src/components/CollectionViewBinding/hooks/useCollectionInlineEditor.ts +0 -81
- package/src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts +0 -181
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +0 -802
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +0 -703
- package/src/components/CollectionViewBinding/useSelectionController.tsx +0 -40
- package/src/components/CollectionViewBinding/utils.ts +0 -22
- package/src/components/DefaultAppBar.tsx +0 -264
- package/src/components/DefaultDrawer.tsx +0 -593
- package/src/components/DeleteEntityDialog.tsx +0 -205
- package/src/components/DetailViewBinding.tsx +0 -731
- package/src/components/DrawerNavigationGroup.tsx +0 -141
- package/src/components/DrawerNavigationItem.tsx +0 -115
- package/src/components/EditFormActions.tsx +0 -303
- package/src/components/EditViewBinding.tsx +0 -1069
- package/src/components/EntityDisplayHeader.tsx +0 -170
- package/src/components/EntityIdentityBar.tsx +0 -428
- package/src/components/EntityInspector.tsx +0 -156
- package/src/components/EntityPreviewBinding.tsx +0 -432
- package/src/components/EntityPreviewNesting.tsx +0 -50
- package/src/components/EntityViewBinding.tsx +0 -374
- package/src/components/FieldCaption.tsx +0 -14
- package/src/components/HomePage/ContentHomePage.tsx +0 -586
- package/src/components/HomePage/FavouritesView.tsx +0 -60
- package/src/components/HomePage/HomePageDnD.tsx +0 -704
- package/src/components/HomePage/NavigationCard.tsx +0 -81
- package/src/components/HomePage/NavigationCardBinding.tsx +0 -107
- package/src/components/HomePage/NavigationGroup.tsx +0 -156
- package/src/components/HomePage/RenameGroupDialog.tsx +0 -123
- package/src/components/HomePage/SmallNavigationCard.tsx +0 -41
- package/src/components/HomePage/index.tsx +0 -5
- package/src/components/InlineEntityPreview.tsx +0 -149
- package/src/components/JsonPreviewBinding.tsx +0 -66
- package/src/components/PropertyCollectionView.tsx +0 -336
- package/src/components/PropertyConfigBadge.tsx +0 -28
- package/src/components/PropertyIdCopyTooltip.tsx +0 -47
- package/src/components/RebaseAuthGate.tsx +0 -44
- package/src/components/RebaseCMS.tsx +0 -30
- package/src/components/RebaseLayout.tsx +0 -80
- package/src/components/RebaseNavigation.tsx +0 -369
- package/src/components/RebaseRouteDefs.tsx +0 -127
- package/src/components/RebaseShell.tsx +0 -83
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +0 -389
- package/src/components/ReferenceWidget.tsx +0 -156
- package/src/components/RelationSelector.tsx +0 -926
- package/src/components/SearchIconsView.tsx +0 -78
- package/src/components/SelectableTable/SelectableTable.tsx +0 -309
- package/src/components/SelectableTable/SelectableTableContext.tsx +0 -7
- package/src/components/SelectableTable/SelectionStore.ts +0 -43
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +0 -59
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +0 -146
- package/src/components/SelectableTable/filters/FilterFieldBinding.tsx +0 -190
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +0 -250
- package/src/components/SelectableTable/filters/RelationFilterField.tsx +0 -238
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +0 -282
- package/src/components/SelectableTable/filters/null_filter.ts +0 -63
- package/src/components/SideDialogs.tsx +0 -238
- package/src/components/SidePanelBinding.tsx +0 -361
- package/src/components/SidePanelProvider.tsx +0 -80
- package/src/components/UserSelector.tsx +0 -513
- package/src/components/admin/CreationResultDialog.tsx +0 -138
- package/src/components/admin/index.ts +0 -1
- package/src/components/app/AppBar.tsx +0 -17
- package/src/components/app/Drawer.tsx +0 -38
- package/src/components/app/Scaffold.tsx +0 -366
- package/src/components/app/index.ts +0 -4
- package/src/components/app/useApp.tsx +0 -36
- package/src/components/common/default_entity_actions.tsx +0 -440
- package/src/components/common/index.ts +0 -1
- package/src/components/field_configs.tsx +0 -506
- package/src/components/history/EntityHistoryEntry.tsx +0 -203
- package/src/components/history/EntityHistoryView.tsx +0 -206
- package/src/components/history/LastEditedByFormAction.tsx +0 -24
- package/src/components/history/LastEditedByIndicator.tsx +0 -95
- package/src/components/history/UserChip.tsx +0 -15
- package/src/components/history/index.tsx +0 -5
- package/src/components/index.ts +0 -63
- package/src/contexts/BreacrumbsContext.tsx +0 -45
- package/src/contexts/SideDialogsControllerContext.tsx +0 -5
- package/src/contexts/SidePanelControllerContext.tsx +0 -5
- package/src/data_export/export/BasicExportAction.tsx +0 -142
- package/src/data_export/export/ExportCollectionAction.tsx +0 -321
- package/src/data_export/export/export.ts +0 -295
- package/src/data_export/export/fetch_export_data.ts +0 -90
- package/src/data_export/export/index.ts +0 -4
- package/src/data_export/index.ts +0 -1
- package/src/data_import/components/DataNewPropertiesMapping.tsx +0 -261
- package/src/data_import/components/ImportFileUpload.tsx +0 -42
- package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +0 -58
- package/src/data_import/components/ImportSaveInProgress.tsx +0 -121
- package/src/data_import/components/index.ts +0 -4
- package/src/data_import/hooks/index.ts +0 -1
- package/src/data_import/hooks/useImportConfig.tsx +0 -34
- package/src/data_import/import/ImportCollectionAction.tsx +0 -461
- package/src/data_import/import/index.ts +0 -1
- package/src/data_import/index.ts +0 -5
- package/src/data_import/types/column_mapping.ts +0 -32
- package/src/data_import/types/index.ts +0 -1
- package/src/data_import/utils/csv.ts +0 -153
- package/src/data_import/utils/data.ts +0 -181
- package/src/data_import/utils/file_headers.ts +0 -14
- package/src/data_import/utils/file_to_json.ts +0 -149
- package/src/data_import/utils/get_import_inference_type.ts +0 -33
- package/src/data_import/utils/get_properties_mapping.ts +0 -67
- package/src/data_import/utils/index.ts +0 -5
- package/src/data_import/utils/save_entities.ts +0 -138
- package/src/data_import/utils/transforms.ts +0 -70
- package/src/editor/components/SlashCommandMenu.tsx +0 -532
- package/src/editor/components/editor-bubble-item.tsx +0 -32
- package/src/editor/components/editor-bubble.tsx +0 -143
- package/src/editor/components/image-bubble.tsx +0 -183
- package/src/editor/components/index.ts +0 -14
- package/src/editor/components/table-bubble.tsx +0 -233
- package/src/editor/editor.tsx +0 -529
- package/src/editor/extensions/HighlightDecorationExtension.ts +0 -115
- package/src/editor/extensions/Image.ts +0 -159
- package/src/editor/extensions/TextLoadingDecorationExtension.tsx +0 -107
- package/src/editor/extensions/clipboard.ts +0 -72
- package/src/editor/extensions/custom-keymap.ts +0 -25
- package/src/editor/extensions/drag-and-drop.tsx +0 -508
- package/src/editor/hooks/useProseMirror.ts +0 -126
- package/src/editor/hooks/useProseMirrorContext.ts +0 -15
- package/src/editor/index.ts +0 -2
- package/src/editor/markdown-it-plugins.d.ts +0 -24
- package/src/editor/markdown.ts +0 -204
- package/src/editor/nodeViews/ImageComponent.tsx +0 -20
- package/src/editor/nodeViews/ReactNodeView.tsx +0 -89
- package/src/editor/nodeViews/TaskItemComponent.tsx +0 -30
- package/src/editor/nodeViews/index.ts +0 -35
- package/src/editor/plugins/index.ts +0 -58
- package/src/editor/plugins/inputrules.ts +0 -82
- package/src/editor/plugins/placeholderPlugin.ts +0 -55
- package/src/editor/plugins/slashCommandPlugin.ts +0 -65
- package/src/editor/schema.ts +0 -339
- package/src/editor/selectors/ai-selector.tsx +0 -110
- package/src/editor/selectors/color-selector.tsx +0 -199
- package/src/editor/selectors/link-selector.tsx +0 -122
- package/src/editor/selectors/node-selector.tsx +0 -159
- package/src/editor/selectors/text-buttons.tsx +0 -79
- package/src/editor/types.ts +0 -6
- package/src/editor/useProseMirror.ts +0 -125
- package/src/editor/utils/prosemirror-utils.ts +0 -113
- package/src/editor/utils/remove_classes.ts +0 -18
- package/src/editor/utils/useDebouncedCallback.ts +0 -25
- package/src/form/EntityForm.tsx +0 -761
- package/src/form/EntityFormActions.tsx +0 -160
- package/src/form/EntityFormBinding.tsx +0 -342
- package/src/form/PropertyFieldBinding.tsx +0 -348
- package/src/form/components/ErrorFocus.tsx +0 -44
- package/src/form/components/FieldBlock.tsx +0 -189
- package/src/form/components/FieldHelperText.tsx +0 -84
- package/src/form/components/FormRail.tsx +0 -153
- package/src/form/components/FormSections.tsx +0 -145
- package/src/form/components/LabelWithIcon.tsx +0 -43
- package/src/form/components/LabelWithIconAndTooltip.tsx +0 -28
- package/src/form/components/LocalChangesMenu.tsx +0 -147
- package/src/form/components/StorageItemPreview.tsx +0 -81
- package/src/form/components/StorageUploadProgress.tsx +0 -118
- package/src/form/components/index.tsx +0 -6
- package/src/form/components/useRailVisible.tsx +0 -47
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +0 -109
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -172
- package/src/form/field_bindings/BinaryFieldBinding.tsx +0 -177
- package/src/form/field_bindings/BlockFieldBinding.tsx +0 -282
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +0 -78
- package/src/form/field_bindings/GeopointFieldBinding.tsx +0 -200
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -574
- package/src/form/field_bindings/MapFieldBinding.tsx +0 -120
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +0 -259
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +0 -130
- package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +0 -168
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +0 -94
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +0 -146
- package/src/form/field_bindings/RelationFieldBinding.tsx +0 -202
- package/src/form/field_bindings/RepeatFieldBinding.tsx +0 -143
- package/src/form/field_bindings/SelectFieldBinding.tsx +0 -111
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +0 -521
- package/src/form/field_bindings/SwitchFieldBinding.tsx +0 -70
- package/src/form/field_bindings/TextFieldBinding.tsx +0 -189
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +0 -71
- package/src/form/field_bindings/VectorFieldBinding.tsx +0 -209
- package/src/form/form_utils.ts +0 -243
- package/src/form/index.tsx +0 -36
- package/src/form/useClearRestoreValue.tsx +0 -36
- package/src/form/useUndoableDiscard.ts +0 -39
- package/src/form/validation.ts +0 -583
- package/src/hooks/index.ts +0 -16
- package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +0 -22
- package/src/hooks/navigation/contexts/NavigationStateContext.tsx +0 -16
- package/src/hooks/navigation/contexts/UrlContext.tsx +0 -24
- package/src/hooks/navigation/contexts/index.ts +0 -14
- package/src/hooks/navigation/useAsyncResolver.ts +0 -100
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +0 -218
- package/src/hooks/navigation/useBuildNavigationStateController.tsx +0 -134
- package/src/hooks/navigation/useBuildUrlController.tsx +0 -66
- package/src/hooks/navigation/useNavigationResolution.ts +0 -271
- package/src/hooks/navigation/useResolvedCollections.ts +0 -102
- package/src/hooks/navigation/useResolvedViews.tsx +0 -139
- package/src/hooks/navigation/useTopLevelNavigation.ts +0 -273
- package/src/hooks/navigation/utils.ts +0 -200
- package/src/hooks/useAdminContext.tsx +0 -83
- package/src/hooks/useBreadcrumbsController.tsx +0 -17
- package/src/hooks/useBuildSideDialogsController.tsx +0 -145
- package/src/hooks/useBuildSidePanel.tsx +0 -315
- package/src/hooks/useChildViewSource.ts +0 -46
- package/src/hooks/useEntityDisplay.tsx +0 -262
- package/src/hooks/useEntityDisplayTitle.tsx +0 -49
- package/src/hooks/useHistory.ts +0 -178
- package/src/hooks/useRecordActions.tsx +0 -69
- package/src/hooks/useResolvedNavigationFrom.tsx +0 -167
- package/src/hooks/useResolvedUsers.ts +0 -148
- package/src/hooks/useSelectionDialog.tsx +0 -58
- package/src/hooks/useSideDialogsController.tsx +0 -25
- package/src/hooks/useSidePanel.tsx +0 -13
- package/src/index.ts +0 -124
- package/src/preview/PropertyPreview.tsx +0 -331
- package/src/preview/compact_text.ts +0 -68
- package/src/preview/components/ArrayEnumPreview.tsx +0 -39
- package/src/preview/components/BooleanPreview.tsx +0 -28
- package/src/preview/components/DatePreview.tsx +0 -106
- package/src/preview/components/EmptyValue.tsx +0 -10
- package/src/preview/components/EnumValuesChip.tsx +0 -39
- package/src/preview/components/ImagePreview.tsx +0 -144
- package/src/preview/components/InlineEntityListPreview.tsx +0 -42
- package/src/preview/components/ReferencePreview.tsx +0 -235
- package/src/preview/components/RelationPreview.tsx +0 -271
- package/src/preview/components/StorageThumbnail.tsx +0 -147
- package/src/preview/components/UrlComponentPreview.tsx +0 -116
- package/src/preview/components/UserPreview.tsx +0 -30
- package/src/preview/components/date_fns_locales.ts +0 -125
- package/src/preview/index.ts +0 -26
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +0 -73
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +0 -67
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +0 -77
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +0 -49
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +0 -70
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +0 -79
- package/src/preview/property_previews/ArrayPropertyEnumPreview.tsx +0 -35
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +0 -79
- package/src/preview/property_previews/MapPropertyPreview.tsx +0 -198
- package/src/preview/property_previews/NumberPropertyPreview.tsx +0 -39
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +0 -276
- package/src/preview/property_previews/StringPropertyPreview.tsx +0 -70
- package/src/preview/util.ts +0 -39
- package/src/routes/CustomViewRoute.tsx +0 -33
- package/src/routes/RebaseRoute.tsx +0 -467
- package/src/routes/index.ts +0 -2
- package/src/types/components/EntityFormActionsProps.tsx +0 -20
- package/src/types/components/EntityFormProps.tsx +0 -119
- package/src/types/components/PropertyPreviewProps.tsx +0 -85
- package/src/types/components/formex.tsx +0 -2
- package/src/types/components/index.ts +0 -3
- package/src/types/fields.tsx +0 -340
- package/src/types/index.ts +0 -4
- package/src/util/dataControllerAdapter.ts +0 -88
- package/src/util/entity_actions.ts +0 -28
- package/src/util/index.ts +0 -5
- package/src/util/navigation_utils.ts +0 -89
- package/src/util/number_format.ts +0 -46
- package/src/util/previews.ts +0 -131
- package/src/util/propertyConfigMapper.ts +0 -134
- package/src/util/property_utils.tsx +0 -236
- package/src/util/resolutions.ts +0 -96
- package/src/util/view_constants.ts +0 -33
- package/src/util/view_mode.ts +0 -176
|
@@ -1,703 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
|
-
import type { Property, RelationProperty } from "@rebasepro/types";
|
|
3
|
-
import type { Entity, EntityRelation } from "@rebasepro/types";
|
|
4
|
-
import type { PropertyConfig, AdminCollection, EntityDisplayRole } from "@rebasepro/cms-types";
|
|
5
|
-
import { getEntityImagePreviewPropertyKey } from "@rebasepro/app";
|
|
6
|
-
import { getDisplayPropertyKey } from "@rebasepro/app";
|
|
7
|
-
import { getLeadingRelationTitleKey, getTitlePropertyCandidates, looksLikeIdentifierValue } from "@rebasepro/app";
|
|
8
|
-
import { getEntityFromCache } from "@rebasepro/app";
|
|
9
|
-
import { getEntityPreviewKeys } from "../../util/previews";
|
|
10
|
-
import { formatRelativeTime, getValueInPath } from "@rebasepro/utils";
|
|
11
|
-
import type { AuthController } from "@rebasepro/cms-types";
|
|
12
|
-
import { ChipColorScheme, CHIP_COLORS, CHIP_SEED_KEYS } from "@rebasepro/ui";
|
|
13
|
-
import { useEntityDisplay } from "../../hooks/useEntityDisplay";
|
|
14
|
-
|
|
15
|
-
/** Whether an authored relation yields many rows. Derived from its kind. */
|
|
16
|
-
function relationCardinality(relation: { kind?: string; cardinality?: string } | undefined): "one" | "many" | undefined {
|
|
17
|
-
if (!relation) return undefined;
|
|
18
|
-
if (relation.kind === "via") return relation.cardinality as "one" | "many" | undefined;
|
|
19
|
-
if (relation.kind === "hasMany" || relation.kind === "manyToMany") return "many";
|
|
20
|
-
if (relation.kind === "belongsTo" || relation.kind === "hasOne") return "one";
|
|
21
|
-
return relation.cardinality as "one" | "many" | undefined;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// ── Slot types ────────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* A resolved "slot": what fills one display role for one record.
|
|
29
|
-
*
|
|
30
|
-
* `property` and `propertyKey` are set when the value was read off the record,
|
|
31
|
-
* and absent when a `display` resolver computed it — a computed title has no
|
|
32
|
-
* column behind it, so there is nothing to render it *as*. Renderers must go
|
|
33
|
-
* through {@link SlotValue}, which makes that choice once: a property renders
|
|
34
|
-
* with its own preview (an enum keeps its coloured chip, a date its format), a
|
|
35
|
-
* computed value renders as text.
|
|
36
|
-
*/
|
|
37
|
-
export interface PreviewSlot {
|
|
38
|
-
property?: Property;
|
|
39
|
-
propertyKey?: string;
|
|
40
|
-
value: unknown;
|
|
41
|
-
/**
|
|
42
|
-
* Which display role this slot fills.
|
|
43
|
-
*
|
|
44
|
-
* Load-bearing for the computed case. Without a property there is nothing to
|
|
45
|
-
* infer from, and "render it as text" is right for a title and wrong for an
|
|
46
|
-
* image: a resolver that returned a URL had that URL printed inside the card's
|
|
47
|
-
* image frame. The role is the one thing that distinguishes them, so it
|
|
48
|
-
* travels with the slot rather than being re-derived at each call site.
|
|
49
|
-
*/
|
|
50
|
-
role?: EntityDisplayRole;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Date slot extends the base with a pre-formatted string.
|
|
55
|
-
*/
|
|
56
|
-
export interface DatePreviewSlot extends PreviewSlot {
|
|
57
|
-
formatted: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* A single resolved relation chip — one item to render as a compact chip.
|
|
62
|
-
* For cardinality:"one" relations there will be one chip.
|
|
63
|
-
* For cardinality:"many" there can be several (capped for layout stability).
|
|
64
|
-
*/
|
|
65
|
-
export interface RelationChipItem {
|
|
66
|
-
/** Human-readable display text for the chip */
|
|
67
|
-
displayName: string;
|
|
68
|
-
/** Relation ID */
|
|
69
|
-
id: string | number;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* A resolved relation slot — groups all chips for one relation property.
|
|
74
|
-
*/
|
|
75
|
-
export interface RelationChipSlot {
|
|
76
|
-
/** The relation property definition */
|
|
77
|
-
property: RelationProperty;
|
|
78
|
-
/** The property key on this entity (e.g. "author", "tags") */
|
|
79
|
-
propertyKey: string;
|
|
80
|
-
/** The relation property's user-facing name (e.g. "Author", "Tags") */
|
|
81
|
-
label: string;
|
|
82
|
-
/** Resolved chip items (display names extracted from eagerly-loaded data) */
|
|
83
|
-
items: RelationChipItem[];
|
|
84
|
-
/** Total count (for "many" relations that exceed the chip cap) */
|
|
85
|
-
totalCount: number;
|
|
86
|
-
/** Deterministic color for this relation property */
|
|
87
|
-
colorScheme: ChipColorScheme;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Fixed set of preview slots resolved from any entity + collection.
|
|
92
|
-
*
|
|
93
|
-
* Every view (list, card, board) consumes these same slots and decides
|
|
94
|
-
* which ones to render and how. This eliminates duplicated inference
|
|
95
|
-
* logic across view components.
|
|
96
|
-
*/
|
|
97
|
-
export interface EntityPreviewSlots {
|
|
98
|
-
/** MEDIA — image thumbnail or `undefined` (view falls back to icon) */
|
|
99
|
-
image: PreviewSlot | undefined;
|
|
100
|
-
/** PRIMARY — title / name field */
|
|
101
|
-
title: PreviewSlot | undefined;
|
|
102
|
-
/** SECONDARY — subtitle or short description (first non-title text field) */
|
|
103
|
-
subtitle: PreviewSlot | undefined;
|
|
104
|
-
/** RELATIONS — compact chips for all relation properties (one + many) */
|
|
105
|
-
relations: RelationChipSlot[];
|
|
106
|
-
/** STATUS — enum chip (first enum field that isn't the title) */
|
|
107
|
-
status: PreviewSlot | undefined;
|
|
108
|
-
/** META — date / timestamp */
|
|
109
|
-
date: DatePreviewSlot | undefined;
|
|
110
|
-
/**
|
|
111
|
-
* TAGS — free chips beside the status.
|
|
112
|
-
*
|
|
113
|
-
* Declared only. Every other role has an inference ladder behind it, and
|
|
114
|
-
* this one deliberately does not: there is no "first enum" or "looks like a
|
|
115
|
-
* timestamp" that means *labels*, so guessing would scatter arbitrary chips
|
|
116
|
-
* across every row of every panel that never asked for them.
|
|
117
|
-
*
|
|
118
|
-
* The role existed on `EntityDisplay` and in `useEntityDisplay` before this
|
|
119
|
-
* field did, which is why a declared `display.tags` resolved correctly and
|
|
120
|
-
* then had nowhere to go.
|
|
121
|
-
*/
|
|
122
|
-
tags: PreviewSlot | undefined;
|
|
123
|
-
/** Entity ID (always available) */
|
|
124
|
-
entityId: string | number;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ── Collection-level slot resolution (entity-independent) ─────────────
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Resolved property keys per slot. This is the expensive part (walks
|
|
131
|
-
* all properties) so we memoize it at the collection level, not per entity.
|
|
132
|
-
*/
|
|
133
|
-
export interface CollectionSlotKeys {
|
|
134
|
-
titleKey: string | undefined;
|
|
135
|
-
/** Every property that could act as the title, best first (see `getTitlePropertyCandidates`) */
|
|
136
|
-
titleKeyCandidates: string[];
|
|
137
|
-
imageKey: string | undefined;
|
|
138
|
-
subtitleKey: string | undefined;
|
|
139
|
-
relationKeys: string[];
|
|
140
|
-
statusKey: string | undefined;
|
|
141
|
-
dateKey: string | undefined;
|
|
142
|
-
/** Declared only — see {@link EntityPreviewSlots.tags}. */
|
|
143
|
-
tagsKey: string | undefined;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Resolve which property key fills each slot for a given collection.
|
|
148
|
-
* Pure function — call inside `useMemo`.
|
|
149
|
-
*/
|
|
150
|
-
export function resolveCollectionSlotKeys(
|
|
151
|
-
collection: AdminCollection<Record<string, unknown>>,
|
|
152
|
-
authController: AuthController,
|
|
153
|
-
propertyConfigs: Record<string, PropertyConfig>
|
|
154
|
-
): CollectionSlotKeys {
|
|
155
|
-
// One row per entity: when the collection leads with a relation and holds no
|
|
156
|
-
// name of its own, that relation *is* what the row is called. See
|
|
157
|
-
// `getLeadingRelationTitleKey` — the general ranking keeps relations last,
|
|
158
|
-
// which is right for a detail header but leaves a junction row titleless.
|
|
159
|
-
const rankedCandidates = getTitlePropertyCandidates(collection);
|
|
160
|
-
const leadingRelationKey = getLeadingRelationTitleKey(collection);
|
|
161
|
-
const titleKeyCandidates = leadingRelationKey
|
|
162
|
-
? [leadingRelationKey, ...rankedCandidates.filter(key => key !== leadingRelationKey)]
|
|
163
|
-
: rankedCandidates;
|
|
164
|
-
const titleKey = titleKeyCandidates[0];
|
|
165
|
-
// `getEntityImagePreviewPropertyKey` prefers `display.image` itself, so
|
|
166
|
-
// every caller of it — this, the reference card, the board — agrees.
|
|
167
|
-
const imageKey = getEntityImagePreviewPropertyKey(collection);
|
|
168
|
-
|
|
169
|
-
// Status: declared, else the first string-enum that isn't the title.
|
|
170
|
-
//
|
|
171
|
-
// Everything below this line is inference — "the first enum", "a key that
|
|
172
|
-
// looks like a timestamp", "the longest remaining string". It is a good
|
|
173
|
-
// guess and it is only a guess, so a collection that states the role wins
|
|
174
|
-
// outright and the ladder is skipped. That is the whole point of
|
|
175
|
-
// `admin.display`: the heuristics stay for the collections that never say
|
|
176
|
-
// anything, and stop overruling the ones that do.
|
|
177
|
-
let statusKey: string | undefined = getDisplayPropertyKey(collection, "status");
|
|
178
|
-
|
|
179
|
-
// 1. Explicitly defined in previewProperties
|
|
180
|
-
if (!statusKey && collection.previewProperties) {
|
|
181
|
-
for (const key of collection.previewProperties) {
|
|
182
|
-
const p = collection.properties[key] as Property | undefined;
|
|
183
|
-
if (p?.type === "string" && "enum" in p && p.enum && key !== titleKey) {
|
|
184
|
-
statusKey = key;
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// 2. Explicitly defined in propertiesOrder
|
|
191
|
-
if (!statusKey && collection.propertiesOrder) {
|
|
192
|
-
for (const key of collection.propertiesOrder) {
|
|
193
|
-
if (typeof key === "string" && !key.startsWith("subcollection:")) {
|
|
194
|
-
const p = collection.properties[key] as Property | undefined;
|
|
195
|
-
if (p?.type === "string" && "enum" in p && p.enum && key !== titleKey) {
|
|
196
|
-
statusKey = key;
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// 3. Default automatic inference
|
|
204
|
-
if (!statusKey) {
|
|
205
|
-
for (const [key, prop] of Object.entries(collection.properties)) {
|
|
206
|
-
const p = prop as Property;
|
|
207
|
-
if (p.type === "string" && "enum" in p && p.enum && key !== titleKey) {
|
|
208
|
-
statusKey = key;
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Date: declared, else a well-known timestamp field, else any date
|
|
215
|
-
let dateKey: string | undefined = getDisplayPropertyKey(collection, "date");
|
|
216
|
-
const dateCandidates = ["updated_at", "updatedAt", "modified_at", "modifiedAt", "created_at", "createdAt"];
|
|
217
|
-
if (!dateKey) {
|
|
218
|
-
for (const candidate of dateCandidates) {
|
|
219
|
-
if (collection.properties[candidate]) {
|
|
220
|
-
dateKey = candidate;
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (!dateKey) {
|
|
226
|
-
for (const [key, prop] of Object.entries(collection.properties)) {
|
|
227
|
-
const p = prop as Property;
|
|
228
|
-
if (p.type === "date") {
|
|
229
|
-
dateKey = key;
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Relations: collect relation property keys for chip rendering.
|
|
236
|
-
// When propertiesOrder is explicitly set, skip this — relations will
|
|
237
|
-
// be shown in the developer-defined order, not as separate chips.
|
|
238
|
-
const hasExplicitOrder = !!(collection.propertiesOrder as string[] | undefined);
|
|
239
|
-
const relationKeys: string[] = [];
|
|
240
|
-
if (!hasExplicitOrder) {
|
|
241
|
-
for (const [key, prop] of Object.entries(collection.properties)) {
|
|
242
|
-
const p = prop as Property;
|
|
243
|
-
// The relation filling the title slot is already the row's headline;
|
|
244
|
-
// repeating it as a chip below says the same thing twice.
|
|
245
|
-
if (p.type === "relation" && key !== titleKey) {
|
|
246
|
-
relationKeys.push(key);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Subtitle: first preview key that isn't title/image/status/date/id.
|
|
252
|
-
// When propertiesOrder is not set, also exclude relation keys (they render as chips).
|
|
253
|
-
// Prefer string fields (especially multiline/description-like) over numbers.
|
|
254
|
-
const excludeKeys = new Set([titleKey, imageKey, statusKey, dateKey, ...relationKeys]);
|
|
255
|
-
const previewKeys = getEntityPreviewKeys(authController, collection, propertyConfigs, undefined, 10)
|
|
256
|
-
.filter(k => !excludeKeys.has(k) && k !== "id");
|
|
257
|
-
|
|
258
|
-
// When propertiesOrder is set, respect the developer-defined order (no re-sorting).
|
|
259
|
-
// When not set, sort: strings first (prefer multiline → good description candidate).
|
|
260
|
-
const sortedPreviewKeys = hasExplicitOrder
|
|
261
|
-
? previewKeys
|
|
262
|
-
: [...previewKeys].sort((a, b) => {
|
|
263
|
-
const propA = collection.properties[a] as Property | undefined;
|
|
264
|
-
const propB = collection.properties[b] as Property | undefined;
|
|
265
|
-
const scoreA = propA?.type === "string" ? (propA.admin?.multiline ? 2 : 1) : 0;
|
|
266
|
-
const scoreB = propB?.type === "string" ? (propB.admin?.multiline ? 2 : 1) : 0;
|
|
267
|
-
return scoreB - scoreA;
|
|
268
|
-
});
|
|
269
|
-
const subtitleKey = getDisplayPropertyKey(collection, "subtitle")
|
|
270
|
-
?? (sortedPreviewKeys.length > 0 ? sortedPreviewKeys[0] : undefined);
|
|
271
|
-
|
|
272
|
-
return { titleKey,
|
|
273
|
-
titleKeyCandidates,
|
|
274
|
-
imageKey,
|
|
275
|
-
subtitleKey,
|
|
276
|
-
relationKeys,
|
|
277
|
-
statusKey,
|
|
278
|
-
dateKey,
|
|
279
|
-
tagsKey: getDisplayPropertyKey(collection, "tags") };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// ── Image unwrapping helper ───────────────────────────────────────────
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Unwrap image property + value from potential array-of-images.
|
|
286
|
-
* Returns `{ property, value }` where property is the *inner* property
|
|
287
|
-
* and value is the first element (for arrays) or the raw value.
|
|
288
|
-
*/
|
|
289
|
-
function resolveImageSlot(
|
|
290
|
-
collection: AdminCollection<Record<string, unknown>>,
|
|
291
|
-
imageKey: string,
|
|
292
|
-
entity: Entity<Record<string, unknown>>
|
|
293
|
-
): PreviewSlot | undefined {
|
|
294
|
-
const imageProperty = collection.properties[imageKey];
|
|
295
|
-
if (!imageProperty) return undefined;
|
|
296
|
-
|
|
297
|
-
const ofProp = "of" in imageProperty ? imageProperty.of : undefined;
|
|
298
|
-
const resolvedProperty = ofProp
|
|
299
|
-
? (Array.isArray(ofProp) ? ofProp[0] : ofProp)
|
|
300
|
-
: imageProperty;
|
|
301
|
-
|
|
302
|
-
const rawValue = getValueInPath(entity.values, imageKey);
|
|
303
|
-
const resolvedValue = ofProp
|
|
304
|
-
? (((rawValue as unknown[]) ?? []).length > 0 ? (rawValue as unknown[])[0] : undefined)
|
|
305
|
-
: rawValue;
|
|
306
|
-
|
|
307
|
-
if (resolvedValue === undefined || resolvedValue === null) return undefined;
|
|
308
|
-
|
|
309
|
-
return {
|
|
310
|
-
role: "image",
|
|
311
|
-
property: resolvedProperty as Property,
|
|
312
|
-
propertyKey: imageKey,
|
|
313
|
-
value: resolvedValue
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// ── Title resolution (entity-level) ───────────────────────────────────
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Walk the collection's ranked title candidates and return the first one that
|
|
321
|
-
* holds something a person can read for this entity. Candidates are already
|
|
322
|
-
* ordered and free of identifier properties; this only rejects values —
|
|
323
|
-
* empties, and strings that turn out to be an id at runtime.
|
|
324
|
-
*/
|
|
325
|
-
function resolveEntityTitleKey(
|
|
326
|
-
entity: Entity<Record<string, unknown>>,
|
|
327
|
-
candidates: string[]
|
|
328
|
-
): string | undefined {
|
|
329
|
-
for (const key of candidates) {
|
|
330
|
-
const value = getValueInPath(entity.values, key);
|
|
331
|
-
if (value === undefined || value === null || value === "") continue;
|
|
332
|
-
if (typeof value === "string") {
|
|
333
|
-
if (looksLikeIdentifierValue(value)) continue;
|
|
334
|
-
if (value === String(entity.id)) continue;
|
|
335
|
-
}
|
|
336
|
-
return key;
|
|
337
|
-
}
|
|
338
|
-
return candidates[0];
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// ── Date formatting ───────────────────────────────────────────────────
|
|
342
|
-
|
|
343
|
-
function formatDateValue(value: unknown): string {
|
|
344
|
-
if (!value) return "";
|
|
345
|
-
const date = value instanceof Date ? value : new Date(value as string | number);
|
|
346
|
-
if (isNaN(date.getTime())) return String(value);
|
|
347
|
-
|
|
348
|
-
const now = new Date();
|
|
349
|
-
// A date slot holds whatever property the collection points it at, which is
|
|
350
|
-
// as often a due or publish date as a `updated_at`. `formatRelativeTime`
|
|
351
|
-
// reads the sign, so a scheduled date says "in 3d" instead of "Just now".
|
|
352
|
-
const relative = formatRelativeTime(date, { now });
|
|
353
|
-
if (relative) return relative;
|
|
354
|
-
|
|
355
|
-
return date.toLocaleDateString(undefined, {
|
|
356
|
-
month: "short",
|
|
357
|
-
day: "numeric",
|
|
358
|
-
year: date.getFullYear() !== now.getFullYear() ? "numeric" : undefined
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// ── Hook: collection-level slot keys ──────────────────────────────────
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Resolves which property key fills each preview slot for a collection.
|
|
366
|
-
* Memoized — only recomputes when the collection definition changes.
|
|
367
|
-
*
|
|
368
|
-
* Use this in the parent list/grid component, then pass the result to
|
|
369
|
-
* `resolveEntitySlots` for each entity row.
|
|
370
|
-
*/
|
|
371
|
-
export function useCollectionSlotKeys(
|
|
372
|
-
collection: AdminCollection<Record<string, unknown>>,
|
|
373
|
-
authController: AuthController,
|
|
374
|
-
propertyConfigs: Record<string, PropertyConfig>
|
|
375
|
-
): CollectionSlotKeys {
|
|
376
|
-
return useMemo(
|
|
377
|
-
() => resolveCollectionSlotKeys(collection, authController, propertyConfigs),
|
|
378
|
-
[collection, authController, propertyConfigs]
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// ── Pure function: entity-level slot resolution ───────────────────────
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Resolve concrete slot values for a single entity.
|
|
386
|
-
* This is a pure function (no hooks) so it can be called inside
|
|
387
|
-
* `React.memo` render functions or loops without violating hook rules.
|
|
388
|
-
*/
|
|
389
|
-
export function resolveEntitySlots(
|
|
390
|
-
entity: Entity<Record<string, unknown>>,
|
|
391
|
-
collection: AdminCollection<Record<string, unknown>>,
|
|
392
|
-
slotKeys: CollectionSlotKeys
|
|
393
|
-
): EntityPreviewSlots {
|
|
394
|
-
const { titleKey, titleKeyCandidates, imageKey, subtitleKey, relationKeys, statusKey, dateKey, tagsKey } = slotKeys;
|
|
395
|
-
|
|
396
|
-
// Image
|
|
397
|
-
const image = imageKey ? resolveImageSlot(collection, imageKey, entity) : undefined;
|
|
398
|
-
|
|
399
|
-
// Title — the best-ranked candidate that carries a readable value for THIS
|
|
400
|
-
// entity, so a row whose name is empty (or whose column happens to hold a
|
|
401
|
-
// UUID) falls through to the next candidate instead of showing an id.
|
|
402
|
-
const resolvedTitleKey = resolveEntityTitleKey(entity, titleKeyCandidates ?? (titleKey ? [titleKey] : []));
|
|
403
|
-
let title: PreviewSlot | undefined;
|
|
404
|
-
if (resolvedTitleKey) {
|
|
405
|
-
const prop = collection.properties[resolvedTitleKey] as Property | undefined;
|
|
406
|
-
const val = getValueInPath(entity.values, resolvedTitleKey);
|
|
407
|
-
if (prop) title = { role: "title",
|
|
408
|
-
property: prop,
|
|
409
|
-
propertyKey: resolvedTitleKey,
|
|
410
|
-
value: val };
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Subtitle
|
|
414
|
-
let subtitle: PreviewSlot | undefined;
|
|
415
|
-
if (subtitleKey && subtitleKey !== resolvedTitleKey) {
|
|
416
|
-
const prop = collection.properties[subtitleKey] as Property | undefined;
|
|
417
|
-
const val = getValueInPath(entity.values, subtitleKey);
|
|
418
|
-
if (prop && val !== undefined && val !== null && val !== "") {
|
|
419
|
-
subtitle = { role: "subtitle",
|
|
420
|
-
property: prop,
|
|
421
|
-
propertyKey: subtitleKey,
|
|
422
|
-
value: val };
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Relations — resolve chip items for every relation property
|
|
427
|
-
const relations: RelationChipSlot[] = [];
|
|
428
|
-
const MAX_CHIPS_PER_RELATION = 3;
|
|
429
|
-
|
|
430
|
-
for (const relKey of relationKeys) {
|
|
431
|
-
const prop = collection.properties[relKey] as RelationProperty | undefined;
|
|
432
|
-
if (!prop || prop.type !== "relation") continue;
|
|
433
|
-
|
|
434
|
-
const val = getValueInPath(entity.values, relKey);
|
|
435
|
-
if (val === undefined || val === null) continue;
|
|
436
|
-
|
|
437
|
-
const items: RelationChipItem[] = [];
|
|
438
|
-
let totalCount = 0;
|
|
439
|
-
|
|
440
|
-
const isMany = relationCardinality(prop.relation) === "many";
|
|
441
|
-
|
|
442
|
-
if (isMany && Array.isArray(val)) {
|
|
443
|
-
// cardinality:"many" → array of EntityRelation
|
|
444
|
-
totalCount = val.length;
|
|
445
|
-
for (let i = 0; i < Math.min(val.length, MAX_CHIPS_PER_RELATION); i++) {
|
|
446
|
-
const rel = val[i];
|
|
447
|
-
if (rel && typeof rel === "object") {
|
|
448
|
-
const displayName = resolveRelationDisplayName(rel as EntityRelation, prop);
|
|
449
|
-
const id = (rel as EntityRelation).id ?? i;
|
|
450
|
-
if (displayName) {
|
|
451
|
-
items.push({ displayName,
|
|
452
|
-
id });
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
} else if (!isMany && val && typeof val === "object") {
|
|
457
|
-
// cardinality:"one" → single EntityRelation
|
|
458
|
-
const obj = val as Record<string, unknown>;
|
|
459
|
-
const isRelation = ("__type" in obj && obj.__type === "relation")
|
|
460
|
-
|| (typeof obj.isEntityRelation === "function"
|
|
461
|
-
&& (obj.isEntityRelation as () => boolean)());
|
|
462
|
-
if (isRelation) {
|
|
463
|
-
const relation = obj as unknown as EntityRelation;
|
|
464
|
-
const displayName = resolveRelationDisplayName(relation, prop);
|
|
465
|
-
totalCount = 1;
|
|
466
|
-
if (displayName) {
|
|
467
|
-
items.push({ displayName,
|
|
468
|
-
id: relation.id });
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
if (items.length > 0) {
|
|
474
|
-
const relIndex = relationKeys.indexOf(relKey);
|
|
475
|
-
// Walking the seed keys rather than `Object.values`: the table also
|
|
476
|
-
// holds a bare-hue alias per hue, so raw values would hand the first
|
|
477
|
-
// two relations the same scheme.
|
|
478
|
-
const colorScheme = CHIP_COLORS[CHIP_SEED_KEYS[relIndex % CHIP_SEED_KEYS.length]];
|
|
479
|
-
relations.push({
|
|
480
|
-
property: prop,
|
|
481
|
-
propertyKey: relKey,
|
|
482
|
-
label: prop.name || relKey,
|
|
483
|
-
items,
|
|
484
|
-
totalCount,
|
|
485
|
-
colorScheme
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// Status
|
|
491
|
-
let status: PreviewSlot | undefined;
|
|
492
|
-
if (statusKey) {
|
|
493
|
-
const prop = collection.properties[statusKey] as Property | undefined;
|
|
494
|
-
const val = getValueInPath(entity.values, statusKey);
|
|
495
|
-
if (prop && val !== undefined && val !== null) {
|
|
496
|
-
status = { role: "status",
|
|
497
|
-
property: prop,
|
|
498
|
-
propertyKey: statusKey,
|
|
499
|
-
value: val };
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Date
|
|
504
|
-
let date: DatePreviewSlot | undefined;
|
|
505
|
-
if (dateKey) {
|
|
506
|
-
const prop = collection.properties[dateKey] as Property | undefined;
|
|
507
|
-
const val = getValueInPath(entity.values, dateKey);
|
|
508
|
-
if (prop && val !== undefined && val !== null) {
|
|
509
|
-
date = {
|
|
510
|
-
role: "date",
|
|
511
|
-
property: prop,
|
|
512
|
-
propertyKey: dateKey,
|
|
513
|
-
value: val,
|
|
514
|
-
formatted: formatDateValue(val)
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Tags — declared only, and empty means absent: an empty array is "this
|
|
520
|
-
// record has no labels", which must render as nothing rather than as an
|
|
521
|
-
// empty chip row that shifts every other slot down.
|
|
522
|
-
let tags: PreviewSlot | undefined;
|
|
523
|
-
if (tagsKey) {
|
|
524
|
-
const prop = collection.properties[tagsKey] as Property | undefined;
|
|
525
|
-
const val = getValueInPath(entity.values, tagsKey);
|
|
526
|
-
const empty = val === undefined || val === null || val === ""
|
|
527
|
-
|| (Array.isArray(val) && val.length === 0);
|
|
528
|
-
if (prop && !empty) {
|
|
529
|
-
tags = { role: "tags",
|
|
530
|
-
property: prop,
|
|
531
|
-
propertyKey: tagsKey,
|
|
532
|
-
value: val };
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
return {
|
|
537
|
-
image,
|
|
538
|
-
title,
|
|
539
|
-
subtitle,
|
|
540
|
-
relations,
|
|
541
|
-
status,
|
|
542
|
-
date,
|
|
543
|
-
tags,
|
|
544
|
-
entityId: entity.id
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// ── Relation display name resolution ─────────────────────────────────
|
|
549
|
-
|
|
550
|
-
/**
|
|
551
|
-
* Extract a human-readable display name from an eagerly-loaded EntityRelation.
|
|
552
|
-
* Uses the target collection's title property when available, otherwise
|
|
553
|
-
* walks the entity values for the first short string.
|
|
554
|
-
*/
|
|
555
|
-
function resolveRelationDisplayName(
|
|
556
|
-
relation: EntityRelation | Record<string, unknown>,
|
|
557
|
-
prop: RelationProperty
|
|
558
|
-
): string | undefined {
|
|
559
|
-
// Support both EntityRelation instances and plain objects
|
|
560
|
-
const data = "data" in relation ? (relation as EntityRelation).data : undefined;
|
|
561
|
-
|
|
562
|
-
// Resolve target collection from either `prop.relation.target()` or `prop.relation?.target()` (inline API)
|
|
563
|
-
let targetCollection: AdminCollection | undefined;
|
|
564
|
-
try {
|
|
565
|
-
const resolved = prop.relation?.target?.() ?? (typeof prop.relation?.target === "function" ? prop.relation?.target() : undefined);
|
|
566
|
-
if (resolved && typeof resolved === "object") {
|
|
567
|
-
targetCollection = resolved as AdminCollection;
|
|
568
|
-
}
|
|
569
|
-
} catch {
|
|
570
|
-
// Target collection may not be resolvable
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// Helper: extract display name from entity values using the target collection
|
|
574
|
-
//
|
|
575
|
-
// This used to carry a ranking of its own — priority keys `name/title/label/
|
|
576
|
-
// displayName`, then the first visible non-id string — which is a *seventh*
|
|
577
|
-
// answer to "what is this record called", and one that disagreed with the
|
|
578
|
-
// shared one: it read `titleProperty` with a flat lookup (so a dotted path
|
|
579
|
-
// was ignored), never saw `display.title`, and ranked by key name where the
|
|
580
|
-
// shared ranking excludes identifiers structurally. A chip and the record it
|
|
581
|
-
// points at could therefore be labelled differently on the same screen.
|
|
582
|
-
//
|
|
583
|
-
// What is left here is the part that is genuinely local: a relation carries
|
|
584
|
-
// *eagerly loaded* values, not an entity, so a resolver cannot run against
|
|
585
|
-
// it — a computed title falls back to the ranked property, which is the same
|
|
586
|
-
// thing the list row shows while its own resolver is in flight.
|
|
587
|
-
const extractDisplayName = (values: Record<string, unknown>): string | undefined => {
|
|
588
|
-
if (targetCollection) {
|
|
589
|
-
for (const key of getTitlePropertyCandidates(targetCollection)) {
|
|
590
|
-
const value = getValueInPath(values, key);
|
|
591
|
-
if (value === undefined || value === null || value === "") continue;
|
|
592
|
-
if (typeof value === "string" && looksLikeIdentifierValue(value)) continue;
|
|
593
|
-
return String(value);
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
// Generic fallback: walk entity values for any short string.
|
|
597
|
-
for (const [, v] of Object.entries(values)) {
|
|
598
|
-
if (typeof v === "string" && v.length > 0 && v.length < 200) {
|
|
599
|
-
return v;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
return undefined;
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
// 1. Try eagerly-loaded data on the relation object
|
|
606
|
-
if (data && data.values) {
|
|
607
|
-
const result = extractDisplayName(data.values as Record<string, unknown>);
|
|
608
|
-
if (result) return result;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
const id = "id" in relation ? relation.id : undefined;
|
|
612
|
-
|
|
613
|
-
// 2. Try the entity cache (sessionStorage) as a fallback
|
|
614
|
-
if (id !== undefined && targetCollection) {
|
|
615
|
-
try {
|
|
616
|
-
const slug = targetCollection.slug ?? ("table" in targetCollection ? targetCollection.table : undefined);
|
|
617
|
-
if (slug) {
|
|
618
|
-
const cacheKey = `${slug}/${id}`;
|
|
619
|
-
const cached = getEntityFromCache(cacheKey) as { values?: Record<string, unknown> } | undefined;
|
|
620
|
-
if (cached?.values) {
|
|
621
|
-
const result = extractDisplayName(cached.values);
|
|
622
|
-
if (result) return result;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
} catch {
|
|
626
|
-
// Cache lookup failed — fall through
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// 3. Final fallback: show the ID
|
|
631
|
-
return id !== undefined ? String(id) : undefined;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* The slots for one record, with `admin.display` resolvers applied.
|
|
636
|
-
*
|
|
637
|
-
* {@link resolveEntitySlots} answers the question from values already in hand —
|
|
638
|
-
* which is everything a slot filled by a property needs, and nothing a slot
|
|
639
|
-
* filled by a *resolver* can use. A computed title may have to be fetched, so it
|
|
640
|
-
* arrives after the first render; this hook is where the two meet.
|
|
641
|
-
*
|
|
642
|
-
* Until a resolver lands, the derived slot shows. That is deliberate: a list of
|
|
643
|
-
* fifty rows must not blank its titles for a beat while fifty promises settle,
|
|
644
|
-
* and the derived answer — the best-ranked property carrying a readable value —
|
|
645
|
-
* is a reasonable thing to be looking at meanwhile. When the resolved value
|
|
646
|
-
* arrives it replaces the slot outright, property and all, because there is no
|
|
647
|
-
* property behind a computed value (see {@link PreviewSlot}).
|
|
648
|
-
*
|
|
649
|
-
* Every role is hooked unconditionally, so the hook order is fixed regardless of
|
|
650
|
-
* what a given collection declares.
|
|
651
|
-
*/
|
|
652
|
-
export function useEntitySlots(
|
|
653
|
-
entity: Entity<Record<string, unknown>>,
|
|
654
|
-
collection: AdminCollection<Record<string, unknown>>,
|
|
655
|
-
slotKeys: CollectionSlotKeys
|
|
656
|
-
): EntityPreviewSlots {
|
|
657
|
-
|
|
658
|
-
const derived = useMemo(
|
|
659
|
-
() => resolveEntitySlots(entity, collection, slotKeys),
|
|
660
|
-
[entity, collection, slotKeys]
|
|
661
|
-
);
|
|
662
|
-
|
|
663
|
-
const params = { collection,
|
|
664
|
-
entity };
|
|
665
|
-
const title = useEntityDisplay<string>("title", params);
|
|
666
|
-
const subtitle = useEntityDisplay<string>("subtitle", params);
|
|
667
|
-
const image = useEntityDisplay<string>("image", params);
|
|
668
|
-
const status = useEntityDisplay<string>("status", params);
|
|
669
|
-
const date = useEntityDisplay<Date | string | number>("date", params);
|
|
670
|
-
const tags = useEntityDisplay<string[] | string>("tags", params);
|
|
671
|
-
|
|
672
|
-
return useMemo(() => {
|
|
673
|
-
// `source` is set only when the value came from a property, so its
|
|
674
|
-
// absence is exactly the "this was computed" signal — no second flag to
|
|
675
|
-
// keep in step with the first.
|
|
676
|
-
// The role travels with the computed slot too. It is the only thing left
|
|
677
|
-
// to say about a value with no property behind it, and `SlotValue` needs
|
|
678
|
-
// it to tell a computed image from a computed title.
|
|
679
|
-
const computed = (
|
|
680
|
-
role: EntityDisplayRole,
|
|
681
|
-
resolved: { value: unknown, source?: unknown }
|
|
682
|
-
): PreviewSlot | undefined =>
|
|
683
|
-
resolved.value !== undefined && resolved.source === undefined
|
|
684
|
-
? { role,
|
|
685
|
-
value: resolved.value }
|
|
686
|
-
: undefined;
|
|
687
|
-
|
|
688
|
-
const computedDate = computed("date", date);
|
|
689
|
-
|
|
690
|
-
return {
|
|
691
|
-
...derived,
|
|
692
|
-
title: computed("title", title) ?? derived.title,
|
|
693
|
-
subtitle: computed("subtitle", subtitle) ?? derived.subtitle,
|
|
694
|
-
image: computed("image", image) ?? derived.image,
|
|
695
|
-
status: computed("status", status) ?? derived.status,
|
|
696
|
-
date: computedDate
|
|
697
|
-
? { ...computedDate,
|
|
698
|
-
formatted: formatDateValue(computedDate.value) }
|
|
699
|
-
: derived.date,
|
|
700
|
-
tags: computed("tags", tags) ?? derived.tags
|
|
701
|
-
};
|
|
702
|
-
}, [derived, title, subtitle, image, status, date, tags]);
|
|
703
|
-
}
|