rimecms 0.31.3 → 0.31.5

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 (88) hide show
  1. package/README.md +6 -207
  2. package/dist/adapter-sqlite/auth.server.d.ts +1 -1
  3. package/dist/adapter-sqlite/generate-schema/templates.server.d.ts +2 -2
  4. package/dist/adapter-sqlite/generate-schema/templates.server.js +3 -1
  5. package/dist/adapter-sqlite/generate-schema/write.server.js +7 -3
  6. package/dist/adapter-sqlite/transform.server.js +1 -1
  7. package/dist/core/collections/upload/util/path.d.ts +2 -1
  8. package/dist/core/collections/upload/util/path.js +3 -3
  9. package/dist/core/config/auth/better-auth-hooks.server.d.ts +1 -1
  10. package/dist/core/config/auth/better-auth-hooks.server.js +1 -2
  11. package/dist/core/config/auth/better-auth-permissions.d.ts +27 -67
  12. package/dist/core/config/auth/better-auth.server.d.ts +649 -481
  13. package/dist/core/config/auth/better-auth.server.js +2 -1
  14. package/dist/core/config/server/write.server.js +5 -0
  15. package/dist/core/constant.d.ts +10 -0
  16. package/dist/core/constant.js +22 -0
  17. package/dist/core/dev/cli/build/templates.js +2 -0
  18. package/dist/core/dev/cli/clear/index.server.js +5 -5
  19. package/dist/core/dev/cli/init/index.server.js +15 -28
  20. package/dist/core/dev/cli/init/templates.d.ts +14 -0
  21. package/dist/core/dev/cli/init/templates.js +58 -3
  22. package/dist/core/dev/constants.d.ts +20 -7
  23. package/dist/core/dev/constants.js +58 -10
  24. package/dist/core/dev/generate/routes/common.server.js +36 -33
  25. package/dist/core/dev/generate/routes/index.server.d.ts +6 -3
  26. package/dist/core/dev/generate/routes/index.server.js +13 -7
  27. package/dist/core/dev/generate/routes/util.server.d.ts +14 -6
  28. package/dist/core/dev/generate/routes/util.server.js +17 -6
  29. package/dist/core/dev/generate/sanitize/index.server.js +3 -3
  30. package/dist/core/dev/generate/types/index.server.js +3 -2
  31. package/dist/core/dev/vite/index.server.js +4 -8
  32. package/dist/core/ensure.server.d.ts +2 -2
  33. package/dist/core/ensure.server.js +17 -8
  34. package/dist/core/handlers/auth.server.js +18 -11
  35. package/dist/core/handlers/routes.server.d.ts +1 -1
  36. package/dist/core/handlers/routes.server.js +4 -3
  37. package/dist/core/operations/hooks/before-read/populate-url.server.js +2 -1
  38. package/dist/core/plugins/cache/handler.server.js +1 -1
  39. package/dist/core/rime.server.d.ts +1302 -966
  40. package/dist/core/rime.server.js +6 -0
  41. package/dist/fields/number/component/number.css +0 -2
  42. package/dist/fields/relation/component/upload/upload.css +9 -0
  43. package/dist/fields/rich-text/component/drag-handle/drag-handle.css +0 -1
  44. package/dist/fields/rich-text/component/styles/rich-text.css +7 -0
  45. package/dist/fields/rich-text/core/extensions/drag-handle/helpers/findNextElementFromCursor.d.ts +1 -1
  46. package/dist/fields/rich-text/core/svelte/node-view-wrapper.svelte.d.ts +12 -5
  47. package/dist/panel/components/sections/collection/header/SelectUI.svelte.d.ts +3 -3
  48. package/dist/panel/components/sections/collection/list/row/Row.svelte +2 -1
  49. package/dist/panel/components/sections/collection/tree/CollectionTreeNode.svelte +2 -1
  50. package/dist/panel/components/sections/document/AuthAPIKeyDialog.svelte.d.ts +3 -3
  51. package/dist/panel/components/sections/document/AuthFooter.svelte +2 -1
  52. package/dist/panel/components/sections/document/Versions.svelte +1 -1
  53. package/dist/panel/components/sections/live/Consumer.svelte.d.ts +11 -4
  54. package/dist/panel/components/sections/live/LiveEdit.svelte.d.ts +13 -6
  55. package/dist/panel/components/sections/live/LiveFloatingUI.svelte.d.ts +3 -3
  56. package/dist/panel/components/ui/calendar/calendar.css +0 -11
  57. package/dist/panel/components/ui/card-resource/card-resource.svelte +2 -1
  58. package/dist/panel/components/ui/command/command-group.css +0 -1
  59. package/dist/panel/components/ui/label/label.css +1 -0
  60. package/dist/panel/components/ui/nav/Nav.svelte +1 -1
  61. package/dist/panel/components/ui/nav/NavItem.svelte +3 -2
  62. package/dist/panel/components/ui/nav/UserButton.svelte +3 -3
  63. package/dist/panel/components/ui/radio-group/radio-group.css +0 -2
  64. package/dist/panel/components/ui/scroll-area/scroll-area.css +4 -0
  65. package/dist/panel/components/ui/sheet/sheet.css +16 -0
  66. package/dist/panel/components/ui/tabs/tabs.css +0 -7
  67. package/dist/panel/context/documentForm.svelte.js +5 -6
  68. package/dist/panel/navigation.d.ts +7 -2
  69. package/dist/panel/navigation.js +11 -6
  70. package/dist/panel/pages/area/actions.server.js +4 -3
  71. package/dist/panel/pages/area/load.server.js +3 -2
  72. package/dist/panel/pages/auth/forgot-password/ForgotPassword.svelte +2 -1
  73. package/dist/panel/pages/auth/reset-password/ResetPassword.svelte +2 -1
  74. package/dist/panel/pages/auth/sign-in/SignIn.svelte +3 -2
  75. package/dist/panel/pages/auth/sign-in/actions.server.js +2 -2
  76. package/dist/panel/pages/auth/sign-in/load.server.d.ts +1 -1
  77. package/dist/panel/pages/auth/sign-in/load.server.js +2 -2
  78. package/dist/panel/pages/collection/load.server.js +9 -4
  79. package/dist/panel/pages/collection-document/actions.server.js +5 -3
  80. package/dist/panel/pages/collection-document/load.server.js +10 -6
  81. package/dist/panel/pages/dashboard/load.server.js +5 -4
  82. package/dist/panel/pages/live/Live.svelte +3 -7
  83. package/dist/panel/style/index.css +0 -6
  84. package/dist/panel/util/auth.js +1 -1
  85. package/dist/panel/util/url.d.ts +30 -1
  86. package/dist/panel/util/url.js +36 -2
  87. package/package.json +8 -6
  88. package/dist/panel/components/ui/dropdown-menu/index.d.ts +0 -23
@@ -4,7 +4,6 @@
4
4
  }
5
5
 
6
6
  /**************************************/
7
-
8
7
  /* Trigger
9
8
  /**************************************/
10
9
 
@@ -33,11 +32,9 @@
33
32
  .rz-tabs-trigger[data-state='active']:not([data-error='true']) {
34
33
  color: hsl(var(--rz-color-fg));
35
34
  }
36
-
37
35
  .rz-tabs-trigger[data-error='true'] {
38
36
  color: hsl(var(--rz-color-alert) / 1);
39
37
  }
40
-
41
38
  .rz-tabs-trigger[data-state='inactive'] {
42
39
  opacity: 0.8;
43
40
  }
@@ -49,21 +46,18 @@
49
46
  0 0 0 calc(var(--rz-ring-offset, 0px) + 1px) hsl(var(--rz-color-ring) / var(--rz-ring-opacity, 1));
50
47
  outline: none;
51
48
  }
52
-
53
49
  .rz-tabs-trigger:disabled {
54
50
  pointer-events: none;
55
51
  opacity: 0.5;
56
52
  }
57
53
 
58
54
  /**************************************/
59
-
60
55
  /* Content
61
56
  /**************************************/
62
57
 
63
58
  .rz-tabs-content {
64
59
  margin-top: var(--rz-size-2);
65
60
  }
66
-
67
61
  .rz-tabs-content:focus-visible {
68
62
  outline: none;
69
63
  border-radius: var(--rz-radius-sm);
@@ -75,7 +69,6 @@
75
69
  }
76
70
 
77
71
  /**************************************/
78
-
79
72
  /* List
80
73
  /**************************************/
81
74
 
@@ -4,6 +4,7 @@ import { PARAMS, VERSIONS_STATUS } from '../../core/constant.js';
4
4
  import { getFieldAtPath } from '../../core/fields/util.js';
5
5
  import { buildConfigMap } from '../../core/operations/configMap/index.js';
6
6
  import { isJSONContent, richTextJSONToText } from '../../fields/rich-text/index.js';
7
+ import { panelUrl } from '../util/url.js';
7
8
  import { normalizeFieldPath } from '../../util/doc.js';
8
9
  import { apiUrl, random } from '../../util/index.js';
9
10
  import { isObjectLiteral, omit } from '../../util/object.js';
@@ -590,12 +591,10 @@ function createDocumentFormState({ initial, config, readOnly, key, onNestedDocum
590
591
  * This function constructs the appropriate URL to which the form data will be submitted.
591
592
  */
592
593
  const buildPanelActionUrl = () => {
593
- // Start with the base URI for the panel
594
- let panelUri = `/panel/${config.kebab}`;
595
- // Add the item ID to the URI if we're updating a collection doc
596
- if (operation === 'update' && initial._prototype === 'collection' && initial.id) {
597
- panelUri += `/${initial.id}`;
598
- }
594
+ // Start with the base URI for the panel, adding the item ID if we're updating a collection doc
595
+ const panelUri = operation === 'update' && initial._prototype === 'collection' && initial.id
596
+ ? panelUrl(config.kebab, initial.id)
597
+ : panelUrl(config.kebab);
599
598
  // Determine the appropriate action based on whether we're creating or updating
600
599
  const actionSuffix = operation === 'create' ? '/create?/create' : '?/update';
601
600
  // Add a redirect parameter if we're in a nested form ex: relation creation
@@ -3,11 +3,16 @@ import type { BuildConfig } from '../core/config/server/index.server.js';
3
3
  import type { Config } from '../types.js';
4
4
  import type { Dic } from '../util/types.js';
5
5
  /**
6
- * Builds navigation structure based on config and user permissions
6
+ * Builds navigation structure based on config and user permissions. Called
7
+ * directly from the routes handle hook, before resolve() — page.params isn't
8
+ * populated yet at that point, so the panel segment is passed in explicitly
9
+ * (from the same event.params.panel the caller already has) rather than
10
+ * relying on panelUrl()'s page-based default.
7
11
  * @param config - Compiled configuration object
8
12
  * @param user - Current user object (optional)
13
+ * @param panelSegment - The resolved [panel=panel] segment for this request
9
14
  * @returns Dictionary of navigation groups
10
15
  */
11
- declare const buildNavigation: <C extends Config>(config: BuildConfig<C>, user: User | undefined) => Dic;
16
+ declare const buildNavigation: <C extends Config>(config: BuildConfig<C>, user: User | undefined, panelSegment: string | undefined) => Dic;
12
17
  export default buildNavigation;
13
18
  export type Navigation = ReturnType<typeof buildNavigation>;
@@ -1,11 +1,16 @@
1
- import { panelUrl } from './util/url.js';
1
+ import { panelUrlFor } from './util/url.js';
2
2
  /**
3
- * Builds navigation structure based on config and user permissions
3
+ * Builds navigation structure based on config and user permissions. Called
4
+ * directly from the routes handle hook, before resolve() — page.params isn't
5
+ * populated yet at that point, so the panel segment is passed in explicitly
6
+ * (from the same event.params.panel the caller already has) rather than
7
+ * relying on panelUrl()'s page-based default.
4
8
  * @param config - Compiled configuration object
5
9
  * @param user - Current user object (optional)
10
+ * @param panelSegment - The resolved [panel=panel] segment for this request
6
11
  * @returns Dictionary of navigation groups
7
12
  */
8
- const buildNavigation = (config, user) => {
13
+ const buildNavigation = (config, user, panelSegment) => {
9
14
  const groups = {};
10
15
  /**
11
16
  * Adds a route to the appropriate navigation group
@@ -29,7 +34,7 @@ const buildNavigation = (config, user) => {
29
34
  const route = {
30
35
  title: collection.label.plural,
31
36
  icon: collection.slug,
32
- url: panelUrl(collection.kebab)
37
+ url: panelUrlFor(panelSegment, collection.kebab)
33
38
  };
34
39
  addRouteToGroup(route, (collection.panel && collection.panel?.group) || 'collections');
35
40
  }
@@ -40,7 +45,7 @@ const buildNavigation = (config, user) => {
40
45
  const route = {
41
46
  title: area.label,
42
47
  icon: area.slug,
43
- url: panelUrl(area.kebab)
48
+ url: panelUrlFor(panelSegment, area.kebab)
44
49
  };
45
50
  addRouteToGroup(route, (area.panel && area.panel?.group) || 'areas');
46
51
  }
@@ -50,7 +55,7 @@ const buildNavigation = (config, user) => {
50
55
  const route = {
51
56
  title: routeConfig.label,
52
57
  icon: `custom-${routePath}`,
53
- url: panelUrl(routePath)
58
+ url: panelUrlFor(panelSegment, routePath)
54
59
  };
55
60
  addRouteToGroup(route, routeConfig.group);
56
61
  });
@@ -1,7 +1,7 @@
1
1
  import { PARAMS } from '../../../core/constant.js';
2
2
  import { ERROR_CONTEXT, handleError } from '../../../core/errors/handler.server.js';
3
3
  import { extractData } from '../../../core/operations/extract-data.server.js';
4
- import { panelUrl } from '../../util/url.js';
4
+ import { panelUrlFor } from '../../util/url.js';
5
5
  import { trycatch } from '../../../util/function.js';
6
6
  import { toKebabCase } from '../../../util/string.js';
7
7
  import { redirect } from '@sveltejs/kit';
@@ -10,6 +10,7 @@ export const areaFormActions = {
10
10
  update: async (event) => {
11
11
  const { rime, locale } = event.locals;
12
12
  const slug = (event.params.slug || '');
13
+ const panelSegment = event.params.panel;
13
14
  const versionId = event.url.searchParams.get(PARAMS.VERSION_ID) || undefined;
14
15
  const draft = event.url.searchParams.get(PARAMS.DRAFT) === 'true';
15
16
  const [extractError, data] = await trycatch(() => extractData(event.request));
@@ -28,10 +29,10 @@ export const areaFormActions = {
28
29
  if (draft && 'versionId' in document) {
29
30
  const referer = event.request.headers.get('referer');
30
31
  if (referer && referer.includes('/versions')) {
31
- return redirect(303, `${panelUrl(toKebabCase(slug))}/versions?versionId=${document.versionId}`);
32
+ return redirect(303, `${panelUrlFor(panelSegment, toKebabCase(slug))}/versions?versionId=${document.versionId}`);
32
33
  }
33
34
  else {
34
- return redirect(303, `${panelUrl(toKebabCase(slug))}?versionId=${document.versionId}`);
35
+ return redirect(303, `${panelUrlFor(panelSegment, toKebabCase(slug))}?versionId=${document.versionId}`);
35
36
  }
36
37
  }
37
38
  return { document, message: t__('common.doc_updated') };
@@ -2,7 +2,7 @@ import { PARAMS } from '../../../core/constant.js';
2
2
  import { ERROR_CONTEXT, handleError } from '../../../core/errors/handler.server.js';
3
3
  import { RimeError } from '../../../core/errors/index.js';
4
4
  import { withVersionsSuffix } from '../../../core/naming.js';
5
- import { panelUrl } from '../../util/url.js';
5
+ import { panelUrlFor } from '../../util/url.js';
6
6
  import { trycatch } from '../../../util/function.js';
7
7
  import { apiUrl } from '../../../util/index.js';
8
8
  import { toKebabCase } from '../../../util/string.js';
@@ -11,6 +11,7 @@ export async function areaLoad(event, withVersions) {
11
11
  const { locals, url, fetch } = event;
12
12
  const { rime, locale } = locals;
13
13
  const slug = (event.params.slug || '');
14
+ const panelSegment = event.params.panel;
14
15
  const area = rime.area(slug);
15
16
  const authorizedRead = area.config.access.read(locals.user, {});
16
17
  const authorizedUpdate = area.config.access.update(locals.user, {});
@@ -18,7 +19,7 @@ export async function areaLoad(event, withVersions) {
18
19
  throw handleError(new RimeError(RimeError.UNAUTHORIZED), { context: ERROR_CONTEXT.LOAD });
19
20
  }
20
21
  const aria = [
21
- { title: 'Dashboard', icon: 'dashboard', url: panelUrl() },
22
+ { title: 'Dashboard', icon: 'dashboard', url: panelUrlFor(panelSegment) },
22
23
  { title: area.config.label }
23
24
  ];
24
25
  const versionId = url.searchParams.get(PARAMS.VERSION_ID) || undefined;
@@ -6,6 +6,7 @@
6
6
  import { setFormContext } from '../../../context/form.svelte';
7
7
  import { emailField } from '../fields.js';
8
8
  import { authClient } from '../../../util/auth';
9
+ import { panelUrl } from '../../../util/url.js';
9
10
  import { toast } from 'svelte-sonner';
10
11
 
11
12
  type Props = {
@@ -29,7 +30,7 @@
29
30
  async function sendResetPasswordMail() {
30
31
  const { data, error } = await authClient.requestPasswordReset({
31
32
  email: context.values.email,
32
- redirectTo: `/reset-password`
33
+ redirectTo: panelUrl('reset-password')
33
34
  });
34
35
  if (error && error.message) {
35
36
  toast.error(error.message);
@@ -6,6 +6,7 @@
6
6
  import { setFormContext } from '../../../context/form.svelte';
7
7
  import { confirmPasswordField, passwordField } from '../fields.js';
8
8
  import { authClient } from '../../../util/auth';
9
+ import { panelUrl } from '../../../util/url.js';
9
10
  import { toast } from 'svelte-sonner';
10
11
 
11
12
  let success = $state(false);
@@ -42,7 +43,7 @@
42
43
  <AuthForm image={data.image} title={t__('common.resetPassword')}>
43
44
  {#if success}
44
45
  <p>{t__('common.reset_password_success')}</p>
45
- <Button size="xl" href="/panel/sign-in">{t__('common.signin')}</Button>
46
+ <Button size="xl" href={panelUrl('sign-in')}>{t__('common.signin')}</Button>
46
47
  {:else}
47
48
  <Text type="password" config={passwordConfig} {form} />
48
49
  <Text type="password" config={confirmPasswordConfig} {form} />
@@ -8,6 +8,7 @@
8
8
  import SpinLoader from '../../../components/ui/spin-loader/SpinLoader.svelte';
9
9
  import { setFormContext } from '../../../context/form.svelte';
10
10
  import { emailField, passwordField } from '../fields.js';
11
+ import { panelPath, panelUrl } from '../../../util/url.js';
11
12
  import type { FormErrors } from '../../../../types.js';
12
13
  import { KeyRound } from '@lucide/svelte';
13
14
  import { toast } from 'svelte-sonner';
@@ -34,7 +35,7 @@
34
35
 
35
36
  <AuthForm image={data.image} title={t__('common.signin')}>
36
37
  {#if context.status !== 429}
37
- <form method="POST" action="/panel/sign-in" use:enhance={context.enhance}>
38
+ <form method="POST" action={panelPath('sign-in')} use:enhance={context.enhance}>
38
39
  <Email config={emailField} form={context} />
39
40
  <Text
40
41
  type="password"
@@ -50,7 +51,7 @@
50
51
  </Button>
51
52
 
52
53
  {#if data.forgotPasswordEnabled}
53
- <Button variant="link" href="/forgot-password">
54
+ <Button variant="link" href={panelUrl('forgot-password')}>
54
55
  {t__('common.forgotPassword')}
55
56
  </Button>
56
57
  {/if}
@@ -3,7 +3,7 @@ import { handleError } from '../../../../core/errors/handler.server.js';
3
3
  import { RimeFormError } from '../../../../core/errors/index.js';
4
4
  import { redirect } from '@sveltejs/kit';
5
5
  export const signInActions = {
6
- default: async ({ cookies, request, locals }) => {
6
+ default: async ({ cookies, request, locals, params }) => {
7
7
  const { rime } = locals;
8
8
  const data = await request.formData();
9
9
  const email = data.get('email')?.toString() || '';
@@ -36,6 +36,6 @@ export const signInActions = {
36
36
  secure: !dev
37
37
  });
38
38
  }
39
- throw redirect(302, '/panel');
39
+ throw redirect(302, `/${params.panel}`);
40
40
  }
41
41
  };
@@ -1,5 +1,5 @@
1
1
  import { type ServerLoadEvent } from '@sveltejs/kit';
2
- export declare const signInLoad: ({ locals }: ServerLoadEvent) => Promise<{
2
+ export declare const signInLoad: ({ locals, params }: ServerLoadEvent) => Promise<{
3
3
  forgotPasswordEnabled: boolean;
4
4
  image: string | null;
5
5
  form: {};
@@ -1,11 +1,11 @@
1
1
  import { redirect } from '@sveltejs/kit';
2
2
  import { existsSync } from 'node:fs';
3
3
  import path from 'node:path';
4
- export const signInLoad = async ({ locals }) => {
4
+ export const signInLoad = async ({ locals, params }) => {
5
5
  const { session, rime } = locals;
6
6
  const imageExist = existsSync(path.join(process.cwd(), 'static', 'panel', 'panel.jpg'));
7
7
  if (session) {
8
- throw redirect(302, '/panel');
8
+ throw redirect(302, `/${params.panel}`);
9
9
  }
10
10
  else {
11
11
  return {
@@ -4,7 +4,7 @@ import { handleError } from '../../../core/errors/handler.server.js';
4
4
  import { RimeError } from '../../../core/errors/index.js';
5
5
  import { logger } from '../../../core/logger/index.server.js';
6
6
  import { withDirectoriesSuffix } from '../../../core/naming.js';
7
- import { panelUrl } from '../../util/url.js';
7
+ import { panelUrlFor } from '../../util/url.js';
8
8
  import { trycatch } from '../../../util/function.js';
9
9
  import { redirect } from '@sveltejs/kit';
10
10
  /**
@@ -13,6 +13,7 @@ import { redirect } from '@sveltejs/kit';
13
13
  export async function collectionLoad(event) {
14
14
  //
15
15
  const { rime, locale, user } = event.locals;
16
+ const panelSegment = event.params.panel;
16
17
  const slug = event.params.slug || '';
17
18
  if (!rime.config.isCollection(slug)) {
18
19
  throw handleError(new RimeError(RimeError.NOT_FOUND), { context: 'load' });
@@ -24,7 +25,7 @@ export async function collectionLoad(event) {
24
25
  draft: true
25
26
  });
26
27
  let aria = [
27
- { title: 'Dashboard', url: panelUrl() },
28
+ { title: 'Dashboard', url: panelUrlFor(panelSegment) },
28
29
  { title: collection.config.label.plural }
29
30
  ];
30
31
  let data = {
@@ -68,10 +69,14 @@ export async function collectionLoad(event) {
68
69
  parentDirectory = result;
69
70
  const collectionAria = {
70
71
  title: collection.config.label.plural,
71
- url: panelUrl(collection.config.kebab)
72
+ url: panelUrlFor(panelSegment, collection.config.kebab)
72
73
  };
73
74
  aria = [...aria].slice(0, -1);
74
- aria = [...aria, collectionAria, ...buildUploadAria({ path: currentDirectoryPath, slug })];
75
+ aria = [
76
+ ...aria,
77
+ collectionAria,
78
+ ...buildUploadAria({ path: currentDirectoryPath, slug, panelSegment })
79
+ ];
75
80
  data.aria = removePathFromLastAria(aria);
76
81
  }
77
82
  data = {
@@ -2,7 +2,7 @@ import { PARAMS, UPLOAD_PATH } from '../../../core/constant.js';
2
2
  import { ERROR_CONTEXT, handleError } from '../../../core/errors/handler.server.js';
3
3
  import { RimeError } from '../../../core/errors/index.js';
4
4
  import { extractData } from '../../../core/operations/extract-data.server.js';
5
- import { panelUrl } from '../../util/url.js';
5
+ import { panelUrlFor } from '../../util/url.js';
6
6
  import { trycatch } from '../../../util/function.js';
7
7
  import { toKebabCase } from '../../../util/string.js';
8
8
  import {} from '@sveltejs/kit';
@@ -15,6 +15,7 @@ export const collectionFormActions = {
15
15
  */
16
16
  create: async (event) => {
17
17
  const { rime, locale } = event.locals;
18
+ const panelSegment = event.params.panel;
18
19
  const slug = event.params.slug;
19
20
  if (!rime.config.isCollection(slug)) {
20
21
  throw handleError(new RimeError(RimeError.NOT_FOUND), { context: ERROR_CONTEXT.ACTION });
@@ -44,7 +45,7 @@ export const collectionFormActions = {
44
45
  const params = collection.config.upload
45
46
  ? `?${PARAMS.UPLOAD_PATH}=${data._path || UPLOAD_PATH.ROOT_NAME}`
46
47
  : '';
47
- const redirectUrl = `${panelUrl(toKebabCase(slug), document.id)}${params}`;
48
+ const redirectUrl = `${panelUrlFor(panelSegment, toKebabCase(slug), document.id)}${params}`;
48
49
  return {
49
50
  redirectUrl,
50
51
  document,
@@ -58,6 +59,7 @@ export const collectionFormActions = {
58
59
  */
59
60
  update: async (event) => {
60
61
  const { rime, locale } = event.locals;
62
+ const panelSegment = event.params.panel;
61
63
  const slug = event.params.slug || '';
62
64
  const id = event.params.id || '';
63
65
  const versionId = event.url.searchParams.get(PARAMS.VERSION_ID) || undefined;
@@ -83,7 +85,7 @@ export const collectionFormActions = {
83
85
  return {
84
86
  document,
85
87
  message: t__('common.version_created'),
86
- redirectUrl: `${panelUrl(toKebabCase(slug), document.id)}/versions?versionId=${document.versionId}`
88
+ redirectUrl: `${panelUrlFor(panelSegment, toKebabCase(slug), document.id)}/versions?versionId=${document.versionId}`
87
89
  };
88
90
  }
89
91
  return {
@@ -3,7 +3,7 @@ import { PARAMS, UPLOAD_PATH } from '../../../core/constant.js';
3
3
  import { ERROR_CONTEXT, handleError } from '../../../core/errors/handler.server.js';
4
4
  import { RimeError } from '../../../core/errors/index.js';
5
5
  import { withVersionsSuffix } from '../../../core/naming.js';
6
- import { panelUrl } from '../../util/url.js';
6
+ import { panelUrlFor } from '../../util/url.js';
7
7
  import { trycatch } from '../../../util/function.js';
8
8
  import { apiUrl } from '../../../util/index.js';
9
9
  import { toKebabCase } from '../../../util/string.js';
@@ -16,6 +16,7 @@ export async function documentLoad(event, withVersion) {
16
16
  const { locale, user, rime } = event.locals;
17
17
  const { id } = event.params;
18
18
  const slug = event.params.slug || '';
19
+ const panelSegment = event.params.panel;
19
20
  if (!id)
20
21
  throw error(404, 'Not found');
21
22
  let doc;
@@ -55,22 +56,25 @@ export async function documentLoad(event, withVersion) {
55
56
  let aria;
56
57
  const collectionAria = {
57
58
  title: collection.config.label.plural,
58
- url: panelUrl(collection.config.kebab)
59
+ url: panelUrlFor(panelSegment, collection.config.kebab)
59
60
  };
60
61
  if (collection.config.upload) {
61
62
  const paramUploadPath = event.url.searchParams.get('uploadPath');
62
63
  const currentDirectoryPath = paramUploadPath || UPLOAD_PATH.ROOT_NAME;
63
64
  aria = [
64
- { title: 'Dashboard', icon: 'dashboard', url: panelUrl() },
65
+ { title: 'Dashboard', icon: 'dashboard', url: panelUrlFor(panelSegment) },
65
66
  collectionAria,
66
- ...buildUploadAria({ path: currentDirectoryPath, slug }),
67
+ ...buildUploadAria({ path: currentDirectoryPath, slug, panelSegment }),
67
68
  { title: undefined } // Will be populated by title context
68
69
  ];
69
70
  }
70
71
  else {
71
72
  aria = [
72
- { title: 'Dashboard', icon: 'dashboard', url: panelUrl() },
73
- { title: collection.config.label.plural, url: panelUrl(collection.config.kebab) },
73
+ { title: 'Dashboard', icon: 'dashboard', url: panelUrlFor(panelSegment) },
74
+ {
75
+ title: collection.config.label.plural,
76
+ url: panelUrlFor(panelSegment, collection.config.kebab)
77
+ },
74
78
  { title: undefined } // Will be populated by title context
75
79
  ];
76
80
  }
@@ -1,7 +1,8 @@
1
- import { panelUrl } from '../../util/url.js';
1
+ import { panelUrlFor } from '../../util/url.js';
2
2
  import { capitalize } from '../../../util/string.js';
3
3
  export const dashboardLoad = async (event) => {
4
4
  const { locale, user, rime } = event.locals;
5
+ const panelSegment = event.params.panel;
5
6
  const entries = [];
6
7
  const normalizedPanelConfig = (c) => {
7
8
  const incomingConfig = c.panel || {};
@@ -21,7 +22,7 @@ export const dashboardLoad = async (event) => {
21
22
  description: panelConfig.description || null,
22
23
  slug: c.slug,
23
24
  canCreate: user && c.access.create(user, {}),
24
- link: panelUrl(c.kebab),
25
+ link: panelUrlFor(panelSegment, c.kebab),
25
26
  titleSingular: c.label.singular,
26
27
  title: c.label.plural,
27
28
  layout: panelConfig.dashboard.layout
@@ -72,11 +73,11 @@ export const dashboardLoad = async (event) => {
72
73
  prototype: 'area',
73
74
  description: (area.panel && area.panel?.description) || null,
74
75
  slug: area.slug,
75
- link: panelUrl(area.kebab),
76
+ link: panelUrlFor(panelSegment, area.kebab),
76
77
  title: area.label || capitalize(area.slug)
77
78
  });
78
79
  }
79
80
  }
80
- const aria = [{ title: 'Dashboard', icon: 'dashboard', url: panelUrl() }];
81
+ const aria = [{ title: 'Dashboard', icon: 'dashboard', url: panelUrlFor(panelSegment) }];
81
82
  return { entries, aria };
82
83
  };
@@ -14,6 +14,7 @@
14
14
  import { setLocaleContext } from '../../context/locale.svelte';
15
15
  import { setTitleContext } from '../../context/title';
16
16
  import { setUserContext } from '../../context/user.svelte';
17
+ import { panelPath } from '../../util/url.js';
17
18
  import type { GenericDoc, User } from '../../../types';
18
19
  import { trycatchFetch } from '../../../util/function';
19
20
  import { snapshot } from '../../../util/state';
@@ -240,13 +241,8 @@
240
241
  const slug = data.slug;
241
242
  const id = data.doc.id;
242
243
 
243
- // Start with the base URI for the panel
244
- let panelUri = `/panel/${toKebabCase(slug)}`;
245
-
246
- // Add the item ID to the URI if we're updating a collection doc
247
- if (id) {
248
- panelUri += `/${id}`;
249
- }
244
+ // Start with the base URI for the panel, adding the item ID if present
245
+ const panelUri = id ? panelPath(toKebabCase(slug), id) : panelPath(toKebabCase(slug));
250
246
  return goto(panelUri);
251
247
  }
252
248
 
@@ -312,7 +312,6 @@ menu {
312
312
  /*
313
313
  Reset default styling for dialogs.
314
314
  */
315
-
316
315
  dialog {
317
316
  padding: 0;
318
317
  }
@@ -353,7 +352,6 @@ button,
353
352
  /*
354
353
  Make sure disabled buttons don't get the pointer cursor.
355
354
  */
356
-
357
355
  :disabled {
358
356
  cursor: default;
359
357
  }
@@ -387,11 +385,9 @@ video {
387
385
  }
388
386
 
389
387
  /* Make elements with the HTML hidden attribute stay hidden by default */
390
-
391
388
  [hidden] {
392
389
  display: none;
393
390
  }
394
-
395
391
  :root{
396
392
  --ease-in-quad: cubic-bezier(.55, .085, .68, .53);
397
393
  --ease-in-cubic: cubic-bezier(.550, .055, .675, .19);
@@ -414,7 +410,6 @@ video {
414
410
  --ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
415
411
  --ease-in-out-circ: cubic-bezier(.785, .135, .15, .86);
416
412
  }
417
-
418
413
  :root {
419
414
  --rz-size-0-5: 0.125rem;
420
415
  --rz-size-1: 0.25rem;
@@ -458,7 +453,6 @@ video {
458
453
  --rz-size-tablet: 48rem;
459
454
  --rz-size-laptop: 64rem;
460
455
  }
461
-
462
456
  @font-face {
463
457
  font-family: 'Lora';
464
458
  src: url('/assets/panel/fonts/Lora-Italic-VariableFont_wght.ttf') format('truetype');
@@ -1,5 +1,5 @@
1
+ import { apiKeyClient } from '@better-auth/api-key/client';
1
2
  import { env } from '$env/dynamic/public';
2
- import { apiKeyClient } from 'better-auth/client/plugins';
3
3
  import { createAuthClient } from 'better-auth/svelte';
4
4
  export const authClient = createAuthClient({
5
5
  baseURL: env.PUBLIC_RIME_URL,
@@ -1,6 +1,35 @@
1
1
  /**
2
- * Build a panel url for given segments
2
+ * Build a panel url for given segments. Reads the current panel segment off
3
+ * the live matched route (page.params.panel, resolved by the [panel=panel]
4
+ * matcher) rather than from a public env var — the segment can be configured
5
+ * via RIME_PANEL_ROUTE to hide the panel from admin-path scanners, and that
6
+ * would be defeated by shipping it into every page's public env bundle.
7
+ *
8
+ * Only call this from actual component rendering (.svelte files, or a
9
+ * .svelte.ts context set up during component init) — page.params is bound to
10
+ * the current request via component context and SvelteKit throws if it's read
11
+ * anywhere else, including a +page.server.ts/+layout.server.ts load function
12
+ * or a form action, even though those also run server-side. Every load/action
13
+ * function in this codebase already receives an `event` with `event.params.panel`
14
+ * populated the same way — use panelUrlFor(event.params.panel, ...) there instead.
3
15
  * @example
4
16
  * panelUrl('some-collection') // -> http://localhost:5713/panel/some-collection
5
17
  */
6
18
  export declare function panelUrl(...args: string[]): string;
19
+ /**
20
+ * Same as panelUrl(), but takes the panel segment explicitly instead of
21
+ * reading it off page.params — for any caller that isn't component rendering:
22
+ * every +page.server.ts/+layout.server.ts load function and form action
23
+ * (pass event.params.panel), plus the one hook-level caller that runs before
24
+ * resolve() (buildNavigation(), which gets it forwarded from routes.server.ts).
25
+ */
26
+ export declare function panelUrlFor(panelSegment: string | undefined, ...args: string[]): string;
27
+ /**
28
+ * Same as panelUrl(), but returns a same-origin-relative path instead of an
29
+ * absolute URL — for goto()/form-action targets where PUBLIC_RIME_URL isn't
30
+ * guaranteed to match the actual origin the page was served from (a reverse
31
+ * proxy, differing dev host, etc). Everywhere else (real cross-page <a href>
32
+ * links, window.location.href, emailed reset links) stays absolute, matching
33
+ * apiUrl()'s existing convention.
34
+ */
35
+ export declare function panelPath(...args: string[]): string;
@@ -1,9 +1,43 @@
1
+ import { page } from '$app/state';
1
2
  import { env } from '$env/dynamic/public';
2
3
  /**
3
- * Build a panel url for given segments
4
+ * Build a panel url for given segments. Reads the current panel segment off
5
+ * the live matched route (page.params.panel, resolved by the [panel=panel]
6
+ * matcher) rather than from a public env var — the segment can be configured
7
+ * via RIME_PANEL_ROUTE to hide the panel from admin-path scanners, and that
8
+ * would be defeated by shipping it into every page's public env bundle.
9
+ *
10
+ * Only call this from actual component rendering (.svelte files, or a
11
+ * .svelte.ts context set up during component init) — page.params is bound to
12
+ * the current request via component context and SvelteKit throws if it's read
13
+ * anywhere else, including a +page.server.ts/+layout.server.ts load function
14
+ * or a form action, even though those also run server-side. Every load/action
15
+ * function in this codebase already receives an `event` with `event.params.panel`
16
+ * populated the same way — use panelUrlFor(event.params.panel, ...) there instead.
4
17
  * @example
5
18
  * panelUrl('some-collection') // -> http://localhost:5713/panel/some-collection
6
19
  */
7
20
  export function panelUrl(...args) {
8
- return `${env.PUBLIC_RIME_URL}/panel/${args.join('/')}`;
21
+ return panelUrlFor(page.params.panel, ...args);
22
+ }
23
+ /**
24
+ * Same as panelUrl(), but takes the panel segment explicitly instead of
25
+ * reading it off page.params — for any caller that isn't component rendering:
26
+ * every +page.server.ts/+layout.server.ts load function and form action
27
+ * (pass event.params.panel), plus the one hook-level caller that runs before
28
+ * resolve() (buildNavigation(), which gets it forwarded from routes.server.ts).
29
+ */
30
+ export function panelUrlFor(panelSegment, ...args) {
31
+ return `${env.PUBLIC_RIME_URL}/${panelSegment}/${args.join('/')}`;
32
+ }
33
+ /**
34
+ * Same as panelUrl(), but returns a same-origin-relative path instead of an
35
+ * absolute URL — for goto()/form-action targets where PUBLIC_RIME_URL isn't
36
+ * guaranteed to match the actual origin the page was served from (a reverse
37
+ * proxy, differing dev host, etc). Everywhere else (real cross-page <a href>
38
+ * links, window.location.href, emailed reset links) stays absolute, matching
39
+ * apiUrl()'s existing convention.
40
+ */
41
+ export function panelPath(...args) {
42
+ return `/${page.params.panel}/${args.join('/')}`;
9
43
  }