@rebasepro/admin 0.13.1-canary.gf57a27e → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CollectionEditorDialog-BnfOcqM_.js → CollectionEditorDialog-CSheud_E.js} +19 -9
- package/dist/CollectionEditorDialog-CSheud_E.js.map +1 -0
- package/dist/{PropertyEditView-vLXglzIy.js → PropertyEditView-CWZlC1gq.js} +5 -5
- package/dist/PropertyEditView-CWZlC1gq.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-DF3aiOuv.js → RouterCollectionsStudioView-CwTpdzWF.js} +4 -4
- package/dist/{RouterCollectionsStudioView-DF3aiOuv.js.map → RouterCollectionsStudioView-CwTpdzWF.js.map} +1 -1
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/ClearFilterSortButton.d.ts +7 -1
- package/dist/components/CollectionViewBinding/SortButton.d.ts +7 -1
- package/dist/components/RelationSelector.d.ts +13 -0
- package/dist/data_export/export/export.d.ts +7 -0
- package/dist/data_export/export/fetch_export_data.d.ts +48 -0
- package/dist/data_export/export/index.d.ts +1 -0
- package/dist/data_import/utils/csv.d.ts +30 -0
- package/dist/data_import/utils/index.d.ts +2 -0
- package/dist/data_import/utils/save_entities.d.ts +38 -0
- package/dist/data_import/utils/transforms.d.ts +9 -0
- package/dist/editor.js +3 -0
- package/dist/editor.js.map +1 -1
- package/dist/{export-C26xQN-g.js → export-L53WektO.js} +175 -39
- package/dist/export-L53WektO.js.map +1 -0
- package/dist/{history-DSS-Ebwh.js → history-D5SKygpJ.js} +2 -2
- package/dist/{history-DSS-Ebwh.js.map → history-D5SKygpJ.js.map} +1 -1
- package/dist/{import-Dhx96B6x.js → import-C_4edkoD.js} +5 -4
- package/dist/import-C_4edkoD.js.map +1 -0
- package/dist/index.js +93 -74
- package/dist/index.js.map +1 -1
- package/dist/preview/components/date_fns_locales.d.ts +9 -0
- package/dist/{util-Bt6VadeZ.js → util-C-D5yD2n.js} +567 -113
- package/dist/util-C-D5yD2n.js.map +1 -0
- package/package.json +10 -9
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +8 -3
- package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +1 -0
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +9 -8
- package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +1 -0
- package/src/collection_editor/useJsonCollectionsConfigController.tsx +92 -84
- package/src/collection_editor/useLocalCollectionsConfigController.tsx +10 -3
- package/src/components/ClearFilterSortButton.tsx +16 -7
- package/src/components/CollectionTableBinding/table_bindings.tsx +7 -1
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +13 -7
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +1 -0
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +9 -0
- package/src/components/CollectionViewBinding/SortButton.tsx +16 -4
- package/src/components/CollectionViewBinding/ViewModeToggle.tsx +10 -5
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +11 -3
- package/src/components/DefaultDrawer.tsx +7 -0
- package/src/components/DrawerNavigationItem.tsx +12 -0
- package/src/components/RelationSelector.tsx +52 -5
- package/src/components/SearchIconsView.tsx +14 -33
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -0
- package/src/components/UserSelector.tsx +47 -5
- package/src/data_export/export/ExportCollectionAction.tsx +101 -48
- package/src/data_export/export/export.ts +63 -16
- package/src/data_export/export/fetch_export_data.ts +90 -0
- package/src/data_export/export/index.ts +1 -0
- package/src/data_import/components/DataNewPropertiesMapping.tsx +20 -3
- package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +1 -0
- package/src/data_import/components/ImportSaveInProgress.tsx +35 -36
- package/src/data_import/import/ImportCollectionAction.tsx +13 -2
- package/src/data_import/utils/csv.ts +153 -0
- package/src/data_import/utils/data.ts +7 -1
- package/src/data_import/utils/file_to_json.ts +77 -6
- package/src/data_import/utils/index.ts +2 -0
- package/src/data_import/utils/save_entities.ts +138 -0
- package/src/data_import/utils/transforms.ts +23 -0
- package/src/editor/components/image-bubble.tsx +2 -0
- package/src/editor/selectors/link-selector.tsx +1 -0
- package/src/form/field_bindings/BinaryFieldBinding.tsx +1 -0
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +4 -0
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +3 -0
- package/src/form/field_bindings/TextFieldBinding.tsx +18 -0
- package/src/form/field_bindings/VectorFieldBinding.tsx +1 -0
- package/src/preview/components/DatePreview.tsx +15 -2
- package/src/preview/components/ReferencePreview.tsx +5 -8
- package/src/preview/components/RelationPreview.tsx +8 -7
- package/src/preview/components/date_fns_locales.ts +125 -0
- package/dist/CollectionEditorDialog-BnfOcqM_.js.map +0 -1
- package/dist/PropertyEditView-vLXglzIy.js.map +0 -1
- package/dist/export-C26xQN-g.js.map +0 -1
- package/dist/import-Dhx96B6x.js.map +0 -1
- package/dist/util-Bt6VadeZ.js.map +0 -1
package/dist/{RouterCollectionsStudioView-DF3aiOuv.js → RouterCollectionsStudioView-CwTpdzWF.js}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import {
|
|
3
|
-
import { s as AIModifiedPathsProvider } from "./PropertyEditView-
|
|
4
|
-
import { t as CollectionEditor } from "./CollectionEditorDialog-
|
|
2
|
+
import { ln as useUrlController } from "./util-C-D5yD2n.js";
|
|
3
|
+
import { s as AIModifiedPathsProvider } from "./PropertyEditView-CWZlC1gq.js";
|
|
4
|
+
import { t as CollectionEditor } from "./CollectionEditorDialog-CSheud_E.js";
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
6
|
import { Button, IconButton, PlusIcon, ResizablePanels, Tooltip, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
7
7
|
import { IconForView, UnsavedChangesDialog, useUnsavedChangesDialog } from "@rebasepro/app";
|
|
@@ -185,4 +185,4 @@ function RouterCollectionsStudioView(props) {
|
|
|
185
185
|
//#endregion
|
|
186
186
|
export { CollectionStudioView as i, RouterCollectionsStudioView_exports as n, CollectionsStudioView as r, RouterCollectionsStudioView as t };
|
|
187
187
|
|
|
188
|
-
//# sourceMappingURL=RouterCollectionsStudioView-
|
|
188
|
+
//# sourceMappingURL=RouterCollectionsStudioView-CwTpdzWF.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouterCollectionsStudioView-DF3aiOuv.js","names":[],"sources":["../src/collection_editor/ui/collection_editor/CollectionStudioView.tsx","../src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx","../src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx"],"sourcesContent":["\nimport { useUnsavedChangesDialog, UnsavedChangesDialog } from \"@rebasepro/app\";\nimport * as React from \"react\";\nimport { useState } from \"react\";\nimport { Button, PlusIcon, Typography } from \"@rebasepro/ui\";\nimport { CollectionEditorDialogProps } from \"./CollectionEditorDialog\";\nimport { AIModifiedPathsProvider } from \"./AIModifiedPathsContext\";\nimport { CollectionEditor } from \"./CollectionEditorDialog\";\nimport type { AdminCollection } from \"@rebasepro/admin-types\";\n\nexport type CollectionStudioViewProps = Omit<CollectionEditorDialogProps, \"open\" | \"isNewCollection\" | \"editedCollectionId\" | \"handleClose\" | \"handleCancel\"> & {\n collectionId?: string | \"new\";\n\n /**\n * Called after a successful save.\n * Receives the saved collection (or undefined if dismissed without saving).\n * When not provided, save completes silently.\n */\n onSave?: (collection?: AdminCollection) => void;\n\n /**\n * Called when the user cancels editing.\n * When not provided, cancel is a no-op.\n */\n onCancel?: () => void;\n};\n\nexport function CollectionStudioView({ collectionId, onSave, onCancel, ...props }: CollectionStudioViewProps) {\n\n // Form state from the editor\n const [formDirty, setFormDirty] = useState<boolean>(false);\n const [cancelRequested, setCancelRequested] = useState<boolean>(false);\n\n const { dialogProps, triggerDialog } = useUnsavedChangesDialog(\n formDirty,\n () => setFormDirty(false)\n );\n\n const activeCollectionId = collectionId;\n\n const handleCancelClick = () => {\n if (!formDirty) {\n onCancel?.();\n } else {\n setCancelRequested(true);\n triggerDialog();\n }\n };\n\n return (\n <div className=\"flex-grow flex flex-col h-full w-full bg-surface-50 dark:bg-surface-800\">\n <AIModifiedPathsProvider>\n {activeCollectionId ? (\n <CollectionEditor\n key={activeCollectionId}\n {...props}\n fullScreen={true}\n open={true}\n isNewCollection={activeCollectionId === \"new\"}\n editedCollectionId={activeCollectionId !== \"new\" ? activeCollectionId : undefined}\n handleCancel={handleCancelClick}\n handleClose={(savedCollection) => {\n setFormDirty(false);\n if (savedCollection) {\n setTimeout(() => {\n onSave?.(savedCollection);\n }, 0);\n }\n }}\n setFormDirty={setFormDirty}\n />\n ) : (\n <div className=\"flex-grow flex flex-col items-center justify-center h-full gap-4\">\n <Typography variant=\"label\">\n Select a collection or create a new one\n </Typography>\n <Button\n disabled={props.configController?.readOnly}\n onClick={() => onSave?.()}\n >\n <PlusIcon/>\n Add new collection\n </Button>\n </div>\n )}\n\n <UnsavedChangesDialog\n {...dialogProps}\n handleOk={() => {\n dialogProps.handleOk();\n if (cancelRequested) {\n onCancel?.();\n setCancelRequested(false);\n }\n }}\n handleCancel={() => {\n dialogProps.handleCancel();\n setCancelRequested(false);\n }}\n />\n </AIModifiedPathsProvider>\n </div>\n );\n}\n","\nimport { IconForView } from \"@rebasepro/app\";\nimport React, { useState, useEffect } from \"react\";\nimport {\n Button,\n cls,\n defaultBorderMixin,\n IconButton,\n iconSize,\n PlusIcon,\n ResizablePanels,\n Tooltip,\n Typography\n} from \"@rebasepro/ui\";\n\nimport { CollectionsConfigController } from \"../../types/config_controller\";\nimport { CollectionStudioView } from \"./CollectionStudioView\";\nimport type { CollectionEditorExtensionProps } from \"../../extensibility_types\";\nimport type { AdminCollection } from \"@rebasepro/admin-types\";\nimport { readStoredString, writeStoredString } from \"@rebasepro/utils\";\n\nexport interface CollectionsStudioViewProps extends CollectionEditorExtensionProps {\n configController: CollectionsConfigController;\n\n /**\n * Collections to show in the sidebar.\n * When provided, overrides the collections from `configController`.\n * Use this to control exactly which collections the editor displays.\n */\n collections?: AdminCollection[];\n\n /**\n * Controlled active collection ID.\n * When provided together with `onActiveCollectionChange`,\n * navigation is fully external — no internal state is used.\n */\n activeCollectionId?: string;\n\n /**\n * Called when the user clicks a collection, \"new\", or navigates.\n * When provided, the component is fully controlled (no internal state).\n * When not provided, the component manages selection via internal state.\n *\n * Pass `\"new\"` to create a new collection. Pass `undefined` to deselect.\n */\n onActiveCollectionChange?: (collectionId: string | undefined) => void;\n}\n\nexport function CollectionsStudioView({\n configController,\n collections: collectionsProp,\n activeCollectionId: controlledActiveId,\n onActiveCollectionChange,\n propertyTypePresets,\n hiddenPropertyTypes,\n renderExtraPropertyFields,\n renderExtraCollectionFields,\n visibleTabs,\n standalone,\n}: CollectionsStudioViewProps) {\n\n // ── Navigation state ────────────────────────────────────────────────\n // If onActiveCollectionChange is provided, the component is controlled.\n // Otherwise, use internal state.\n const [internalActiveId, setInternalActiveId] = useState<string | undefined>(undefined);\n const isControlled = onActiveCollectionChange !== undefined;\n const activeCollectionId = isControlled ? controlledActiveId : internalActiveId;\n\n const setActiveCollectionId = (id: string | undefined) => {\n if (isControlled) {\n onActiveCollectionChange?.(id);\n } else {\n setInternalActiveId(id);\n }\n };\n\n // ── Collections list ────────────────────────────────────────────────\n const collections = collectionsProp ?? configController.collections ?? [];\n\n // ── Sidebar sizing ──────────────────────────────────────────────────\n // Checked, not just parsed: `parseFloat` answers NaN for anything it cannot\n // read, and a NaN pane size lays the sidebar out to nothing — with the bad\n // value still in storage on reload. `SplitListView.getSavedPanelSize` is the\n // same read, done this way.\n const [sidebarSize, setSidebarSize] = useState(() => {\n const saved = readStoredString(\"rebase_collections_editor_sidebar_size\");\n const parsed = saved === null ? NaN : parseFloat(saved);\n return Number.isFinite(parsed) && parsed > 0 && parsed < 100 ? parsed : 25;\n });\n\n useEffect(() => {\n writeStoredString(\"rebase_collections_editor_sidebar_size\", sidebarSize.toString());\n }, [sidebarSize]);\n\n return (\n <div className=\"flex h-full w-full bg-surface-50 dark:bg-surface-800 overflow-hidden text-text-primary dark:text-text-primary-dark\">\n <ResizablePanels\n orientation=\"horizontal\"\n panelSizePercent={sidebarSize}\n onPanelSizeChange={setSidebarSize}\n minPanelSizePx={220}\n firstPanel={\n <div className={cls(\"flex flex-col h-full w-full bg-surface-50 dark:bg-surface-800 border-r\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]\", defaultBorderMixin)}>\n <Typography variant=\"caption\" className=\"font-semibold text-[11px] uppercase tracking-wider text-surface-400 dark:text-surface-400\">\n Collections\n </Typography>\n <Tooltip title={configController.readOnly ? configController.readOnlyReason || \"Read only\" : \"Add collection\"}>\n <div>\n <IconButton\n size=\"small\"\n disabled={configController.readOnly}\n onClick={() => setActiveCollectionId(\"new\")}\n className={activeCollectionId === \"new\" ? \"text-primary dark:text-primary-dark\" : \"text-text-secondary dark:text-text-secondary-dark\"}\n >\n <PlusIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n </Tooltip>\n </div>\n\n <div className=\"flex-grow overflow-y-auto w-full no-scrollbar p-2 space-y-0.5\">\n {collections.length === 0 && (\n <div className=\"p-4 text-center\">\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark italic\">\n No collections yet.\n </Typography>\n </div>\n )}\n {collections.map((collection) => {\n const collectionKey = collection.slug;\n const isSelected = activeCollectionId === collectionKey;\n return (\n <div\n key={collectionKey}\n onClick={() => setActiveCollectionId(collectionKey)}\n className={cls(\n \"flex items-center gap-2.5 px-3 h-[30px] cursor-pointer rounded-lg text-[13px] font-medium transition-colors\",\n isSelected\n ? \"bg-primary/8 text-primary dark:bg-primary/10 dark:text-primary-light font-semibold\"\n : \"hover:bg-primary/5 dark:hover:bg-primary/5 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white\"\n )}\n >\n <IconForView collectionOrView={collection} size={\"smallest\"} className={cls(\n isSelected\n ? \"text-primary dark:text-primary-light\"\n : \"text-surface-500 dark:text-text-secondary-dark\"\n )}/>\n <span className=\"truncate flex-1\">\n {collection.name || collection.slug}\n </span>\n </div>\n );\n })}\n </div>\n </div>\n }\n secondPanel={\n <div className=\"flex-grow flex flex-col min-w-0 h-full w-full\">\n {/* We use key to force unmount when switching collections, preventing stale state */}\n {activeCollectionId ? (\n <CollectionStudioView\n key={activeCollectionId}\n configController={configController}\n collectionId={activeCollectionId}\n onSave={(savedCollection) => {\n // After creating a new collection, switch to it\n if (activeCollectionId === \"new\" && savedCollection?.slug) {\n setActiveCollectionId(savedCollection.slug);\n }\n }}\n onCancel={() => setActiveCollectionId(undefined)}\n propertyTypePresets={propertyTypePresets}\n hiddenPropertyTypes={hiddenPropertyTypes}\n renderExtraPropertyFields={renderExtraPropertyFields}\n renderExtraCollectionFields={renderExtraCollectionFields}\n visibleTabs={visibleTabs}\n standalone={standalone}\n />\n ) : (\n <div className=\"flex flex-col items-center justify-center h-full gap-4\">\n <Typography variant=\"label\">\n Select a collection or create a new one to start editing\n </Typography>\n <Button\n disabled={configController.readOnly}\n onClick={() => setActiveCollectionId(\"new\")}\n >\n <PlusIcon/>\n Add new collection\n </Button>\n </div>\n )}\n </div>\n }\n />\n </div>\n );\n}\n","/**\n * Router-aware wrapper around CollectionsStudioView.\n *\n * This component provides the react-router-based navigation behavior\n * that Rebase Studio expects: the active collection is derived from the\n * URL, and clicking a collection navigates via react-router.\n *\n * External consumers should use `CollectionsStudioView` directly\n * (which has no react-router dependency).\n */\nimport React from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { useUrlController } from \"../../_cms_internals\";\nimport { CollectionsStudioView, CollectionsStudioViewProps } from \"./CollectionsStudioView\";\n\nexport type RouterCollectionsStudioViewProps = Omit<CollectionsStudioViewProps, \"activeCollectionId\" | \"onActiveCollectionChange\">;\n\nexport function RouterCollectionsStudioView(props: RouterCollectionsStudioViewProps) {\n const navigate = useNavigate();\n const urlController = useUrlController();\n const location = useLocation();\n\n // Determine the active collection from the URL segment after \"schema/\"\n const basePath = urlController.buildAppUrlPath(\"schema\");\n const relativePath = location.pathname.replace(basePath, \"\").replace(/^\\//, \"\");\n const activeCollectionId = relativePath.split(\"/\")[0] || undefined;\n\n return (\n <CollectionsStudioView\n {...props}\n activeCollectionId={activeCollectionId}\n onActiveCollectionChange={(id) => {\n if (id) {\n navigate(urlController.buildAppUrlPath(`schema/${id}`));\n } else {\n navigate(urlController.buildAppUrlPath(\"schema\"));\n }\n }}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;AA2BA,SAAgB,qBAAqB,EAAE,cAAc,QAAQ,UAAU,GAAG,SAAoC;CAG1G,MAAM,CAAC,WAAW,gBAAgB,SAAkB,KAAK;CACzD,MAAM,CAAC,iBAAiB,sBAAsB,SAAkB,KAAK;CAErE,MAAM,EAAE,aAAa,kBAAkB,wBACnC,iBACM,aAAa,KAAK,CAC5B;CAEA,MAAM,qBAAqB;CAE3B,MAAM,0BAA0B;EAC5B,IAAI,CAAC,WACD,WAAW;OACR;GACH,mBAAmB,IAAI;GACvB,cAAc;EAClB;CACJ;CAEA,OACI,oBAAC,OAAD;EAAK,WAAU;YACX,qBAAC,yBAAD,EAAA,UAAA,CACK,qBACG,oBAAC,kBAAD;GAEI,GAAI;GACJ,YAAY;GACZ,MAAM;GACN,iBAAiB,uBAAuB;GACxC,oBAAoB,uBAAuB,QAAQ,qBAAqB,KAAA;GACxE,cAAc;GACd,cAAc,oBAAoB;IAC9B,aAAa,KAAK;IAClB,IAAI,iBACA,iBAAiB;KACb,SAAS,eAAe;IAC5B,GAAG,CAAC;GAEZ;GACc;EACjB,GAhBQ,kBAgBR,IAED,qBAAC,OAAD;GAAK,WAAU;aAAf,CACI,oBAAC,YAAD;IAAY,SAAQ;cAAQ;GAEhB,CAAA,GACZ,qBAAC,QAAD;IACI,UAAU,MAAM,kBAAkB;IAClC,eAAe,SAAS;cAF5B,CAII,oBAAC,UAAD,CAAU,CAAA,GAAC,oBAEP;KACP;MAGT,oBAAC,sBAAD;GACI,GAAI;GACJ,gBAAgB;IACZ,YAAY,SAAS;IACrB,IAAI,iBAAiB;KACjB,WAAW;KACX,mBAAmB,KAAK;IAC5B;GACJ;GACA,oBAAoB;IAChB,YAAY,aAAa;IACzB,mBAAmB,KAAK;GAC5B;EACH,CAAA,CACoB,EAAA,CAAA;CACxB,CAAA;AAEb;;;ACvDA,SAAgB,sBAAsB,EAClC,kBACA,aAAa,iBACb,oBAAoB,oBACpB,0BACA,qBACA,qBACA,2BACA,6BACA,aACA,cAC2B;CAK3B,MAAM,CAAC,kBAAkB,uBAAuB,SAA6B,KAAA,CAAS;CACtF,MAAM,eAAe,6BAA6B,KAAA;CAClD,MAAM,qBAAqB,eAAe,qBAAqB;CAE/D,MAAM,yBAAyB,OAA2B;EACtD,IAAI,cACA,2BAA2B,EAAE;OAE7B,oBAAoB,EAAE;CAE9B;CAGA,MAAM,cAAc,mBAAmB,iBAAiB,eAAe,CAAC;CAOxE,MAAM,CAAC,aAAa,kBAAkB,eAAe;EACjD,MAAM,QAAQ,iBAAiB,wCAAwC;EACvE,MAAM,SAAS,UAAU,OAAO,MAAM,WAAW,KAAK;EACtD,OAAO,OAAO,SAAS,MAAM,KAAK,SAAS,KAAK,SAAS,MAAM,SAAS;CAC5E,CAAC;CAED,gBAAgB;EACZ,kBAAkB,0CAA0C,YAAY,SAAS,CAAC;CACtF,GAAG,CAAC,WAAW,CAAC;CAEhB,OACI,oBAAC,OAAD;EAAK,WAAU;YACX,oBAAC,iBAAD;GACI,aAAY;GACZ,kBAAkB;GAClB,mBAAmB;GACnB,gBAAgB;GAChB,YACI,qBAAC,OAAD;IAAK,WAAW,IAAI,0EAA0E,kBAAkB;cAAhH,CACI,qBAAC,OAAD;KAAK,WAAW,IAAI,uGAAuG,kBAAkB;eAA7I,CACI,oBAAC,YAAD;MAAY,SAAQ;MAAU,WAAU;gBAA4F;KAExH,CAAA,GACZ,oBAAC,SAAD;MAAS,OAAO,iBAAiB,WAAW,iBAAiB,kBAAkB,cAAc;gBACzF,oBAAC,OAAD,EAAA,UACI,oBAAC,YAAD;OACI,MAAK;OACL,UAAU,iBAAiB;OAC3B,eAAe,sBAAsB,KAAK;OAC1C,WAAW,uBAAuB,QAAQ,wCAAwC;iBAElF,oBAAC,UAAD,EAAU,MAAM,SAAS,SAAU,CAAA;MAC3B,CAAA,EACX,CAAA;KACA,CAAA,CACR;QAEL,qBAAC,OAAD;KAAK,WAAU;eAAf,CACK,YAAY,WAAW,KACpB,oBAAC,OAAD;MAAK,WAAU;gBACX,oBAAC,YAAD;OAAY,SAAQ;OAAU,WAAU;iBAAyD;MAErF,CAAA;KACX,CAAA,GAER,YAAY,KAAK,eAAe;MAC7B,MAAM,gBAAgB,WAAW;MACjC,MAAM,aAAa,uBAAuB;MAC1C,OACI,qBAAC,OAAD;OAEI,eAAe,sBAAsB,aAAa;OAClD,WAAW,IACP,+GACA,aACM,uFACA,gIACV;iBARJ,CAUI,oBAAC,aAAD;QAAa,kBAAkB;QAAY,MAAM;QAAY,WAAW,IACpE,aACM,yCACA,gDACV;OAAG,CAAA,GACH,oBAAC,QAAD;QAAM,WAAU;kBACX,WAAW,QAAQ,WAAW;OAC7B,CAAA,CACL;SAjBI,aAiBJ;KAEb,CAAC,CACA;MACJ;;GAET,aACI,oBAAC,OAAD;IAAK,WAAU;cAEV,qBACG,oBAAC,sBAAD;KAEsB;KAClB,cAAc;KACd,SAAS,oBAAoB;MAEzB,IAAI,uBAAuB,SAAS,iBAAiB,MACjD,sBAAsB,gBAAgB,IAAI;KAElD;KACA,gBAAgB,sBAAsB,KAAA,CAAS;KAC1B;KACA;KACM;KACE;KAChB;KACD;IACf,GAhBQ,kBAgBR,IAED,qBAAC,OAAD;KAAK,WAAU;eAAf,CACI,oBAAC,YAAD;MAAY,SAAQ;gBAAQ;KAEhB,CAAA,GACZ,qBAAC,QAAD;MACI,UAAU,iBAAiB;MAC3B,eAAe,sBAAsB,KAAK;gBAF9C,CAII,oBAAC,UAAD,CAAU,CAAA,GAAC,oBAEP;OACP;;GAER,CAAA;EAEZ,CAAA;CACA,CAAA;AAEb;;;;ACrLA,SAAgB,4BAA4B,OAAyC;CACjF,MAAM,WAAW,YAAY;CAC7B,MAAM,gBAAgB,iBAAiB;CACvC,MAAM,WAAW,YAAY;CAG7B,MAAM,WAAW,cAAc,gBAAgB,QAAQ;CAEvD,MAAM,qBADe,SAAS,SAAS,QAAQ,UAAU,EAAE,CAAC,CAAC,QAAQ,OAAO,EACjD,CAAA,CAAa,MAAM,GAAG,CAAC,CAAC,MAAM,KAAA;CAEzD,OACI,oBAAC,uBAAD;EACI,GAAI;EACgB;EACpB,2BAA2B,OAAO;GAC9B,IAAI,IACA,SAAS,cAAc,gBAAgB,UAAU,IAAI,CAAC;QAEtD,SAAS,cAAc,gBAAgB,QAAQ,CAAC;EAExD;CACH,CAAA;AAET"}
|
|
1
|
+
{"version":3,"file":"RouterCollectionsStudioView-CwTpdzWF.js","names":[],"sources":["../src/collection_editor/ui/collection_editor/CollectionStudioView.tsx","../src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx","../src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx"],"sourcesContent":["\nimport { useUnsavedChangesDialog, UnsavedChangesDialog } from \"@rebasepro/app\";\nimport * as React from \"react\";\nimport { useState } from \"react\";\nimport { Button, PlusIcon, Typography } from \"@rebasepro/ui\";\nimport { CollectionEditorDialogProps } from \"./CollectionEditorDialog\";\nimport { AIModifiedPathsProvider } from \"./AIModifiedPathsContext\";\nimport { CollectionEditor } from \"./CollectionEditorDialog\";\nimport type { AdminCollection } from \"@rebasepro/admin-types\";\n\nexport type CollectionStudioViewProps = Omit<CollectionEditorDialogProps, \"open\" | \"isNewCollection\" | \"editedCollectionId\" | \"handleClose\" | \"handleCancel\"> & {\n collectionId?: string | \"new\";\n\n /**\n * Called after a successful save.\n * Receives the saved collection (or undefined if dismissed without saving).\n * When not provided, save completes silently.\n */\n onSave?: (collection?: AdminCollection) => void;\n\n /**\n * Called when the user cancels editing.\n * When not provided, cancel is a no-op.\n */\n onCancel?: () => void;\n};\n\nexport function CollectionStudioView({ collectionId, onSave, onCancel, ...props }: CollectionStudioViewProps) {\n\n // Form state from the editor\n const [formDirty, setFormDirty] = useState<boolean>(false);\n const [cancelRequested, setCancelRequested] = useState<boolean>(false);\n\n const { dialogProps, triggerDialog } = useUnsavedChangesDialog(\n formDirty,\n () => setFormDirty(false)\n );\n\n const activeCollectionId = collectionId;\n\n const handleCancelClick = () => {\n if (!formDirty) {\n onCancel?.();\n } else {\n setCancelRequested(true);\n triggerDialog();\n }\n };\n\n return (\n <div className=\"flex-grow flex flex-col h-full w-full bg-surface-50 dark:bg-surface-800\">\n <AIModifiedPathsProvider>\n {activeCollectionId ? (\n <CollectionEditor\n key={activeCollectionId}\n {...props}\n fullScreen={true}\n open={true}\n isNewCollection={activeCollectionId === \"new\"}\n editedCollectionId={activeCollectionId !== \"new\" ? activeCollectionId : undefined}\n handleCancel={handleCancelClick}\n handleClose={(savedCollection) => {\n setFormDirty(false);\n if (savedCollection) {\n setTimeout(() => {\n onSave?.(savedCollection);\n }, 0);\n }\n }}\n setFormDirty={setFormDirty}\n />\n ) : (\n <div className=\"flex-grow flex flex-col items-center justify-center h-full gap-4\">\n <Typography variant=\"label\">\n Select a collection or create a new one\n </Typography>\n <Button\n disabled={props.configController?.readOnly}\n onClick={() => onSave?.()}\n >\n <PlusIcon/>\n Add new collection\n </Button>\n </div>\n )}\n\n <UnsavedChangesDialog\n {...dialogProps}\n handleOk={() => {\n dialogProps.handleOk();\n if (cancelRequested) {\n onCancel?.();\n setCancelRequested(false);\n }\n }}\n handleCancel={() => {\n dialogProps.handleCancel();\n setCancelRequested(false);\n }}\n />\n </AIModifiedPathsProvider>\n </div>\n );\n}\n","\nimport { IconForView } from \"@rebasepro/app\";\nimport React, { useState, useEffect } from \"react\";\nimport {\n Button,\n cls,\n defaultBorderMixin,\n IconButton,\n iconSize,\n PlusIcon,\n ResizablePanels,\n Tooltip,\n Typography\n} from \"@rebasepro/ui\";\n\nimport { CollectionsConfigController } from \"../../types/config_controller\";\nimport { CollectionStudioView } from \"./CollectionStudioView\";\nimport type { CollectionEditorExtensionProps } from \"../../extensibility_types\";\nimport type { AdminCollection } from \"@rebasepro/admin-types\";\nimport { readStoredString, writeStoredString } from \"@rebasepro/utils\";\n\nexport interface CollectionsStudioViewProps extends CollectionEditorExtensionProps {\n configController: CollectionsConfigController;\n\n /**\n * Collections to show in the sidebar.\n * When provided, overrides the collections from `configController`.\n * Use this to control exactly which collections the editor displays.\n */\n collections?: AdminCollection[];\n\n /**\n * Controlled active collection ID.\n * When provided together with `onActiveCollectionChange`,\n * navigation is fully external — no internal state is used.\n */\n activeCollectionId?: string;\n\n /**\n * Called when the user clicks a collection, \"new\", or navigates.\n * When provided, the component is fully controlled (no internal state).\n * When not provided, the component manages selection via internal state.\n *\n * Pass `\"new\"` to create a new collection. Pass `undefined` to deselect.\n */\n onActiveCollectionChange?: (collectionId: string | undefined) => void;\n}\n\nexport function CollectionsStudioView({\n configController,\n collections: collectionsProp,\n activeCollectionId: controlledActiveId,\n onActiveCollectionChange,\n propertyTypePresets,\n hiddenPropertyTypes,\n renderExtraPropertyFields,\n renderExtraCollectionFields,\n visibleTabs,\n standalone,\n}: CollectionsStudioViewProps) {\n\n // ── Navigation state ────────────────────────────────────────────────\n // If onActiveCollectionChange is provided, the component is controlled.\n // Otherwise, use internal state.\n const [internalActiveId, setInternalActiveId] = useState<string | undefined>(undefined);\n const isControlled = onActiveCollectionChange !== undefined;\n const activeCollectionId = isControlled ? controlledActiveId : internalActiveId;\n\n const setActiveCollectionId = (id: string | undefined) => {\n if (isControlled) {\n onActiveCollectionChange?.(id);\n } else {\n setInternalActiveId(id);\n }\n };\n\n // ── Collections list ────────────────────────────────────────────────\n const collections = collectionsProp ?? configController.collections ?? [];\n\n // ── Sidebar sizing ──────────────────────────────────────────────────\n // Checked, not just parsed: `parseFloat` answers NaN for anything it cannot\n // read, and a NaN pane size lays the sidebar out to nothing — with the bad\n // value still in storage on reload. `SplitListView.getSavedPanelSize` is the\n // same read, done this way.\n const [sidebarSize, setSidebarSize] = useState(() => {\n const saved = readStoredString(\"rebase_collections_editor_sidebar_size\");\n const parsed = saved === null ? NaN : parseFloat(saved);\n return Number.isFinite(parsed) && parsed > 0 && parsed < 100 ? parsed : 25;\n });\n\n useEffect(() => {\n writeStoredString(\"rebase_collections_editor_sidebar_size\", sidebarSize.toString());\n }, [sidebarSize]);\n\n return (\n <div className=\"flex h-full w-full bg-surface-50 dark:bg-surface-800 overflow-hidden text-text-primary dark:text-text-primary-dark\">\n <ResizablePanels\n orientation=\"horizontal\"\n panelSizePercent={sidebarSize}\n onPanelSizeChange={setSidebarSize}\n minPanelSizePx={220}\n firstPanel={\n <div className={cls(\"flex flex-col h-full w-full bg-surface-50 dark:bg-surface-800 border-r\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]\", defaultBorderMixin)}>\n <Typography variant=\"caption\" className=\"font-semibold text-[11px] uppercase tracking-wider text-surface-400 dark:text-surface-400\">\n Collections\n </Typography>\n <Tooltip title={configController.readOnly ? configController.readOnlyReason || \"Read only\" : \"Add collection\"}>\n <div>\n <IconButton\n size=\"small\"\n disabled={configController.readOnly}\n onClick={() => setActiveCollectionId(\"new\")}\n className={activeCollectionId === \"new\" ? \"text-primary dark:text-primary-dark\" : \"text-text-secondary dark:text-text-secondary-dark\"}\n >\n <PlusIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n </Tooltip>\n </div>\n\n <div className=\"flex-grow overflow-y-auto w-full no-scrollbar p-2 space-y-0.5\">\n {collections.length === 0 && (\n <div className=\"p-4 text-center\">\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark italic\">\n No collections yet.\n </Typography>\n </div>\n )}\n {collections.map((collection) => {\n const collectionKey = collection.slug;\n const isSelected = activeCollectionId === collectionKey;\n return (\n <div\n key={collectionKey}\n onClick={() => setActiveCollectionId(collectionKey)}\n className={cls(\n \"flex items-center gap-2.5 px-3 h-[30px] cursor-pointer rounded-lg text-[13px] font-medium transition-colors\",\n isSelected\n ? \"bg-primary/8 text-primary dark:bg-primary/10 dark:text-primary-light font-semibold\"\n : \"hover:bg-primary/5 dark:hover:bg-primary/5 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white\"\n )}\n >\n <IconForView collectionOrView={collection} size={\"smallest\"} className={cls(\n isSelected\n ? \"text-primary dark:text-primary-light\"\n : \"text-surface-500 dark:text-text-secondary-dark\"\n )}/>\n <span className=\"truncate flex-1\">\n {collection.name || collection.slug}\n </span>\n </div>\n );\n })}\n </div>\n </div>\n }\n secondPanel={\n <div className=\"flex-grow flex flex-col min-w-0 h-full w-full\">\n {/* We use key to force unmount when switching collections, preventing stale state */}\n {activeCollectionId ? (\n <CollectionStudioView\n key={activeCollectionId}\n configController={configController}\n collectionId={activeCollectionId}\n onSave={(savedCollection) => {\n // After creating a new collection, switch to it\n if (activeCollectionId === \"new\" && savedCollection?.slug) {\n setActiveCollectionId(savedCollection.slug);\n }\n }}\n onCancel={() => setActiveCollectionId(undefined)}\n propertyTypePresets={propertyTypePresets}\n hiddenPropertyTypes={hiddenPropertyTypes}\n renderExtraPropertyFields={renderExtraPropertyFields}\n renderExtraCollectionFields={renderExtraCollectionFields}\n visibleTabs={visibleTabs}\n standalone={standalone}\n />\n ) : (\n <div className=\"flex flex-col items-center justify-center h-full gap-4\">\n <Typography variant=\"label\">\n Select a collection or create a new one to start editing\n </Typography>\n <Button\n disabled={configController.readOnly}\n onClick={() => setActiveCollectionId(\"new\")}\n >\n <PlusIcon/>\n Add new collection\n </Button>\n </div>\n )}\n </div>\n }\n />\n </div>\n );\n}\n","/**\n * Router-aware wrapper around CollectionsStudioView.\n *\n * This component provides the react-router-based navigation behavior\n * that Rebase Studio expects: the active collection is derived from the\n * URL, and clicking a collection navigates via react-router.\n *\n * External consumers should use `CollectionsStudioView` directly\n * (which has no react-router dependency).\n */\nimport React from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { useUrlController } from \"../../_cms_internals\";\nimport { CollectionsStudioView, CollectionsStudioViewProps } from \"./CollectionsStudioView\";\n\nexport type RouterCollectionsStudioViewProps = Omit<CollectionsStudioViewProps, \"activeCollectionId\" | \"onActiveCollectionChange\">;\n\nexport function RouterCollectionsStudioView(props: RouterCollectionsStudioViewProps) {\n const navigate = useNavigate();\n const urlController = useUrlController();\n const location = useLocation();\n\n // Determine the active collection from the URL segment after \"schema/\"\n const basePath = urlController.buildAppUrlPath(\"schema\");\n const relativePath = location.pathname.replace(basePath, \"\").replace(/^\\//, \"\");\n const activeCollectionId = relativePath.split(\"/\")[0] || undefined;\n\n return (\n <CollectionsStudioView\n {...props}\n activeCollectionId={activeCollectionId}\n onActiveCollectionChange={(id) => {\n if (id) {\n navigate(urlController.buildAppUrlPath(`schema/${id}`));\n } else {\n navigate(urlController.buildAppUrlPath(\"schema\"));\n }\n }}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as PropertyForm, r as PropertyFormDialog } from "./PropertyEditView-
|
|
3
|
-
import { n as CollectionEditorDialog } from "./CollectionEditorDialog-
|
|
4
|
-
import { i as CollectionStudioView, r as CollectionsStudioView, t as RouterCollectionsStudioView } from "./RouterCollectionsStudioView-
|
|
1
|
+
import { ln as useUrlController } from "./util-C-D5yD2n.js";
|
|
2
|
+
import { n as PropertyForm, r as PropertyFormDialog } from "./PropertyEditView-CWZlC1gq.js";
|
|
3
|
+
import { n as CollectionEditorDialog } from "./CollectionEditorDialog-CSheud_E.js";
|
|
4
|
+
import { i as CollectionStudioView, r as CollectionsStudioView, t as RouterCollectionsStudioView } from "./RouterCollectionsStudioView-CwTpdzWF.js";
|
|
5
5
|
import "react";
|
|
6
6
|
import { useSnackbarController } from "@rebasepro/app";
|
|
7
7
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { EntityTableController } from "@rebasepro/admin-types";
|
|
2
|
-
export declare function ClearFilterSortButton({ tableController, enabled }: {
|
|
2
|
+
export declare function ClearFilterSortButton({ tableController, enabled, compact }: {
|
|
3
3
|
enabled: boolean;
|
|
4
4
|
tableController: EntityTableController;
|
|
5
|
+
/**
|
|
6
|
+
* Toolbar showing icon-only controls (split view, or a layout too narrow
|
|
7
|
+
* for the filters button's label). Only decides the icon size, which
|
|
8
|
+
* follows that button — see `CollectionViewStartActions`.
|
|
9
|
+
*/
|
|
10
|
+
compact?: boolean;
|
|
5
11
|
}): import("react").JSX.Element | null;
|
|
@@ -8,6 +8,12 @@ export type SortButtonProps<M extends Record<string, unknown>> = {
|
|
|
8
8
|
* the options offered.
|
|
9
9
|
*/
|
|
10
10
|
properties: Properties;
|
|
11
|
+
/**
|
|
12
|
+
* Toolbar showing icon-only controls (split view, or a layout too narrow
|
|
13
|
+
* for the filters button's label). Only decides the icon size, which
|
|
14
|
+
* follows that button — see `CollectionViewStartActions`.
|
|
15
|
+
*/
|
|
16
|
+
compact?: boolean;
|
|
11
17
|
};
|
|
12
18
|
/**
|
|
13
19
|
* Toolbar control that orders the collection by one property.
|
|
@@ -21,4 +27,4 @@ export type SortButtonProps<M extends Record<string, unknown>> = {
|
|
|
21
27
|
* is one click rather than a trip through a direction selector; clicking a
|
|
22
28
|
* different one starts it ascending. The popover stays open through both.
|
|
23
29
|
*/
|
|
24
|
-
export declare function SortButton<M extends Record<string, unknown>>({ tableController, properties }: SortButtonProps<M>): React.JSX.Element | null;
|
|
30
|
+
export declare function SortButton<M extends Record<string, unknown>>({ tableController, properties, compact }: SortButtonProps<M>): React.JSX.Element | null;
|
|
@@ -47,7 +47,20 @@ export interface RelationSelectorProps {
|
|
|
47
47
|
pageSize?: number;
|
|
48
48
|
emptyPlaceholder?: string;
|
|
49
49
|
searchPlaceholder?: string;
|
|
50
|
+
/** Shown in the open list when a search matched nothing. */
|
|
50
51
|
noResultsText?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Shown on the closed trigger when nothing is selected. Says only that the
|
|
54
|
+
* field is empty — see the note by `resolvedPlaceholder` for why it cannot
|
|
55
|
+
* say whether there is anything to pick.
|
|
56
|
+
*/
|
|
57
|
+
emptyText?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Shown in the open list when the target collection turned out to hold no
|
|
60
|
+
* rows at all, as opposed to a search simply not matching. Defaults to a
|
|
61
|
+
* sentence naming the target collection.
|
|
62
|
+
*/
|
|
63
|
+
emptyCollectionText?: string;
|
|
51
64
|
loadingText?: string;
|
|
52
65
|
}
|
|
53
66
|
export declare const RelationSelector: React.ForwardRefExoticComponent<RelationSelectorProps & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -17,6 +17,13 @@ export interface DownloadEntitiesExportParams<M extends Record<string, unknown>>
|
|
|
17
17
|
export declare function downloadEntitiesExport<M extends Record<string, unknown>>({ data, additionalData, properties, propertiesOrder, name, flattenArrays, additionalHeaders, exportType, dateExportType }: DownloadEntitiesExportParams<M>): void;
|
|
18
18
|
export declare function getEntityCSVExportableData(data: Entity<Record<string, unknown>>[], additionalData: Record<string, unknown>[] | undefined, properties: Properties, headers: Header[], dateExportType: "timestamp" | "string"): unknown[][];
|
|
19
19
|
export declare function getEntityJsonExportableData(data: Entity<Record<string, unknown>>[], additionalData: Record<string, unknown>[] | undefined, properties: Properties, dateExportType: "timestamp" | "string"): Record<string, unknown>[];
|
|
20
|
+
/**
|
|
21
|
+
* Neutralise a cell that a spreadsheet would otherwise evaluate, by prefixing a
|
|
22
|
+
* single quote — the standard OWASP mitigation. The apostrophe is consumed by
|
|
23
|
+
* the spreadsheet as "treat the rest as text", so the value reads back intact.
|
|
24
|
+
*/
|
|
25
|
+
export declare function escapeCsvFormula(value: string): string;
|
|
26
|
+
export declare function entryToCSVRow(entry: unknown[]): string;
|
|
20
27
|
export declare function downloadBlob(content: BlobPart[], filename: string, contentType: string): void;
|
|
21
28
|
export declare function downloadDataAsCsv(data: object[], name: string): void;
|
|
22
29
|
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Entity, FindParams, FindResponse } from "@rebasepro/types";
|
|
2
|
+
/**
|
|
3
|
+
* Rows asked for per request.
|
|
4
|
+
*
|
|
5
|
+
* Kept at or below the server's `MAX_LIST_LIMIT`, which is now the largest page
|
|
6
|
+
* a read will serve *and refuses* anything larger — it used to clamp silently,
|
|
7
|
+
* and asking for 100 000 while receiving 1 000 looked exactly like a collection
|
|
8
|
+
* with 1 000 rows in it. The walk still never trusts the page size it asked for:
|
|
9
|
+
* it advances by the rows it was actually handed.
|
|
10
|
+
*/
|
|
11
|
+
export declare const EXPORT_PAGE_SIZE = 500;
|
|
12
|
+
/**
|
|
13
|
+
* Most rows one browser-side export will materialise.
|
|
14
|
+
*
|
|
15
|
+
* Everything here is built in memory and handed to a Blob, so there is a
|
|
16
|
+
* ceiling whether or not it is named. Naming it means going over it **throws**
|
|
17
|
+
* instead of writing a short file that reads like the whole collection.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MAX_EXPORT_ROWS = 100000;
|
|
20
|
+
export interface FetchAllForExportParams<M extends Record<string, unknown>> {
|
|
21
|
+
/** The collection accessor to read through — `dataClient.collection(path)`. */
|
|
22
|
+
accessor: {
|
|
23
|
+
find(params?: FindParams<M>): Promise<FindResponse<M>>;
|
|
24
|
+
};
|
|
25
|
+
/** Query to export. The window (`limit` / `offset` / `page`) is owned by the walk. */
|
|
26
|
+
params?: Omit<FindParams<M>, "limit" | "offset" | "page">;
|
|
27
|
+
pageSize?: number;
|
|
28
|
+
maxRows?: number;
|
|
29
|
+
/** Called after every page with the rows so far, and the total when the server reports one. */
|
|
30
|
+
onProgress?: (loaded: number, total: number | undefined) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Read **every** row matching a query, one page at a time.
|
|
34
|
+
*
|
|
35
|
+
* `find({})` sends no `limit`, and an absent limit is resolved server-side to
|
|
36
|
+
* `DEFAULT_LIST_LIMIT` — 50 rows. The export used to hand those 50 rows back as
|
|
37
|
+
* `<collection>.csv` with nothing saying it was a sample, next to a dialog
|
|
38
|
+
* warning that the collection had 100 000 documents.
|
|
39
|
+
*
|
|
40
|
+
* Termination is driven by the server's `meta.hasMore` where it is reported and
|
|
41
|
+
* by an empty page otherwise — never by comparing a page's length against the
|
|
42
|
+
* requested limit, since a final page that happens to be exactly full is
|
|
43
|
+
* indistinguishable that way and a walk that stops there drops rows.
|
|
44
|
+
*
|
|
45
|
+
* @throws When more than `maxRows` rows match. A truncated export is worse than
|
|
46
|
+
* no export: it is indistinguishable from a complete one.
|
|
47
|
+
*/
|
|
48
|
+
export declare function fetchAllEntitiesForExport<M extends Record<string, unknown>>({ accessor, params, pageSize, maxRows, onProgress }: FetchAllForExportParams<M>): Promise<Entity<M>[]>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split CSV text into rows of raw cells, RFC 4180 style.
|
|
3
|
+
*
|
|
4
|
+
* Quoted fields may contain the delimiter, newlines and doubled quotes; this is
|
|
5
|
+
* the exact inverse of what the export's `entryToCSVRow` writes, so a file this
|
|
6
|
+
* admin produced reads back into the cells it was built from.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseCsvRows(text: string, delimiter?: string): string[][];
|
|
9
|
+
/**
|
|
10
|
+
* Pick the delimiter that splits the header row into the most cells. A
|
|
11
|
+
* single-column file matches nothing and stays on the comma.
|
|
12
|
+
*/
|
|
13
|
+
export declare function detectCsvDelimiter(text: string): string;
|
|
14
|
+
export interface ParsedCsv {
|
|
15
|
+
headers: string[];
|
|
16
|
+
data: Record<string, string>[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse CSV text into one object per row, keyed by the header row.
|
|
20
|
+
*
|
|
21
|
+
* The picker accepts `.csv` and the dialog promises "Upload a CSV, Excel or JSON
|
|
22
|
+
* file", but every non-JSON file used to be handed to ExcelJS'
|
|
23
|
+
* `workbook.xlsx.load`, which is a zip reader — so every CSV failed with
|
|
24
|
+
* "Can't find end of central directory : is this a zip file ?". The export
|
|
25
|
+
* writes CSV by default, so the round trip could not close.
|
|
26
|
+
*
|
|
27
|
+
* A blank header keeps its column rather than collapsing it, so the cells to its
|
|
28
|
+
* right stay under their own names.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseCsvToObjects(text: string): ParsedCsv;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Entity, RebaseData } from "@rebasepro/types";
|
|
2
|
+
/** Rows written per request, and per transaction where the data source has one. */
|
|
3
|
+
export declare const IMPORT_BATCH_SIZE = 25;
|
|
4
|
+
/**
|
|
5
|
+
* An import failure that says where it stopped.
|
|
6
|
+
*
|
|
7
|
+
* "Error saving data / null value in column "sku" violates not-null constraint"
|
|
8
|
+
* on a ten-thousand-row import named no row, and the Retry button restarted at
|
|
9
|
+
* zero — where the rows already written now conflicted, so it could never
|
|
10
|
+
* succeed. Both halves of that are what this carries.
|
|
11
|
+
*/
|
|
12
|
+
export interface ImportSaveError extends Error {
|
|
13
|
+
/** Rows written and confirmed before the failure. Where a retry resumes. */
|
|
14
|
+
committed: number;
|
|
15
|
+
/** Index of the first row that was not written, 0-based. */
|
|
16
|
+
failedFrom: number;
|
|
17
|
+
/** Index after the last row of the batch that was not written. */
|
|
18
|
+
failedTo: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Write imported entities in batches, reporting what was committed.
|
|
22
|
+
*
|
|
23
|
+
* Resumable on purpose: a failure at row 4 000 leaves rows 1–3 999 in the table
|
|
24
|
+
* whatever this does, so the only useful thing to report is where to carry on
|
|
25
|
+
* from. `offset` is that point — the Retry button passes back the `committed`
|
|
26
|
+
* count of the error it is retrying, instead of starting from zero and
|
|
27
|
+
* conflicting on every row that already exists.
|
|
28
|
+
*
|
|
29
|
+
* @throws {ImportSaveError} naming the rows that were not written.
|
|
30
|
+
*/
|
|
31
|
+
export declare function saveImportedEntities(dataClient: RebaseData, path: string, data: Partial<Entity<any>>[], { offset, batchSize, onBatchCommitted, bulkUnsupported }?: {
|
|
32
|
+
offset?: number;
|
|
33
|
+
batchSize?: number;
|
|
34
|
+
onBatchCommitted?: (rowsWritten: number) => void;
|
|
35
|
+
bulkUnsupported?: {
|
|
36
|
+
current: boolean;
|
|
37
|
+
};
|
|
38
|
+
}): Promise<void>;
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Take an object with keys of type `address.street`, `address.city` and
|
|
3
3
|
* convert it to an object with nested objects like `{ address: { street: ..., city: ... } }`
|
|
4
|
+
*
|
|
5
|
+
* Keys here are the **header row of an uploaded file**, so they are attacker
|
|
6
|
+
* data. `res["__proto__"] = …` is the prototype setter rather than an own
|
|
7
|
+
* property, so a column named `__proto__.polluted` walked out of the accumulator
|
|
8
|
+
* and wrote onto `Object.prototype` for the life of the tab — and
|
|
9
|
+
* `constructor.prototype.x` threw a `TypeError` that surfaced as an unreadable
|
|
10
|
+
* file. Such a column is refused, not sanitised, the way `setIn` settled it
|
|
11
|
+
* (`docs/bug-classes.md` class 22).
|
|
12
|
+
*
|
|
4
13
|
* @param flatObj
|
|
5
14
|
*/
|
|
6
15
|
export declare function unflattenObject(flatObj: {
|
package/dist/editor.js
CHANGED
|
@@ -948,6 +948,7 @@ var ImageBubble = forwardRef(({ options, className }, ref) => {
|
|
|
948
948
|
},
|
|
949
949
|
children: [/* @__PURE__ */ jsx(TextField, {
|
|
950
950
|
size: "small",
|
|
951
|
+
"aria-label": t("alt_text"),
|
|
951
952
|
placeholder: t("alt_text"),
|
|
952
953
|
value: alt,
|
|
953
954
|
onChange: (e) => {
|
|
@@ -956,6 +957,7 @@ var ImageBubble = forwardRef(({ options, className }, ref) => {
|
|
|
956
957
|
}
|
|
957
958
|
}), /* @__PURE__ */ jsx(TextField, {
|
|
958
959
|
size: "small",
|
|
960
|
+
"aria-label": t("title"),
|
|
959
961
|
placeholder: t("title"),
|
|
960
962
|
value: title,
|
|
961
963
|
onChange: (e) => {
|
|
@@ -1614,6 +1616,7 @@ var LinkSelector = ({ open, onOpenChange }) => {
|
|
|
1614
1616
|
children: [/* @__PURE__ */ jsx(TextField, {
|
|
1615
1617
|
inputRef,
|
|
1616
1618
|
autoFocus: open,
|
|
1619
|
+
"aria-label": t("editor_paste_or_type_link"),
|
|
1617
1620
|
placeholder: t("editor_paste_or_type_link"),
|
|
1618
1621
|
defaultValue: href,
|
|
1619
1622
|
size: "small",
|