@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
@@ -3,76 +3,117 @@ import {
3
3
  measuredRowHeight,
4
4
  revealListRow,
5
5
  watchListWindow
6
- } from "./studio-sccge2wf.js";
6
+ } from "./studio-p8j86wag.js";
7
+ import {
8
+ formatBytes,
9
+ loadMediaMeta,
10
+ peekMediaMeta,
11
+ recordImageSize
12
+ } from "./studio-gv8vvshc.js";
7
13
  import {
8
14
  autoSyncProjectOnOpen,
9
15
  cleanupGitPanel,
10
16
  maybePromptJxsuiteUpdate,
11
17
  shouldInstallAutomation
12
- } from "./studio-ke244pam.js";
18
+ } from "./studio-ex8zyp98.js";
19
+ import {
20
+ collectionOfPath
21
+ } from "./studio-sf7zjgv1.js";
13
22
  import {
14
23
  openCsvGridTab,
15
24
  openPagesGrid
16
- } from "./studio-0b6fs05q.js";
25
+ } from "./studio-4pqqxbrt.js";
17
26
  import {
18
- collectionDirs,
19
- collectionInfo,
20
27
  confirmFileDelete,
21
- markLocalMutation,
28
+ disarmPreviewOverlay,
29
+ loadMediaUsages,
30
+ mediaUsageHeadline,
22
31
  parseSourceForPath,
32
+ peekMediaUsages,
23
33
  renamePromptMessage
24
- } from "./studio-26sv44br.js";
34
+ } from "./studio-cb12yz9w.js";
25
35
  import {
26
- UPLOAD_ACCEPT,
27
- invalidateUsages,
36
+ ensureIgnoreLayers,
37
+ isIgnoredEntry,
38
+ markLocalMutation,
39
+ resetIgnoreCache
40
+ } from "./studio-q43a5y9h.js";
41
+ import {
42
+ extensionOf,
28
43
  isImage,
44
+ isMediaFile,
45
+ mediaKind,
46
+ mediaSiteUrl,
47
+ previewFileSrc,
48
+ uploadAccept,
29
49
  uploadAssets
30
- } from "./studio-cz2j1c0g.js";
50
+ } from "./studio-htsf0w6m.js";
51
+ import {
52
+ invalidateUsages
53
+ } from "./studio-cft9r4r7.js";
31
54
  import {
32
55
  showProgressModal
33
- } from "./studio-28xc8ws4.js";
56
+ } from "./studio-9c4k4vhv.js";
34
57
  import {
35
58
  rectOf,
36
59
  registerPanel
37
- } from "./studio-pyrw8h0w.js";
60
+ } from "./studio-kjjsjak4.js";
61
+ import {
62
+ markSessionRestored,
63
+ persistedSession,
64
+ resetProjectShell,
65
+ restoreSession,
66
+ setActivityTab
67
+ } from "./studio-hbtp0eh1.js";
38
68
  import {
39
- activateTab,
40
69
  activeRegistry,
41
- errorMessage,
42
- findContentTypeSchema,
70
+ paneRegion,
71
+ renderPopover,
72
+ showPromptDialog
73
+ } from "./studio-t7qb9db8.js";
74
+ import {
75
+ errorMessage
76
+ } from "./studio-9n4mrmnb.js";
77
+ import {
78
+ activateTab,
79
+ collectionForDirectory,
80
+ collectionForFile,
43
81
  isCollabPath,
44
82
  localeLabel,
45
83
  localeOfPath,
46
- markSessionRestored,
47
84
  moveTabToPane,
48
- notify,
49
- now,
50
85
  openTab,
51
86
  paneOfTab,
52
- persistedSession,
53
87
  receivingPane,
54
88
  renameTab,
55
- renderPopover,
56
89
  replaceAllTabs,
57
- resetProjectShell,
58
90
  resolveI18n,
59
- restoreSession,
60
- setActivityTab,
61
91
  setWorkspaceProject,
62
- showPromptDialog,
63
92
  workspace
64
- } from "./studio-ra7ar50f.js";
93
+ } from "./studio-2zkz8evn.js";
94
+ import {
95
+ SETTINGS,
96
+ readStoredSetting,
97
+ setSetting
98
+ } from "./studio-92ab2jqe.js";
99
+ import {
100
+ effect,
101
+ effectScope,
102
+ notify,
103
+ now
104
+ } from "./studio-xrapxwwc.js";
65
105
  import {
66
106
  classMap
67
- } from "./studio-twsafr1e.js";
107
+ } from "./studio-m3hwn7y1.js";
68
108
  import {
69
109
  ref,
70
110
  repeat
71
- } from "./studio-kvfx9z2c.js";
111
+ } from "./studio-4rzyrtjz.js";
72
112
  import {
73
113
  html,
74
- nothing
75
- } from "./studio-5x5774ak.js";
114
+ nothing,
115
+ render
116
+ } from "./studio-kryvsw6j.js";
76
117
  import {
77
118
  combine,
78
119
  draggable,
@@ -81,42 +122,47 @@ import {
81
122
  } from "./studio-g1wdche0.js";
82
123
  import {
83
124
  loadComponentRegistry
84
- } from "./studio-s519zc99.js";
125
+ } from "./studio-bgxt93ga.js";
85
126
  import {
86
127
  projectState,
87
128
  requireProjectState,
88
129
  setProjectState
89
- } from "./studio-cjm7fdjf.js";
130
+ } from "./studio-p4ngc046.js";
90
131
  import {
91
132
  documentExtensions,
133
+ formatByExtension,
92
134
  formatForPath,
135
+ getFormats,
93
136
  loadFormats,
94
137
  noFormatError,
95
138
  refreshExtensionUi,
96
139
  refreshFormats
97
- } from "./studio-rqemfhz6.js";
140
+ } from "./studio-eenfkt4e.js";
98
141
  import {
99
142
  getPlatform,
100
143
  hasPlatform
101
- } from "./studio-sas7mt3k.js";
144
+ } from "./studio-9ab8gqsm.js";
145
+ import {
146
+ __require
147
+ } from "./studio-a0tswy0w.js";
102
148
 
103
149
  // src/packages/ensure-deps.ts
104
150
  async function ensureDependenciesInstalled() {
105
151
  if (shouldInstallAutomation(location.search)) {
106
- return;
152
+ return false;
107
153
  }
108
154
  const platform = getPlatform();
109
155
  if (!platform.dependenciesNeedInstall || !platform.installDependencies) {
110
- return;
156
+ return false;
111
157
  }
112
158
  let needs = false;
113
159
  try {
114
160
  needs = await platform.dependenciesNeedInstall();
115
161
  } catch {
116
- return;
162
+ return false;
117
163
  }
118
164
  if (!needs) {
119
- return;
165
+ return false;
120
166
  }
121
167
  const progress = showProgressModal({
122
168
  status: "Running bun install…",
@@ -126,89 +172,287 @@ async function ensureDependenciesInstalled() {
126
172
  const result = await platform.installDependencies();
127
173
  if (result.ok) {
128
174
  progress.done();
129
- } else {
130
- progress.fail(result.log ?? "bun install failed");
175
+ return true;
131
176
  }
177
+ progress.fail(result.log ?? "bun install failed");
132
178
  } catch (error) {
133
179
  progress.fail(error instanceof Error ? error.message : String(error));
134
180
  }
181
+ return false;
135
182
  }
136
183
 
137
- // src/content/entry-model.ts
138
- function collectionNames() {
139
- return Object.keys(projectState?.projectConfig?.content ?? {});
140
- }
141
- function entryCollection(name) {
142
- const info = collectionInfo(name);
143
- if (!info) {
144
- return null;
184
+ // src/media/media-pane.ts
185
+ var MEDIA_MODE = "media";
186
+ var FONT_EXTENSIONS = new Set([".woff", ".woff2", ".ttf", ".otf"]);
187
+ var SPECIMEN = "The quick brown fox jumps over the lazy dog";
188
+ var SPECIMEN_SIZES = [48, 32, 24, 18, 14];
189
+ var _active = new Map;
190
+ function activeIn(paneId) {
191
+ return _active.get(paneId) ?? null;
192
+ }
193
+ function mediaPaneMounted(paneId, tab) {
194
+ const panel = activeIn(paneId);
195
+ return panel !== null && panel.tabId === tab.id && panel.wrap.isConnected;
196
+ }
197
+ function detachMediaPane(paneId) {
198
+ const panel = _active.get(paneId);
199
+ if (!panel) {
200
+ return;
145
201
  }
146
- if (/\.[a-z\d]+$/i.test(info.dir)) {
147
- return null;
202
+ panel.scope.stop();
203
+ _active.delete(paneId);
204
+ }
205
+ function assetTpl(path, src, redraw) {
206
+ const ext = extensionOf(path);
207
+ const kind = mediaKind({ name: path });
208
+ if (FONT_EXTENSIONS.has(ext)) {
209
+ const family = `jx-specimen-${path.replaceAll(/[^a-zA-Z0-9]/g, "-")}`;
210
+ return html`
211
+ <style>
212
+ @font-face {
213
+ font-family: "${family}";
214
+ src: url("${src}");
215
+ }
216
+ </style>
217
+ <div class="media-specimen" style="font-family: '${family}', system-ui, sans-serif">
218
+ ${SPECIMEN_SIZES.map((size) => html`<p class="media-specimen-line" style="font-size: ${size}px">
219
+ ${SPECIMEN}
220
+ </p>`)}
221
+ </div>
222
+ `;
223
+ }
224
+ switch (kind) {
225
+ case "image": {
226
+ return html`<img
227
+ class="media-image"
228
+ src=${src}
229
+ alt=""
230
+ @load=${(e) => {
231
+ const img = e.target;
232
+ if (recordImageSize(path, img.naturalWidth, img.naturalHeight)) {
233
+ redraw();
234
+ }
235
+ }}
236
+ />`;
237
+ }
238
+ case "video": {
239
+ return html`<video class="media-video" src=${src} controls></video>`;
240
+ }
241
+ case "audio": {
242
+ return html`<audio class="media-audio" src=${src} controls></audio>`;
243
+ }
244
+ default: {
245
+ return ext === ".pdf" ? html`<embed class="media-embed" src=${src} type="application/pdf" />` : html`<div class="media-unviewable">
246
+ <p>Jx has no viewer for <code>${ext || "this file"}</code>.</p>
247
+ <p class="media-note">The file is in the project and builds normally.</p>
248
+ </div>`;
249
+ }
148
250
  }
149
- return {
150
- def: info.def,
151
- dir: info.dir,
152
- ext: info.ext,
153
- name: info.name,
154
- schema: info.def.schema ?? null
155
- };
156
251
  }
157
- function entryCollections() {
158
- return collectionNames().map((name) => entryCollection(name)).filter((collection) => collection !== null);
252
+ function factsTpl(path, meta) {
253
+ const facts = [mediaKind({ name: path })];
254
+ if (meta?.width != null && meta.height != null) {
255
+ facts.push(`${meta.width} × ${meta.height}`);
256
+ }
257
+ if (meta?.bytes != null) {
258
+ facts.push(formatBytes(meta.bytes));
259
+ }
260
+ if (meta?.modified) {
261
+ facts.push(`modified ${meta.modified.slice(0, 10)}`);
262
+ }
263
+ return html`<p class="media-facts">${facts.join(" · ")}</p>`;
159
264
  }
160
- function collectionOfPath(path) {
161
- const matched = findContentTypeSchema(path ?? null, projectState?.projectConfig);
162
- return matched ? entryCollection(matched.name) : null;
265
+ function refTpl(path) {
266
+ const ref2 = mediaSiteUrl(path);
267
+ return html`
268
+ <div class="media-ref">
269
+ <code class="media-ref-value">${ref2}</code>
270
+ <sp-action-button
271
+ size="s"
272
+ quiet
273
+ title="Copy the reference"
274
+ @click=${() => {
275
+ navigator.clipboard?.writeText(ref2);
276
+ }}
277
+ >Copy</sp-action-button
278
+ >
279
+ </div>
280
+ `;
163
281
  }
164
- function cloneDefault(value) {
165
- if (value === null || typeof value !== "object") {
166
- return value;
282
+ function usageTpl(usage) {
283
+ const headline = mediaUsageHeadline(usage);
284
+ if (headline === null) {
285
+ return nothing;
167
286
  }
168
- return JSON.parse(JSON.stringify(value));
287
+ const files = usage?.status === "ready" ? usage.result.files : [];
288
+ return html`
289
+ <div class="media-usage">
290
+ <h4 class="media-usage-title">Used by</h4>
291
+ <p class="media-usage-headline">${headline}</p>
292
+ ${files.length === 0 ? nothing : html`<ul class="media-usage-list">
293
+ ${files.map((file) => html`<li>
294
+ <button
295
+ type="button"
296
+ class="media-usage-link"
297
+ @click=${() => {
298
+ openDocument(file.path);
299
+ }}
300
+ >
301
+ ${file.path}
302
+ </button>
303
+ <span class="media-usage-count">${file.count}</span>
304
+ </li>`)}
305
+ </ul>`}
306
+ </div>
307
+ `;
308
+ }
309
+ async function openDocument(path) {
310
+ const { openFileInTab } = await import("./files-0cratsr0.js");
311
+ await openFileInTab(path);
169
312
  }
170
- function emptyForField(field) {
171
- if (typeof field.$ref === "string") {
172
- return "";
313
+ function renderMediaMode(surface, tab) {
314
+ const { paneId, wrap: canvasWrap } = surface;
315
+ if (mediaPaneMounted(paneId, tab)) {
316
+ return;
173
317
  }
174
- switch (field.type) {
175
- case "boolean": {
176
- return false;
318
+ detachMediaPane(paneId);
319
+ const scope = effectScope();
320
+ const panel = { paneId, scope, tabId: tab.id, wrap: canvasWrap };
321
+ _active.set(paneId, panel);
322
+ const path = tab.documentPath ?? "";
323
+ const redraw = () => {
324
+ if (activeIn(paneId) === panel) {
325
+ draw();
177
326
  }
178
- case "integer":
179
- case "number": {
180
- return 0;
327
+ };
328
+ const draw = () => {
329
+ const meta = peekMediaMeta(path);
330
+ const usage = peekMediaUsages(path);
331
+ if (meta === null) {
332
+ loadMediaMeta(path).then(redraw);
181
333
  }
182
- case "array": {
183
- return [];
334
+ if (usage === null || usage.status === "pending") {
335
+ loadMediaUsages(path).then(redraw);
184
336
  }
185
- case "object": {
186
- return seedEntry(field);
337
+ render(html`
338
+ <div class="media-viewer" data-jx-region=${paneRegion(paneId, "media")}>
339
+ <div class="media-stage">${assetTpl(path, previewFileSrc(path), redraw)}</div>
340
+ <div class="media-details">
341
+ <h3 class="media-name">${path.split("/").pop()}</h3>
342
+ ${factsTpl(path, meta)} ${refTpl(path)} ${usageTpl(usage)}
343
+ </div>
344
+ </div>
345
+ `, canvasWrap);
346
+ };
347
+ scope.run(() => {
348
+ effect(() => {
349
+ if (activeIn(paneId) !== panel) {
350
+ return;
351
+ }
352
+ tab.documentPath;
353
+ draw();
354
+ });
355
+ });
356
+ }
357
+
358
+ // src/media/media-open.ts
359
+ var MEDIA_STUB_DOCUMENT = { children: [], tagName: "div" };
360
+ function isViewableMedia(path) {
361
+ return isMediaFile(path);
362
+ }
363
+ function mediaTabModes(path) {
364
+ return extensionOf(path) === ".svg" ? [MEDIA_MODE, "source"] : [MEDIA_MODE];
365
+ }
366
+ function openMediaTab(path) {
367
+ const existing = workspace.tabs.get(path);
368
+ if (existing) {
369
+ activateTab(path);
370
+ return existing;
371
+ }
372
+ return openTab({
373
+ capabilities: { modes: mediaTabModes(path) },
374
+ document: structuredClone(MEDIA_STUB_DOCUMENT),
375
+ documentPath: path,
376
+ id: path,
377
+ sourceFormat: null
378
+ });
379
+ }
380
+
381
+ // src/format/format-choices.ts
382
+ var DOCUMENT_KINDS = ["page", "component"];
383
+ var DOCUMENT_ROOTS = { components: "component", pages: "page" };
384
+ function normalize(path) {
385
+ return path.replaceAll("\\", "/").replace(/^\.\//, "");
386
+ }
387
+ function documentRootOf(path) {
388
+ const lower = normalize(path).toLowerCase();
389
+ for (const [dir, kind] of Object.entries(DOCUMENT_ROOTS)) {
390
+ if (lower.startsWith(`${dir}/`)) {
391
+ return kind;
187
392
  }
188
- default: {
189
- return "";
393
+ }
394
+ return null;
395
+ }
396
+ function declaresDocumentKind(kinds) {
397
+ return DOCUMENT_KINDS.some((kind) => kinds.includes(kind));
398
+ }
399
+ function isLayoutPath(path) {
400
+ const declared = projectState?.projectConfig?.layout;
401
+ if (typeof declared !== "string") {
402
+ return false;
403
+ }
404
+ return normalize(declared).toLowerCase() === normalize(path).toLowerCase();
405
+ }
406
+ function creationFormats(kind) {
407
+ const rows = [{ ext: ".json", label: "JSON (.json)" }];
408
+ for (const ext of documentExtensions(kind)) {
409
+ const reader = formatByExtension(ext, "parse");
410
+ if (reader && formatByExtension(ext, "serialize")) {
411
+ rows.push({ ext, label: `${reader.name} (${ext})` });
190
412
  }
191
413
  }
414
+ return rows;
192
415
  }
193
- function seedEntry(schema) {
194
- const properties = schema?.properties;
195
- if (!properties) {
196
- return {};
416
+ function knownDocumentExtensions() {
417
+ return new Set([".json", ...getFormats().flatMap((format) => format.extensions)]);
418
+ }
419
+ function convertTargets(path) {
420
+ if (path === null || path === undefined || path === "") {
421
+ return [];
422
+ }
423
+ const root = documentRootOf(path);
424
+ if (root === null || isLayoutPath(path) || collectionForFile(path)) {
425
+ return [];
426
+ }
427
+ const source = formatForPath(path);
428
+ const readable = source ? Boolean(source.capabilities.parse) && declaresDocumentKind(source.documentKinds) : normalize(path).toLowerCase().endsWith(".json");
429
+ if (!readable) {
430
+ return [];
197
431
  }
198
- const required = new Set(Array.isArray(schema?.required) ? schema.required : []);
199
- const seed = {};
200
- for (const [name, field] of Object.entries(properties)) {
201
- if (field.default !== undefined) {
202
- seed[name] = cloneDefault(field.default);
203
- } else if (required.has(name)) {
204
- seed[name] = emptyForField(field);
432
+ const rows = [];
433
+ if (source) {
434
+ rows.push({ ext: ".json", label: "JSON (.json)" });
435
+ }
436
+ for (const ext of documentExtensions()) {
437
+ const writer = formatByExtension(ext, "serialize");
438
+ if (!writer || writer.name === source?.name || !declaresDocumentKind(writer.documentKinds) || !writer.documentKinds.includes(root)) {
439
+ continue;
205
440
  }
441
+ rows.push({ ext, label: `${writer.name} (${ext})` });
206
442
  }
207
- return seed;
443
+ return rows;
208
444
  }
209
- function missingRequired(schema, value) {
210
- const required = Array.isArray(schema?.required) ? schema.required : [];
211
- return required.filter((name) => value[name] === undefined);
445
+ function convertTargetExtensions() {
446
+ const exts = new Set([".json"]);
447
+ for (const format of getFormats()) {
448
+ if (!format.capabilities.serialize || !declaresDocumentKind(format.documentKinds)) {
449
+ continue;
450
+ }
451
+ for (const ext of format.extensions) {
452
+ exts.add(ext);
453
+ }
454
+ }
455
+ return [...exts].toSorted();
212
456
  }
213
457
 
214
458
  // src/recent-projects.ts
@@ -330,12 +574,21 @@ async function loadDirectory(dirPath) {
330
574
  }
331
575
  try {
332
576
  const platform = getPlatform();
333
- const entries = await platform.listDirectory(dirPath);
577
+ const [entries] = await Promise.all([
578
+ platform.listDirectory(dirPath),
579
+ ensureIgnoreLayers(dirPath)
580
+ ]);
334
581
  projectState.dirs.set(dirPath, entries);
335
582
  } catch {
336
583
  projectState.dirs.set(dirPath, []);
337
584
  }
338
585
  }
586
+ function showIgnoredFiles() {
587
+ return readStoredSetting(SETTINGS.showIgnoredFiles) === "true";
588
+ }
589
+ function setShowIgnoredFiles(show) {
590
+ setSetting(SETTINGS.showIgnoredFiles, show ? "true" : "");
591
+ }
339
592
  async function loadProject() {
340
593
  try {
341
594
  const platform = getPlatform();
@@ -347,6 +600,7 @@ async function loadProject() {
347
600
  refreshFormats();
348
601
  loadFormats();
349
602
  refreshExtensionUi(platform);
603
+ resetIgnoreCache();
350
604
  setProjectState({
351
605
  dirs: new Map,
352
606
  expanded: new Set,
@@ -365,7 +619,11 @@ async function loadProject() {
365
619
  if (info.isSiteProject) {
366
620
  addRecentProject(requireProjectState().name, meta.root);
367
621
  await autoSyncProjectOnOpen();
368
- await ensureDependenciesInstalled();
622
+ if (await ensureDependenciesInstalled()) {
623
+ refreshFormats();
624
+ await loadFormats();
625
+ refreshExtensionUi(platform);
626
+ }
369
627
  await loadDirectory(".");
370
628
  await loadComponentRegistry();
371
629
  await openLastSessionOrHome();
@@ -387,9 +645,11 @@ async function openProject({
387
645
  document: { children: [], tagName: "div" },
388
646
  id: "initial"
389
647
  });
648
+ disarmPreviewOverlay();
390
649
  refreshFormats();
391
650
  loadFormats();
392
651
  refreshExtensionUi(platform);
652
+ resetIgnoreCache();
393
653
  setProjectState({
394
654
  ...projectState,
395
655
  dirs: new Map,
@@ -403,7 +663,11 @@ async function openProject({
403
663
  });
404
664
  setWorkspaceProject(handle.root, config);
405
665
  await autoSyncProjectOnOpen();
406
- await ensureDependenciesInstalled();
666
+ if (await ensureDependenciesInstalled()) {
667
+ refreshFormats();
668
+ await loadFormats();
669
+ refreshExtensionUi(platform);
670
+ }
407
671
  await loadDirectory(".");
408
672
  await loadComponentRegistry();
409
673
  const conventionalDirs = new Set([
@@ -549,6 +813,7 @@ function renderFilesTemplate({
549
813
  <sp-button variant="accent" size="s" @click=${openProjectFn}>Open Project</sp-button>
550
814
  </div>`;
551
815
  }
816
+ const showingIgnored = showIgnoredFiles();
552
817
  return html`
553
818
  ${projectState.isSiteProject ? html`
554
819
  <div class="project-header">
@@ -571,6 +836,7 @@ function renderFilesTemplate({
571
836
  label="Refresh"
572
837
  @click=${async () => {
573
838
  requireProjectState().dirs.clear();
839
+ resetIgnoreCache();
574
840
  await loadDirectory(".");
575
841
  for (const dir of requireProjectState().expanded) {
576
842
  await loadDirectory(dir);
@@ -580,6 +846,17 @@ function renderFilesTemplate({
580
846
  >
581
847
  <sp-icon-refresh slot="icon"></sp-icon-refresh>
582
848
  </sp-action-button>
849
+ <sp-action-button
850
+ size="xs"
851
+ label=${showingIgnored ? "Hide ignored files" : "Show ignored files"}
852
+ ?selected=${showingIgnored}
853
+ @click=${() => {
854
+ setShowIgnoredFiles(!showingIgnored);
855
+ renderLeftPanel();
856
+ }}
857
+ >
858
+ ${showingIgnored ? html`<sp-icon-visibility slot="icon"></sp-icon-visibility>` : html`<sp-icon-visibility-off slot="icon"></sp-icon-visibility-off>`}
859
+ </sp-action-button>
583
860
  </sp-action-group>
584
861
  <sp-search
585
862
  size="s"
@@ -645,8 +922,9 @@ function collectFileRows(dirPath, depth, rows, ctx, i18n) {
645
922
  }
646
923
  return a.name.localeCompare(b.name);
647
924
  });
925
+ const visible = showIgnoredFiles() ? sorted : sorted.filter((e) => !isIgnoredEntry(dirPath, e.path, e.type === "directory"));
648
926
  const query = requireProjectState().searchQuery.toLowerCase();
649
- const filtered = query ? sorted.filter((e) => e.type === "directory" || e.name.toLowerCase().includes(query)) : sorted;
927
+ const filtered = query ? visible.filter((e) => e.type === "directory" || e.name.toLowerCase().includes(query)) : visible;
650
928
  for (const [index, entry] of filtered.entries()) {
651
929
  const isDir = entry.type === "directory";
652
930
  const isExpanded = isDir && requireProjectState().expanded.has(entry.path);
@@ -889,7 +1167,7 @@ function pickAndUploadTo(dir, renderLeftPanel) {
889
1167
  const input = document.createElement("input");
890
1168
  input.type = "file";
891
1169
  input.multiple = true;
892
- input.accept = UPLOAD_ACCEPT;
1170
+ input.accept = uploadAccept();
893
1171
  input.addEventListener("change", () => {
894
1172
  if (input.files?.length) {
895
1173
  uploadFilesToDir(input.files, dir, renderLeftPanel);
@@ -1023,6 +1301,7 @@ async function moveFileEntry(oldPath, newPath, renderLeftPanel) {
1023
1301
  markLocalMutation(oldPath, newPath);
1024
1302
  try {
1025
1303
  const report = await platform.renameFile(oldPath, newPath);
1304
+ invalidateUsages();
1026
1305
  for (const [id] of workspace.tabs.entries()) {
1027
1306
  if (id === oldPath) {
1028
1307
  renameTab(oldPath, newPath, newPath);
@@ -1042,7 +1321,7 @@ async function moveFileEntry(oldPath, newPath, renderLeftPanel) {
1042
1321
  }
1043
1322
  reloadRewrittenTabs(report, newPath);
1044
1323
  renderLeftPanel();
1045
- notify.success(`Moved to ${newPath}`);
1324
+ notifyMoveOutcome(`Moved to ${newPath}`, report, newPath);
1046
1325
  } catch (error) {
1047
1326
  notify.error(`Could not move ${oldPath}.`, {
1048
1327
  detail: errorMessage(error),
@@ -1066,13 +1345,18 @@ function dismissFileContextMenu() {
1066
1345
  function fileRowFacts(entry) {
1067
1346
  const facts = {};
1068
1347
  if (entry.type === "directory") {
1069
- const collection = collectionDirs().find(({ dir }) => entry.path === dir || entry.path.endsWith(`/${dir}`));
1070
- if (collection) {
1348
+ const collection = collectionForDirectory(entry.path);
1349
+ if (collection?.isSourceRoot === true && !collection.fileBacked) {
1071
1350
  facts.name = collection.name;
1072
1351
  }
1073
- } else if (collectionOfPath(entry.path)) {
1352
+ return facts;
1353
+ }
1354
+ if (collectionOfPath(entry.path)) {
1074
1355
  facts.path = entry.path;
1075
1356
  }
1357
+ if (convertTargets(entry.path).length > 0) {
1358
+ facts.source = entry.path;
1359
+ }
1076
1360
  return facts;
1077
1361
  }
1078
1362
  function placedFileItems(entry) {
@@ -1182,40 +1466,122 @@ function showFileContextMenu(e, entry, ctx) {
1182
1466
  });
1183
1467
  }
1184
1468
  var BLANK_DOCUMENT = JSON.stringify({ children: [{ children: [], tagName: "p" }], tagName: "div" }, null, 2);
1469
+ var OTHER_FORMAT = "__other__";
1470
+ function slugify(input) {
1471
+ return input.trim().toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z\d-]/g, "");
1472
+ }
1473
+ function extensionOfName(name) {
1474
+ const dot = name.lastIndexOf(".");
1475
+ return dot > 0 ? name.slice(dot) : "";
1476
+ }
1477
+ function formatRowsFor(choice) {
1478
+ const rows = choice.kind === "locked" ? [
1479
+ {
1480
+ ext: choice.ext,
1481
+ label: `${formatByExtension(choice.ext)?.name ?? choice.ext} (${choice.ext})`
1482
+ }
1483
+ ] : creationFormats(choice.docKind);
1484
+ return [
1485
+ ...rows.map((row) => ({ label: row.label, value: row.ext })),
1486
+ { dividerBefore: true, label: "Other…", value: OTHER_FORMAT }
1487
+ ];
1488
+ }
1489
+ function initialPickFor(choice, rows, suggested) {
1490
+ const stated = choice.kind === "choose" ? choice.defaultExt : choice.ext;
1491
+ if (stated !== undefined && rows.some((row) => row.value === stated)) {
1492
+ return stated;
1493
+ }
1494
+ const fromPrefill = extensionOfName(suggested);
1495
+ if (fromPrefill !== "" && rows.some((row) => row.value === fromPrefill)) {
1496
+ return fromPrefill;
1497
+ }
1498
+ return rows[0]?.value ?? OTHER_FORMAT;
1499
+ }
1185
1500
  async function createFileIn(request) {
1186
- const { dir, ext, source = "Files" } = request;
1501
+ const { dir, format: choice, source = "Files" } = request;
1187
1502
  await loadFormats();
1188
1503
  let taken = new Set;
1189
1504
  try {
1190
1505
  const listing = await getPlatform().listDirectory(dir);
1191
- taken = new Set(listing.map((entry) => entry.name));
1506
+ taken = new Set(listing.map((entry) => entry.name.toLowerCase()));
1192
1507
  } catch {}
1193
- const fileNameFor = (input) => ext === undefined ? input.trim() : `${input.trim().toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z\d-]/g, "")}${ext}`;
1508
+ const picker = choice !== undefined && choice.kind !== "fixed" ? choice : null;
1509
+ const rows = picker === null ? null : formatRowsFor(picker);
1510
+ const initialPick = picker === null || rows === null ? "" : initialPickFor(picker, rows, request.suggestedName ?? "");
1511
+ const fileNameFor = (input, picked2) => {
1512
+ const trimmed = input.trim();
1513
+ if (choice === undefined || picked2 === OTHER_FORMAT) {
1514
+ return trimmed;
1515
+ }
1516
+ if (choice.kind === "fixed") {
1517
+ const slug = slugify(trimmed);
1518
+ return slug === "" ? "" : `${slug}${choice.ext}`;
1519
+ }
1520
+ return trimmed.toLowerCase().endsWith(picked2.toLowerCase()) ? trimmed : `${trimmed}${picked2}`;
1521
+ };
1522
+ const known = knownDocumentExtensions();
1523
+ const validate = (value, picked2) => {
1524
+ const trimmed = value.trim();
1525
+ if (!trimmed) {
1526
+ return choice?.kind === "fixed" ? "Enter a name." : "Enter a file name.";
1527
+ }
1528
+ if (choice?.kind === "fixed" && slugify(trimmed) === "") {
1529
+ return "Enter at least one letter or number.";
1530
+ }
1531
+ const normalized = trimmed.replaceAll("\\", "/");
1532
+ if (normalized.startsWith("/") || normalized.endsWith("/")) {
1533
+ return "Enter a name, not a path that starts or ends with a slash.";
1534
+ }
1535
+ if (normalized.split("/").includes("..")) {
1536
+ return "A name cannot step outside the destination folder.";
1537
+ }
1538
+ if (normalized.includes("/") && choice !== undefined && picked2 !== OTHER_FORMAT) {
1539
+ return `Pick Other… to create a file in a subfolder of ${dir === "." ? "the project root" : dir}.`;
1540
+ }
1541
+ const typedExt = extensionOfName(normalized);
1542
+ if (picked2 !== "" && picked2 !== OTHER_FORMAT && typedExt !== "" && typedExt.toLowerCase() !== picked2.toLowerCase() && known.has(typedExt.toLowerCase())) {
1543
+ const label = rows?.find((row) => row.value === picked2)?.label ?? picked2;
1544
+ return `You picked ${label}; this name ends in ${typedExt}.`;
1545
+ }
1546
+ if (choice?.kind === "locked" && picked2 === OTHER_FORMAT) {
1547
+ const foreign = typedExt !== "" && typedExt.toLowerCase() !== choice.ext.toLowerCase();
1548
+ if (foreign && known.has(typedExt.toLowerCase())) {
1549
+ return choice.because;
1550
+ }
1551
+ }
1552
+ const candidate = fileNameFor(value, picked2);
1553
+ return taken.has(candidate.toLowerCase()) ? `${candidate} already exists in ${dir}/.` : "";
1554
+ };
1555
+ let picked = initialPick;
1194
1556
  const entered = await showPromptDialog(request.title ?? "New File", {
1557
+ ...rows === null ? {} : {
1558
+ choice: {
1559
+ initial: initialPick,
1560
+ label: "Format",
1561
+ onChange: (next) => {
1562
+ picked = next;
1563
+ },
1564
+ options: () => rows
1565
+ }
1566
+ },
1195
1567
  confirmLabel: "Create",
1196
1568
  message: dir === "." ? "Creating in the project root." : `Creating in ${dir}/`,
1197
1569
  ...request.suggestedName === undefined ? {} : { placeholder: request.suggestedName },
1198
- select: "stem",
1199
- validate: (value) => {
1200
- if (!value.trim()) {
1201
- return ext === undefined ? "Enter a file name." : "Enter a name.";
1202
- }
1203
- const candidate = fileNameFor(value);
1204
- if (!candidate || candidate === ext) {
1205
- return "Enter at least one letter or number.";
1206
- }
1207
- return taken.has(candidate) ? `${candidate} already exists in ${dir}/.` : "";
1208
- },
1570
+ select: rows === null ? "stem" : "all",
1571
+ validate,
1209
1572
  value: request.suggestedName ?? "untitled.json"
1210
1573
  });
1211
1574
  if (!entered) {
1212
1575
  return null;
1213
1576
  }
1214
- const fileName = fileNameFor(entered);
1577
+ const fileName = fileNameFor(entered, picked);
1215
1578
  const path = dir === "." ? fileName : `${dir}/${fileName}`;
1216
1579
  markLocalMutation(path);
1217
1580
  const format = formatForPath(fileName);
1218
- const content = request.content ?? format?.studio?.newFileTemplate ?? (format ? "" : BLANK_DOCUMENT);
1581
+ const suppliedBody = typeof request.content === "function" ? await request.content(extensionOfName(fileName), fileName) : request.content;
1582
+ const chosenAsDocument = choice !== undefined && picked !== OTHER_FORMAT;
1583
+ const content = suppliedBody ?? format?.studio?.newFileTemplate ?? (format ? "" : fileName.toLowerCase().endsWith(".json") ? chosenAsDocument ? BLANK_DOCUMENT : `{}
1584
+ ` : "");
1219
1585
  try {
1220
1586
  await getPlatform().writeFile(path, content);
1221
1587
  await loadDirectory(dir);
@@ -1231,7 +1597,32 @@ async function createFileIn(request) {
1231
1597
  }
1232
1598
  }
1233
1599
  async function createNewFile(dirPath, renderLeftPanel) {
1234
- const created = await createFileIn({ dir: dirPath, suggestedName: "untitled.json" });
1600
+ await loadFormats();
1601
+ const collection = collectionForDirectory(dirPath);
1602
+ if (collection?.unresolvedFormat != null) {
1603
+ notify.error(`New files in ${dirPath}/ cannot be constrained to the collection's format.`, {
1604
+ detail: `The "${collection.name}" content type declares format "${collection.unresolvedFormat}", ` + "which no installed extension provides. Install the extension that supplies it, or correct " + "the name in Project Settings › Content Types.",
1605
+ path: dirPath,
1606
+ source: "Files"
1607
+ });
1608
+ }
1609
+ const constrained = collection?.ext != null && collection.unresolvedFormat === null;
1610
+ const seedable = constrained && collection.isSourceRoot && collection.def.schema !== undefined;
1611
+ let created;
1612
+ if (seedable) {
1613
+ const { createEntry } = await import("./entry-commands-6z6bhxg0.js");
1614
+ created = await createEntry(collection.name, { dir: dirPath });
1615
+ } else {
1616
+ created = await createFileIn({
1617
+ dir: dirPath,
1618
+ format: constrained && collection.ext !== null ? {
1619
+ because: `${collection.name} entries are ${collection.ext} files, so a document of another format here would not be one.`,
1620
+ ext: collection.ext,
1621
+ kind: "locked"
1622
+ } : { defaultExt: ".json", kind: "choose" },
1623
+ suggestedName: "untitled"
1624
+ });
1625
+ }
1235
1626
  if (created !== null) {
1236
1627
  renderLeftPanel();
1237
1628
  }
@@ -1257,6 +1648,20 @@ function renameStatus(newName, report) {
1257
1648
  }
1258
1649
  return `Renamed to ${newName}`;
1259
1650
  }
1651
+ function notifyMoveOutcome(headline, report, path) {
1652
+ const stuck = report.errors ?? [];
1653
+ if (stuck.length === 0) {
1654
+ notify.success(headline);
1655
+ return;
1656
+ }
1657
+ const files = stuck.length === 1 ? "1 file" : `${stuck.length} files`;
1658
+ notify.warn(`${headline} — references in ${files} could not be updated`, {
1659
+ detail: stuck.map((e) => `${e.path}: ${e.error}`).join(`
1660
+ `),
1661
+ path,
1662
+ source: "Files"
1663
+ });
1664
+ }
1260
1665
  function reloadRewrittenTabs(report, skipPath) {
1261
1666
  for (const f of report.references?.files ?? []) {
1262
1667
  if (f.path !== skipPath && workspace.tabs.has(f.path)) {
@@ -1264,29 +1669,40 @@ function reloadRewrittenTabs(report, skipPath) {
1264
1669
  }
1265
1670
  }
1266
1671
  }
1672
+ function parentDirOf(path) {
1673
+ const normalized = path.replaceAll("\\", "/");
1674
+ return normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : ".";
1675
+ }
1676
+ async function settleRename(from, to, report) {
1677
+ invalidateUsages();
1678
+ const fromDir = parentDirOf(from);
1679
+ const toDir = parentDirOf(to);
1680
+ await loadDirectory(fromDir);
1681
+ if (toDir !== fromDir) {
1682
+ await loadDirectory(toDir);
1683
+ }
1684
+ if (requireProjectState().selectedPath === from) {
1685
+ requireProjectState().selectedPath = to;
1686
+ }
1687
+ reloadRewrittenTabs(report, to);
1688
+ }
1267
1689
  async function renameFile(entry, renderLeftPanel) {
1268
1690
  const newName = await showRenameFileDialog(entry.name, entry.path);
1269
1691
  if (!newName || newName === entry.name) {
1270
1692
  return;
1271
1693
  }
1272
- const entryPath = entry.path.replaceAll("\\", "/");
1273
- const parentDirPath = entryPath.includes("/") ? entryPath.slice(0, entryPath.lastIndexOf("/")) : ".";
1694
+ const parentDirPath = parentDirOf(entry.path);
1274
1695
  const newPath = parentDirPath === "." ? newName : `${parentDirPath}/${newName}`;
1275
1696
  markLocalMutation(entry.path, newPath);
1276
1697
  try {
1277
1698
  const platform = getPlatform();
1278
1699
  const report = await platform.renameFile(entry.path, newPath);
1279
- invalidateUsages();
1280
- await loadDirectory(parentDirPath);
1281
- if (requireProjectState().selectedPath === entry.path) {
1282
- requireProjectState().selectedPath = newPath;
1283
- }
1284
1700
  if (workspace.tabs.has(entry.path)) {
1285
1701
  renameTab(entry.path, newPath, newPath);
1286
1702
  }
1287
- reloadRewrittenTabs(report, newPath);
1703
+ await settleRename(entry.path, newPath, report);
1288
1704
  renderLeftPanel();
1289
- notify.success(renameStatus(newName, report));
1705
+ notifyMoveOutcome(renameStatus(newName, report), report, newPath);
1290
1706
  } catch (error) {
1291
1707
  notify.error(`Could not rename ${entry.name}.`, {
1292
1708
  detail: errorMessage(error),
@@ -1343,6 +1759,18 @@ async function openFileInTab(path, opts = {}) {
1343
1759
  return;
1344
1760
  }
1345
1761
  }
1762
+ if (isViewableMedia(path)) {
1763
+ openMediaTab(path);
1764
+ if (follows) {
1765
+ requireProjectState().selectedPath = path;
1766
+ }
1767
+ trackRecentFile({
1768
+ name: path.split("/").pop() || path,
1769
+ path,
1770
+ root: requireProjectState().projectRoot
1771
+ });
1772
+ return;
1773
+ }
1346
1774
  if (path.toLowerCase().endsWith(".csv")) {
1347
1775
  try {
1348
1776
  await openCsvGridTab(path);
@@ -1365,6 +1793,10 @@ async function openFileInTab(path, opts = {}) {
1365
1793
  try {
1366
1794
  const content = await platform.readFile(path);
1367
1795
  if (!content) {
1796
+ await loadFormats();
1797
+ if (!formatForPath(path) && !path.toLowerCase().endsWith(".json")) {
1798
+ throw noFormatError(path);
1799
+ }
1368
1800
  return;
1369
1801
  }
1370
1802
  await loadFormats();
@@ -1465,7 +1897,7 @@ function registerFilesPanel() {
1465
1897
  });
1466
1898
  }
1467
1899
 
1468
- export { ensureDependenciesInstalled, entryCollection, entryCollections, collectionOfPath, seedEntry, missingRequired, hydrateRecentProjects, getRecentProjects, addRecentProject, removeRecentProject, clearRecentProjects, getRecentFiles, trackRecentFile, loadDirectory, loadProject, openProject, initProjectRepo, findHomePage, openHomePage, openLastSessionOrHome, renderFilesTemplate, FILE_ROW_HEIGHT, isFileDrag, uploadFilesToDir, pickAndUploadTo, registerFileTreeDnD, createFileIn, openFileInTab, openFileInPane, reloadCleanTab, reloadFileInTab, registerFilesPanel };
1900
+ export { ensureDependenciesInstalled, mediaPaneMounted, detachMediaPane, renderMediaMode, convertTargets, convertTargetExtensions, hydrateRecentProjects, getRecentProjects, addRecentProject, removeRecentProject, clearRecentProjects, getRecentFiles, trackRecentFile, loadDirectory, showIgnoredFiles, setShowIgnoredFiles, loadProject, openProject, initProjectRepo, findHomePage, openHomePage, openLastSessionOrHome, renderFilesTemplate, FILE_ROW_HEIGHT, isFileDrag, uploadFilesToDir, pickAndUploadTo, registerFileTreeDnD, OTHER_FORMAT, createFileIn, notifyMoveOutcome, parentDirOf, settleRename, openFileInTab, openFileInPane, reloadCleanTab, reloadFileInTab, registerFilesPanel };
1469
1901
 
1470
- //# debugId=AF54EB1634A6A49164756E2164756E21
1471
- //# sourceMappingURL=studio-v2c5pmjj.js.map
1902
+ //# debugId=3C1BF8083E5F1C8564756E2164756E21
1903
+ //# sourceMappingURL=studio-5rywrkxz.js.map