rimecms 0.27.5 → 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.
Files changed (231) hide show
  1. package/dist/adapter-sqlite/{area.js → area.server.js} +2 -2
  2. package/dist/adapter-sqlite/{blocks.d.ts → blocks.server.d.ts} +1 -1
  3. package/dist/adapter-sqlite/{blocks.js → blocks.server.js} +1 -1
  4. package/dist/adapter-sqlite/{collection.js → collection.server.js} +17 -8
  5. package/dist/adapter-sqlite/generate-schema/templates.server.d.ts +1 -1
  6. package/dist/adapter-sqlite/generate-schema/templates.server.js +3 -2
  7. package/dist/adapter-sqlite/generate-schema/write.server.js +2 -2
  8. package/dist/adapter-sqlite/index.server.d.ts +8 -8
  9. package/dist/adapter-sqlite/index.server.js +7 -7
  10. package/dist/adapter-sqlite/{orderBy.js → orderBy.server.js} +1 -1
  11. package/dist/adapter-sqlite/{relations.d.ts → relations.server.d.ts} +1 -1
  12. package/dist/adapter-sqlite/{relations.js → relations.server.js} +1 -1
  13. package/dist/adapter-sqlite/{transform.js → transform.server.js} +2 -3
  14. package/dist/adapter-sqlite/{tree.d.ts → tree.server.d.ts} +1 -1
  15. package/dist/adapter-sqlite/{tree.js → tree.server.js} +1 -1
  16. package/dist/adapter-sqlite/url.server.d.ts +1 -1
  17. package/dist/core/areas/local-api.server.d.ts +1 -1
  18. package/dist/core/areas/local-api.server.js +1 -0
  19. package/dist/core/collections/api/delete.server.js +1 -1
  20. package/dist/core/collections/api/get.server.js +1 -1
  21. package/dist/core/collections/api/getById.server.js +3 -1
  22. package/dist/core/collections/config/augment-panel.d.ts +13 -0
  23. package/dist/core/collections/config/augment-panel.js +19 -0
  24. package/dist/core/collections/config/augment-upload.server.d.ts +1 -0
  25. package/dist/core/collections/config/augment-upload.server.js +1 -0
  26. package/dist/core/collections/config/builder.js +3 -1
  27. package/dist/core/collections/config/builder.server.js +3 -1
  28. package/dist/core/collections/local-api.server.js +3 -1
  29. package/dist/core/collections/operations/findById.js +3 -1
  30. package/dist/core/collections/upload/hooks/convert-base64.server.js +1 -1
  31. package/dist/core/config/auth/better-auth.server.d.ts +3 -3
  32. package/dist/core/config/auth/better-auth.server.js +1 -1
  33. package/dist/core/config/client/augment-panel.d.ts +4 -4
  34. package/dist/core/config/client/build-config.d.ts +5 -5
  35. package/dist/core/config/server/augment-directories.server.js +3 -3
  36. package/dist/core/config/server/augment-staff.server.d.ts +1 -1
  37. package/dist/core/config/server/augment-staff.server.js +1 -1
  38. package/dist/core/config/server/build-config.server.d.ts +12 -12
  39. package/dist/core/config/server/build-config.server.js +1 -1
  40. package/dist/core/config/server/{validate.js → validate.server.js} +1 -1
  41. package/dist/core/config/server/{write.js → write.server.js} +1 -1
  42. package/dist/core/config/shared/augment-icons.d.ts +1 -2
  43. package/dist/core/config/shared/get-staff-collection.d.ts +1 -1
  44. package/dist/core/config/shared/upload-directories.js +1 -1
  45. package/dist/core/config/types.d.ts +20 -8
  46. package/dist/core/dev/cli/generate/{index.js → index.server.js} +2 -2
  47. package/dist/core/dev/cli/index.js +3 -3
  48. package/dist/core/dev/cli/init/{index.js → index.server.js} +68 -25
  49. package/dist/core/dev/cli/init/{packageManagerUtil.js → package-manager-util.server.js} +5 -5
  50. package/dist/core/dev/generate/routes/{area.d.ts → area.server.d.ts} +1 -1
  51. package/dist/core/dev/generate/routes/{area.js → area.server.js} +1 -1
  52. package/dist/core/dev/generate/routes/{collection.d.ts → collection.server.d.ts} +1 -1
  53. package/dist/core/dev/generate/routes/{collection.js → collection.server.js} +1 -1
  54. package/dist/core/dev/generate/routes/{common.d.ts → common.server.d.ts} +1 -1
  55. package/dist/core/dev/generate/routes/{common.js → common.server.js} +26 -11
  56. package/dist/core/dev/generate/routes/{index.js → index.server.js} +5 -5
  57. package/dist/core/dev/generate/routes/{util.js → util.server.js} +1 -1
  58. package/dist/core/dev/generate/types/{index.js → index.server.js} +1 -1
  59. package/dist/core/dev/vite/{index.js → index.server.js} +1 -1
  60. package/dist/core/fields/util.d.ts +13 -0
  61. package/dist/core/fields/util.js +77 -4
  62. package/dist/core/i18n/en/common.d.ts +2 -0
  63. package/dist/core/i18n/en/common.js +2 -0
  64. package/dist/core/i18n/fr/common.d.ts +2 -0
  65. package/dist/core/i18n/fr/common.js +2 -0
  66. package/dist/core/operations/hooks/before-read/populate-url.server.js +10 -2
  67. package/dist/core/operations/hooks/before-upsert/handle-new-version.server.js +5 -4
  68. package/dist/core/operations/relations/diff.server.d.ts +1 -1
  69. package/dist/core/operations/relations/extract.server.d.ts +1 -1
  70. package/dist/core/operations/shared/fallback-data-from-original.d.ts +1 -0
  71. package/dist/core/operations/shared/fallback-data-from-original.js +5 -0
  72. package/dist/core/rime.server.d.ts +22 -8
  73. package/dist/core/rime.server.js +5 -5
  74. package/dist/fields/blocks/component/Block.svelte +13 -19
  75. package/dist/fields/blocks/component/Blocks.svelte +6 -13
  76. package/dist/fields/checkbox/index.server.js +1 -1
  77. package/dist/fields/combobox/index.server.js +3 -2
  78. package/dist/fields/date/index.server.js +1 -1
  79. package/dist/fields/email/index.server.js +1 -1
  80. package/dist/fields/group/component/Group.svelte +6 -13
  81. package/dist/fields/link/index.server.js +1 -1
  82. package/dist/fields/number/component/number.css +2 -0
  83. package/dist/fields/number/index.server.js +1 -1
  84. package/dist/fields/radio/index.server.js +1 -1
  85. package/dist/fields/relation/component/default/Default.svelte +1 -7
  86. package/dist/fields/relation/component/upload/upload.css +1 -10
  87. package/dist/fields/relation/index.d.ts +21 -6
  88. package/dist/fields/relation/index.js +41 -11
  89. package/dist/fields/relation/index.server.js +1 -1
  90. package/dist/fields/rich-text/component/RichText.svelte +4 -4
  91. package/dist/fields/rich-text/component/bubble-menu/bubble-menu.svelte +6 -6
  92. package/dist/fields/rich-text/component/bubble-menu/bubble-menu.svelte.d.ts +2 -0
  93. package/dist/fields/rich-text/component/drag-handle/drag-handle.css +1 -0
  94. package/dist/fields/rich-text/component/styles/rich-text.css +0 -7
  95. package/dist/fields/rich-text/core/features/resource/resource.svelte +2 -9
  96. package/dist/fields/rich-text/core/features/upload/upload.svelte +5 -12
  97. package/dist/fields/rich-text/core/render-rich-text.svelte +5 -3
  98. package/dist/fields/rich-text/index.d.ts +1 -1
  99. package/dist/fields/rich-text/index.server.d.ts +1 -1
  100. package/dist/fields/rich-text/index.server.js +1 -1
  101. package/dist/fields/select/component/Select.svelte +5 -11
  102. package/dist/fields/select/index.server.d.ts +1 -1
  103. package/dist/fields/select/index.server.js +2 -1
  104. package/dist/fields/slug/index.server.js +1 -1
  105. package/dist/fields/tabs/index.d.ts +1 -0
  106. package/dist/fields/tabs/index.js +3 -0
  107. package/dist/fields/text/index.server.js +1 -1
  108. package/dist/fields/textarea/component/TextArea.svelte +5 -13
  109. package/dist/fields/textarea/index.server.js +1 -1
  110. package/dist/fields/time/index.server.js +1 -1
  111. package/dist/fields/toggle/index.server.js +1 -1
  112. package/dist/fields/tree/component/TreeBlock.svelte +12 -18
  113. package/dist/fields/types.d.ts +3 -2
  114. package/dist/live.d.ts +2 -1
  115. package/dist/live.js +2 -1
  116. package/dist/panel/components/fields/Error.svelte +1 -7
  117. package/dist/panel/components/sections/auth/AuthForm.svelte +2 -4
  118. package/dist/panel/components/sections/collection/Empty.svelte +1 -0
  119. package/dist/panel/components/sections/collection/folder/Folder.svelte +7 -16
  120. package/dist/panel/components/sections/collection/folder/FolderWithActions.svelte +1 -1
  121. package/dist/panel/components/sections/collection/grid/CollectionGrid.svelte +22 -12
  122. package/dist/panel/components/sections/collection/grid/create-directory-dialog/CreateDirectoryDialog.svelte +4 -5
  123. package/dist/panel/components/sections/collection/grid/grid-item/GridItem.svelte +41 -33
  124. package/dist/panel/components/sections/collection/grid/grid-item/GridItem.svelte.d.ts +4 -0
  125. package/dist/panel/components/sections/collection/header/ButtonCreate.svelte +14 -16
  126. package/dist/panel/components/sections/collection/header/ButtonCreate.svelte.d.ts +2 -0
  127. package/dist/panel/components/sections/collection/header/DisplayMode.svelte +10 -10
  128. package/dist/panel/components/sections/collection/header/SearchInput.svelte +2 -8
  129. package/dist/panel/components/sections/collection/list/CollectionList.svelte +32 -3
  130. package/dist/panel/components/sections/collection/list/header/Header.svelte +3 -9
  131. package/dist/panel/components/sections/collection/list/row/Row.svelte +41 -27
  132. package/dist/panel/components/sections/collection/list/row/Row.svelte.d.ts +9 -0
  133. package/dist/panel/components/sections/collection/tree/CollectionTree.svelte +1 -1
  134. package/dist/panel/components/sections/collection/upload-thumb-cell/UploadThumbCell.svelte +11 -1
  135. package/dist/panel/components/sections/document/ButtonStatus.svelte +3 -11
  136. package/dist/panel/components/sections/document/CurrentlyEdited.svelte +1 -2
  137. package/dist/panel/components/sections/document/Document.svelte +24 -41
  138. package/dist/panel/components/sections/document/Document.svelte.d.ts +0 -1
  139. package/dist/panel/components/sections/document/Versions.svelte +1 -2
  140. package/dist/panel/components/sections/document/upload-header/UploadHeader.svelte +11 -21
  141. package/dist/panel/components/sections/live/LiveEdit.svelte +114 -0
  142. package/dist/panel/components/sections/live/LiveEdit.svelte.d.ts +32 -0
  143. package/dist/panel/components/sections/live/LiveEditPanel.svelte +53 -0
  144. package/dist/panel/components/sections/live/LiveEditPanel.svelte.d.ts +11 -0
  145. package/dist/panel/components/sections/live/LiveFloatingUI.svelte +106 -0
  146. package/dist/panel/components/sections/live/LiveFloatingUI.svelte.d.ts +12 -0
  147. package/dist/panel/components/sections/live/Provider.svelte +4 -4
  148. package/dist/panel/components/sections/live/Provider.svelte.d.ts +5 -3
  149. package/dist/panel/components/sections/live/live-edit-panel.css +12 -0
  150. package/dist/panel/components/sections/page-layout/Page.svelte +0 -4
  151. package/dist/panel/components/ui/breadcrumb/BreadCrumb.svelte +2 -9
  152. package/dist/panel/components/ui/button/button.svelte +35 -54
  153. package/dist/panel/components/ui/calendar/calendar.css +11 -0
  154. package/dist/panel/components/ui/card-document/card-document.svelte +21 -14
  155. package/dist/panel/components/ui/card-resource/card-resource.svelte +8 -14
  156. package/dist/panel/components/ui/checkbox/checkbox.svelte +1 -1
  157. package/dist/panel/components/ui/command/command-group.css +1 -0
  158. package/dist/panel/components/ui/dialog/dialog-overlay.css +1 -2
  159. package/dist/panel/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +1 -7
  160. package/dist/panel/components/ui/input/input.svelte +13 -23
  161. package/dist/panel/components/ui/label/label.css +0 -1
  162. package/dist/panel/components/ui/nav/NavItemButton.svelte +2 -8
  163. package/dist/panel/components/ui/page-header/PageHeader.svelte +6 -13
  164. package/dist/panel/components/ui/radio-group/radio-group.css +2 -0
  165. package/dist/panel/components/ui/scroll-area/scroll-area.css +0 -4
  166. package/dist/panel/components/ui/sheet/sheet.css +1 -18
  167. package/dist/panel/components/ui/tabs/tabs.css +7 -0
  168. package/dist/panel/context/collection.svelte.d.ts +0 -4
  169. package/dist/panel/context/collection.svelte.js +0 -3
  170. package/dist/panel/context/documentForm.svelte.js +1 -0
  171. package/dist/panel/context/live.svelte.d.ts +8 -20
  172. package/dist/panel/context/live.svelte.js +65 -35
  173. package/dist/panel/context/livePanel.svelte.d.ts +11 -0
  174. package/dist/panel/context/livePanel.svelte.js +8 -0
  175. package/dist/panel/pages/auth/forgot-password/ForgotPassword.svelte +1 -7
  176. package/dist/panel/pages/collection/Collection.svelte +3 -1
  177. package/dist/panel/pages/dashboard/Dashboard.svelte +52 -28
  178. package/dist/panel/pages/dashboard/DashboardCollection.svelte +91 -0
  179. package/dist/panel/pages/dashboard/DashboardCollection.svelte.d.ts +9 -0
  180. package/dist/panel/pages/dashboard/load.server.js +30 -14
  181. package/dist/panel/pages/dashboard/types.d.ts +1 -0
  182. package/dist/panel/pages/live/Live.svelte +244 -67
  183. package/dist/panel/pages/live/Live.svelte.d.ts +9 -1
  184. package/dist/panel/pages/live/load.server.js +14 -1
  185. package/dist/panel/style/index.css +563 -8
  186. package/dist/panel/style/mixins/index.css +1 -3
  187. package/dist/panel/util/upload.d.ts +1 -1
  188. package/dist/util/object.js +3 -0
  189. package/package.json +12 -10
  190. package/dist/panel/components/sections/document/FloatingUI.svelte +0 -81
  191. package/dist/panel/components/sections/document/FloatingUI.svelte.d.ts +0 -8
  192. package/dist/panel/components/sections/live/SidePanel.svelte +0 -61
  193. package/dist/panel/components/sections/live/SidePanel.svelte.d.ts +0 -14
  194. package/dist/panel/components/sections/live/side-panel.css +0 -15
  195. /package/dist/adapter-sqlite/{area.d.ts → area.server.d.ts} +0 -0
  196. /package/dist/adapter-sqlite/{collection.d.ts → collection.server.d.ts} +0 -0
  197. /package/dist/adapter-sqlite/{orderBy.d.ts → orderBy.server.d.ts} +0 -0
  198. /package/dist/adapter-sqlite/{transform.d.ts → transform.server.d.ts} +0 -0
  199. /package/dist/adapter-sqlite/{types.d.ts → types.server.d.ts} +0 -0
  200. /package/dist/adapter-sqlite/{types.js → types.server.js} +0 -0
  201. /package/dist/adapter-sqlite/{util.d.ts → util.server.d.ts} +0 -0
  202. /package/dist/adapter-sqlite/{util.js → util.server.js} +0 -0
  203. /package/dist/adapter-sqlite/{where.d.ts → where.server.d.ts} +0 -0
  204. /package/dist/adapter-sqlite/{where.js → where.server.js} +0 -0
  205. /package/dist/adapter-sqlite/{with.d.ts → with.server.d.ts} +0 -0
  206. /package/dist/adapter-sqlite/{with.js → with.server.js} +0 -0
  207. /package/dist/core/collections/upload/util/{converter.d.ts → converter.server.d.ts} +0 -0
  208. /package/dist/core/collections/upload/util/{converter.js → converter.server.js} +0 -0
  209. /package/dist/core/config/auth/{better-auth-hooks.d.ts → better-auth-hooks.server.d.ts} +0 -0
  210. /package/dist/core/config/auth/{better-auth-hooks.js → better-auth-hooks.server.js} +0 -0
  211. /package/dist/core/config/server/{augment-cors.d.ts → augment-cors.server.d.ts} +0 -0
  212. /package/dist/core/config/server/{augment-cors.js → augment-cors.server.js} +0 -0
  213. /package/dist/core/config/server/{validate.d.ts → validate.server.d.ts} +0 -0
  214. /package/dist/core/config/server/{write.d.ts → write.server.d.ts} +0 -0
  215. /package/dist/core/dev/cache/{index.d.ts → index.server.d.ts} +0 -0
  216. /package/dist/core/dev/cache/{index.js → index.server.js} +0 -0
  217. /package/dist/core/dev/cli/clear/{index.d.ts → index.server.d.ts} +0 -0
  218. /package/dist/core/dev/cli/clear/{index.js → index.server.js} +0 -0
  219. /package/dist/core/dev/cli/generate/{index.d.ts → index.server.d.ts} +0 -0
  220. /package/dist/core/dev/cli/init/{getPackageName.d.ts → get-package-info.server.d.ts} +0 -0
  221. /package/dist/core/dev/cli/init/{getPackageName.js → get-package-info.server.js} +0 -0
  222. /package/dist/core/dev/cli/init/{index.d.ts → index.server.d.ts} +0 -0
  223. /package/dist/core/dev/cli/init/{packageManagerUtil.d.ts → package-manager-util.server.d.ts} +0 -0
  224. /package/dist/core/dev/generate/routes/{custom-css.d.ts → custom-css.server.d.ts} +0 -0
  225. /package/dist/core/dev/generate/routes/{custom-css.js → custom-css.server.js} +0 -0
  226. /package/dist/core/dev/generate/routes/{index.d.ts → index.server.d.ts} +0 -0
  227. /package/dist/core/dev/generate/routes/{util.d.ts → util.server.d.ts} +0 -0
  228. /package/dist/core/dev/generate/sanitize/{index.d.ts → index.server.d.ts} +0 -0
  229. /package/dist/core/dev/generate/sanitize/{index.js → index.server.js} +0 -0
  230. /package/dist/core/dev/generate/types/{index.d.ts → index.server.d.ts} +0 -0
  231. /package/dist/core/dev/vite/{index.d.ts → index.server.d.ts} +0 -0
@@ -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 type { GenericDoc } from '../../../core/types/doc';
6
- import LiveSidePanel from '../../components/sections/live/SidePanel.svelte';
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 { Laptop, Smartphone } from '@lucide/svelte';
20
+ import { toKebabCase } from '../../../util/string';
12
21
  import { onMount } from 'svelte';
13
22
  import { fade } from 'svelte/transition';
14
23
 
15
- type Props = { data: any; config: BuiltConfigClient };
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
- const onDataChange = (args: Partial<GenericDoc>) => {
32
- /** Send message to iframe */
33
- if (iframe?.contentWindow) {
34
- // Use snapshot to ensure we're sending a plain object without reactive proxies
35
- iframe.contentWindow.postMessage(snapshot(args));
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
- const onFieldFocus = (path: string) => {
40
- /** Send message to iframe */
41
- if (iframe?.contentWindow) {
42
- iframe.contentWindow.postMessage({ focus: path });
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, 1000);
107
+ setTimeout(handshake, 300);
57
108
  });
58
109
  }
59
110
  }
60
111
 
61
- const onIframeMessage = async (e: any) => {
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
- // Start handshake process when not synced
82
- if (!sync) {
83
- handshake();
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: 350 }} class="rz-live-container__overlay">
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
- <PaneGroup autoSaveId="rz-live:panel-state" direction="horizontal">
104
- <Pane defaultSize={40}>
105
- <div class="rz-live-container__side-panel">
106
- {#key data.src + data.doc.id + data.locale + data.slug}
107
- <LiveSidePanel
108
- {config}
109
- {onDataChange}
110
- {onFieldFocus}
111
- doc={data.doc}
112
- user={data.user}
113
- locale={data.locale}
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
- </div>
298
+ {/if}
117
299
  </Pane>
300
+
118
301
  <PaneResizer />
119
302
  <Pane class="rz-live-container__pane-right" defaultSize={70}>
120
- <div class="rz-live-container__devices">
121
- <Button
122
- onclick={() => (currentDevice = 'mobile')}
123
- variant={currentDevice === 'mobile' ? 'secondary' : 'ghost'}
124
- size="icon"
125
- icon={Smartphone}
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: 133%;
175
- aspect-ratio: 16 / 11;
176
- scale: 0.75;
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-10));
181
- opacity: 0.8;
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: any;
11
+ data: Data;
4
12
  config: BuiltConfigClient;
5
13
  };
6
14
  declare const Live: import("svelte").Component<Props, {}, "">;
@@ -1,5 +1,6 @@
1
- import { error, redirect } from '@sveltejs/kit';
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)) {