@jxsuite/studio 2.4.3 → 4.0.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 (208) hide show
  1. package/README.md +5 -5
  2. package/dist/chunks/Overlay.dev-723bv0df.js +23 -0
  3. package/dist/chunks/cf-settings-45fgyx8y.js +21 -0
  4. package/dist/chunks/{components-z6cnkyy1.js → components-zmx10t62.js} +6 -5
  5. package/dist/chunks/content-source-t88vb2dr.js +49 -0
  6. package/dist/chunks/{convert-to-repeater-k10dvcc7.js → convert-to-repeater-dhw1nrz9.js} +27 -18
  7. package/dist/chunks/entry-commands-6z6bhxg0.js +46 -0
  8. package/dist/chunks/{extension-sections-0ada0xcw.js → extension-sections-3ydgjbdy.js} +32 -23
  9. package/dist/chunks/file-ops-rskmhxyq.js +48 -0
  10. package/dist/chunks/files-0cratsr0.js +92 -0
  11. package/dist/chunks/{format-host-wewba4nq.js → format-host-tgcmfs4p.js} +3 -3
  12. package/dist/chunks/fs-events-hrrg328g.js +21 -0
  13. package/dist/chunks/{git-panel-2dh1aw50.js → git-panel-0yywyk76.js} +21 -15
  14. package/dist/chunks/{jx-validate-jj9rnt4n.js → jx-validate-8q2g05x1.js} +6 -4
  15. package/dist/chunks/layers-panel-z3tqe5cd.js +67 -0
  16. package/dist/chunks/layers-q75tj2mf.js +45 -0
  17. package/dist/chunks/lit-html-j055mkh8.js +22 -0
  18. package/dist/chunks/monaco-buffer-dqw0xxpx.js +25 -0
  19. package/dist/chunks/{monaco-setup-a5wkmbgh.js → monaco-setup-rws7k4br.js} +4 -4
  20. package/dist/chunks/notify-yjm79n4s.js +30 -0
  21. package/dist/chunks/parse-24d0gay3.js +19 -0
  22. package/dist/chunks/platform-ba1j1gbh.js +16 -0
  23. package/dist/chunks/publish-panel-54jvhskq.js +28 -0
  24. package/dist/chunks/quick-search-cgfmjwq5.js +65 -0
  25. package/dist/chunks/references-3qx8fkyp.js +29 -0
  26. package/dist/chunks/right-panel-cqp8bc4v.js +46 -0
  27. package/dist/chunks/sp-overlay.dev-yztmtrs1.js +18 -0
  28. package/dist/chunks/sp-popover.dev-n2etmqdp.js +9 -0
  29. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +15 -0
  30. package/dist/chunks/sp-tooltip.dev-xcvpnqr0.js +15 -0
  31. package/dist/chunks/{sp-tray.dev-64v4pxw8.js → sp-tray.dev-tkbscm06.js} +5 -5
  32. package/dist/chunks/{studio-f5yyvqxv.js → studio-1hbqnt19.js} +37 -31
  33. package/dist/chunks/studio-2zkz8evn.js +3163 -0
  34. package/dist/chunks/{studio-0b6fs05q.js → studio-4pqqxbrt.js} +23 -15
  35. package/dist/chunks/{studio-kvfx9z2c.js → studio-4rzyrtjz.js} +70 -31
  36. package/dist/chunks/{studio-ef8t53vc.js → studio-5q69gfag.js} +60 -56
  37. package/dist/chunks/{studio-v2c5pmjj.js → studio-5rywrkxz.js} +565 -133
  38. package/dist/chunks/{studio-6kv3apa9.js → studio-68n67094.js} +25 -9
  39. package/dist/chunks/{studio-0rqqhrdy.js → studio-7nte2hj4.js} +2 -2
  40. package/dist/chunks/{studio-hyn3vhwr.js → studio-8swrjbny.js} +31 -18
  41. package/dist/chunks/studio-92ab2jqe.js +282 -0
  42. package/dist/chunks/{studio-sas7mt3k.js → studio-9ab8gqsm.js} +2 -2
  43. package/dist/chunks/{studio-28xc8ws4.js → studio-9c4k4vhv.js} +14 -10
  44. package/dist/chunks/studio-9n4mrmnb.js +184 -0
  45. package/dist/chunks/{studio-tr20m8g8.js → studio-9r6j5ev1.js} +37 -2
  46. package/dist/chunks/{studio-aweyr4v1.js → studio-ahjfdy6e.js} +9 -7
  47. package/dist/chunks/{studio-s519zc99.js → studio-bgxt93ga.js} +3 -3
  48. package/dist/chunks/{studio-26sv44br.js → studio-cb12yz9w.js} +8543 -8784
  49. package/dist/chunks/studio-cft9r4r7.js +119 -0
  50. package/dist/chunks/{studio-rqemfhz6.js → studio-eenfkt4e.js} +6 -6
  51. package/dist/chunks/studio-eesa1myv.js +498 -0
  52. package/dist/chunks/{studio-ay8wcr2b.js → studio-eqq1w638.js} +7 -9
  53. package/dist/chunks/{studio-ke244pam.js → studio-ex8zyp98.js} +51 -25
  54. package/dist/chunks/studio-gv8vvshc.js +118 -0
  55. package/dist/chunks/studio-hbtp0eh1.js +1315 -0
  56. package/dist/chunks/{studio-cz2j1c0g.js → studio-htsf0w6m.js} +75 -123
  57. package/dist/chunks/{studio-pyrw8h0w.js → studio-kjjsjak4.js} +528 -212
  58. package/dist/chunks/{studio-5x5774ak.js → studio-kryvsw6j.js} +3 -3
  59. package/dist/chunks/studio-kyh9avvv.js +28 -0
  60. package/dist/chunks/{studio-jnakhg87.js → studio-kyk2v92a.js} +14 -14
  61. package/dist/chunks/{studio-twsafr1e.js → studio-m3hwn7y1.js} +3 -3
  62. package/dist/chunks/{studio-tzx3xmvx.js → studio-mmv72a27.js} +8 -8
  63. package/dist/chunks/{studio-vhk5fmfv.js → studio-n8b9wqwy.js} +5 -5
  64. package/dist/chunks/{studio-cjm7fdjf.js → studio-p4ngc046.js} +8 -104
  65. package/dist/chunks/{studio-sccge2wf.js → studio-p8j86wag.js} +2 -2
  66. package/dist/chunks/{studio-v1va0vhd.js → studio-pdfjntbk.js} +75 -139
  67. package/dist/chunks/studio-q43a5y9h.js +394 -0
  68. package/dist/chunks/studio-q7v10cg2.js +106 -0
  69. package/dist/chunks/{studio-2w11grav.js → studio-qq6jym04.js} +3 -3
  70. package/dist/chunks/studio-r1r7afxx.js +1066 -0
  71. package/dist/chunks/{studio-3fk1ac9z.js → studio-rdx8jcxy.js} +4 -4
  72. package/dist/chunks/{studio-4mqb9a0j.js → studio-rn9ta2mz.js} +4 -4
  73. package/dist/chunks/studio-sf7zjgv1.js +91 -0
  74. package/dist/chunks/studio-t7qb9db8.js +665 -0
  75. package/dist/chunks/studio-tmc5emrw.js +8 -0
  76. package/dist/chunks/{studio-wdt9y92a.js → studio-txwkm6p9.js} +2 -2
  77. package/dist/chunks/{studio-dgj7gq44.js → studio-v38hmvx1.js} +7 -7
  78. package/dist/chunks/{studio-7ejemhej.js → studio-vadzgkrz.js} +6 -6
  79. package/dist/chunks/studio-wcy8trfx.js +263 -0
  80. package/dist/chunks/{studio-hb7c46jy.js → studio-wx5609xd.js} +21 -15
  81. package/dist/chunks/studio-xrapxwwc.js +1539 -0
  82. package/dist/chunks/{studio-ws6ef6js.js → studio-yxrkyv8a.js} +41 -29
  83. package/dist/chunks/{studio-xm3dbxvn.js → studio-zczps70q.js} +2 -2
  84. package/dist/chunks/workspace-xpres40v.js +89 -0
  85. package/dist/iframe-entry.js +450 -48
  86. package/dist/studio.js +2455 -1761
  87. package/package.json +16 -15
  88. package/src/browse/library-layouts.ts +3 -3
  89. package/src/browse/library-pane.ts +59 -5
  90. package/src/browse/library-preview.ts +19 -3
  91. package/src/canvas/asset-refs.ts +119 -0
  92. package/src/canvas/asset-resolve.ts +256 -0
  93. package/src/canvas/canvas-live-render.ts +23 -15
  94. package/src/canvas/canvas-render.ts +23 -1
  95. package/src/canvas/iframe-entry.ts +1 -0
  96. package/src/canvas/iframe-host.ts +4 -0
  97. package/src/canvas/iframe-patch.ts +47 -21
  98. package/src/canvas/iframe-protocol.ts +9 -0
  99. package/src/canvas/iframe-render.ts +50 -16
  100. package/src/commands/app-commands.ts +4 -0
  101. package/src/commands/command-args.ts +29 -0
  102. package/src/commands/context.ts +15 -1
  103. package/src/commands/defaults.ts +29 -1
  104. package/src/commands/levels.ts +16 -0
  105. package/src/commands/live-context.ts +3 -0
  106. package/src/content/collection-match.ts +267 -0
  107. package/src/content/entry-commands.ts +38 -29
  108. package/src/content/entry-fields.ts +6 -0
  109. package/src/editor/file-drop-action.ts +2 -1
  110. package/src/editor/shortcuts.ts +5 -0
  111. package/src/files/file-ops.ts +10 -42
  112. package/src/files/files.ts +487 -61
  113. package/src/files/fs-events.ts +9 -0
  114. package/src/files/gitignore.ts +432 -0
  115. package/src/files/media-paths.ts +37 -14
  116. package/src/files/media-upload.ts +47 -6
  117. package/src/files/serialize-document.ts +60 -0
  118. package/src/format/convert-file.ts +511 -0
  119. package/src/format/format-choices.ts +199 -0
  120. package/src/grid/grid-idle.ts +45 -0
  121. package/src/grid/grid-view.ts +35 -0
  122. package/src/grid/schema-columns.ts +2 -1
  123. package/src/grid/sources/content-source.ts +24 -3
  124. package/src/media/media-open.ts +58 -0
  125. package/src/media/media-pane.ts +310 -0
  126. package/src/new-project/import-tab.ts +333 -160
  127. package/src/new-project/new-project-modal.ts +27 -34
  128. package/src/packages/ensure-deps.ts +14 -7
  129. package/src/packages/jxsuite-update.ts +11 -11
  130. package/src/page-params.ts +10 -46
  131. package/src/panels/activity-bar.ts +45 -16
  132. package/src/panels/ai-chat/attached-context.ts +6 -1
  133. package/src/panels/ai-chat/chat-view.ts +255 -13
  134. package/src/panels/ai-chat/composer.ts +21 -80
  135. package/src/panels/ai-panel.ts +116 -2
  136. package/src/panels/frontmatter-fields.ts +6 -2
  137. package/src/panels/navigator-panels.ts +7 -6
  138. package/src/panels/properties-panel.ts +8 -2
  139. package/src/panels/seo-modal.ts +5 -6
  140. package/src/panels/settings-menu.ts +799 -0
  141. package/src/panels/signals-panel.ts +2 -1
  142. package/src/panels/toolbar.ts +135 -15
  143. package/src/platforms/cloud.ts +34 -3
  144. package/src/platforms/devserver.ts +72 -34
  145. package/src/preview/preview-overlay.ts +226 -0
  146. package/src/services/ai-ask.ts +247 -0
  147. package/src/services/ai-import-tools.ts +494 -0
  148. package/src/services/ai-models.ts +101 -11
  149. package/src/services/ai-project-tools.ts +20 -26
  150. package/src/services/ai-settings.ts +69 -71
  151. package/src/services/ai-system-prompt.ts +88 -2
  152. package/src/services/ai-turn-signal.ts +70 -0
  153. package/src/services/cf-settings.ts +19 -37
  154. package/src/services/connection.ts +74 -0
  155. package/src/services/context-manager.ts +89 -0
  156. package/src/services/document-assistant.ts +49 -7
  157. package/src/services/idle.ts +6 -1
  158. package/src/services/import-client.ts +33 -6
  159. package/src/services/import-run.ts +158 -0
  160. package/src/services/import-seed.ts +120 -0
  161. package/src/services/jx-validate.ts +58 -6
  162. package/src/services/project-adoption.ts +71 -0
  163. package/src/services/references.ts +0 -0
  164. package/src/services/settings/definitions.ts +181 -0
  165. package/src/services/settings/kernel.ts +335 -0
  166. package/src/services/settings/write-queue.ts +116 -0
  167. package/src/services/tool-executor.ts +34 -2
  168. package/src/settings/contexts-section.ts +45 -3
  169. package/src/settings/dependencies-editor.ts +43 -23
  170. package/src/settings/general-settings.ts +30 -12
  171. package/src/settings/preferences-accounts.ts +6 -19
  172. package/src/settings/preferences-dialog.ts +18 -41
  173. package/src/settings/preferences-keymap.ts +8 -18
  174. package/src/settings/preferences-sections.ts +57 -0
  175. package/src/settings/schema-field-ui.ts +2 -2
  176. package/src/settings/settings-document.ts +65 -9
  177. package/src/shell.ts +6 -14
  178. package/src/studio.ts +65 -7
  179. package/src/tabs/transact.ts +42 -19
  180. package/src/types.ts +164 -8
  181. package/src/ui/ai-credentials-form.ts +52 -27
  182. package/src/ui/ai-model-picker.ts +162 -0
  183. package/src/ui/choice-field.ts +72 -0
  184. package/src/ui/layers.ts +83 -5
  185. package/src/ui/media-picker.ts +6 -11
  186. package/src/utils/studio-utils.ts +43 -31
  187. package/styles/forced-colors.css +7 -0
  188. package/styles/overlays.css +256 -0
  189. package/styles/panels.css +13 -1
  190. package/styles/shell.css +111 -0
  191. package/dist/chunks/Overlay.dev-d2ajy6sj.js +0 -23
  192. package/dist/chunks/cf-settings-g5fbf2zp.js +0 -17
  193. package/dist/chunks/content-source-k9724vcv.js +0 -39
  194. package/dist/chunks/files-yew27sn9.js +0 -69
  195. package/dist/chunks/layers-panel-qnsghwwd.js +0 -59
  196. package/dist/chunks/publish-panel-a0pdft5w.js +0 -21
  197. package/dist/chunks/quick-search-9avyc4pc.js +0 -54
  198. package/dist/chunks/right-panel-kcnhtc5j.js +0 -38
  199. package/dist/chunks/sp-overlay.dev-s18hymn9.js +0 -18
  200. package/dist/chunks/sp-popover.dev-dj9x7e7a.js +0 -9
  201. package/dist/chunks/sp-progress-circle.dev-0zxcyz3r.js +0 -15
  202. package/dist/chunks/sp-tooltip.dev-5c6d1kjz.js +0 -15
  203. package/dist/chunks/studio-a7pzveq1.js +0 -110
  204. package/dist/chunks/studio-c31yb4t6.js +0 -55
  205. package/dist/chunks/studio-ra7ar50f.js +0 -6682
  206. package/src/canvas/content-assets.ts +0 -263
  207. package/src/canvas/site-style-css.ts +0 -121
  208. package/src/services/settings-store.ts +0 -99
@@ -1,263 +0,0 @@
1
- /**
2
- * Content-relative asset references, resolved for the canvas.
3
- *
4
- * A content entry references media relative to ITSELF — `![](./images/hero.png)` — so the file
5
- * reads correctly in a plain markdown editor and so collections sourced outside the project root
6
- * still reach their media (specs/site-architecture.md §9.3). On the built site the collection
7
- * loader rewrites those refs onto the content type's asset mount,
8
- * `/content/<type>/images/hero.png`.
9
- *
10
- * Studio opens an entry as a STANDALONE document, so that loader never runs and the canvas would
11
- * render the authored path verbatim — which the browser resolves against `canvas.html`, not the
12
- * entry. This module applies the same mount mapping to the render document so the canvas previews
13
- * the URL production actually serves.
14
- *
15
- * The URL math is `assetUrlFor` from `@jxsuite/schema/asset-paths` — the same function the loader
16
- * and the compiler use — so the three cannot drift. It is pure string containment, which works
17
- * unchanged on the project-relative paths Studio deals in.
18
- *
19
- * The SOURCE document is never touched: the authored relative ref is what gets serialized back to
20
- * disk, and the properties panel shows the author what they wrote.
21
- *
22
- * One deliberate divergence from the loader: it declines to rewrite a ref whose target does not
23
- * exist (`existsSync`), leaving an unresolvable path alone rather than changing its meaning. The
24
- * browser has no filesystem, so this rewrites optimistically. A missing file is broken either way;
25
- * pointing at the mount URL is the more informative failure, and it is the URL the built site would
26
- * have attempted.
27
- *
28
- * @docs studio/projects/media
29
- */
30
-
31
- import { assetUrlFor } from "@jxsuite/schema/asset-paths";
32
- import { projectState } from "../store";
33
- import { activeTab } from "../workspace/workspace";
34
- import type { AssetMount } from "@jxsuite/schema/asset-paths";
35
- import type { ContentSectionEntry } from "../types";
36
- import type { JxMutableNode } from "@jxsuite/schema/types";
37
-
38
- /** Node keys whose value is a media reference. Mirrors `ASSET_KEYS` in the content loader. */
39
- const ASSET_KEYS = ["src", "poster"] as const;
40
-
41
- /** Content type names safe in a URL path segment. Mirrors `SAFE_TYPE_NAME` in the content loader. */
42
- const SAFE_TYPE_NAME = /^[\w.~-]+$/;
43
-
44
- /** The project.json section key content types live under; also their URL namespace. */
45
- const SECTION_KEY = "content";
46
-
47
- /**
48
- * A value that is already a URL, a template, or a fragment — never a content-relative file. Mirrors
49
- * `isNonRelativeRef` in the content loader.
50
- */
51
- function isNonRelativeRef(value: string): boolean {
52
- return (
53
- value === "" ||
54
- value.startsWith("/") ||
55
- value.startsWith("#") ||
56
- value.includes("${") ||
57
- /^[a-z][\w+.-]*:/i.test(value)
58
- );
59
- }
60
-
61
- /** Strip `./` and any trailing slash from a configured `source`, the way collectionDirs does. */
62
- function normalizeSource(source: string): string {
63
- return source.replaceAll("\\", "/").replace(/^\.\//, "").replace(/\/+$/, "");
64
- }
65
-
66
- /**
67
- * Resolve `ref` against `dir` in a pure, POSIX-ish way — no `node:path`, and no leading-slash
68
- * assumption, because Studio's paths are project-relative. Returns null when the result would climb
69
- * above the project root (`../` past the top), which can never name a mounted file.
70
- */
71
- export function resolveRelativePath(dir: string, ref: string): string | null {
72
- const segments = dir === "" || dir === "." ? [] : dir.replaceAll("\\", "/").split("/");
73
- const out = segments.filter((s) => s !== "" && s !== ".");
74
- for (const segment of ref.replaceAll("\\", "/").split("/")) {
75
- if (segment === "" || segment === ".") {
76
- continue;
77
- }
78
- if (segment === "..") {
79
- if (out.length === 0) {
80
- return null; // Escapes the project root.
81
- }
82
- out.pop();
83
- continue;
84
- }
85
- out.push(segment);
86
- }
87
- return out.join("/");
88
- }
89
-
90
- /** The directory portion of a project-relative file path (`""` for a root-level file). */
91
- export function dirOf(path: string): string {
92
- const normalized = path.replaceAll("\\", "/");
93
- const slash = normalized.lastIndexOf("/");
94
- return slash === -1 ? "" : normalized.slice(0, slash);
95
- }
96
-
97
- /**
98
- * The asset mount for the content type that owns `documentPath`, or null when the document is not a
99
- * directory-backed content entry.
100
- *
101
- * Only in-project sources can match: an entry Studio can address project-relatively is by
102
- * definition inside the project, and a source pointing elsewhere (`../../docs`) names files the
103
- * file tree cannot reach in the first place.
104
- */
105
- export function contentMountFor(
106
- documentPath: string | null | undefined,
107
- content: Record<string, ContentSectionEntry> | null | undefined,
108
- ): AssetMount | null {
109
- if (!documentPath || !content) {
110
- return null;
111
- }
112
- const path = documentPath.replaceAll("\\", "/").replace(/^\.\//, "");
113
- let best: AssetMount | null = null;
114
- for (const [name, def] of Object.entries(content)) {
115
- if (!def?.source || !SAFE_TYPE_NAME.test(name)) {
116
- continue;
117
- }
118
- const dir = normalizeSource(def.source);
119
- // A single-file source is not a collection directory — its siblings are not its assets.
120
- if (dir === "" || dir.startsWith("..") || /\.[a-z0-9]+$/i.test(dir)) {
121
- continue;
122
- }
123
- if (path.startsWith(`${dir}/`) && (!best || dir.length > best.dir.length)) {
124
- best = { dir, urlPrefix: `/${SECTION_KEY}/${name}` };
125
- }
126
- }
127
- return best;
128
- }
129
-
130
- /**
131
- * The mounted URL for one authored reference, or null to leave it as written. A ref is rewritten
132
- * only when it is relative and lands inside the collection directory — anything else (an absolute
133
- * URL, a project-root path, a bound template, a `../` escape out of the collection) keeps the
134
- * meaning it already had.
135
- */
136
- export function mountedRefFor(value: string, entryDir: string, mount: AssetMount): string | null {
137
- if (isNonRelativeRef(value)) {
138
- return null;
139
- }
140
- const bare = value.split("#")[0]!.split("?")[0]!;
141
- let decoded: string;
142
- try {
143
- decoded = decodeURIComponent(bare);
144
- } catch {
145
- return null;
146
- }
147
- const target = resolveRelativePath(entryDir, decoded);
148
- if (target === null) {
149
- return null;
150
- }
151
- const url = assetUrlFor([mount], target);
152
- if (!url) {
153
- return null; // Outside the collection — not the mount's to publish.
154
- }
155
- return `${url}${value.slice(bare.length)}`;
156
- }
157
-
158
- /**
159
- * Rewrite one node's media refs, returning the node unchanged when nothing applies.
160
- *
161
- * PURE REBUILD: a node with a rewritten ref is replaced by a shallow copy, and so is every ancestor
162
- * on the path to it. Untouched subtrees keep their original references, so the render doc still
163
- * shares the vast majority of its nodes with the source doc.
164
- */
165
- function rewriteNode(node: JxMutableNode, entryDir: string, mount: AssetMount): JxMutableNode {
166
- const record = node as unknown as Record<string, unknown>;
167
- /** Accumulated changed keys; stays null (and the node is returned as-is) when nothing applies. */
168
- let patch: Record<string, unknown> | null = null;
169
-
170
- for (const key of ASSET_KEYS) {
171
- const value = record[key];
172
- if (typeof value === "string") {
173
- const rewritten = mountedRefFor(value, entryDir, mount);
174
- if (rewritten) {
175
- patch ??= {};
176
- patch[key] = rewritten;
177
- }
178
- }
179
- }
180
-
181
- const attributes = record.attributes as Record<string, unknown> | undefined;
182
- if (attributes) {
183
- let nextAttrs: Record<string, unknown> | null = null;
184
- for (const key of ASSET_KEYS) {
185
- const value = attributes[key];
186
- if (typeof value === "string") {
187
- const rewritten = mountedRefFor(value, entryDir, mount);
188
- if (rewritten) {
189
- nextAttrs ??= { ...attributes };
190
- nextAttrs[key] = rewritten;
191
- }
192
- }
193
- }
194
- if (nextAttrs) {
195
- patch ??= {};
196
- patch.attributes = nextAttrs;
197
- }
198
- }
199
-
200
- const children = record.children as unknown[] | undefined;
201
- if (Array.isArray(children)) {
202
- let nextChildren: unknown[] | null = null;
203
- for (const [i, child] of children.entries()) {
204
- if (!child || typeof child !== "object") {
205
- continue;
206
- }
207
- const rewrittenChild = rewriteNode(child as JxMutableNode, entryDir, mount);
208
- if (rewrittenChild !== child) {
209
- nextChildren ??= [...children];
210
- nextChildren[i] = rewrittenChild;
211
- }
212
- }
213
- if (nextChildren) {
214
- patch ??= {};
215
- patch.children = nextChildren;
216
- }
217
- }
218
-
219
- return patch ? ({ ...node, ...patch } as JxMutableNode) : node;
220
- }
221
-
222
- /**
223
- * Map a content entry's relative media refs onto its content type's asset mount, for rendering.
224
- *
225
- * Returns `doc` itself when the document is not a content entry or carries no rewritable ref, so
226
- * the common case costs one walk and allocates nothing.
227
- */
228
- export function rewriteContentAssets(
229
- doc: JxMutableNode,
230
- documentPath: string | null | undefined,
231
- content: Record<string, ContentSectionEntry> | null | undefined,
232
- ): JxMutableNode {
233
- const mount = contentMountFor(documentPath, content);
234
- if (!mount) {
235
- return doc;
236
- }
237
- return rewriteNode(doc, dirOf(documentPath!.replaceAll("\\", "/").replace(/^\.\//, "")), mount);
238
- }
239
-
240
- /**
241
- * The src a PARENT-REALM preview (a media-picker thumbnail) should load for an authored value.
242
- *
243
- * The canvas gets its mapping through {@link rewriteContentAssets} on the render doc, but panel
244
- * chrome renders in the parent document, where a content-relative value would resolve against
245
- * `index.html` and 404 exactly as it did in the canvas. Same mapping, same rule — resolved against
246
- * the ACTIVE tab, which is the entry whose field is being edited.
247
- *
248
- * Non-content documents and already-absolute values pass through untouched.
249
- */
250
- export function previewAssetSrc(value: string): string {
251
- if (!value) {
252
- return value;
253
- }
254
- const documentPath = activeTab.value?.documentPath;
255
- const mount = contentMountFor(
256
- documentPath,
257
- projectState?.projectConfig?.content as Record<string, ContentSectionEntry> | undefined,
258
- );
259
- if (!mount) {
260
- return value;
261
- }
262
- return mountedRefFor(value, dirOf(documentPath!), mount) ?? value;
263
- }
@@ -1,121 +0,0 @@
1
- /// <reference lib="dom" />
2
- /**
3
- * Site-style stylesheet builder — pure functions extracted for testability.
4
- *
5
- * The canvas iframe injects the project's `style` as a real stylesheet instead of inline properties
6
- * on the root element: inline custom properties would defeat the forced-scheme override selectors
7
- * (`:root[data-color-scheme]`, spec §9.5), and object-valued `@--name` blocks used to be dropped
8
- * entirely. Scheme-query blocks dual-emit through the runtime's schemeSelectors so the canvas
9
- * honors both the OS preference and the preview toggle.
10
- */
11
-
12
- import { pureSchemeOf, schemeSelectors, camelToKebab } from "@jxsuite/runtime";
13
-
14
- /** Id of the injected site-style tag (replace-in-place, never accumulate). */
15
- export const SITE_STYLE_ID = "jx-site-style";
16
-
17
- /** Serialize scalar declarations, kebab-casing property names and transposing values. */
18
- function toDecls(props: Record<string, unknown>, transpose: (value: string) => string): string {
19
- return Object.entries(props)
20
- .map(([key, value]) => `${camelToKebab(key)}: ${transpose(String(value))}`)
21
- .join("; ");
22
- }
23
-
24
- /**
25
- * Build the site-style sheet text: custom properties on `:root`, plain properties on `body`,
26
- * conditional `@`-blocks resolved against `mediaQueries` (scheme queries dual-emitted per the
27
- * forced-scheme contract), and `color-scheme: light dark` declared when a scheme query exists.
28
- *
29
- * @param {Record<string, unknown>} siteStyle
30
- * @param {Record<string, string>} mediaQueries
31
- * @param {(value: string) => string} transpose - Unit transposer (canvas vh→cqh etc.)
32
- * @returns {string}
33
- */
34
- export function buildSiteStyleCSS(
35
- siteStyle: Record<string, unknown>,
36
- mediaQueries: Record<string, string>,
37
- transpose: (value: string) => string,
38
- ): string {
39
- const rules: string[] = [];
40
- const rootProps: Record<string, unknown> = {};
41
- const bodyProps: Record<string, unknown> = {};
42
- const condBlocks: [string, Record<string, unknown>][] = [];
43
-
44
- for (const [key, value] of Object.entries(siteStyle)) {
45
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
46
- if (key.startsWith("@")) {
47
- condBlocks.push([key, value as Record<string, unknown>]);
48
- }
49
- // Non-@ objects (nested element selectors) are page-content styling — the resolved doc's
50
- // Own style pass covers those; the site sheet handles tokens + conditional overrides.
51
- continue;
52
- }
53
- if (key.startsWith(":") || key.startsWith(".") || key.startsWith("[") || key.startsWith("@")) {
54
- continue;
55
- }
56
- if (key.startsWith("--")) {
57
- rootProps[key] = value;
58
- } else {
59
- bodyProps[key] = value;
60
- }
61
- }
62
-
63
- const rootCSS = toDecls(rootProps, transpose);
64
- if (rootCSS) {
65
- rules.push(`:root { ${rootCSS} }`);
66
- }
67
- const bodyCSS = toDecls(bodyProps, transpose);
68
- if (bodyCSS) {
69
- rules.push(`body { ${bodyCSS} }`);
70
- }
71
-
72
- for (const [atKey, block] of condBlocks) {
73
- const query = atKey.startsWith("@--")
74
- ? (mediaQueries[atKey.slice(1)] ?? atKey.slice(1))
75
- : atKey.startsWith("@(")
76
- ? atKey.slice(1)
77
- : null;
78
- if (query === null) {
79
- continue;
80
- }
81
- const scheme = pureSchemeOf(query);
82
- const emit = (selector: string, props: string) => {
83
- if (!props) {
84
- return;
85
- }
86
- if (scheme) {
87
- const { auto, forced } = schemeSelectors(selector, scheme);
88
- rules.push(`@media ${query} { ${auto} { ${props} } }`, `${forced} { ${props} }`);
89
- } else {
90
- rules.push(`@media ${query} { ${selector} { ${props} } }`);
91
- }
92
- };
93
- const condRoot: Record<string, unknown> = {};
94
- const condBody: Record<string, unknown> = {};
95
- const condSubs: [string, Record<string, unknown>][] = [];
96
- for (const [k, v] of Object.entries(block)) {
97
- if (v !== null && typeof v === "object" && !Array.isArray(v)) {
98
- if (!k.startsWith("@")) {
99
- condSubs.push([k, v as Record<string, unknown>]);
100
- }
101
- continue;
102
- }
103
- if (k.startsWith("--")) {
104
- condRoot[k] = v;
105
- } else {
106
- condBody[k] = v;
107
- }
108
- }
109
- emit(":root", toDecls(condRoot, transpose));
110
- emit("body", toDecls(condBody, transpose));
111
- for (const [sel, sub] of condSubs) {
112
- emit(sel, toDecls(sub, transpose));
113
- }
114
- }
115
-
116
- if (Object.values(mediaQueries).some((q) => pureSchemeOf(q) !== null)) {
117
- rules.push(":root { color-scheme: light dark }");
118
- }
119
-
120
- return rules.join("\n");
121
- }
@@ -1,99 +0,0 @@
1
- /// <reference lib="dom" />
2
- /**
3
- * Settings-store.js — sync of user settings between localStorage and the platform backend.
4
- *
5
- * LocalStorage stays the synchronous source of truth for every consumer (ai-settings getters read
6
- * it directly). On platforms with a backend user-settings store (desktop/chromium — where
7
- * localStorage does not roam across browser profiles), the persisted keys are hydrated from the
8
- * backend at boot and written through on every change. The dev server omits the platform methods
9
- * and settings remain localStorage-only.
10
- *
11
- * @license MIT
12
- */
13
-
14
- import { getPlatform, hasPlatform } from "../platform";
15
-
16
- /** The localStorage keys mirrored into the platform's user-settings store. */
17
- export const PERSISTED_SETTINGS_KEYS = [
18
- "jx.ai.openaiKey",
19
- "jx.ai.baseUrl",
20
- "jx.ai.model",
21
- "jx.cf.token",
22
- "jx.cf.accountId",
23
- ] as const;
24
-
25
- /** Read a localStorage value defensively, treating unavailable/throwing storage as empty. */
26
- function readLocal(key: string): string {
27
- try {
28
- return globalThis.localStorage?.getItem(key) ?? "";
29
- } catch {
30
- return "";
31
- }
32
- }
33
-
34
- /**
35
- * Pull the persisted settings from the backend store into localStorage. Call once after the
36
- * platform is registered; a no-op on platforms without a settings store (dev server).
37
- *
38
- * Migration: keys the backend does not know yet but localStorage does (an existing user upgrading
39
- * from localStorage-only builds) are pushed back to the backend once, fire-and-forget.
40
- */
41
- export async function hydrateSettings(): Promise<void> {
42
- if (!hasPlatform()) {
43
- return;
44
- }
45
- const platform = getPlatform();
46
- if (!platform.getSettings) {
47
- return;
48
- }
49
- let stored: Record<string, string>;
50
- try {
51
- stored = await platform.getSettings();
52
- } catch {
53
- return;
54
- }
55
- const merged: Record<string, string> = { ...stored };
56
- let needsMigration = false;
57
- for (const key of PERSISTED_SETTINGS_KEYS) {
58
- const backendValue = stored[key];
59
- if (backendValue) {
60
- try {
61
- globalThis.localStorage?.setItem(key, backendValue);
62
- } catch {
63
- /* Storage unavailable — consumers fall back to defaults. */
64
- }
65
- } else {
66
- const localValue = readLocal(key);
67
- if (localValue) {
68
- merged[key] = localValue;
69
- needsMigration = true;
70
- }
71
- }
72
- }
73
- if (needsMigration && platform.saveSettings) {
74
- void platform.saveSettings(merged).catch(() => {});
75
- }
76
- }
77
-
78
- /**
79
- * Write the current values of the persisted keys through to the backend store, fire-and-forget.
80
- * Call after mutating any of {@link PERSISTED_SETTINGS_KEYS} in localStorage; a no-op on platforms
81
- * without a settings store (dev server).
82
- */
83
- export function persistSettings(): void {
84
- if (!hasPlatform()) {
85
- return;
86
- }
87
- const platform = getPlatform();
88
- if (!platform.saveSettings) {
89
- return;
90
- }
91
- const settings: Record<string, string> = {};
92
- for (const key of PERSISTED_SETTINGS_KEYS) {
93
- const value = readLocal(key);
94
- if (value) {
95
- settings[key] = value;
96
- }
97
- }
98
- void platform.saveSettings(settings).catch(() => {});
99
- }