rimecms 0.27.6 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter-sqlite/{area.js → area.server.js} +2 -2
- package/dist/adapter-sqlite/{blocks.d.ts → blocks.server.d.ts} +1 -1
- package/dist/adapter-sqlite/{blocks.js → blocks.server.js} +1 -1
- package/dist/adapter-sqlite/{collection.js → collection.server.js} +17 -8
- package/dist/adapter-sqlite/generate-schema/write.server.js +2 -2
- package/dist/adapter-sqlite/index.server.d.ts +8 -8
- package/dist/adapter-sqlite/index.server.js +7 -7
- package/dist/adapter-sqlite/{orderBy.js → orderBy.server.js} +1 -1
- package/dist/adapter-sqlite/{relations.d.ts → relations.server.d.ts} +1 -1
- package/dist/adapter-sqlite/{relations.js → relations.server.js} +1 -1
- package/dist/adapter-sqlite/{transform.js → transform.server.js} +2 -3
- package/dist/adapter-sqlite/{tree.d.ts → tree.server.d.ts} +1 -1
- package/dist/adapter-sqlite/{tree.js → tree.server.js} +1 -1
- package/dist/adapter-sqlite/url.server.d.ts +1 -1
- package/dist/core/areas/local-api.server.d.ts +1 -1
- package/dist/core/areas/local-api.server.js +1 -0
- package/dist/core/collections/api/delete.server.js +1 -1
- package/dist/core/collections/api/get.server.js +1 -1
- package/dist/core/collections/api/getById.server.js +3 -1
- package/dist/core/collections/config/augment-panel.d.ts +13 -0
- package/dist/core/collections/config/augment-panel.js +19 -0
- package/dist/core/collections/config/augment-upload.server.d.ts +1 -0
- package/dist/core/collections/config/augment-upload.server.js +1 -0
- package/dist/core/collections/config/builder.js +3 -1
- package/dist/core/collections/config/builder.server.js +3 -1
- package/dist/core/collections/local-api.server.js +3 -1
- package/dist/core/collections/operations/findById.js +3 -1
- package/dist/core/collections/upload/hooks/convert-base64.server.js +1 -1
- package/dist/core/config/auth/better-auth.server.d.ts +3 -3
- package/dist/core/config/auth/better-auth.server.js +1 -1
- package/dist/core/config/client/augment-panel.d.ts +4 -4
- package/dist/core/config/client/build-config.d.ts +5 -5
- package/dist/core/config/server/augment-directories.server.js +3 -3
- package/dist/core/config/server/augment-staff.server.d.ts +1 -1
- package/dist/core/config/server/augment-staff.server.js +1 -1
- package/dist/core/config/server/build-config.server.d.ts +12 -12
- package/dist/core/config/server/build-config.server.js +1 -1
- package/dist/core/config/server/{validate.js → validate.server.js} +1 -1
- package/dist/core/config/server/{write.js → write.server.js} +1 -1
- package/dist/core/config/shared/augment-icons.d.ts +1 -2
- package/dist/core/config/shared/get-staff-collection.d.ts +1 -1
- package/dist/core/config/shared/upload-directories.js +1 -1
- package/dist/core/config/types.d.ts +20 -8
- package/dist/core/dev/cli/generate/{index.js → index.server.js} +2 -2
- package/dist/core/dev/cli/index.js +3 -3
- package/dist/core/dev/cli/init/{index.js → index.server.js} +68 -25
- package/dist/core/dev/cli/init/{packageManagerUtil.js → package-manager-util.server.js} +5 -5
- package/dist/core/dev/generate/routes/{area.d.ts → area.server.d.ts} +1 -1
- package/dist/core/dev/generate/routes/{area.js → area.server.js} +1 -1
- package/dist/core/dev/generate/routes/{collection.d.ts → collection.server.d.ts} +1 -1
- package/dist/core/dev/generate/routes/{collection.js → collection.server.js} +1 -1
- package/dist/core/dev/generate/routes/{common.d.ts → common.server.d.ts} +1 -1
- package/dist/core/dev/generate/routes/{common.js → common.server.js} +3 -3
- package/dist/core/dev/generate/routes/{index.js → index.server.js} +5 -5
- package/dist/core/dev/generate/routes/{util.js → util.server.js} +1 -1
- package/dist/core/dev/generate/types/{index.js → index.server.js} +1 -1
- package/dist/core/dev/vite/{index.js → index.server.js} +1 -1
- package/dist/core/fields/util.d.ts +13 -0
- package/dist/core/fields/util.js +77 -4
- package/dist/core/i18n/en/common.d.ts +1 -0
- package/dist/core/i18n/en/common.js +1 -0
- package/dist/core/i18n/fr/common.d.ts +1 -0
- package/dist/core/i18n/fr/common.js +1 -0
- package/dist/core/operations/hooks/before-read/populate-url.server.js +10 -2
- package/dist/core/operations/hooks/before-upsert/handle-new-version.server.js +2 -2
- package/dist/core/operations/relations/diff.server.d.ts +1 -1
- package/dist/core/operations/relations/extract.server.d.ts +1 -1
- package/dist/core/rime.server.d.ts +22 -8
- package/dist/core/rime.server.js +5 -5
- package/dist/fields/blocks/component/Block.svelte +13 -19
- package/dist/fields/blocks/component/Blocks.svelte +6 -13
- package/dist/fields/group/component/Group.svelte +6 -13
- package/dist/fields/number/component/number.css +2 -0
- package/dist/fields/relation/component/default/Default.svelte +1 -7
- package/dist/fields/relation/component/upload/upload.css +1 -10
- package/dist/fields/relation/index.d.ts +21 -6
- package/dist/fields/relation/index.js +41 -11
- package/dist/fields/rich-text/component/RichText.svelte +4 -4
- package/dist/fields/rich-text/component/bubble-menu/bubble-menu.svelte +6 -6
- package/dist/fields/rich-text/component/bubble-menu/bubble-menu.svelte.d.ts +2 -0
- package/dist/fields/rich-text/component/drag-handle/drag-handle.css +1 -0
- package/dist/fields/rich-text/component/styles/rich-text.css +0 -7
- package/dist/fields/rich-text/core/features/resource/resource.svelte +2 -9
- package/dist/fields/rich-text/core/features/upload/upload.svelte +5 -12
- package/dist/fields/rich-text/core/render-rich-text.svelte +5 -3
- package/dist/fields/rich-text/index.d.ts +1 -1
- package/dist/fields/select/component/Select.svelte +5 -11
- package/dist/fields/tabs/index.d.ts +1 -0
- package/dist/fields/tabs/index.js +3 -0
- package/dist/fields/textarea/component/TextArea.svelte +5 -13
- package/dist/fields/tree/component/TreeBlock.svelte +12 -18
- package/dist/fields/types.d.ts +3 -2
- package/dist/live.d.ts +2 -1
- package/dist/live.js +2 -1
- package/dist/panel/components/fields/Error.svelte +1 -7
- package/dist/panel/components/sections/collection/Empty.svelte +1 -0
- package/dist/panel/components/sections/collection/folder/Folder.svelte +7 -16
- package/dist/panel/components/sections/collection/folder/FolderWithActions.svelte +1 -1
- package/dist/panel/components/sections/collection/grid/CollectionGrid.svelte +22 -12
- package/dist/panel/components/sections/collection/grid/create-directory-dialog/CreateDirectoryDialog.svelte +4 -5
- package/dist/panel/components/sections/collection/grid/grid-item/GridItem.svelte +41 -33
- package/dist/panel/components/sections/collection/grid/grid-item/GridItem.svelte.d.ts +4 -0
- package/dist/panel/components/sections/collection/header/ButtonCreate.svelte +14 -16
- package/dist/panel/components/sections/collection/header/ButtonCreate.svelte.d.ts +2 -0
- package/dist/panel/components/sections/collection/header/DisplayMode.svelte +10 -10
- package/dist/panel/components/sections/collection/header/SearchInput.svelte +2 -8
- package/dist/panel/components/sections/collection/list/CollectionList.svelte +32 -3
- package/dist/panel/components/sections/collection/list/header/Header.svelte +3 -9
- package/dist/panel/components/sections/collection/list/row/Row.svelte +34 -24
- package/dist/panel/components/sections/collection/list/row/Row.svelte.d.ts +9 -0
- package/dist/panel/components/sections/collection/upload-thumb-cell/UploadThumbCell.svelte +11 -1
- package/dist/panel/components/sections/document/ButtonStatus.svelte +3 -11
- package/dist/panel/components/sections/document/Document.svelte +22 -41
- package/dist/panel/components/sections/document/Document.svelte.d.ts +0 -1
- package/dist/panel/components/sections/document/upload-header/UploadHeader.svelte +11 -21
- package/dist/panel/components/sections/live/LiveEdit.svelte +114 -0
- package/dist/panel/components/sections/live/LiveEdit.svelte.d.ts +32 -0
- package/dist/panel/components/sections/live/LiveEditPanel.svelte +53 -0
- package/dist/panel/components/sections/live/LiveEditPanel.svelte.d.ts +11 -0
- package/dist/panel/components/sections/live/LiveFloatingUI.svelte +106 -0
- package/dist/panel/components/sections/live/LiveFloatingUI.svelte.d.ts +12 -0
- package/dist/panel/components/sections/live/Provider.svelte +4 -4
- package/dist/panel/components/sections/live/Provider.svelte.d.ts +5 -3
- package/dist/panel/components/sections/live/live-edit-panel.css +12 -0
- package/dist/panel/components/ui/breadcrumb/BreadCrumb.svelte +2 -9
- package/dist/panel/components/ui/button/button.svelte +35 -54
- package/dist/panel/components/ui/calendar/calendar.css +11 -0
- package/dist/panel/components/ui/card-document/card-document.svelte +21 -14
- package/dist/panel/components/ui/card-resource/card-resource.svelte +8 -14
- package/dist/panel/components/ui/checkbox/checkbox.svelte +1 -1
- package/dist/panel/components/ui/command/command-group.css +1 -0
- package/dist/panel/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -7
- package/dist/panel/components/ui/input/input.svelte +13 -23
- package/dist/panel/components/ui/label/label.css +0 -1
- package/dist/panel/components/ui/nav/NavItemButton.svelte +2 -8
- package/dist/panel/components/ui/page-header/PageHeader.svelte +6 -13
- package/dist/panel/components/ui/radio-group/radio-group.css +2 -0
- package/dist/panel/components/ui/scroll-area/scroll-area.css +0 -4
- package/dist/panel/components/ui/sheet/sheet.css +0 -16
- package/dist/panel/components/ui/tabs/tabs.css +7 -0
- package/dist/panel/context/collection.svelte.d.ts +0 -4
- package/dist/panel/context/collection.svelte.js +0 -3
- package/dist/panel/context/documentForm.svelte.js +1 -0
- package/dist/panel/context/live.svelte.d.ts +8 -20
- package/dist/panel/context/live.svelte.js +65 -35
- package/dist/panel/context/livePanel.svelte.d.ts +11 -0
- package/dist/panel/context/livePanel.svelte.js +8 -0
- package/dist/panel/pages/auth/forgot-password/ForgotPassword.svelte +1 -7
- package/dist/panel/pages/collection/Collection.svelte +3 -1
- package/dist/panel/pages/dashboard/Dashboard.svelte +52 -28
- package/dist/panel/pages/dashboard/DashboardCollection.svelte +91 -0
- package/dist/panel/pages/dashboard/DashboardCollection.svelte.d.ts +9 -0
- package/dist/panel/pages/dashboard/load.server.js +30 -14
- package/dist/panel/pages/dashboard/types.d.ts +1 -0
- package/dist/panel/pages/live/Live.svelte +244 -67
- package/dist/panel/pages/live/Live.svelte.d.ts +9 -1
- package/dist/panel/pages/live/load.server.js +14 -1
- package/dist/panel/style/index.css +562 -8
- package/dist/panel/style/mixins/index.css +1 -3
- package/dist/panel/util/upload.d.ts +1 -1
- package/dist/util/object.js +3 -0
- package/package.json +12 -10
- package/dist/panel/components/sections/document/FloatingUI.svelte +0 -81
- package/dist/panel/components/sections/document/FloatingUI.svelte.d.ts +0 -8
- package/dist/panel/components/sections/live/SidePanel.svelte +0 -61
- package/dist/panel/components/sections/live/SidePanel.svelte.d.ts +0 -14
- package/dist/panel/components/sections/live/side-panel.css +0 -15
- /package/dist/adapter-sqlite/{area.d.ts → area.server.d.ts} +0 -0
- /package/dist/adapter-sqlite/{collection.d.ts → collection.server.d.ts} +0 -0
- /package/dist/adapter-sqlite/{orderBy.d.ts → orderBy.server.d.ts} +0 -0
- /package/dist/adapter-sqlite/{transform.d.ts → transform.server.d.ts} +0 -0
- /package/dist/adapter-sqlite/{types.d.ts → types.server.d.ts} +0 -0
- /package/dist/adapter-sqlite/{types.js → types.server.js} +0 -0
- /package/dist/adapter-sqlite/{util.d.ts → util.server.d.ts} +0 -0
- /package/dist/adapter-sqlite/{util.js → util.server.js} +0 -0
- /package/dist/adapter-sqlite/{where.d.ts → where.server.d.ts} +0 -0
- /package/dist/adapter-sqlite/{where.js → where.server.js} +0 -0
- /package/dist/adapter-sqlite/{with.d.ts → with.server.d.ts} +0 -0
- /package/dist/adapter-sqlite/{with.js → with.server.js} +0 -0
- /package/dist/core/collections/upload/util/{converter.d.ts → converter.server.d.ts} +0 -0
- /package/dist/core/collections/upload/util/{converter.js → converter.server.js} +0 -0
- /package/dist/core/config/auth/{better-auth-hooks.d.ts → better-auth-hooks.server.d.ts} +0 -0
- /package/dist/core/config/auth/{better-auth-hooks.js → better-auth-hooks.server.js} +0 -0
- /package/dist/core/config/server/{augment-cors.d.ts → augment-cors.server.d.ts} +0 -0
- /package/dist/core/config/server/{augment-cors.js → augment-cors.server.js} +0 -0
- /package/dist/core/config/server/{validate.d.ts → validate.server.d.ts} +0 -0
- /package/dist/core/config/server/{write.d.ts → write.server.d.ts} +0 -0
- /package/dist/core/dev/cache/{index.d.ts → index.server.d.ts} +0 -0
- /package/dist/core/dev/cache/{index.js → index.server.js} +0 -0
- /package/dist/core/dev/cli/clear/{index.d.ts → index.server.d.ts} +0 -0
- /package/dist/core/dev/cli/clear/{index.js → index.server.js} +0 -0
- /package/dist/core/dev/cli/generate/{index.d.ts → index.server.d.ts} +0 -0
- /package/dist/core/dev/cli/init/{getPackageName.d.ts → get-package-info.server.d.ts} +0 -0
- /package/dist/core/dev/cli/init/{getPackageName.js → get-package-info.server.js} +0 -0
- /package/dist/core/dev/cli/init/{index.d.ts → index.server.d.ts} +0 -0
- /package/dist/core/dev/cli/init/{packageManagerUtil.d.ts → package-manager-util.server.d.ts} +0 -0
- /package/dist/core/dev/generate/routes/{custom-css.d.ts → custom-css.server.d.ts} +0 -0
- /package/dist/core/dev/generate/routes/{custom-css.js → custom-css.server.js} +0 -0
- /package/dist/core/dev/generate/routes/{index.d.ts → index.server.d.ts} +0 -0
- /package/dist/core/dev/generate/routes/{util.d.ts → util.server.d.ts} +0 -0
- /package/dist/core/dev/generate/sanitize/{index.d.ts → index.server.d.ts} +0 -0
- /package/dist/core/dev/generate/sanitize/{index.js → index.server.js} +0 -0
- /package/dist/core/dev/generate/types/{index.d.ts → index.server.d.ts} +0 -0
- /package/dist/core/dev/vite/{index.d.ts → index.server.d.ts} +0 -0
|
@@ -3,20 +3,36 @@ import { capitalize } from '../../../util/string.js';
|
|
|
3
3
|
export const dashboardLoad = async (event) => {
|
|
4
4
|
const { locale, user, rime } = event.locals;
|
|
5
5
|
const entries = [];
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
const normalizedPanelConfig = (c) => {
|
|
7
|
+
const incomingConfig = c.panel || {};
|
|
8
|
+
const incomingDashboardConfig = incomingConfig.dashboard || {};
|
|
9
|
+
return {
|
|
10
|
+
...incomingConfig,
|
|
11
|
+
dashboard: {
|
|
12
|
+
layout: incomingDashboardConfig.layout ? incomingDashboardConfig.layout : 'rows',
|
|
13
|
+
maxEntries: incomingDashboardConfig.maxEntries || 8
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
const buildBaseEntry = (c) => {
|
|
18
|
+
const panelConfig = normalizedPanelConfig(c);
|
|
19
|
+
return {
|
|
20
|
+
prototype: 'collection',
|
|
21
|
+
description: panelConfig.description || null,
|
|
22
|
+
slug: c.slug,
|
|
23
|
+
canCreate: user && c.access.create(user, {}),
|
|
24
|
+
link: panelUrl(c.kebab),
|
|
25
|
+
titleSingular: c.label.singular,
|
|
26
|
+
title: c.label.plural,
|
|
27
|
+
layout: panelConfig.dashboard.layout
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
const getLastEdited = async (c, limit = 8) => {
|
|
16
31
|
try {
|
|
17
32
|
return await rime.collection(c.slug).find({
|
|
18
|
-
limit
|
|
19
|
-
locale
|
|
33
|
+
limit,
|
|
34
|
+
locale,
|
|
35
|
+
draft: true
|
|
20
36
|
});
|
|
21
37
|
}
|
|
22
38
|
catch (err) {
|
|
@@ -27,10 +43,10 @@ export const dashboardLoad = async (event) => {
|
|
|
27
43
|
};
|
|
28
44
|
const promiseEntries = rime.config.raw.collections
|
|
29
45
|
.filter((collection) => user && collection.access.read(user, {}))
|
|
30
|
-
.filter((collection) => collection.panel !== false)
|
|
46
|
+
.filter((collection) => collection.panel !== false && collection.panel?.dashboard !== false)
|
|
31
47
|
.map(async (collection) => {
|
|
32
48
|
if (collection.panel) {
|
|
33
|
-
return getLastEdited(collection).then((docs) => ({
|
|
49
|
+
return getLastEdited(collection, normalizedPanelConfig(collection).dashboard?.maxEntries).then((docs) => ({
|
|
34
50
|
...buildBaseEntry(collection),
|
|
35
51
|
lastEdited: docs
|
|
36
52
|
}));
|
|
@@ -2,19 +2,58 @@
|
|
|
2
2
|
import { goto } from '$app/navigation';
|
|
3
3
|
import type { BuiltConfigClient } from '../../../core/config/types';
|
|
4
4
|
import { t__ } from '../../../core/i18n';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import Button from '../../components/ui/button/button.svelte';
|
|
5
|
+
import LiveEditPanel from '../../components/sections/live/LiveEditPanel.svelte';
|
|
6
|
+
import LiveFloatingUI from '../../components/sections/live/LiveFloatingUI.svelte';
|
|
8
7
|
import { Pane, PaneGroup, PaneResizer } from '../../components/ui/pane/index.js';
|
|
8
|
+
import { Toaster } from '../../components/ui/sonner';
|
|
9
9
|
import SpinLoader from '../../components/ui/spin-loader/SpinLoader.svelte';
|
|
10
|
+
import { setAPIProxyContext } from '../../context/api-proxy.svelte';
|
|
11
|
+
import { setConfigContext } from '../../context/config.svelte';
|
|
12
|
+
import type { DocumentFormContext } from '../../context/documentForm.svelte.js';
|
|
13
|
+
import { setLivePanelContext, type ActivePanel } from '../../context/livePanel.svelte.js';
|
|
14
|
+
import { setLocaleContext } from '../../context/locale.svelte';
|
|
15
|
+
import { setTitleContext } from '../../context/title';
|
|
16
|
+
import { setUserContext } from '../../context/user.svelte';
|
|
17
|
+
import type { GenericDoc, User } from '../../../types';
|
|
18
|
+
import { trycatchFetch } from '../../../util/function';
|
|
10
19
|
import { snapshot } from '../../../util/state';
|
|
11
|
-
import {
|
|
20
|
+
import { toKebabCase } from '../../../util/string';
|
|
12
21
|
import { onMount } from 'svelte';
|
|
13
22
|
import { fade } from 'svelte/transition';
|
|
14
23
|
|
|
15
|
-
type
|
|
24
|
+
type Data = {
|
|
25
|
+
doc: GenericDoc;
|
|
26
|
+
user: User;
|
|
27
|
+
src: string;
|
|
28
|
+
slug: string;
|
|
29
|
+
locale: string | undefined;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type Props = { data: Data; config: BuiltConfigClient };
|
|
16
33
|
const { data, config }: Props = $props();
|
|
17
34
|
|
|
35
|
+
setAPIProxyContext();
|
|
36
|
+
// svelte-ignore state_referenced_locally
|
|
37
|
+
setConfigContext(config);
|
|
38
|
+
// svelte-ignore state_referenced_locally
|
|
39
|
+
setUserContext(data.user);
|
|
40
|
+
// svelte-ignore state_referenced_locally
|
|
41
|
+
setLocaleContext(data.locale);
|
|
42
|
+
setTitleContext('[untitled]');
|
|
43
|
+
|
|
44
|
+
// One entry per unique `update` key — never removed once added
|
|
45
|
+
let panelContexts = $state<Record<string, { doc: any }>>({});
|
|
46
|
+
// Collected from LivePanelContext children after mount
|
|
47
|
+
let panelForms = $state<Record<string, DocumentFormContext>>({});
|
|
48
|
+
// Stack-based navigation: last entry is the active panel, Escape pops one level
|
|
49
|
+
let panelStack = $state<ActivePanel[]>([]);
|
|
50
|
+
// The active panel is the last entry in the stack, or null if the stack is empty
|
|
51
|
+
const activePanel = $derived(panelStack.at(-1) ?? null);
|
|
52
|
+
// The root document panel is the first panel activated with fieldPath === ''
|
|
53
|
+
let rootDocumentPanel = $state<ActivePanel>();
|
|
54
|
+
|
|
55
|
+
let paneLeft: ReturnType<typeof Pane>;
|
|
56
|
+
const VALID_UPDATE = /^\/[a-z][a-z0-9-]*(?:\/[a-zA-Z0-9_-]+)?(?:\?[a-zA-Z0-9_=&%.-]*)?$/;
|
|
18
57
|
let iframe: HTMLIFrameElement;
|
|
19
58
|
let iframeSrc = $state('');
|
|
20
59
|
|
|
@@ -28,20 +67,32 @@
|
|
|
28
67
|
// Compare URLs regardless of trailing slash
|
|
29
68
|
let sync = $derived(normalizeUrl(iframeSrc) === normalizeUrl(data.src));
|
|
30
69
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
iframe.contentWindow.postMessage(snapshot(
|
|
36
|
-
}
|
|
37
|
-
}
|
|
70
|
+
// Send a snapshot of the current state to the iframe
|
|
71
|
+
function makePanelOnDataChange(update: string) {
|
|
72
|
+
return ({ path, value }: { path: string; value: any }) => {
|
|
73
|
+
if (!iframe?.contentWindow) return;
|
|
74
|
+
iframe.contentWindow.postMessage(snapshot({ update, path, value }));
|
|
75
|
+
};
|
|
76
|
+
}
|
|
38
77
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
78
|
+
// Send a snapshot of the current state to the iframe after a successful save
|
|
79
|
+
function makeAfterSuccess(update: string) {
|
|
80
|
+
return (savedDoc: any) => {
|
|
81
|
+
// Update the cached doc so future re-activations seed the correct base
|
|
82
|
+
if (panelContexts[update]) panelContexts[update] = { doc: savedDoc };
|
|
83
|
+
// Re-seed the iframe with server-confirmed data
|
|
84
|
+
if (iframe?.contentWindow) {
|
|
85
|
+
iframe.contentWindow.postMessage(snapshot({ update, path: '', value: savedDoc }));
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Validate the update URI format and extract slug and id
|
|
91
|
+
function parseUpdate(update: string): { slug: string; id?: string } | null {
|
|
92
|
+
if (!VALID_UPDATE.test(update)) return null;
|
|
93
|
+
const [, slug, id] = update.split('/');
|
|
94
|
+
return { slug, id };
|
|
95
|
+
}
|
|
45
96
|
|
|
46
97
|
// Wrapper tells the iframe it's live - using requestAnimationFrame for better performance
|
|
47
98
|
function handshake() {
|
|
@@ -53,12 +104,49 @@
|
|
|
53
104
|
}
|
|
54
105
|
// Continue handshake attempts until synced
|
|
55
106
|
// Use setTimeout outside to ensure we don't create a tight animation frame loop
|
|
56
|
-
setTimeout(handshake,
|
|
107
|
+
setTimeout(handshake, 300);
|
|
57
108
|
});
|
|
58
109
|
}
|
|
59
110
|
}
|
|
60
111
|
|
|
61
|
-
const
|
|
112
|
+
const iframeOrigin = $derived.by(() => {
|
|
113
|
+
try {
|
|
114
|
+
return new URL(data.src).origin;
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
async function activatePanel(key: string, update: string, fieldPath: string) {
|
|
121
|
+
const parsed = parseUpdate(update);
|
|
122
|
+
if (!parsed) return console.warn(`Invalid update key: ${update}`);
|
|
123
|
+
|
|
124
|
+
if (!panelContexts[update]) {
|
|
125
|
+
const url = parsed.id ? `/api/${parsed.slug}/${parsed.id}` : `/api/${parsed.slug}`;
|
|
126
|
+
const res = await fetch(url);
|
|
127
|
+
if (!res.ok) return;
|
|
128
|
+
panelContexts[update] = { doc: (await res.json()).doc };
|
|
129
|
+
|
|
130
|
+
// Seed liveStore only on FIRST activation — never overwrite live edits on re-click
|
|
131
|
+
if (iframe?.contentWindow) {
|
|
132
|
+
iframe.contentWindow.postMessage(
|
|
133
|
+
snapshot({ update, path: '', value: panelContexts[update].doc })
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!rootDocumentPanel && fieldPath === '') {
|
|
139
|
+
rootDocumentPanel = { key, update, fieldPath };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Push onto the stack — each nested LivePanel click adds a level
|
|
143
|
+
panelStack = [...panelStack.filter((item) => item.key !== key), { key, update, fieldPath }];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const onIframeMessage = async (e: MessageEvent) => {
|
|
147
|
+
// Only accept messages from the expected iframe origin
|
|
148
|
+
if (!iframeOrigin || e.origin !== iframeOrigin) return;
|
|
149
|
+
|
|
62
150
|
// Handle handshake response from iframe - process in next microtask
|
|
63
151
|
if (e.data.handshake) {
|
|
64
152
|
// Use Promise.resolve().then to defer state update to next microtask
|
|
@@ -66,10 +154,25 @@
|
|
|
66
154
|
iframeSrc = e.data.handshake;
|
|
67
155
|
});
|
|
68
156
|
}
|
|
157
|
+
|
|
69
158
|
// Handle navigation request from iframe
|
|
70
159
|
if (e.data.location) {
|
|
71
160
|
goto(e.data.location);
|
|
72
161
|
}
|
|
162
|
+
|
|
163
|
+
// Handle custom panel activation
|
|
164
|
+
if (e.data.activatePanel) {
|
|
165
|
+
const { key, update, fieldPath } = e.data.activatePanel;
|
|
166
|
+
activatePanel(key, update, fieldPath);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (e.data.deactivatePanel) {
|
|
170
|
+
// Pop the top level — Escape returns to the previous panel in the stack
|
|
171
|
+
const { key } = e.data.deactivatePanel;
|
|
172
|
+
if (panelStack.at(-1)?.key === key) {
|
|
173
|
+
panelStack = panelStack.slice(0, -1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
73
176
|
};
|
|
74
177
|
|
|
75
178
|
onMount(() => {
|
|
@@ -78,10 +181,17 @@
|
|
|
78
181
|
});
|
|
79
182
|
|
|
80
183
|
$effect(() => {
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
184
|
+
// Check wether the iframe src has a 200 status code and is reachable
|
|
185
|
+
trycatchFetch(data.src, { method: 'HEAD' }).then(([error, response]) => {
|
|
186
|
+
if (error || !response?.ok) {
|
|
187
|
+
console.error(`Error loading iframe, check your routes for ${data.src}`);
|
|
188
|
+
} else {
|
|
189
|
+
// Start handshake process when not synced
|
|
190
|
+
if (!sync) {
|
|
191
|
+
handshake();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
85
195
|
});
|
|
86
196
|
|
|
87
197
|
$effect(() => {
|
|
@@ -91,47 +201,112 @@
|
|
|
91
201
|
}
|
|
92
202
|
});
|
|
93
203
|
|
|
204
|
+
$effect(() => {
|
|
205
|
+
if (!paneLeft) return;
|
|
206
|
+
if (activePanel) {
|
|
207
|
+
paneLeft.expand();
|
|
208
|
+
} else {
|
|
209
|
+
paneLeft.collapse();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Notify the iframe whenever the active panel changes so LivePanel knows its state
|
|
214
|
+
$effect(() => {
|
|
215
|
+
if (!iframe?.contentWindow) return;
|
|
216
|
+
iframe.contentWindow.postMessage({ activePanel: activePanel?.key ?? null });
|
|
217
|
+
});
|
|
218
|
+
|
|
94
219
|
let currentDevice = $state<'mobile' | 'desktop'>('mobile');
|
|
220
|
+
|
|
221
|
+
// Expose active panel state to all child components (LiveEditPanel etc.)
|
|
222
|
+
setLivePanelContext({
|
|
223
|
+
get activePanel() {
|
|
224
|
+
return activePanel;
|
|
225
|
+
},
|
|
226
|
+
get panelStack() {
|
|
227
|
+
return panelStack;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
function closeActivePanel() {
|
|
232
|
+
if (activePanel) {
|
|
233
|
+
panelStack = panelStack.slice(0, -1);
|
|
234
|
+
} else {
|
|
235
|
+
backToDocumentPanel();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function backToDocumentPanel() {
|
|
240
|
+
const slug = data.slug;
|
|
241
|
+
const id = data.doc.id;
|
|
242
|
+
|
|
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
|
+
}
|
|
250
|
+
return goto(panelUri);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function toggleRootPanel() {
|
|
254
|
+
if (rootDocumentPanel) {
|
|
255
|
+
if (activePanel?.key === rootDocumentPanel.key) {
|
|
256
|
+
closeActivePanel();
|
|
257
|
+
} else {
|
|
258
|
+
activatePanel(rootDocumentPanel.key, rootDocumentPanel.update, rootDocumentPanel.fieldPath);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function handleIFrameError() {
|
|
264
|
+
console.error('Error loading iframe:', data.src);
|
|
265
|
+
}
|
|
95
266
|
</script>
|
|
96
267
|
|
|
97
268
|
<div class="rz-live-container">
|
|
98
269
|
{#if !sync}
|
|
99
|
-
<div out:fade={{ duration:
|
|
270
|
+
<div out:fade={{ duration: 150 }} class="rz-live-container__overlay">
|
|
100
271
|
<div><SpinLoader /> {t__('common.live_in_sync')}</div>
|
|
101
272
|
</div>
|
|
102
273
|
{/if}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
274
|
+
|
|
275
|
+
<Toaster />
|
|
276
|
+
|
|
277
|
+
<LiveFloatingUI
|
|
278
|
+
bind:currentDevice
|
|
279
|
+
forms={panelForms}
|
|
280
|
+
onClose={closeActivePanel}
|
|
281
|
+
{activePanel}
|
|
282
|
+
toggleRootPanel={rootDocumentPanel ? toggleRootPanel : null}
|
|
283
|
+
/>
|
|
284
|
+
|
|
285
|
+
<PaneGroup direction="horizontal">
|
|
286
|
+
<Pane bind:this={paneLeft} collapsedSize={0} collapsible={true} defaultSize={30}>
|
|
287
|
+
{#if activePanel && panelContexts[activePanel.update]}
|
|
288
|
+
{#key activePanel.update}
|
|
289
|
+
<LiveEditPanel
|
|
290
|
+
doc={panelContexts[activePanel.update].doc}
|
|
291
|
+
onDataChange={makePanelOnDataChange(activePanel.update)}
|
|
292
|
+
afterSuccess={makeAfterSuccess(activePanel.update)}
|
|
293
|
+
onFormReady={(form) => {
|
|
294
|
+
panelForms[activePanel.update] = form;
|
|
295
|
+
}}
|
|
114
296
|
/>
|
|
115
297
|
{/key}
|
|
116
|
-
|
|
298
|
+
{/if}
|
|
117
299
|
</Pane>
|
|
300
|
+
|
|
118
301
|
<PaneResizer />
|
|
119
302
|
<Pane class="rz-live-container__pane-right" defaultSize={70}>
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
<Button
|
|
128
|
-
onclick={() => (currentDevice = 'desktop')}
|
|
129
|
-
variant={currentDevice === 'desktop' ? 'secondary' : 'ghost'}
|
|
130
|
-
size="icon"
|
|
131
|
-
icon={Laptop}
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
<iframe class={currentDevice} bind:this={iframe} title="edit" src={data.src}></iframe>
|
|
303
|
+
<iframe
|
|
304
|
+
class={currentDevice}
|
|
305
|
+
bind:this={iframe}
|
|
306
|
+
title="edit"
|
|
307
|
+
src={data.src}
|
|
308
|
+
onerror={handleIFrameError}
|
|
309
|
+
></iframe>
|
|
135
310
|
</Pane>
|
|
136
311
|
</PaneGroup>
|
|
137
312
|
</div>
|
|
@@ -147,7 +322,6 @@
|
|
|
147
322
|
flex-direction: column;
|
|
148
323
|
align-items: center;
|
|
149
324
|
justify-content: flex-start;
|
|
150
|
-
padding: var(--rz-size-3);
|
|
151
325
|
height: 100vh;
|
|
152
326
|
background-color: hsl(var(--rz-gray-3));
|
|
153
327
|
}
|
|
@@ -159,8 +333,11 @@
|
|
|
159
333
|
border-right: var(--rz-border);
|
|
160
334
|
}
|
|
161
335
|
|
|
336
|
+
:global(.rz-live-hidden) {
|
|
337
|
+
display: none;
|
|
338
|
+
}
|
|
339
|
+
|
|
162
340
|
.rz-live-container iframe {
|
|
163
|
-
border: var(--rz-border);
|
|
164
341
|
transform-origin: center 0;
|
|
165
342
|
}
|
|
166
343
|
|
|
@@ -168,17 +345,25 @@
|
|
|
168
345
|
width: 320px;
|
|
169
346
|
aspect-ratio: 2 / 3.3;
|
|
170
347
|
scale: 1.25;
|
|
348
|
+
transform: translateY(-6.25vh);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
:global(.rz-live-container__pane-right):has(iframe.mobile) {
|
|
352
|
+
display: flex;
|
|
353
|
+
justify-content: center;
|
|
354
|
+
align-items: center;
|
|
171
355
|
}
|
|
172
356
|
|
|
173
357
|
.rz-live-container iframe.desktop {
|
|
174
|
-
width:
|
|
175
|
-
|
|
176
|
-
|
|
358
|
+
width: 100%;
|
|
359
|
+
height: 100%;
|
|
360
|
+
padding: var(--rz-size-6);
|
|
177
361
|
}
|
|
178
362
|
|
|
179
363
|
.rz-live-container__overlay {
|
|
180
|
-
background-color: hsl(var(--rz-gray-
|
|
181
|
-
|
|
364
|
+
background-color: light-dark(hsl(var(--rz-gray-18)), hsl(var(--rz-gray-2)));
|
|
365
|
+
color: light-dark(hsl(var(--rz-gray-2)), hsl(var(--rz-gray-18)));
|
|
366
|
+
opacity: 0.93;
|
|
182
367
|
position: absolute;
|
|
183
368
|
inset: 0;
|
|
184
369
|
display: flex;
|
|
@@ -192,12 +377,4 @@
|
|
|
192
377
|
justify-content: center;
|
|
193
378
|
}
|
|
194
379
|
}
|
|
195
|
-
|
|
196
|
-
.rz-live-container__devices {
|
|
197
|
-
display: inline-flex;
|
|
198
|
-
gap: var(--rz-size-3);
|
|
199
|
-
padding: var(--rz-size-2);
|
|
200
|
-
border-radius: var(--rz-radius-md);
|
|
201
|
-
background-color: hsl(var(--rz-gray-2));
|
|
202
|
-
}
|
|
203
380
|
</style>
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { BuiltConfigClient } from '../../../core/config/types';
|
|
2
|
+
import type { GenericDoc, User } from '../../../types';
|
|
3
|
+
type Data = {
|
|
4
|
+
doc: GenericDoc;
|
|
5
|
+
user: User;
|
|
6
|
+
src: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
locale: string | undefined;
|
|
9
|
+
};
|
|
2
10
|
type Props = {
|
|
3
|
-
data:
|
|
11
|
+
data: Data;
|
|
4
12
|
config: BuiltConfigClient;
|
|
5
13
|
};
|
|
6
14
|
declare const Live: import("svelte").Component<Props, {}, "">;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { env } from '$env/dynamic/public';
|
|
2
2
|
import { PARAMS } from '../../../core/constant.js';
|
|
3
|
+
import { error, redirect } from '@sveltejs/kit';
|
|
3
4
|
export async function liveLoad(event) {
|
|
4
5
|
const { user, rime } = event.locals;
|
|
5
6
|
event.depends('data:src');
|
|
@@ -12,6 +13,18 @@ export async function liveLoad(event) {
|
|
|
12
13
|
if (!user) {
|
|
13
14
|
error(404, 'Not found');
|
|
14
15
|
}
|
|
16
|
+
// Validate src is from the trusted frontend origin to prevent iframe injection
|
|
17
|
+
if (src) {
|
|
18
|
+
try {
|
|
19
|
+
const trustedOrigin = new URL(env.PUBLIC_RIME_URL).origin;
|
|
20
|
+
if (new URL(src).origin !== trustedOrigin) {
|
|
21
|
+
error(400, 'Invalid src');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
error(400, 'Invalid src');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
15
28
|
if (user && src && slug && id) {
|
|
16
29
|
const output = { user, src: src, slug, locale };
|
|
17
30
|
if (rime.config.isCollection(slug)) {
|