rimecms 0.31.4 → 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 (49) hide show
  1. package/README.md +1 -1
  2. package/dist/adapter-sqlite/transform.server.js +1 -1
  3. package/dist/core/collections/upload/util/path.d.ts +2 -1
  4. package/dist/core/collections/upload/util/path.js +3 -3
  5. package/dist/core/constant.d.ts +10 -0
  6. package/dist/core/constant.js +22 -0
  7. package/dist/core/dev/cli/build/templates.js +2 -0
  8. package/dist/core/dev/cli/init/index.server.js +2 -1
  9. package/dist/core/dev/cli/init/templates.d.ts +6 -4
  10. package/dist/core/dev/cli/init/templates.js +20 -9
  11. package/dist/core/dev/constants.d.ts +5 -0
  12. package/dist/core/dev/constants.js +9 -0
  13. package/dist/core/dev/generate/routes/common.server.js +33 -33
  14. package/dist/core/dev/generate/routes/index.server.d.ts +6 -3
  15. package/dist/core/dev/generate/routes/index.server.js +13 -7
  16. package/dist/core/dev/generate/routes/util.server.d.ts +14 -6
  17. package/dist/core/dev/generate/routes/util.server.js +17 -6
  18. package/dist/core/handlers/auth.server.js +16 -8
  19. package/dist/core/handlers/routes.server.d.ts +1 -1
  20. package/dist/core/handlers/routes.server.js +4 -3
  21. package/dist/core/operations/hooks/before-read/populate-url.server.js +2 -1
  22. package/dist/core/plugins/cache/handler.server.js +1 -1
  23. package/dist/panel/components/sections/collection/list/row/Row.svelte +2 -1
  24. package/dist/panel/components/sections/collection/tree/CollectionTreeNode.svelte +2 -1
  25. package/dist/panel/components/sections/document/AuthFooter.svelte +2 -1
  26. package/dist/panel/components/sections/document/Versions.svelte +1 -1
  27. package/dist/panel/components/ui/card-resource/card-resource.svelte +2 -1
  28. package/dist/panel/components/ui/nav/Nav.svelte +1 -1
  29. package/dist/panel/components/ui/nav/NavItem.svelte +3 -2
  30. package/dist/panel/components/ui/nav/UserButton.svelte +3 -3
  31. package/dist/panel/context/documentForm.svelte.js +5 -6
  32. package/dist/panel/navigation.d.ts +7 -2
  33. package/dist/panel/navigation.js +11 -6
  34. package/dist/panel/pages/area/actions.server.js +4 -3
  35. package/dist/panel/pages/area/load.server.js +3 -2
  36. package/dist/panel/pages/auth/forgot-password/ForgotPassword.svelte +2 -1
  37. package/dist/panel/pages/auth/reset-password/ResetPassword.svelte +2 -1
  38. package/dist/panel/pages/auth/sign-in/SignIn.svelte +3 -2
  39. package/dist/panel/pages/auth/sign-in/actions.server.js +2 -2
  40. package/dist/panel/pages/auth/sign-in/load.server.d.ts +1 -1
  41. package/dist/panel/pages/auth/sign-in/load.server.js +2 -2
  42. package/dist/panel/pages/collection/load.server.js +9 -4
  43. package/dist/panel/pages/collection-document/actions.server.js +5 -3
  44. package/dist/panel/pages/collection-document/load.server.js +10 -6
  45. package/dist/panel/pages/dashboard/load.server.js +5 -4
  46. package/dist/panel/pages/live/Live.svelte +3 -7
  47. package/dist/panel/util/url.d.ts +30 -1
  48. package/dist/panel/util/url.js +36 -2
  49. package/package.json +1 -1
@@ -1,16 +1,24 @@
1
+ import { isPublicPanelAuthRoute } from '../constant.js';
1
2
  import { RimeError } from '../errors/index.js';
2
3
  import { error, redirect } from '@sveltejs/kit';
3
4
  import { BETTER_AUTH_ROLES } from '../collections/auth/constant.server.js';
4
5
  import { logger } from '../logger/index.server.js';
5
6
  const dev = process.env.NODE_ENV === 'development';
6
7
  /**
7
- * Analyzes the current route to determine authentication requirements
8
+ * Analyzes the current route to determine authentication requirements.
9
+ * isPanel/isPublicAuthRoute are derived from the matched route, not the
10
+ * pathname — event.params.panel only resolves when the request matched the
11
+ * [panel=panel] matcher, and event.route.id's literal folder names never
12
+ * carry the configured RIME_PANEL_ROUTE value — so neither check needs to
13
+ * know the actual (hideable) panel segment. sign-in/forgot-password/reset-password
14
+ * all live under [panel=panel] too but must stay reachable without a session,
15
+ * hence the isPublicAuthRoute carve-out.
8
16
  */
9
- function analyzeRoute(pathname) {
10
- const isSignIn = pathname === '/panel/sign-in';
11
- const isPanel = pathname.startsWith('/panel') && !isSignIn;
12
- const isAPI = pathname.startsWith('/api');
13
- return { isSignIn, isPanel, isAPI };
17
+ function analyzeRoute(event) {
18
+ const isPublicAuthRoute = isPublicPanelAuthRoute(event.route.id);
19
+ const isPanel = event.params.panel !== undefined && !isPublicAuthRoute;
20
+ const isAPI = event.url.pathname.startsWith('/api');
21
+ return { isPublicAuthRoute, isPanel, isAPI };
14
22
  }
15
23
  /**
16
24
  * Ensures panel is properly set up before allowing access
@@ -31,7 +39,7 @@ async function authenticateRequest(headers, auth) {
31
39
  */
32
40
  function handleUnauthenticated(event, resolve, routeInfo) {
33
41
  if (routeInfo.isPanel) {
34
- throw redirect(303, '/panel/sign-in');
42
+ throw redirect(303, `/${event.params.panel}/sign-in`);
35
43
  }
36
44
  event.locals.user = undefined;
37
45
  event.locals.session = undefined;
@@ -132,7 +140,7 @@ function cleanupUser(userData, routeInfo) {
132
140
  */
133
141
  export const handleAuth = async ({ event, resolve }) => {
134
142
  const rime = event.locals.rime;
135
- const routeInfo = analyzeRoute(event.url.pathname);
143
+ const routeInfo = analyzeRoute(event);
136
144
  // Ensure auth is set up
137
145
  if (routeInfo.isPanel) {
138
146
  await ensureFirstAuthSetup(rime);
@@ -31,7 +31,7 @@ export declare const routeHandlers: {
31
31
  aria: import("../../types.js").Route[];
32
32
  }>;
33
33
  live: typeof liveLoad;
34
- signIn: ({ locals }: ServerLoadEvent) => Promise<{
34
+ signIn: ({ locals, params }: ServerLoadEvent) => Promise<{
35
35
  forgotPasswordEnabled: boolean;
36
36
  image: string | null;
37
37
  form: {};
@@ -1,3 +1,4 @@
1
+ import { isPublicPanelAuthRoute } from '../constant.js';
1
2
  import { prototypeKebabToSlug } from '../naming.js';
2
3
  import buildNavigation from '../../panel/navigation.js';
3
4
  import { areaFormActions } from '../../panel/pages/area/actions.server.js';
@@ -56,9 +57,9 @@ export const routeHandlers = {
56
57
  };
57
58
  export const handleRoutes = async ({ event, resolve }) => {
58
59
  const { rime, user } = event.locals;
59
- const IS_SIGN_IN_ROUTE = event.url.pathname === '/panel/sign-in';
60
+ const IS_PUBLIC_AUTH_ROUTE = isPublicPanelAuthRoute(event.route.id);
60
61
  const IS_API_ROUTE = event.url.pathname.startsWith('/api');
61
- const IS_PANEL_ROUTE = event.url.pathname.startsWith('/panel') && !IS_SIGN_IN_ROUTE;
62
+ const IS_PANEL_ROUTE = event.params.panel !== undefined && !IS_PUBLIC_AUTH_ROUTE;
62
63
  // event.params.slug comes straight from the URL, always kebab-case (see
63
64
  // src/params/collection.ts, area.ts). Collection/area slugs themselves are
64
65
  // camelCase, so rewrite once here — every downstream handler (panel
@@ -72,7 +73,7 @@ export const handleRoutes = async ({ event, resolve }) => {
72
73
  }
73
74
  // build panel navigation
74
75
  if (IS_PANEL_ROUTE && event.request.method === 'GET') {
75
- event.locals.navigation = buildNavigation(rime.config.raw, user);
76
+ event.locals.navigation = buildNavigation(rime.config.raw, user, event.params.panel);
76
77
  }
77
78
  event.locals.routes = routeHandlers;
78
79
  // Handle custom routes from config and plugins
@@ -1,3 +1,4 @@
1
+ import { env } from '$env/dynamic/private';
1
2
  import { PARAMS } from '../../../constant.js';
2
3
  import { logger } from '../../../logger/index.server.js';
3
4
  import { getValueAtPath } from '../../../../util/object.js';
@@ -98,7 +99,7 @@ export const populateURL = Hooks.beforeRead(async (args) => {
98
99
  }
99
100
  // Add the live url if needed
100
101
  if (config.live && event.locals.user && url) {
101
- args.doc._live = `${process.env.PUBLIC_RIME_URL}/panel/live-edit?src=${url}&slug=${config.slug}&id=${args.doc.id}`;
102
+ args.doc._live = `${process.env.PUBLIC_RIME_URL}/${env.RIME_PANEL_ROUTE || 'panel'}/live-edit?src=${url}&slug=${config.slug}&id=${args.doc.id}`;
102
103
  args.doc._live += args.doc.versionId ? `&${PARAMS.VERSION_ID}=${args.doc.versionId}` : '';
103
104
  args.doc._live += locale ? `&${PARAMS.LOCALE}=${locale}` : '';
104
105
  }
@@ -10,7 +10,7 @@ export const handler = async ({ event, resolve }) => {
10
10
  return await resolve(event);
11
11
  }
12
12
  // Do not cache panel routes
13
- if (event.url.pathname.startsWith('/panel')) {
13
+ if (event.params.panel !== undefined) {
14
14
  return await resolve(event);
15
15
  }
16
16
  // Use configuration function
@@ -3,6 +3,7 @@
3
3
  import type { GenericDoc } from '../../../../../../core/types/doc.js';
4
4
  import Checkbox from '../../../../ui/checkbox/checkbox.svelte';
5
5
  import { getLocaleContext } from '../../../../../context/locale.svelte';
6
+ import { panelUrl } from '../../../../../util/url.js';
6
7
  import { getValueAtPath } from '../../../../../../util/object';
7
8
  import StatusDot from '../../StatusDot.svelte';
8
9
  import UploadThumbCell from '../../upload-thumb-cell/UploadThumbCell.svelte';
@@ -59,7 +60,7 @@
59
60
  {/if}
60
61
  <label for="checkbox-{doc.id}" class="rz-list-row__title">{doc.title || '[untitled]'}</label>
61
62
  {:else}
62
- <a class="rz-list-row__link" href="/panel/{config.kebab}/{doc.id}">
63
+ <a class="rz-list-row__link" href={panelUrl(config.kebab, doc.id)}>
63
64
  {#if doc._thumbnail}
64
65
  <UploadThumbCell url={doc._thumbnail} mimeType={doc.mimeType} />
65
66
  {:else}
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { GenericDoc } from '../../../../../core/types/doc.js';
3
3
  import type { CollectionContext } from '../../../../context/collection.svelte.js';
4
+ import { panelUrl } from '../../../../util/url.js';
4
5
  import { GripVertical } from '@lucide/svelte';
5
6
  import StatusDot from '../StatusDot.svelte';
6
7
  import CollectionTreeNode from './CollectionTreeNode.svelte';
@@ -12,7 +13,7 @@
12
13
 
13
14
  {#key `${parentId}-${doc.id}-${doc._position}`}
14
15
  <div data-parent={parentId} data-id={doc.id} class="rz-collection-node">
15
- <a href="/panel/{doc._type}/{doc.id}" class="rz-collection-node__row">
16
+ <a href={panelUrl(doc._type, doc.id)} class="rz-collection-node__row">
16
17
  <div class="rz-collection-node__grip"><GripVertical size="12" /></div>
17
18
  <div>
18
19
  {doc.title}
@@ -7,6 +7,7 @@
7
7
  import type { DocumentFormContext } from '../../../context/documentForm.svelte.js';
8
8
  import { getUserContext } from '../../../context/user.svelte.js';
9
9
  import { authClient } from '../../../util/auth';
10
+ import { panelUrl } from '../../../util/url.js';
10
11
  import validate from '../../../../util/validate';
11
12
  import { toast } from 'svelte-sonner';
12
13
  import Button from '../../ui/button/button.svelte';
@@ -27,7 +28,7 @@
27
28
  async function sendPasswordResetLink() {
28
29
  const { data, error } = await authClient.requestPasswordReset({
29
30
  email: form.values.email,
30
- redirectTo: `/reset-password?slug=staff`
31
+ redirectTo: `${panelUrl('reset-password')}?slug=staff`
31
32
  });
32
33
  if (error && error.message) {
33
34
  toast.error(error.message);
@@ -32,7 +32,7 @@
32
32
  if (doc._prototype === 'collection') {
33
33
  return `${panelUrl(kebabSlug, doc.id)}?versionId=${doc.versionId}`;
34
34
  } else {
35
- return `${panelUrl(kebabSlug)}/panel/${toKebabCase(doc._type)}?versionId=${doc.versionId}`;
35
+ return `${panelUrl(kebabSlug)}?versionId=${doc.versionId}`;
36
36
  }
37
37
  });
38
38
  </script>
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { mimeTypeToIcon } from '../../../util/upload.js';
3
+ import { panelUrl } from '../../../util/url.js';
3
4
  import { Edit, FileIcon, X } from '@lucide/svelte';
4
5
 
5
6
  type Resource = {
@@ -40,7 +41,7 @@
40
41
 
41
42
  <div class="rz-card-resource__info">
42
43
  <p class="rz-card-resource__title">
43
- {resource.title} <a href="/panel/{resource._type}/{resource.id}"><Edit size="12" /></a>
44
+ {resource.title} <a href={panelUrl(resource._type, resource.id)}><Edit size="12" /></a>
44
45
  </p>
45
46
  {#if isUpload}
46
47
  <p class="rz-card-resource__info-text">{resource.filesize}</p>
@@ -37,7 +37,7 @@
37
37
  <div class:rz-nav--collapsed={isCollapsed} class="rz-nav">
38
38
  <div class="rz-nav__content">
39
39
  <div class="rz-nav__header" class:rz-nav__header--collapsed={isCollapsed}>
40
- <NavItem href="/panel" {isCollapsed} route={dashBoardRoute} />
40
+ <NavItem href={panelUrl()} {isCollapsed} route={dashBoardRoute} />
41
41
  </div>
42
42
 
43
43
  <div class="rz-nav__body">
@@ -23,8 +23,9 @@
23
23
 
24
24
  let active = $derived.by(() => {
25
25
  const routePathname = new URL(route.url).pathname;
26
- if (routePathname === '/panel') {
27
- return pathname === '/panel';
26
+ const panelRoot = `/${page.params.panel}`;
27
+ if (routePathname === panelRoot) {
28
+ return pathname === panelRoot;
28
29
  }
29
30
  const reg = new RegExp(`^${routePathname}(/.*)?$`);
30
31
  return reg.test(pathname);
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
- import { env } from '$env/dynamic/public';
3
2
  import * as Tooltip from '../tooltip';
4
3
  import { getUserContext } from '../../../context/user.svelte.js';
5
4
  import { authClient } from '../../../util/auth.js';
5
+ import { panelUrl } from '../../../util/url.js';
6
6
  import { LogOut } from '@lucide/svelte';
7
7
  import { toast } from 'svelte-sonner';
8
8
  import Button from '../button/button.svelte';
@@ -15,7 +15,7 @@
15
15
  async function signout() {
16
16
  const result = await authClient.signOut();
17
17
  if (result.data?.success) {
18
- window.location.href = `${env.PUBLIC_RIME_URL}/panel/sign-in`;
18
+ window.location.href = panelUrl('sign-in');
19
19
  } else {
20
20
  toast.error(`Can't sign-out, please try again`);
21
21
  }
@@ -26,7 +26,7 @@
26
26
  {#if !navCollapsed}
27
27
  <div class="rz-user-button">
28
28
  <div class="rz-user-button__left">
29
- <a href="/panel/staff/{user.attributes.id}">
29
+ <a href={panelUrl('staff', user.attributes.id)}>
30
30
  {user.attributes.name?.charAt(0) || ''}
31
31
  </a>
32
32
  <div class="rz-user-button__name">{user.attributes.name}</div>
@@ -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
  }