@firecms/core 3.4.0-canary.8635253 → 3.4.0-canary.ce97da7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +4 -0
  2. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +6 -1
  3. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +3 -1
  4. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +3 -1
  5. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +8 -0
  6. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +3 -1
  7. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +3 -1
  8. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +3 -1
  9. package/dist/components/ErrorBoundary.d.ts +1 -3
  10. package/dist/components/LicenseBanner.d.ts +23 -0
  11. package/dist/components/ReferenceTable/ReferenceSelectionTable.d.ts +10 -1
  12. package/dist/components/SelectableTable/filters/filter_text_input.d.ts +30 -0
  13. package/dist/components/SelectableTable/filters/filter_text_input.test.d.ts +1 -0
  14. package/dist/components/common/table_url_params.d.ts +24 -0
  15. package/dist/components/common/useDataSourceTableController.d.ts +9 -1
  16. package/dist/components/common/useTableSearchHelper.d.ts +9 -1
  17. package/dist/components/index.d.ts +1 -0
  18. package/dist/contexts/LicenseStatusContext.d.ts +16 -0
  19. package/dist/core/EntityEditView.d.ts +9 -1
  20. package/dist/core/EntityEditViewFormActions.d.ts +1 -1
  21. package/dist/core/index.d.ts +1 -0
  22. package/dist/core/plugin_translations.d.ts +38 -0
  23. package/dist/core/pro_plugins.d.ts +40 -0
  24. package/dist/de-DMp6cIY1.js +702 -0
  25. package/dist/de-DMp6cIY1.js.map +1 -0
  26. package/dist/editor/extensions/Image.d.ts +11 -0
  27. package/dist/editor/index.d.ts +2 -1
  28. package/dist/editor/lazy_editor.d.ts +6 -0
  29. package/dist/editor-BSo_aS1I.js +4563 -0
  30. package/dist/editor-BSo_aS1I.js.map +1 -0
  31. package/dist/form/EntityForm.d.ts +8 -1
  32. package/dist/form/EntityFormActions.d.ts +2 -0
  33. package/dist/fr-Cckz4IkR.js +702 -0
  34. package/dist/fr-Cckz4IkR.js.map +1 -0
  35. package/dist/hi-CDCoIqbu.js +702 -0
  36. package/dist/hi-CDCoIqbu.js.map +1 -0
  37. package/dist/hooks/data/delete.d.ts +1 -1
  38. package/dist/hooks/data/save.d.ts +1 -1
  39. package/dist/hooks/data/useCollectionFetch.d.ts +7 -1
  40. package/dist/hooks/data/useEntityFetch.d.ts +9 -1
  41. package/dist/hooks/index.d.ts +2 -0
  42. package/dist/hooks/useLicenseStatus.d.ts +16 -0
  43. package/dist/hooks/useProjectLog.d.ts +11 -6
  44. package/dist/hooks/useTranslation.d.ts +1 -1
  45. package/dist/i18n/__tests__/FireCMSi18nProvider.test.d.ts +1 -0
  46. package/dist/i18n/__tests__/nested_provider_lifecycle.test.d.ts +1 -0
  47. package/dist/index.es.js +3921 -11331
  48. package/dist/index.es.js.map +1 -1
  49. package/dist/index.umd.js +8950 -7585
  50. package/dist/index.umd.js.map +1 -1
  51. package/dist/it-BJCUqixj.js +702 -0
  52. package/dist/it-BJCUqixj.js.map +1 -0
  53. package/dist/pl-DgT_joFf.js +704 -0
  54. package/dist/pl-DgT_joFf.js.map +1 -0
  55. package/dist/pt-B3dN3x0X.js +704 -0
  56. package/dist/pt-B3dN3x0X.js.map +1 -0
  57. package/dist/types/analytics.d.ts +1 -1
  58. package/dist/types/collections.d.ts +14 -0
  59. package/dist/types/datasource.d.ts +67 -4
  60. package/dist/types/entities.d.ts +21 -1
  61. package/dist/types/entity_actions.d.ts +6 -0
  62. package/dist/types/entity_callbacks.d.ts +21 -0
  63. package/dist/types/firecms.d.ts +18 -1
  64. package/dist/types/firecms_context.d.ts +8 -0
  65. package/dist/types/index.d.ts +1 -0
  66. package/dist/types/license.d.ts +54 -0
  67. package/dist/types/navigation.d.ts +34 -5
  68. package/dist/types/plugins.d.ts +22 -1
  69. package/dist/types/properties.d.ts +17 -1
  70. package/dist/types/side_entity_controller.d.ts +7 -0
  71. package/dist/types/translations.d.ts +41 -2
  72. package/dist/util/__tests__/collections.test.d.ts +1 -0
  73. package/dist/util/__tests__/urls.test.d.ts +1 -0
  74. package/dist/util/collections.d.ts +1 -1
  75. package/dist/util/date_fns_locales.d.ts +18 -0
  76. package/dist/util/entity_cache.d.ts +11 -0
  77. package/dist/util/index.d.ts +2 -0
  78. package/dist/util/lazy_eager.d.ts +3 -3
  79. package/dist/util/navigation_from_path.d.ts +7 -0
  80. package/dist/util/navigation_utils.d.ts +87 -3
  81. package/dist/util/parent_references_from_path.d.ts +16 -0
  82. package/dist/util/paths.d.ts +28 -0
  83. package/dist/util/permissions.d.ts +10 -4
  84. package/dist/util/urls.d.ts +22 -0
  85. package/package.json +5 -5
  86. package/src/app/Scaffold.tsx +7 -0
  87. package/src/components/DeleteEntityDialog.tsx +2 -0
  88. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
  89. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +1 -0
  90. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +7 -0
  91. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -1
  92. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -0
  93. package/src/components/EntityCollectionView/EntityCollectionView.tsx +66 -15
  94. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +6 -2
  95. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -0
  96. package/src/components/EntityCollectionView/useBoardDataController.tsx +17 -1
  97. package/src/components/EntityPreview.tsx +4 -1
  98. package/src/components/ErrorBoundary.tsx +12 -18
  99. package/src/components/LicenseBanner.tsx +209 -0
  100. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +19 -2
  101. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +33 -7
  102. package/src/components/SelectableTable/filters/filter_text_input.test.ts +184 -0
  103. package/src/components/SelectableTable/filters/filter_text_input.ts +71 -0
  104. package/src/components/common/default_entity_actions.tsx +22 -2
  105. package/src/components/common/table_url_params.ts +172 -0
  106. package/src/components/common/useDataSourceTableController.tsx +86 -179
  107. package/src/components/common/useTableSearchHelper.ts +26 -2
  108. package/src/components/index.tsx +1 -0
  109. package/src/contexts/LicenseStatusContext.tsx +50 -0
  110. package/src/contexts/SnackbarProvider.tsx +14 -1
  111. package/src/core/DefaultAppBar.tsx +28 -16
  112. package/src/core/EntityEditView.tsx +20 -3
  113. package/src/core/EntityEditViewFormActions.tsx +3 -2
  114. package/src/core/EntitySidePanel.tsx +8 -4
  115. package/src/core/FireCMS.tsx +73 -47
  116. package/src/core/SideDialogs.tsx +44 -1
  117. package/src/core/index.tsx +1 -0
  118. package/src/core/plugin_translations.ts +87 -0
  119. package/src/core/pro_plugins.ts +79 -0
  120. package/src/editor/components/SlashCommandMenu.tsx +4 -1
  121. package/src/editor/extensions/Image.ts +17 -1
  122. package/src/editor/hooks/useProseMirror.ts +5 -0
  123. package/src/editor/index.ts +11 -1
  124. package/src/editor/lazy_editor.tsx +33 -0
  125. package/src/form/EntityForm.tsx +31 -10
  126. package/src/form/EntityFormActions.tsx +2 -0
  127. package/src/hooks/data/delete.ts +8 -0
  128. package/src/hooks/data/save.ts +6 -1
  129. package/src/hooks/data/useCollectionFetch.tsx +21 -1
  130. package/src/hooks/data/useEntityFetch.tsx +35 -4
  131. package/src/hooks/index.tsx +2 -0
  132. package/src/hooks/useBuildNavigationController.tsx +70 -15
  133. package/src/hooks/useFireCMSContext.tsx +7 -3
  134. package/src/hooks/useLicenseStatus.tsx +32 -0
  135. package/src/hooks/useProjectLog.tsx +27 -9
  136. package/src/hooks/useResolvedNavigationFrom.tsx +3 -1
  137. package/src/hooks/useTranslation.ts +3 -1
  138. package/src/hooks/useValidateAuthenticator.tsx +4 -0
  139. package/src/i18n/FireCMSi18nProvider.tsx +199 -25
  140. package/src/i18n/__tests__/FireCMSi18nProvider.test.tsx +91 -0
  141. package/src/i18n/__tests__/nested_provider_lifecycle.test.tsx +67 -0
  142. package/src/internal/useBuildDataSource.ts +37 -15
  143. package/src/internal/useBuildSideEntityController.tsx +19 -5
  144. package/src/locales/de.ts +23 -2
  145. package/src/locales/en.ts +26 -3
  146. package/src/locales/es.ts +23 -2
  147. package/src/locales/fr.ts +23 -2
  148. package/src/locales/hi.ts +23 -2
  149. package/src/locales/it.ts +23 -2
  150. package/src/locales/pl.ts +23 -2
  151. package/src/locales/pt.ts +23 -2
  152. package/src/preview/components/DatePreview.tsx +2 -4
  153. package/src/preview/components/ReferencePreview.tsx +7 -3
  154. package/src/preview/components/StorageThumbnail.tsx +24 -2
  155. package/src/routes/FireCMSRoute.tsx +11 -3
  156. package/src/types/analytics.ts +3 -0
  157. package/src/types/collections.ts +16 -0
  158. package/src/types/datasource.ts +67 -4
  159. package/src/types/entities.ts +24 -1
  160. package/src/types/entity_actions.tsx +6 -0
  161. package/src/types/entity_callbacks.ts +24 -0
  162. package/src/types/firecms.tsx +19 -1
  163. package/src/types/firecms_context.tsx +9 -0
  164. package/src/types/index.ts +1 -0
  165. package/src/types/license.ts +62 -0
  166. package/src/types/navigation.ts +35 -5
  167. package/src/types/plugins.tsx +23 -1
  168. package/src/types/properties.ts +17 -0
  169. package/src/types/side_entity_controller.tsx +7 -0
  170. package/src/types/translations.ts +41 -2
  171. package/src/util/__tests__/collections.test.ts +32 -0
  172. package/src/util/__tests__/urls.test.ts +131 -0
  173. package/src/util/collections.ts +1 -1
  174. package/src/util/date_fns_locales.ts +168 -0
  175. package/src/util/entities.ts +3 -1
  176. package/src/util/entity_cache.ts +18 -0
  177. package/src/util/index.ts +2 -0
  178. package/src/util/lazy_eager.tsx +30 -8
  179. package/src/util/navigation_from_path.ts +25 -2
  180. package/src/util/navigation_utils.ts +214 -3
  181. package/src/util/parent_references_from_path.ts +42 -3
  182. package/src/util/paths.ts +40 -3
  183. package/src/util/permissions.ts +22 -10
  184. package/src/util/previews.ts +8 -1
  185. package/src/util/resolutions.ts +8 -0
  186. package/src/util/urls.ts +52 -0
  187. package/src/util/useStorageUploadController.tsx +6 -1
package/src/locales/pt.ts CHANGED
@@ -70,8 +70,19 @@ export const pt: FireCMSTranslations = {
70
70
  select_from: "Selecionar de {{name}}",
71
71
  done: "Concluído",
72
72
  log_out: "Terminar sessão",
73
- license_needed: "Licença necessária",
74
- license_description: "Precisa de uma licença válida para usar o FireCMS PRO. Por favor contacte {{email}} para mais informações.",
73
+ license_trial_banner: "Avaliação do FireCMS PRO: faltam {{days}} dias em produção.",
74
+ license_trial_banner_one: "Avaliação do FireCMS PRO: falta {{days}} dia em produção.",
75
+ license_expired_banner: "A sua avaliação PRO terminou a {{date}}. As funcionalidades PRO, como o editor de esquemas, a importação/exportação e o histórico, estão em pausa; os seus dados e coleções continuam a funcionar.",
76
+ license_expired_banner_undated: "A sua avaliação PRO terminou. As funcionalidades PRO, como o editor de esquemas, a importação/exportação e o histórico, estão em pausa; os seus dados e coleções continuam a funcionar.",
77
+ license_invalid_project_banner: "Esta chave de licença não está associada ao projeto {{projectId}}. As funcionalidades PRO ficam em pausa até que esteja.",
78
+ license_invalid_project_banner_unnamed: "Esta chave de licença não está associada a este projeto. As funcionalidades PRO ficam em pausa até que esteja.",
79
+ license_over_quota_banner: "Esta licença cobre {{licensed}} projetos e tem {{actual}} associados.",
80
+ license_over_quota_banner_one: "Esta licença cobre {{licensed}} projeto e tem {{actual}} associados.",
81
+ license_get_license: "Obter uma licença",
82
+ license_update_license: "Atualizar a sua licença",
83
+ license_add_project_to_license: "Adicioná-lo à sua licença",
84
+ pro_features_paused: "Funcionalidades PRO em pausa",
85
+ user_management_paused_description: "A gestão de utilizadores e funções faz parte do FireCMS PRO. Os seus utilizadores e funções atuais continuam a decidir quem pode iniciar sessão e o que pode fazer, e poderá voltar a editá-los assim que este projeto tiver uma licença.",
75
86
  column_cannot_be_edited: "Esta coluna não pode ser editada diretamente",
76
87
  close: "Fechar",
77
88
  unsaved_local_changes: "Alterações locais não guardadas",
@@ -394,6 +405,9 @@ export const pt: FireCMSTranslations = {
394
405
  download: "Descarregar",
395
406
  large_number_of_documents: "Esta coleção tem um grande número de documentos ({{count}}).",
396
407
  include_undefined_values: "Incluir valores indefinidos",
408
+ export_apply_filter_sort: "Exportar apenas os resultados que correspondem ao filtro/ordenação atual",
409
+ export_all_entities: "Todas as entidades",
410
+ export_selected_entities: "Entidades selecionadas ({{count}})",
397
411
  submit: "Submeter",
398
412
 
399
413
  no_filterable_properties: "Não há propriedades filtráveis disponíveis",
@@ -443,6 +457,7 @@ export const pt: FireCMSTranslations = {
443
457
  roles_menu: "Funções",
444
458
  project_settings: "Definições do projeto",
445
459
  firestore_manager: "Gerenciador do Firestore",
460
+ datatalk: "DataTalk",
446
461
  manage_your_firestore_data: "Gerencie os seus dados do Firestore",
447
462
 
448
463
  // ─── FireCMS Cloud Login ──────────────────────────────────────
@@ -459,6 +474,7 @@ export const pt: FireCMSTranslations = {
459
474
  not_the_admin_panel: "não no painel de administração.",
460
475
  join_our_newsletter: "Subscreva a nossa newsletter. Sem spam, apenas atualizações importantes!",
461
476
  by_signing_in_you_agree_to_our: "Ao iniciar sessão, concorda com os nossos",
477
+ please_accept_the_terms_to_continue: "Aceite os termos e condições para continuar",
462
478
  terms_and_conditions: "Termos e Condições",
463
479
  and_our: "e a nossa",
464
480
  privacy_policy: "Política de privacidade",
@@ -501,6 +517,7 @@ export const pt: FireCMSTranslations = {
501
517
  licensed_projects_lowercase: "projetos licenciados",
502
518
  manage: "Gerir",
503
519
  update_payment_method: "Atualizar método de pagamento",
520
+ stripe_portal_error: "Não foi possível abrir o Stripe: {{message}}",
504
521
  your_firecms_cloud_projects: "Os seus projetos FireCMS Cloud",
505
522
  status_active: "Ativo",
506
523
  status_trialing: "Em avaliação",
@@ -595,6 +612,10 @@ export const pt: FireCMSTranslations = {
595
612
  order_property_not_found: "A propriedade \"{{property}}\" não existe ou não é uma propriedade numérica. Selecione uma propriedade válida ou limpe a seleção.",
596
613
  no_number_properties: "Nenhuma propriedade numérica encontrada. Adicione uma propriedade numérica para habilitar a ordenação.",
597
614
  order_property_description: "Selecione uma propriedade numérica para manter a ordem dos itens",
615
+ image_property: "Propriedade de imagem",
616
+ image_property_not_found: "A propriedade \"{{property}}\" não existe ou não é uma propriedade de imagem. Selecione uma propriedade válida ou limpe a seleção.",
617
+ no_image_properties: "Nenhuma propriedade de imagem encontrada. Adicione uma propriedade de armazenamento ou URL de imagem para usar como miniatura.",
618
+ image_property_description: "Selecione a propriedade usada como miniatura nas visualizações de Cartões e Kanban. Se não for definida, a primeira propriedade de imagem correspondente será usada automaticamente.",
598
619
  display_settings: "Configurações de exibição",
599
620
  default_row_size: "Tamanho de linha padrão",
600
621
  side_dialog_width: "Largura do diálogo lateral",
@@ -1,9 +1,8 @@
1
1
  import React from "react";
2
2
 
3
3
  import { format } from "date-fns";
4
- import * as locales from "date-fns/locale";
5
4
  import { useCustomizationController } from "../../hooks";
6
- import { defaultDateFormat } from "../../util";
5
+ import { defaultDateFormat, useDateFnsLocale } from "../../util";
7
6
 
8
7
  export interface DatePreviewProps {
9
8
  date: Date;
@@ -28,8 +27,7 @@ export function DatePreview({
28
27
  }: DatePreviewProps): React.ReactElement {
29
28
 
30
29
  const customizationController = useCustomizationController();
31
- // @ts-ignore
32
- const dateUtilsLocale = customizationController?.locale ? locales[customizationController?.locale] : undefined;
30
+ const dateUtilsLocale = useDateFnsLocale(customizationController?.locale);
33
31
 
34
32
  if (!date) {
35
33
  return <></>;
@@ -7,6 +7,7 @@ import { Skeleton } from "@firecms/ui";
7
7
  import { ErrorBoundary, ErrorView } from "../../components";
8
8
  import { EntityPreview, EntityPreviewContainer } from "../../components/EntityPreview";
9
9
  import { jsonStringifyReplacer } from "../../util/objects";
10
+ import { entityCacheKey } from "../../util/entity_cache";
10
11
 
11
12
  export type ReferencePreviewProps = {
12
13
  disabled?: boolean;
@@ -53,7 +54,7 @@ function ReferencePreviewInternal({
53
54
 
54
55
  const navigationController = useNavigationController();
55
56
 
56
- const collection = navigationController.getCollection(reference.path);
57
+ const collection = navigationController.getCollection(reference.path, false, reference.pathSegments);
57
58
  if (!collection) {
58
59
  if (customizationController.components?.missingReference) {
59
60
  return <customizationController.components.missingReference path={reference.path}/>;
@@ -99,16 +100,19 @@ function ReferencePreviewExisting<M extends Record<string, any> = any>({
99
100
  dataLoadingError
100
101
  } = useEntityFetch({
101
102
  path: reference.path,
103
+ // References carry the segments of the collection they point at, when the code that
104
+ // built them knew them (see getReferenceFrom).
105
+ pathSegments: reference.pathSegments,
102
106
  entityId: reference.id,
103
107
  collection,
104
108
  useCache: true
105
109
  });
106
110
 
107
111
  if (entity) {
108
- referencesCache.set(reference.pathWithId, entity);
112
+ referencesCache.set(entityCacheKey(reference.path, reference.id), entity);
109
113
  }
110
114
 
111
- const usedEntity = entity ?? referencesCache.get(reference.pathWithId);
115
+ const usedEntity = entity ?? referencesCache.get(entityCacheKey(reference.path, reference.id));
112
116
 
113
117
  let body: React.ReactNode;
114
118
 
@@ -6,6 +6,7 @@ import { useStorageSource } from "../../hooks";
6
6
  import { DownloadConfig, FileType } from "../../types";
7
7
  import { PreviewSize } from "../PropertyPreviewProps";
8
8
  import { ErrorView } from "../../components";
9
+ import { getPreviewTypeFromUrl, isAbsoluteHttpUrl } from "../../util/urls";
9
10
 
10
11
  type StorageThumbnailProps = {
11
12
  storagePathOrDownloadUrl: string;
@@ -41,10 +42,20 @@ export function StorageThumbnailInternal({
41
42
  const [error, setError] = React.useState<Error | undefined>(undefined);
42
43
  const storage = useStorageSource();
43
44
 
45
+ /**
46
+ * The saved value may already be a resolved download URL instead of a
47
+ * storage path: either because the property is configured with
48
+ * `storeUrl: true`, or because the value was written directly in the
49
+ * datasource pointing to an externally hosted file. Such values can be
50
+ * previewed as they are, and must not be handed over to the storage source,
51
+ * which can only resolve paths of its own bucket.
52
+ */
53
+ const valueIsUrl = storeUrl || isAbsoluteHttpUrl(storagePathOrDownloadUrl);
54
+
44
55
  const [downloadConfig, setDownloadConfig] = React.useState<DownloadConfig>(URL_CACHE[storagePathOrDownloadUrl]);
45
56
 
46
57
  useEffect(() => {
47
- if (!storagePathOrDownloadUrl)
58
+ if (!storagePathOrDownloadUrl || valueIsUrl)
48
59
  return;
49
60
  let unmounted = false;
50
61
  storage.getDownloadURL(storagePathOrDownloadUrl)
@@ -57,10 +68,21 @@ export function StorageThumbnailInternal({
57
68
  return () => {
58
69
  unmounted = true;
59
70
  };
60
- }, [storagePathOrDownloadUrl]);
71
+ }, [storagePathOrDownloadUrl, valueIsUrl]);
61
72
 
62
73
  if (!storagePathOrDownloadUrl) return null;
63
74
 
75
+ if (valueIsUrl) {
76
+ // There is no storage metadata for a value that is already a URL, so the
77
+ // preview type is inferred from its file extension when possible.
78
+ return <UrlComponentPreview previewType={getPreviewTypeFromUrl(storagePathOrDownloadUrl) ?? "file"}
79
+ url={storagePathOrDownloadUrl}
80
+ interactive={interactive}
81
+ size={size}
82
+ fill={fill}
83
+ hint={storagePathOrDownloadUrl} />;
84
+ }
85
+
64
86
  const filetype = downloadConfig?.metadata ? getFiletype(downloadConfig?.metadata.contentType) : undefined;
65
87
  const previewType = filetype?.startsWith("image")
66
88
  ? "image"
@@ -90,7 +90,7 @@ export function FireCMSRoute() {
90
90
  let collection: EntityCollection<any> | undefined;
91
91
  collection = navigation.getCollectionById(navigationEntries[0].id);
92
92
  if (!collection)
93
- collection = navigation.getCollection(navigationEntries[0].path);
93
+ collection = navigation.getCollection(navigationEntries[0].path, false, navigationEntries[0].pathSegments);
94
94
  if (!collection)
95
95
  return null;
96
96
  return <React.Suspense fallback={null}>
@@ -99,6 +99,7 @@ export function FireCMSRoute() {
99
99
  isSubCollection={false}
100
100
  parentCollectionIds={[]}
101
101
  fullPath={collection.path}
102
+ pathSegments={navigationEntries[0].pathSegments}
102
103
  fullIdPath={collection.id}
103
104
  updateUrl={true}
104
105
  {...collection}
@@ -113,7 +114,7 @@ export function FireCMSRoute() {
113
114
  const firstEntry = navigationEntries[0] as NavigationViewCollectionInternal<any>;
114
115
  collection = navigation.getCollectionById(firstEntry.id);
115
116
  if (!collection)
116
- collection = navigation.getCollection(firstEntry.path);
117
+ collection = navigation.getCollection(firstEntry.path, false, firstEntry.pathSegments);
117
118
  if (!collection)
118
119
  return null;
119
120
  return <React.Suspense fallback={null}>
@@ -123,6 +124,7 @@ export function FireCMSRoute() {
123
124
  isSubCollection={false}
124
125
  parentCollectionIds={[]}
125
126
  fullPath={collection.path}
127
+ pathSegments={firstEntry.pathSegments}
126
128
  updateUrl={true}
127
129
  {...collection}
128
130
  Actions={toArray(collection.Actions)} />
@@ -184,6 +186,8 @@ function EntityFullScreenRoute({
184
186
  const urlTab = getSelectedTabFromUrl(isNew, lastCustomView);
185
187
  const [selectedTab, setSelectedTab] = useState<string | undefined>(urlTab);
186
188
 
189
+ // `navigationPath` comes from the URL, where entity ids are still escaped (see
190
+ // encodeEntityId), so splitting it on "/" inside is correct and no segments are needed.
187
191
  const parentCollectionIds = navigation.getParentCollectionIds(navigationPath);
188
192
  useEffect(() => {
189
193
  if (urlTab !== selectedTab) {
@@ -248,13 +252,17 @@ function EntityFullScreenRoute({
248
252
  // `fullIdPath` is used downstream to build URLs, so it carries the escaped chain;
249
253
  // `collectionPath` addresses the datasource, so it is resolved from the raw one.
250
254
  const fullIdPath = isNew ? lastCollectionEntry!.fullPath : (parentCollectionEntry?.fullPath ?? lastEntityEntry!.fullPath);
251
- const collectionPath = navigation.resolveIdsFrom(isNew ? lastCollectionEntry!.path : lastEntityEntry!.path);
255
+ // The navigation entries know the real segment boundaries, so the path is resolved
256
+ // from them rather than re-parsed out of the flattened string.
257
+ const pathSegments = isNew ? lastCollectionEntry!.pathSegments : lastEntityEntry!.pathSegments;
258
+ const collectionPath = navigation.resolveIdsFrom(isNew ? lastCollectionEntry!.path : lastEntityEntry!.path, pathSegments);
252
259
  return <>
253
260
  <React.Suspense fallback={null}>
254
261
  <EntityEditView
255
262
  key={collection.id + "_" + (isNew ? "new" : (isCopy ? entityId + "_copy" : entityId))}
256
263
  entityId={isNew ? undefined : entityId}
257
264
  fullIdPath={fullIdPath}
265
+ pathSegments={pathSegments}
258
266
  collection={collection}
259
267
  layout={"full_screen"}
260
268
  path={collectionPath}
@@ -44,5 +44,8 @@ export type CMSAnalyticsEvent =
44
44
 
45
45
  | "card_view_entity_click"
46
46
 
47
+ | "entity_history_opened"
48
+ | "entity_history_reverted"
49
+
47
50
  | "unmapped_event"
48
51
  ;
@@ -101,6 +101,14 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
101
101
  */
102
102
  titleProperty?: keyof M;
103
103
 
104
+ /**
105
+ * Property used as the image thumbnail in Cards and Kanban view modes.
106
+ * Must reference a storage/image property (or an array of them), or a
107
+ * string property with `url: "image"`.
108
+ * If not specified, the first matching image property will be used.
109
+ */
110
+ imageProperty?: keyof M;
111
+
104
112
  /**
105
113
  * When editing an entity, you can choose to open the entity in a side dialog
106
114
  * or in a full screen dialog. Defaults to `full_screen`.
@@ -489,6 +497,14 @@ export interface CollectionActionsProps<M extends Record<string, any> = any, USE
489
497
  */
490
498
  path: string;
491
499
 
500
+ /**
501
+ * `path` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
502
+ *
503
+ * Optional, and never derived by splitting `path` — a parent entity id may itself
504
+ * contain "/". Absent means "not known here", not "no slashes".
505
+ */
506
+ pathSegments?: string[];
507
+
492
508
  /**
493
509
  * Path of the last collection, like `addresses`
494
510
  */
@@ -8,6 +8,20 @@ import { FireCMSContext } from "./firecms_context";
8
8
  */
9
9
  export interface FetchEntityProps<M extends Record<string, any> = any> {
10
10
  path: string;
11
+ /**
12
+ * The path split at its real segment boundaries, e.g.
13
+ * `["nodes", "node/42", "edges"]`.
14
+ *
15
+ * `path` is a single flattened string, so a parent entity id containing "/" cannot be
16
+ * recovered from it — `"nodes/node/42/edges"` is indistinguishable from a three-level
17
+ * nesting. This is the unambiguous form: one array element per segment, entity ids
18
+ * kept whole however many slashes they contain.
19
+ *
20
+ * For any backend whose ids cannot contain "/" this is exactly `path.split("/")`, so
21
+ * delegates that do not care can keep using `path`. Delegates that do should prefer
22
+ * `pathSegments ?? path.split("/")`.
23
+ */
24
+ pathSegments?: string[];
11
25
  entityId: string;
12
26
  databaseId?: string;
13
27
  collection?: EntityCollection<M, any>
@@ -28,6 +42,20 @@ export type ListenEntityProps<M extends Record<string, any> = any> =
28
42
  */
29
43
  export interface FetchCollectionProps<M extends Record<string, any> = any> {
30
44
  path: string;
45
+ /**
46
+ * The path split at its real segment boundaries, e.g.
47
+ * `["nodes", "node/42", "edges"]`.
48
+ *
49
+ * `path` is a single flattened string, so a parent entity id containing "/" cannot be
50
+ * recovered from it — `"nodes/node/42/edges"` is indistinguishable from a three-level
51
+ * nesting. This is the unambiguous form: one array element per segment, entity ids
52
+ * kept whole however many slashes they contain.
53
+ *
54
+ * For any backend whose ids cannot contain "/" this is exactly `path.split("/")`, so
55
+ * delegates that do not care can keep using `path`. Delegates that do should prefer
56
+ * `pathSegments ?? path.split("/")`.
57
+ */
58
+ pathSegments?: string[];
31
59
  collection?: EntityCollection<M> | ResolvedEntityCollection<M>;
32
60
  filter?: FilterValues<Extract<keyof M, string>>,
33
61
  limit?: number;
@@ -52,6 +80,20 @@ export type ListenCollectionProps<M extends Record<string, any> = any> =
52
80
  */
53
81
  export interface SaveEntityProps<M extends Record<string, any> = any> {
54
82
  path: string;
83
+ /**
84
+ * The path split at its real segment boundaries, e.g.
85
+ * `["nodes", "node/42", "edges"]`.
86
+ *
87
+ * `path` is a single flattened string, so a parent entity id containing "/" cannot be
88
+ * recovered from it — `"nodes/node/42/edges"` is indistinguishable from a three-level
89
+ * nesting. This is the unambiguous form: one array element per segment, entity ids
90
+ * kept whole however many slashes they contain.
91
+ *
92
+ * For any backend whose ids cannot contain "/" this is exactly `path.split("/")`, so
93
+ * delegates that do not care can keep using `path`. Delegates that do should prefer
94
+ * `pathSegments ?? path.split("/")`.
95
+ */
96
+ pathSegments?: string[];
55
97
  values: Partial<EntityValues<M>>;
56
98
  entityId?: string; // can be empty for new entities
57
99
  previousValues?: Partial<EntityValues<M>>;
@@ -64,6 +106,11 @@ export interface SaveEntityProps<M extends Record<string, any> = any> {
64
106
  */
65
107
  export interface DeleteEntityProps<M extends Record<string, any> = any> {
66
108
  entity: Entity<M>;
109
+ /**
110
+ * `entity.path` split at its real segment boundaries, when the caller knows them.
111
+ * Never derived from `entity.path` — see `pathSegments` on FetchCollectionProps.
112
+ */
113
+ pathSegments?: string[];
67
114
  collection?: EntityCollection<M> | ResolvedEntityCollection<M>;
68
115
  }
69
116
 
@@ -203,13 +250,19 @@ export interface DataSource {
203
250
  name: string,
204
251
  value: any,
205
252
  entityId?: string,
206
- collection?: EntityCollection
253
+ collection?: EntityCollection,
254
+ /**
255
+ * Appended last, and optional, so existing implementations and callers keep
256
+ * working unchanged — a function declaring fewer parameters stays assignable.
257
+ * See `pathSegments` on FetchCollectionProps.
258
+ */
259
+ pathSegments?: string[]
207
260
  ): Promise<boolean>;
208
261
 
209
262
  /**
210
263
  * Generate an id for a new entity
211
264
  */
212
- generateEntityId(path: string, collection: EntityCollection): string;
265
+ generateEntityId(path: string, collection: EntityCollection, pathSegments?: string[]): string;
213
266
 
214
267
  /**
215
268
  * Count the number of entities in a collection
@@ -230,6 +283,10 @@ export interface DataSource {
230
283
  initTextSearch?: (props: {
231
284
  context: FireCMSContext,
232
285
  path: string,
286
+ /**
287
+ * `path` split at its real segment boundaries, when known. Never derived from `path`.
288
+ */
289
+ pathSegments?: string[],
233
290
  collection: EntityCollection,
234
291
  parentCollectionIds?: string[]
235
292
  }) => Promise<boolean>;
@@ -237,6 +294,8 @@ export interface DataSource {
237
294
  }
238
295
 
239
296
  export type FilterCombinationValidProps = {
297
+ /** `path` split at its real segment boundaries — see `pathSegments` on FetchCollectionProps. */
298
+ pathSegments?: string[];
240
299
  path: string;
241
300
  collection: EntityCollection<any>;
242
301
  filterValues: FilterValues<any>;
@@ -366,12 +425,12 @@ export interface DataSourceDelegate {
366
425
  * @param collection
367
426
  * @return `true` if there are no other fields besides the given entity
368
427
  */
369
- checkUniqueField(path: string, name: string, value: any, entityId?: string, collection?: EntityCollection): Promise<boolean>;
428
+ checkUniqueField(path: string, name: string, value: any, entityId?: string, collection?: EntityCollection, pathSegments?: string[]): Promise<boolean>;
370
429
 
371
430
  /**
372
431
  * Generate an id for a new entity
373
432
  */
374
- generateEntityId(path: string, collection?: EntityCollection): string;
433
+ generateEntityId(path: string, collection?: EntityCollection, pathSegments?: string[]): string;
375
434
 
376
435
  /**
377
436
  * Count the number of entities in a collection
@@ -398,6 +457,10 @@ export interface DataSourceDelegate {
398
457
  initTextSearch?: (props: {
399
458
  context: FireCMSContext,
400
459
  path: string,
460
+ /**
461
+ * `path` split at its real segment boundaries, when known. Never derived from `path`.
462
+ */
463
+ pathSegments?: string[],
401
464
  databaseId?: string,
402
465
  collection: EntityCollection,
403
466
  parentCollectionIds?: string[]
@@ -21,6 +21,17 @@ export interface Entity<M extends object = any> {
21
21
  */
22
22
  path: string;
23
23
 
24
+ /**
25
+ * `path` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
26
+ *
27
+ * `path` is a single flattened string, so a parent entity id containing "/" cannot be
28
+ * recovered from it. This keeps each segment whole.
29
+ *
30
+ * Optional, and never derived by splitting `path` — a guess would be wrong in exactly
31
+ * the case the field exists for. Absent means "not known here", not "no slashes".
32
+ */
33
+ pathSegments?: string[];
34
+
24
35
  /**
25
36
  * Current values
26
37
  */
@@ -55,10 +66,22 @@ export class EntityReference {
55
66
  */
56
67
  readonly databaseId?: string;
57
68
 
58
- constructor(id: string, path: string, databaseId?: string) {
69
+ /**
70
+ * `path` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
71
+ *
72
+ * `path` is a single flattened string, so a parent entity id containing "/" cannot be
73
+ * recovered from it. This keeps each segment whole.
74
+ *
75
+ * Optional, and never derived by splitting `path` — a guess would be wrong in exactly
76
+ * the case the field exists for. Absent means "not known here", not "no slashes".
77
+ */
78
+ readonly pathSegments?: string[];
79
+
80
+ constructor(id: string, path: string, databaseId?: string, pathSegments?: string[]) {
59
81
  this.id = id;
60
82
  this.path = path;
61
83
  this.databaseId = databaseId;
84
+ this.pathSegments = pathSegments;
62
85
  }
63
86
 
64
87
  get pathWithId() {
@@ -65,6 +65,12 @@ export type EntityActionClickProps<M extends object, USER extends User = User> =
65
65
  context: FireCMSContext<USER>;
66
66
 
67
67
  fullPath?: string;
68
+ /**
69
+ * `fullPath` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`,
70
+ * when the caller knows them. Absent means "not known here" — never derived from
71
+ * `fullPath`, since a parent id may itself contain "/".
72
+ */
73
+ pathSegments?: string[];
68
74
  fullIdPath?: string;
69
75
  collection?: EntityCollection<M>;
70
76
 
@@ -86,6 +86,14 @@ export interface EntityOnFetchProps<M extends Record<string, any> = any, USER ex
86
86
  */
87
87
  path: string;
88
88
 
89
+ /**
90
+ * `path` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
91
+ *
92
+ * Optional, and never derived by splitting `path` — a parent entity id may itself
93
+ * contain "/". Absent means "not known here", not "no slashes".
94
+ */
95
+ pathSegments?: string[];
96
+
89
97
  /**
90
98
  * Fetched entity
91
99
  */
@@ -133,6 +141,14 @@ export interface EntityOnSaveProps<M extends Record<string, any> = any, USER ext
133
141
  */
134
142
  path: string;
135
143
 
144
+ /**
145
+ * `path` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
146
+ *
147
+ * Optional, and never derived by splitting `path` — a parent entity id may itself
148
+ * contain "/". Absent means "not known here", not "no slashes".
149
+ */
150
+ pathSegments?: string[];
151
+
136
152
  /**
137
153
  * Full path where this entity is being saved, with alias resolved
138
154
  */
@@ -180,6 +196,14 @@ export interface EntityOnDeleteProps<M extends Record<string, any> = any, USER e
180
196
  */
181
197
  path: string;
182
198
 
199
+ /**
200
+ * `path` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
201
+ *
202
+ * Optional, and never derived by splitting `path` — a parent entity id may itself
203
+ * contain "/". Absent means "not known here", not "no slashes".
204
+ */
205
+ pathSegments?: string[];
206
+
183
207
  /**
184
208
  * Deleted entity id
185
209
  */
@@ -70,10 +70,28 @@ export type FireCMSProps<USER extends User> = {
70
70
  }) => React.ReactNode;
71
71
 
72
72
  /**
73
- * If you have a custom API key, you can use it here.
73
+ * Your FireCMS PRO license key. It is checked against
74
+ * `https://api.firecms.co/access_log` when a user signs in.
74
75
  */
75
76
  apiKey?: string;
76
77
 
78
+ /**
79
+ * On sign-in FireCMS sends one request to `https://api.firecms.co/access_log`
80
+ * with the user's ID token (which carries the Firebase project id, the uid
81
+ * and the email), the user's email, the datasource key and the keys of the
82
+ * mounted plugins; the browser adds the page's referrer. It is how the
83
+ * FireCMS PRO license and trial are checked. No database credentials or
84
+ * content are sent.
85
+ *
86
+ * Set to `false` to skip that request. This only takes effect when the
87
+ * app has no `apiKey` and mounts no PRO plugin (collection editor, user
88
+ * management, import/export, entity history, data enhancement, DataTalk):
89
+ * otherwise the request is the license check and is always sent.
90
+ *
91
+ * Defaults to `true`.
92
+ */
93
+ telemetry?: boolean;
94
+
77
95
  /**
78
96
  * Record of custom form fields to be used in the CMS.
79
97
  * You can use the key to reference the custom field in
@@ -11,6 +11,7 @@ import { DialogsController } from "./dialogs_controller";
11
11
  import { CustomizationController } from "./customization_controller";
12
12
  import { AnalyticsController } from "./analytics_controller";
13
13
  import { InternalUserManagement } from "./internal_user_management";
14
+ import { AccessResponse } from "./license";
14
15
 
15
16
  /**
16
17
  * Context that includes the internal controllers and contexts used by the app.
@@ -97,4 +98,12 @@ export type FireCMSContext<USER extends User = User, AuthControllerType extends
97
98
  */
98
99
  userManagement: InternalUserManagement<USER>
99
100
 
101
+ /**
102
+ * FireCMS license status of this project, `null` until the license check
103
+ * answers or when it is not sent. PRO plugins are paused while `blocked`
104
+ * is true.
105
+ * @see useLicenseStatus
106
+ */
107
+ licenseStatus?: AccessResponse | null;
108
+
100
109
  };
@@ -29,3 +29,4 @@ export * from "./modify_collections";
29
29
  export * from "./analytics_controller";
30
30
  export * from "./customization_controller";
31
31
  export * from "./translations";
32
+ export * from "./license";
@@ -0,0 +1,62 @@
1
+ /**
2
+ * What the FireCMS license check says about this project.
3
+ *
4
+ * - `not_required`: no PRO plugin is mounted.
5
+ * - `whitelisted`: the project is exempt from licensing.
6
+ * - `licensed`: a license covers this project.
7
+ * - `trial`: PRO plugins run on the free trial; see `daysLeft` and `trialEndsAt`.
8
+ * - `expired`: the trial ended without a license. PRO plugins are paused.
9
+ * - `invalid_project`: a license key was presented, but this project is not
10
+ * linked to that license. PRO plugins are paused.
11
+ * - `over_quota`: the license has more projects linked than it pays for.
12
+ * Nothing is paused.
13
+ *
14
+ * @group Models
15
+ */
16
+ export type LicenseState =
17
+ | "not_required"
18
+ | "whitelisted"
19
+ | "licensed"
20
+ | "trial"
21
+ | "expired"
22
+ | "invalid_project"
23
+ | "over_quota";
24
+
25
+ /**
26
+ * Response of the FireCMS license check (`POST https://api.firecms.co/access_log`).
27
+ *
28
+ * Servers older than the PRO relaunch send only `blocked` and `message`, so
29
+ * treat a missing `licenseState` as unknown. `blocked` is honoured either way.
30
+ *
31
+ * @group Models
32
+ */
33
+ export type AccessResponse = {
34
+ /**
35
+ * True when the PRO plugins must be paused. The core CMS is never blocked.
36
+ */
37
+ blocked?: boolean;
38
+ licenseState?: LicenseState;
39
+ /**
40
+ * Number of projects the license pays for. Present with `licensed` and `over_quota`.
41
+ */
42
+ licensedProjects?: number;
43
+ /**
44
+ * Number of projects linked to the license.
45
+ */
46
+ linkedProjects?: number;
47
+ projectId?: string;
48
+ licenseId?: string;
49
+ /**
50
+ * ISO date the trial ends or ended. Present with `trial` and `expired`.
51
+ */
52
+ trialEndsAt?: string;
53
+ /**
54
+ * Whole days left in the trial, present with `trial`.
55
+ */
56
+ daysLeft?: number;
57
+ /**
58
+ * Where to get or update a license for this project.
59
+ */
60
+ subscribeUrl?: string;
61
+ message?: string;
62
+ };