@rebasepro/app 0.12.1-canary.gf5f1d39 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/api.d.ts +3 -8
  3. package/dist/auth/index.d.ts +2 -2
  4. package/dist/auth/types.d.ts +1 -5
  5. package/dist/collections/form-layout.d.ts +107 -0
  6. package/dist/collections/index.d.ts +1 -0
  7. package/dist/collections/title-property.d.ts +22 -0
  8. package/dist/components/Debug/collection-views/sample_data.d.ts +3 -2
  9. package/dist/hooks/ApiConfigContext.d.ts +36 -2
  10. package/dist/hooks/useBackendStorageSource.d.ts +6 -1
  11. package/dist/hooks/useNavigationBlocker.d.ts +1 -1
  12. package/dist/hooks/useSnackbarController.d.ts +2 -1
  13. package/dist/hooks/useStudioBridge.d.ts +27 -0
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.es.js +681 -186
  16. package/dist/index.es.js.map +1 -1
  17. package/dist/internal/common.d.ts +14 -2
  18. package/package.json +29 -25
  19. package/src/auth/api.ts +8 -9
  20. package/src/auth/index.ts +2 -2
  21. package/src/auth/types.ts +5 -6
  22. package/src/collections/form-layout.ts +386 -0
  23. package/src/collections/index.ts +1 -0
  24. package/src/collections/title-property.ts +41 -0
  25. package/src/components/Debug/UIReferenceView.tsx +9 -1
  26. package/src/components/Debug/collection-views/sample_data.ts +4 -2
  27. package/src/components/NotFoundPage.tsx +1 -1
  28. package/src/components/common/useDataTableController.tsx +1 -1
  29. package/src/core/Rebase.tsx +5 -1
  30. package/src/core/RebaseRouter.tsx +2 -1
  31. package/src/core/RebaseRoutes.tsx +1 -1
  32. package/src/hooks/ApiConfigContext.tsx +45 -3
  33. package/src/hooks/useAuthSubscription.ts +29 -3
  34. package/src/hooks/useBackendStorageSource.ts +8 -1
  35. package/src/hooks/useNavigationBlocker.tsx +1 -1
  36. package/src/hooks/useSnackbarController.tsx +34 -6
  37. package/src/hooks/useStudioBridge.tsx +50 -2
  38. package/src/index.ts +1 -1
  39. package/src/internal/common.tsx +15 -3
  40. package/src/internal/useRestoreScroll.tsx +1 -1
  41. package/src/locales/de.ts +23 -2
  42. package/src/locales/en.ts +23 -1
  43. package/src/locales/es.ts +23 -1
  44. package/src/locales/fr.ts +23 -1
  45. package/src/locales/hi.ts +23 -1
  46. package/src/locales/it.ts +23 -1
  47. package/src/locales/pt.ts +23 -1
  48. package/src/util/enums.ts +4 -2
  49. package/src/util/useStorageUploadController.tsx +5 -1
@@ -687,6 +687,14 @@ selected: true }, { name: "Tags" }].map(c => (
687
687
  <TextField label="With value" value="Filled value" onChange={() => {}}/>
688
688
  <TextField label="Error state" error value="Bad value" onChange={() => {}}/>
689
689
  <TextField label="Disabled" disabled value="Read only" onChange={() => {}}/>
690
+ {/* The smaller sizes are the ones dialogs use, and they
691
+ were absent here — which is how a labelled `small`
692
+ field shipped with its label sitting on top of its
693
+ placeholder. Keep all four so the collision is
694
+ visible on this page rather than in a modal. */}
695
+ <TextField size="medium" label="Medium" placeholder="Placeholder under the label"/>
696
+ <TextField size="small" label="Small" placeholder="Placeholder under the label"/>
697
+ <TextField size="smallest" label="Smallest" value="Filled value" onChange={() => {}}/>
690
698
  </div>
691
699
  </div>
692
700
  <div className="col-span-12 sm:col-span-6 flex flex-col gap-4">
@@ -851,7 +859,7 @@ roles: [] }
851
859
  <TableCell>
852
860
  <div className="flex flex-wrap gap-2">
853
861
  {user.roles.map(role => (
854
- <Chip key={role.id} colorScheme={role.isAdmin ? "purpleDark" : "blueDark"} size="small">
862
+ <Chip key={role.id} colorScheme={role.isAdmin ? "purple" : "blue"} size="small">
855
863
  {role.name}
856
864
  </Chip>
857
865
  ))}
@@ -1,3 +1,5 @@
1
+ import type { ChipColorKey } from "@rebasepro/ui";
2
+
1
3
  export type ProjectStatus = "backlog" | "in_progress" | "in_review" | "done";
2
4
  export type ProjectPriority = "low" | "medium" | "high" | "critical";
3
5
 
@@ -14,14 +16,14 @@ export interface SampleProject {
14
16
  createdAt: string;
15
17
  }
16
18
 
17
- export const STATUS_CONFIG: Record<ProjectStatus, { label: string; color: string }> = {
19
+ export const STATUS_CONFIG: Record<ProjectStatus, { label: string; color: ChipColorKey }> = {
18
20
  backlog: { label: "Backlog", color: "gray" },
19
21
  in_progress: { label: "In Progress", color: "blue" },
20
22
  in_review: { label: "In Review", color: "purple" },
21
23
  done: { label: "Done", color: "green" }
22
24
  };
23
25
 
24
- export const PRIORITY_CONFIG: Record<ProjectPriority, { label: string; color: string }> = {
26
+ export const PRIORITY_CONFIG: Record<ProjectPriority, { label: string; color: ChipColorKey }> = {
25
27
  low: { label: "Low", color: "gray" },
26
28
  medium: { label: "Medium", color: "yellow" },
27
29
  high: { label: "High", color: "orange" },
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Link } from "react-router-dom";
2
+ import { Link } from "react-router";
3
3
  import { Button, Typography } from "@rebasepro/ui";
4
4
  import { useTranslation } from "../hooks";
5
5
 
@@ -1,6 +1,6 @@
1
1
 
2
2
  import React, { useCallback, useEffect, useMemo, useState } from "react";
3
- import { useLocation } from "react-router-dom";
3
+ import { useLocation } from "react-router";
4
4
 
5
5
  import { useData, useRebaseContext } from "../../hooks";
6
6
  import { useDataOrder } from "../../hooks/data/useDataOrder";
@@ -415,7 +415,11 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
415
415
 
416
416
  if (resolvedApiUrl) {
417
417
  return (
418
- <ApiConfigProvider apiUrl={resolvedApiUrl} getAuthToken={authController.getAuthToken}>
418
+ <ApiConfigProvider
419
+ apiUrl={resolvedApiUrl}
420
+ apiPath={client?.apiPath}
421
+ getAuthToken={authController.getAuthToken}
422
+ >
419
423
  {content}
420
424
  </ApiConfigProvider>
421
425
  );
@@ -1,4 +1,5 @@
1
- import { createBrowserRouter, RouterProvider } from "react-router-dom"
1
+ import { createBrowserRouter } from "react-router"
2
+ import { RouterProvider } from "react-router/dom"
2
3
  import { removeInitialAndTrailingSlashes } from "@rebasepro/app";
3
4
 
4
5
  export function RebaseRouter({
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Route, Routes, useLocation } from "react-router-dom";
2
+ import { Route, Routes, useLocation } from "react-router";
3
3
 
4
4
 
5
5
  /**
@@ -9,9 +9,24 @@ import React, { useContext } from "react";
9
9
  */
10
10
  export interface ApiConfig {
11
11
  apiUrl: string;
12
+ /**
13
+ * The path the backend mounts its API under, appended to {@link apiUrl}.
14
+ *
15
+ * `"/api"` by default, which is the server's default `basePath`. It is
16
+ * carried here because a good deal of UI builds request URLs by hand
17
+ * instead of going through the client's typed methods, and every one of
18
+ * those sites used to write `/api` as a literal — so a backend configured
19
+ * with any other `basePath` served an admin panel whose auth-provider
20
+ * discovery, storage browser, history panel, user picker and Studio tools
21
+ * all requested paths that did not exist.
22
+ */
23
+ apiPath: string;
12
24
  getAuthToken?: () => Promise<string | null>;
13
25
  }
14
26
 
27
+ /** What the server uses when `basePath` is not configured. */
28
+ export const DEFAULT_API_PATH = "/api";
29
+
15
30
  const ApiConfigContext = React.createContext<ApiConfig | undefined>(undefined);
16
31
 
17
32
  /**
@@ -23,16 +38,43 @@ export function useApiConfig(): ApiConfig | undefined {
23
38
  }
24
39
 
25
40
  /**
26
- * Provide API configuration (apiUrl, getAuthToken) to the entire subtree.
41
+ * `apiUrl` and `apiPath` joined, with no trailing slash — the prefix to build a
42
+ * request URL from.
43
+ *
44
+ * Returns `undefined` when there is no provider, so a caller can keep its own
45
+ * fallback rather than silently requesting a relative path.
46
+ */
47
+ export function useApiBase(): string | undefined {
48
+ const config = useApiConfig();
49
+ if (!config?.apiUrl) return undefined;
50
+ return config.apiUrl.replace(/\/+$/, "") + (config.apiPath || DEFAULT_API_PATH);
51
+ }
52
+
53
+ /**
54
+ * The same prefix, derived from a client rather than from context.
55
+ *
56
+ * For the code that holds a `RebaseClient` but sits outside (or above) an
57
+ * `ApiConfigProvider`. Returns `undefined` when the client has no base URL,
58
+ * which is the signal every caller already treats as "cannot build a request".
59
+ */
60
+ export function apiBaseOf(client?: { baseUrl?: string; apiPath?: string }): string | undefined {
61
+ if (!client?.baseUrl) return undefined;
62
+ return client.baseUrl.replace(/\/+$/, "") + (client.apiPath || DEFAULT_API_PATH);
63
+ }
64
+
65
+ /**
66
+ * Provide API configuration (apiUrl, apiPath, getAuthToken) to the entire subtree.
27
67
  * Typically rendered inside `<Rebase>` or at the app root.
28
68
  */
29
69
  export function ApiConfigProvider({
30
70
  apiUrl,
71
+ apiPath = DEFAULT_API_PATH,
31
72
  getAuthToken,
32
73
  children
33
- }: ApiConfig & { children: React.ReactNode }) {
74
+ }: Omit<ApiConfig, "apiPath"> & { apiPath?: string; children: React.ReactNode }) {
34
75
  const value = React.useMemo(() => ({ apiUrl,
35
- getAuthToken }), [apiUrl, getAuthToken]);
76
+ apiPath,
77
+ getAuthToken }), [apiUrl, apiPath, getAuthToken]);
36
78
  return (
37
79
  <ApiConfigContext.Provider value={value}>
38
80
  {children}
@@ -8,16 +8,42 @@ export function useAuthSubscription(authClient?: AuthClient): AuthController {
8
8
  const currentSession = authClient?.getSession();
9
9
  const [user, setUser] = useState<User | null>(currentSession?.user ?? null);
10
10
 
11
- const [initialLoading, setInitialLoading] = useState(!currentSession);
12
11
  const [authLoading, setAuthLoading] = useState(false);
13
12
  const [authError, setAuthError] = useState<Error>();
14
13
  const [loginSkipped, setLoginSkipped] = useState(false);
15
14
  const [extra, setExtra] = useState<any>();
16
15
 
16
+ /**
17
+ * Whether asking the server "who am I?" could possibly answer.
18
+ *
19
+ * A client configured with `persistSession: false` and JSON auth — the
20
+ * shape used when one client borrows another's credential, as the hosted
21
+ * Studio does — has no stored session and no auth cookie, so the probe
22
+ * below is guaranteed to 401. Sending it anyway put three failing
23
+ * `GET /auth/me` calls on the wire per mount, each one landing in the
24
+ * *customer's* request log as an authentication failure against their own
25
+ * backend.
26
+ *
27
+ * A client that does not implement the capability is treated as "might
28
+ * work" — the historical behaviour.
29
+ */
30
+ const mayHaveRestorableSession = authClient?.canRestoreSession?.() ?? true;
31
+
32
+ /**
33
+ * `true` only while a probe is genuinely outstanding.
34
+ *
35
+ * It used to be seeded from `!currentSession` alone, which left a client
36
+ * that cannot restore a session — and therefore never runs the probe that
37
+ * clears the flag — reporting "still loading" forever. Anything gated on
38
+ * `initialLoading` (a spinner, a redirect to the login view) would never
39
+ * resolve.
40
+ */
41
+ const [initialLoading, setInitialLoading] = useState(!currentSession && mayHaveRestorableSession);
42
+
17
43
  useEffect(() => {
18
44
  if (!authClient) return;
19
45
  // If we don't have a session initially, try to get user which restores session if a persistent token exists
20
- if (!currentSession) {
46
+ if (!currentSession && mayHaveRestorableSession) {
21
47
  setInitialLoading(true);
22
48
  authClient.getUser()
23
49
  .then(user => {
@@ -32,7 +58,7 @@ export function useAuthSubscription(authClient?: AuthClient): AuthController {
32
58
  setInitialLoading(false);
33
59
  });
34
60
  }
35
- }, [authClient, currentSession]);
61
+ }, [authClient, currentSession, mayHaveRestorableSession]);
36
62
 
37
63
  useEffect(() => {
38
64
  if (!authClient) return;
@@ -10,12 +10,18 @@ import {
10
10
  DownloadConfig,
11
11
  StorageListResult
12
12
  } from "@rebasepro/types";
13
+ import { DEFAULT_API_PATH } from "./ApiConfigContext";
13
14
 
14
15
  export interface BackendStorageSourceProps {
15
16
  /**
16
17
  * Backend API URL (e.g., 'http://localhost:3001')
17
18
  */
18
19
  apiUrl: string;
20
+ /**
21
+ * The path the backend mounts its API under. Defaults to the server's own
22
+ * default; pass the backend's `basePath` if it was configured otherwise.
23
+ */
24
+ apiPath?: string;
19
25
  /**
20
26
  * Function to get the current auth token
21
27
  */
@@ -39,10 +45,11 @@ export interface BackendStorageSourceProps {
39
45
  */
40
46
  export function useBackendStorageSource({
41
47
  apiUrl,
48
+ apiPath = DEFAULT_API_PATH,
42
49
  getAuthToken
43
50
  }: BackendStorageSourceProps): StorageSource {
44
51
 
45
- const storageBasePath = `${apiUrl}/api/storage`;
52
+ const storageBasePath = `${apiUrl.replace(/\/+$/, "")}${apiPath}/storage`;
46
53
 
47
54
  // Cache for download URLs to avoid redundant API calls
48
55
  const urlsCache = useMemo(() => new Map<string, DownloadConfig>(), []);
@@ -1,5 +1,5 @@
1
1
  import React, { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
2
- import { Blocker, BlockerFunction, useBlocker } from "react-router-dom";
2
+ import { Blocker, BlockerFunction, useBlocker } from "react-router";
3
3
 
4
4
  const IDLE_BLOCKER: Blocker = {
5
5
  state: "unblocked",
@@ -1,7 +1,7 @@
1
- import type { SnackbarMessageType } from "@rebasepro/admin-types";
1
+ import type { SnackbarAction, SnackbarMessageType } from "@rebasepro/admin-types";
2
2
  import React, { useCallback, useMemo } from "react";
3
3
  import { useSnackbar } from "notistack";
4
- ;
4
+ import { Button } from "@rebasepro/ui";
5
5
 
6
6
  /**
7
7
  * Hook to retrieve the SnackbarContext.
@@ -24,11 +24,13 @@ export const useSnackbarController = () => {
24
24
  title?: string;
25
25
  message: React.ReactNode;
26
26
  autoHideDuration?: number;
27
+ action?: SnackbarAction;
27
28
  }) => {
28
29
  const {
29
30
  type,
30
31
  message,
31
- autoHideDuration
32
+ autoHideDuration,
33
+ action
32
34
  } = props;
33
35
  enqueueSnackbar({
34
36
  message: props.title ? <div className={"flex flex-col"}>
@@ -36,13 +38,36 @@ export const useSnackbarController = () => {
36
38
  {message}
37
39
  </div> : message,
38
40
  variant: type,
39
- autoHideDuration
41
+ autoHideDuration,
42
+ // The provider sets `preventDuplicate`, which collapses two
43
+ // snackbars carrying the same message into one. That is right for a
44
+ // confirmation and wrong for an action: deleting two rows in a row
45
+ // produces the same "Deleted" text twice, and swallowing the second
46
+ // would leave the second deletion with no way to undo it. A key
47
+ // makes each one distinct.
48
+ ...(action ? { key: nextActionKey() } : {}),
49
+ action: action
50
+ ? (key) => (
51
+ <Button variant={"text"}
52
+ size={"small"}
53
+ className={"text-inherit"}
54
+ onClick={() => {
55
+ action.onClick();
56
+ // The offer has been taken; leaving it up
57
+ // invites a second click that undoes nothing,
58
+ // or worse, undoes something else.
59
+ closeSnackbar(key);
60
+ }}>
61
+ {action.label}
62
+ </Button>
63
+ )
64
+ : undefined
40
65
  })
41
- }, []);
66
+ }, [enqueueSnackbar, closeSnackbar]);
42
67
 
43
68
  const close = useCallback(() => {
44
69
  closeSnackbar();
45
- }, []);
70
+ }, [closeSnackbar]);
46
71
 
47
72
  return useMemo(() => ({
48
73
  open,
@@ -50,3 +75,6 @@ export const useSnackbarController = () => {
50
75
  }), [open, close]);
51
76
 
52
77
  };
78
+
79
+ let actionKeySeq = 0;
80
+ const nextActionKey = () => `rebase-snackbar-action-${actionKeySeq++}`;
@@ -28,6 +28,32 @@ export interface StudioBridge {
28
28
  urlController: UrlController;
29
29
  navigationState: NavigationStateController;
30
30
  breadcrumbs: BreadcrumbsController;
31
+ capabilities: StudioCapabilities;
32
+ }
33
+
34
+ /**
35
+ * What the *host* of these tools can do, as opposed to what the backend can.
36
+ *
37
+ * Studio is mounted in two very different places. In a project's own admin
38
+ * panel it runs next to the collection source files and can edit them through
39
+ * the schema-editor routes. In the hosted console it runs against somebody
40
+ * else's deployed container: there is no source to edit — the container is
41
+ * rebuilt from the customer's repository on every deploy — and the routes that
42
+ * would edit it are not mounted at all, because the framework switches the
43
+ * schema editor off under `NODE_ENV=production`.
44
+ *
45
+ * Tools that would otherwise offer a write into the codebase read this to
46
+ * decide whether that write is even meaningful.
47
+ */
48
+ export interface StudioCapabilities {
49
+ /**
50
+ * Whether the host has the project's collection source at hand and can
51
+ * write to it.
52
+ *
53
+ * Defaults to `true`, which is what an admin panel running beside its own
54
+ * `collectionsDir` has always assumed.
55
+ */
56
+ codebase: boolean;
31
57
  }
32
58
 
33
59
  // ─── Noop defaults ──────────────────────────────────────────────────
@@ -71,12 +97,17 @@ const NOOP_BREADCRUMBS: BreadcrumbsController = {
71
97
  updateCount: () => {}
72
98
  };
73
99
 
100
+ const DEFAULT_CAPABILITIES: StudioCapabilities = {
101
+ codebase: true
102
+ };
103
+
74
104
  const NOOP_BRIDGE: StudioBridge = {
75
105
  collectionRegistry: NOOP_COLLECTION_REGISTRY,
76
106
  sidePanelController: NOOP_SIDE_PANEL,
77
107
  urlController: NOOP_URL_CONTROLLER,
78
108
  navigationState: NOOP_NAVIGATION_STATE,
79
- breadcrumbs: NOOP_BREADCRUMBS
109
+ breadcrumbs: NOOP_BREADCRUMBS,
110
+ capabilities: DEFAULT_CAPABILITIES
80
111
  };
81
112
 
82
113
  // ─── Context & Provider ─────────────────────────────────────────────
@@ -107,10 +138,22 @@ export function StudioBridgeProvider({
107
138
  value: Partial<StudioBridge>;
108
139
  children: React.ReactNode;
109
140
  }) {
141
+ // Keyed on the slices rather than on `value`: callers write the object
142
+ // inline (`value={{ collectionRegistry }}`), so a `[value]` dependency
143
+ // rebuilt the context on every render of the host and re-rendered every
144
+ // Studio consumer with it.
110
145
  const merged = React.useMemo(
111
146
  () => ({ ...NOOP_BRIDGE,
112
147
  ...value }),
113
- [value]
148
+ // eslint-disable-next-line react-hooks/exhaustive-deps
149
+ [
150
+ value.collectionRegistry,
151
+ value.sidePanelController,
152
+ value.urlController,
153
+ value.navigationState,
154
+ value.breadcrumbs,
155
+ value.capabilities
156
+ ]
114
157
  );
115
158
  return (
116
159
  <StudioBridgeContext.Provider value={merged}>
@@ -146,6 +189,11 @@ export function useStudioBreadcrumbs(): BreadcrumbsController {
146
189
  return useContext(StudioBridgeContext).breadcrumbs;
147
190
  }
148
191
 
192
+ /** What the host can do — see {@link StudioCapabilities}. */
193
+ export function useStudioCapabilities(): StudioCapabilities {
194
+ return useContext(StudioBridgeContext).capabilities;
195
+ }
196
+
149
197
  // ─── Self-Assembling Bridge Registry ────────────────────────────────
150
198
 
151
199
  /**
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ export * from "./contexts";
9
9
  // @rebasepro/admin and @rebasepro/studio consume them. Named explicitly
10
10
  // (not `export *`) so this file is the single place that grows or shrinks
11
11
  // the internal surface — see the JSDoc on each symbol for details.
12
- export { CONTAINER_FULL_WIDTH, ADDITIONAL_TAB_WIDTH, FORM_CONTAINER_WIDTH } from "./internal/common";
12
+ export { CONTAINER_FULL_WIDTH, FORM_CONTAINER_WIDTH, SIDE_PANEL_DEFAULT_WIDTH } from "./internal/common";
13
13
  export { useRestoreScroll } from "./internal/useRestoreScroll";
14
14
  export { useUnsavedChangesDialog } from "./hooks/useUnsavedChangesDialog";
15
15
  export { NavigationBlockerProvider, useNavigationBlocker } from "./hooks/useNavigationBlocker";
@@ -9,8 +9,20 @@
9
9
  */
10
10
  export const CONTAINER_FULL_WIDTH = "100vw";
11
11
 
12
- /** @internal See {@link CONTAINER_FULL_WIDTH}. */
13
- export const ADDITIONAL_TAB_WIDTH = "55vw";
14
-
15
12
  /** @internal See {@link CONTAINER_FULL_WIDTH}. */
16
13
  export const FORM_CONTAINER_WIDTH = "768px";
14
+
15
+ /**
16
+ * How wide the side panel opens when neither the caller nor the collection
17
+ * says otherwise.
18
+ *
19
+ * Sized from what it has to hold rather than from a round number: the form's
20
+ * content column plus its metadata rail plus gutters, which is also the width
21
+ * at which the form stops being a single stack and picks up the same
22
+ * two-column layout it has in full screen. Capped against the viewport so a
23
+ * panel never becomes the whole window on a laptop — at that point you want
24
+ * full screen, and there is a button for it.
25
+ *
26
+ * @internal See {@link CONTAINER_FULL_WIDTH}.
27
+ */
28
+ export const SIDE_PANEL_DEFAULT_WIDTH = "min(1160px, 88vw)";
@@ -1,5 +1,5 @@
1
1
  import React, { useCallback, useEffect, useRef } from "react";
2
- import { useLocation } from "react-router-dom";
2
+ import { useLocation } from "react-router";
3
3
 
4
4
  const scrollsMap: Record<string, number> = {};
5
5
 
package/src/locales/de.ts CHANGED
@@ -41,6 +41,8 @@ export const de: RebaseTranslations = {
41
41
  clear_filter: "Filter löschen",
42
42
  clear_filter_sort: "Filter/Sortierung löschen",
43
43
  clear_sort: "Sortierung löschen",
44
+ sort: "Sortieren",
45
+ sort_by: "Sortieren nach",
44
46
  clear_all: "Alle löschen",
45
47
  no_items: "Keine Elemente",
46
48
  no_entries_found: "Keine Einträge gefunden",
@@ -74,6 +76,7 @@ export const de: RebaseTranslations = {
74
76
  log_out: "Abmelden",
75
77
  column_cannot_be_edited: "Diese Spalte kann nicht direkt bearbeitet werden",
76
78
  close: "Schließen",
79
+ hide_list: "Liste ausblenden",
77
80
  unsaved_local_changes: "Ungespeicherte lokale Änderungen",
78
81
  unsaved_local_changes_description: "Dieses Dokument wurde lokal bearbeitet und weist ungespeicherte Änderungen auf. Diese lokalen Änderungen gehen verloren, wenn Sie sie nicht anwenden.",
79
82
  preview_changes: "Änderungen in der Vorschau anzeigen",
@@ -902,6 +905,24 @@ no_filter: "No filter",
902
905
  // ─── Markdown property ──────────────────────────────────────
903
906
  markdown_paste_behavior: "Einfügeverhalten",
904
907
  markdown_strip_html: "HTML beim Einfügen entfernen",
905
- markdown_convert_pasted: "Eingefügten Text in Markdown umwandeln"
908
+ markdown_convert_pasted: "Eingefügten Text in Markdown umwandeln",
909
+
910
+ // ─── Entity identity bar and inspector ──────────────────────
911
+ entity_saved: "Gespeichert",
912
+ more_actions: "Weitere Aktionen",
913
+ record_history: "Verlauf",
914
+ inspect_record: "Datensatz inspizieren",
915
+ inspect: "Inspizieren",
916
+ close_inspector: "Inspektor schließen",
917
+
918
+ // ─── Revision history ───────────────────────────────────────
919
+ history_revert: "Auf diese Version zurücksetzen",
920
+ history_revert_title: "Daten auf diese Version zurücksetzen?",
921
+ history_reverted: "Auf die gewählte Version zurückgesetzt",
922
+ history_revert_error: "Fehler beim Zurücksetzen des Datensatzes",
923
+ history_revert_dirty: "Bitte speichere oder verwirf deine Änderungen vor dem Zurücksetzen",
924
+ history_empty: "Kein Verlauf vorhanden",
925
+ history_empty_description: "Beim Speichern eines Datensatzes wird eine neue Version erstellt und im Verlauf abgelegt.",
926
+ history_new_entity: "Der Verlauf ist nur für vorhandene Datensätze verfügbar",
927
+ history_no_more: "Kein weiterer Verlauf"
906
928
  };
907
-
package/src/locales/en.ts CHANGED
@@ -51,6 +51,8 @@ export const en: RebaseTranslations = {
51
51
  clear_filter: "Clear filter",
52
52
  clear_filter_sort: "Clear filter/sort",
53
53
  clear_sort: "Clear sort",
54
+ sort: "Sort",
55
+ sort_by: "Sort by",
54
56
  clear_all: "Clear all",
55
57
  no_items: "No items",
56
58
  no_entries_found: "No entries found",
@@ -84,6 +86,7 @@ export const en: RebaseTranslations = {
84
86
  log_out: "Log out",
85
87
  column_cannot_be_edited: "This column can't be edited directly",
86
88
  close: "Close",
89
+ hide_list: "Hide list",
87
90
  unsaved_local_changes: "Unsaved Local changes",
88
91
  unsaved_local_changes_description: "This document was edited locally and has unsaved changes. These local changes will be lost if you don't apply them.",
89
92
  preview_changes: "Preview Changes",
@@ -981,5 +984,24 @@ export const en: RebaseTranslations = {
981
984
  // ─── Markdown property ──────────────────────────────────────
982
985
  markdown_paste_behavior: "Paste behavior",
983
986
  markdown_strip_html: "Strip HTML on paste",
984
- markdown_convert_pasted: "Convert pasted text to markdown"
987
+ markdown_convert_pasted: "Convert pasted text to markdown",
988
+
989
+ // ─── Entity identity bar and inspector ──────────────────────
990
+ entity_saved: "Saved",
991
+ more_actions: "More actions",
992
+ record_history: "History",
993
+ inspect_record: "Inspect record",
994
+ inspect: "Inspect",
995
+ close_inspector: "Close inspector",
996
+
997
+ // ─── Revision history ───────────────────────────────────────
998
+ history_revert: "Revert to this version",
999
+ history_revert_title: "Revert data to this version?",
1000
+ history_reverted: "Reverted to selected version",
1001
+ history_revert_error: "Error reverting entity",
1002
+ history_revert_dirty: "Please save or discard your changes before reverting",
1003
+ history_empty: "No history available",
1004
+ history_empty_description: "When you save a record, a new version is created and stored in the history.",
1005
+ history_new_entity: "History is only available for existing records",
1006
+ history_no_more: "No more history"
985
1007
  };
package/src/locales/es.ts CHANGED
@@ -49,6 +49,8 @@ export const es: RebaseTranslations = {
49
49
  clear_filter: "Borrar filtro",
50
50
  clear_filter_sort: "Borrar filtro/orden",
51
51
  clear_sort: "Borrar orden",
52
+ sort: "Ordenar",
53
+ sort_by: "Ordenar por",
52
54
  clear_all: "Borrar todo",
53
55
  no_items: "Sin elementos",
54
56
  no_entries_found: "No se encontraron entradas",
@@ -82,6 +84,7 @@ export const es: RebaseTranslations = {
82
84
  log_out: "Cerrar sesión",
83
85
  column_cannot_be_edited: "Esta columna no se puede editar directamente",
84
86
  close: "Cerrar",
87
+ hide_list: "Ocultar lista",
85
88
  unsaved_local_changes: "Cambios locales sin guardar",
86
89
  unsaved_local_changes_description: "Este documento ha sido editado localmente y tiene cambios sin guardar. Estos cambios locales se perderán si no los aplicas.",
87
90
  preview_changes: "Previsualizar cambios",
@@ -923,5 +926,24 @@ no_filter: "No filter",
923
926
  // ─── Markdown property ──────────────────────────────────────
924
927
  markdown_paste_behavior: "Comportamiento al pegar",
925
928
  markdown_strip_html: "Eliminar HTML al pegar",
926
- markdown_convert_pasted: "Convertir texto pegado a markdown"
929
+ markdown_convert_pasted: "Convertir texto pegado a markdown",
930
+
931
+ // ─── Entity identity bar and inspector ──────────────────────
932
+ entity_saved: "Guardado",
933
+ more_actions: "Más acciones",
934
+ record_history: "Historial",
935
+ inspect_record: "Inspeccionar registro",
936
+ inspect: "Inspeccionar",
937
+ close_inspector: "Cerrar el inspector",
938
+
939
+ // ─── Revision history ───────────────────────────────────────
940
+ history_revert: "Volver a esta versión",
941
+ history_revert_title: "¿Volver a esta versión de los datos?",
942
+ history_reverted: "Se volvió a la versión seleccionada",
943
+ history_revert_error: "Error al revertir el registro",
944
+ history_revert_dirty: "Guarda o descarta tus cambios antes de revertir",
945
+ history_empty: "No hay historial disponible",
946
+ history_empty_description: "Al guardar un registro se crea una nueva versión y se almacena en el historial.",
947
+ history_new_entity: "El historial solo está disponible para registros existentes",
948
+ history_no_more: "No hay más historial"
927
949
  };
package/src/locales/fr.ts CHANGED
@@ -41,6 +41,8 @@ export const fr: RebaseTranslations = {
41
41
  clear_filter: "Effacer le filtre",
42
42
  clear_filter_sort: "Effacer le filtre/tri",
43
43
  clear_sort: "Effacer le tri",
44
+ sort: "Trier",
45
+ sort_by: "Trier par",
44
46
  clear_all: "Tout effacer",
45
47
  no_items: "Aucun élément",
46
48
  no_entries_found: "Aucune entrée trouvée",
@@ -74,6 +76,7 @@ export const fr: RebaseTranslations = {
74
76
  log_out: "Se déconnecter",
75
77
  column_cannot_be_edited: "Cette colonne ne peut pas être modifiée directement",
76
78
  close: "Fermer",
79
+ hide_list: "Masquer la liste",
77
80
  unsaved_local_changes: "Modifications locales non enregistrées",
78
81
  unsaved_local_changes_description: "Ce document a été modifié localement et contient des modifications non enregistrées. Ces modifications locales seront perdues si vous ne les appliquez pas.",
79
82
  preview_changes: "Aperçu des modifications",
@@ -902,5 +905,24 @@ no_filter: "No filter",
902
905
  // ─── Markdown property ──────────────────────────────────────
903
906
  markdown_paste_behavior: "Comportement au collage",
904
907
  markdown_strip_html: "Supprimer le HTML au collage",
905
- markdown_convert_pasted: "Convertir le texte collé en markdown"
908
+ markdown_convert_pasted: "Convertir le texte collé en markdown",
909
+
910
+ // ─── Entity identity bar and inspector ──────────────────────
911
+ entity_saved: "Enregistré",
912
+ more_actions: "Plus d'actions",
913
+ record_history: "Historique",
914
+ inspect_record: "Inspecter l'enregistrement",
915
+ inspect: "Inspecter",
916
+ close_inspector: "Fermer l'inspecteur",
917
+
918
+ // ─── Revision history ───────────────────────────────────────
919
+ history_revert: "Revenir à cette version",
920
+ history_revert_title: "Revenir à cette version des données ?",
921
+ history_reverted: "Retour à la version sélectionnée",
922
+ history_revert_error: "Erreur lors du retour en arrière",
923
+ history_revert_dirty: "Enregistrez ou annulez vos modifications avant de revenir en arrière",
924
+ history_empty: "Aucun historique disponible",
925
+ history_empty_description: "À chaque enregistrement, une nouvelle version est créée et conservée dans l'historique.",
926
+ history_new_entity: "L'historique n'est disponible que pour les enregistrements existants",
927
+ history_no_more: "Plus d'historique"
906
928
  };