@jxsuite/studio 3.0.0 → 4.1.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 (197) hide show
  1. package/README.md +5 -5
  2. package/dist/chunks/{2020-xp8vnwma.js → 2020-n6tfwnda.js} +2 -2
  3. package/dist/chunks/Overlay.dev-etqjwyf9.js +23 -0
  4. package/dist/chunks/cf-account-picker-en86v7x7.js +163 -0
  5. package/dist/chunks/{cf-settings-zj2cj1ar.js → cf-settings-45fgyx8y.js} +6 -5
  6. package/dist/chunks/{components-z6cnkyy1.js → components-zmx10t62.js} +6 -5
  7. package/dist/chunks/content-source-48xg86wj.js +49 -0
  8. package/dist/chunks/{convert-to-repeater-6vvx96n2.js → convert-to-repeater-43tfn7cs.js} +33 -25
  9. package/dist/chunks/entry-commands-e63f1xc0.js +46 -0
  10. package/dist/chunks/{extension-sections-kwzvpemr.js → extension-sections-txv08xxg.js} +34 -26
  11. package/dist/chunks/file-ops-wbh4hhsx.js +48 -0
  12. package/dist/chunks/files-3kfkwhk4.js +93 -0
  13. package/dist/chunks/{focus-trap.esm-ggawf1hy.js → focus-trap.esm-9t2drp0g.js} +6 -5
  14. package/dist/chunks/{format-host-8fr3545e.js → format-host-w16q81tf.js} +3 -3
  15. package/dist/chunks/fs-events-hrrg328g.js +21 -0
  16. package/dist/chunks/{git-panel-g01d93vc.js → git-panel-532whr1y.js} +22 -16
  17. package/dist/chunks/{index-nj44jpzj.js → index-wcnt3r9g.js} +2 -2
  18. package/dist/chunks/{jx-validate-jj9rnt4n.js → jx-validate-8er6gbxy.js} +6 -4
  19. package/dist/chunks/layers-panel-07hm7g5j.js +67 -0
  20. package/dist/chunks/layers-q75tj2mf.js +45 -0
  21. package/dist/chunks/lit-html-j055mkh8.js +22 -0
  22. package/dist/chunks/monaco-buffer-dqw0xxpx.js +25 -0
  23. package/dist/chunks/{monaco-setup-a5wkmbgh.js → monaco-setup-vx9bhp1d.js} +4 -4
  24. package/dist/chunks/notify-yjm79n4s.js +30 -0
  25. package/dist/chunks/parse-24d0gay3.js +19 -0
  26. package/dist/chunks/platform-ba1j1gbh.js +16 -0
  27. package/dist/chunks/publish-panel-v9st96ja.js +29 -0
  28. package/dist/chunks/quick-search-zccjp6gv.js +66 -0
  29. package/dist/chunks/references-3qx8fkyp.js +29 -0
  30. package/dist/chunks/right-panel-vm3b6kwy.js +46 -0
  31. package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +18 -0
  32. package/dist/chunks/sp-popover.dev-n2etmqdp.js +9 -0
  33. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +15 -0
  34. package/dist/chunks/sp-tooltip.dev-4sastdnw.js +15 -0
  35. package/dist/chunks/{sp-tray.dev-64v4pxw8.js → sp-tray.dev-tkbscm06.js} +5 -5
  36. package/dist/chunks/{studio-wdt9y92a.js → studio-0bq6eh7v.js} +2 -2
  37. package/dist/chunks/studio-1ngbejft.js +181 -0
  38. package/dist/chunks/studio-1wagb550.js +118 -0
  39. package/dist/chunks/{studio-j595e748.js → studio-2b946z26.js} +172 -95
  40. package/dist/chunks/{studio-pkp61mh8.js → studio-3bqpr0zh.js} +25 -17
  41. package/dist/chunks/{studio-k4jvjcfx.js → studio-4b5z1x43.js} +46 -12
  42. package/dist/chunks/{studio-ry1vz5sv.js → studio-4n2b5gzb.js} +15 -13
  43. package/dist/chunks/{studio-p5dja96s.js → studio-4r1pv6zf.js} +2 -2
  44. package/dist/chunks/{studio-kvfx9z2c.js → studio-4rzyrtjz.js} +70 -31
  45. package/dist/chunks/{studio-tzx3xmvx.js → studio-5fx3g9jg.js} +33 -52
  46. package/dist/chunks/{studio-rh1cnamv.js → studio-5myknm0a.js} +565 -135
  47. package/dist/chunks/studio-6yw4y3my.js +91 -0
  48. package/dist/chunks/{studio-0rqqhrdy.js → studio-7nte2hj4.js} +2 -2
  49. package/dist/chunks/studio-92ab2jqe.js +282 -0
  50. package/dist/chunks/{studio-6z5eyjwn.js → studio-950k0dx6.js} +72 -135
  51. package/dist/chunks/{studio-sas7mt3k.js → studio-9ab8gqsm.js} +2 -2
  52. package/dist/chunks/studio-9n4mrmnb.js +184 -0
  53. package/dist/chunks/{studio-hcxzyk3x.js → studio-ahn3pdgx.js} +6 -6
  54. package/dist/chunks/studio-b55y6ewx.js +498 -0
  55. package/dist/chunks/{studio-s519zc99.js → studio-bgxt93ga.js} +3 -3
  56. package/dist/chunks/studio-cft9r4r7.js +119 -0
  57. package/dist/chunks/{studio-dc8s8heb.js → studio-cmb03bg5.js} +6904 -8321
  58. package/dist/chunks/studio-dm19q43r.js +195 -0
  59. package/dist/chunks/{studio-2x9zbxmx.js → studio-ef49pnzx.js} +11 -9
  60. package/dist/chunks/{studio-ay8wcr2b.js → studio-eqq1w638.js} +7 -9
  61. package/dist/chunks/{studio-5efrtn1m.js → studio-ew0djmhg.js} +23 -19
  62. package/dist/chunks/{studio-tr20m8g8.js → studio-f3wqjtk5.js} +39 -3
  63. package/dist/chunks/{studio-xntfdy1x.js → studio-fbf92ckf.js} +36 -23
  64. package/dist/chunks/{studio-4q0wznzf.js → studio-gcye7qka.js} +442 -126
  65. package/dist/chunks/{studio-7p35254k.js → studio-gfgb8pe6.js} +36 -193
  66. package/dist/chunks/studio-jc994dnr.js +1066 -0
  67. package/dist/chunks/{studio-hj2se583.js → studio-jkftxyvz.js} +69 -3423
  68. package/dist/chunks/{studio-5x5774ak.js → studio-kryvsw6j.js} +3 -3
  69. package/dist/chunks/{studio-kfpek536.js → studio-kyh9avvv.js} +2 -2
  70. package/dist/chunks/{studio-twsafr1e.js → studio-m3hwn7y1.js} +3 -3
  71. package/dist/chunks/studio-msd847zn.js +1315 -0
  72. package/dist/chunks/{studio-g5gdqnyd.js → studio-mtfnaqs9.js} +19 -19
  73. package/dist/chunks/{studio-7ejemhej.js → studio-n6j9t10q.js} +6 -6
  74. package/dist/chunks/{studio-vhk5fmfv.js → studio-n8b9wqwy.js} +5 -5
  75. package/dist/chunks/{studio-cjm7fdjf.js → studio-p4ngc046.js} +8 -104
  76. package/dist/chunks/studio-q43a5y9h.js +394 -0
  77. package/dist/chunks/studio-q7v10cg2.js +106 -0
  78. package/dist/chunks/{studio-6kv3apa9.js → studio-q9jd1a60.js} +27 -11
  79. package/dist/chunks/studio-qcy05dp8.js +1743 -0
  80. package/dist/chunks/{studio-a0yp7syn.js → studio-qjy2z9bv.js} +29 -132
  81. package/dist/chunks/{studio-2w11grav.js → studio-qq6jym04.js} +3 -3
  82. package/dist/chunks/{studio-3fk1ac9z.js → studio-rdx8jcxy.js} +4 -4
  83. package/dist/chunks/{studio-4mqb9a0j.js → studio-rn9ta2mz.js} +4 -4
  84. package/dist/chunks/studio-t7qb9db8.js +665 -0
  85. package/dist/chunks/studio-tmc5emrw.js +8 -0
  86. package/dist/chunks/{studio-r5xbtctf.js → studio-trxrabqf.js} +292 -2297
  87. package/dist/chunks/studio-wcy8trfx.js +263 -0
  88. package/dist/chunks/{studio-3dkcd4c5.js → studio-x4qkdat5.js} +62 -57
  89. package/dist/chunks/studio-x813zp48.js +195 -0
  90. package/dist/chunks/{studio-qpfq7xhm.js → studio-xrapxwwc.js} +22 -285
  91. package/dist/chunks/{studio-xm3dbxvn.js → studio-zczps70q.js} +2 -2
  92. package/dist/chunks/{studio-8ezdegcx.js → studio-zqazsct2.js} +40 -36
  93. package/dist/chunks/workspace-fjx52ryv.js +89 -0
  94. package/dist/iframe-entry.js +141 -56
  95. package/dist/manifest.json +7 -0
  96. package/dist/studio.js +2605 -1950
  97. package/favicon.ico +0 -0
  98. package/index.html +1 -0
  99. package/package.json +20 -18
  100. package/src/about/about-modal.ts +4 -3
  101. package/src/browse/library-pane.ts +57 -3
  102. package/src/canvas/canvas-render.ts +23 -1
  103. package/src/canvas/iframe-patch.ts +47 -21
  104. package/src/canvas/iframe-render.ts +22 -6
  105. package/src/commands/app-commands.ts +4 -0
  106. package/src/commands/command-args.ts +29 -0
  107. package/src/commands/context.ts +15 -1
  108. package/src/commands/defaults.ts +29 -1
  109. package/src/commands/levels.ts +16 -0
  110. package/src/commands/live-context.ts +3 -0
  111. package/src/content/collection-match.ts +267 -0
  112. package/src/content/entry-commands.ts +38 -29
  113. package/src/content/entry-fields.ts +6 -0
  114. package/src/editor/shortcuts.ts +5 -0
  115. package/src/files/file-ops.ts +10 -42
  116. package/src/files/files.ts +487 -59
  117. package/src/files/fs-events.ts +9 -0
  118. package/src/files/gitignore.ts +432 -0
  119. package/src/files/media-paths.ts +13 -42
  120. package/src/files/media-usage.ts +41 -131
  121. package/src/files/serialize-document.ts +60 -0
  122. package/src/format/convert-file.ts +511 -0
  123. package/src/format/format-choices.ts +199 -0
  124. package/src/grid/grid-view.ts +16 -2
  125. package/src/hosting/document.ts +2 -0
  126. package/src/hosting/layout.ts +8 -0
  127. package/src/media/media-open.ts +58 -0
  128. package/src/media/media-pane.ts +310 -0
  129. package/src/new-project/import-tab.ts +357 -163
  130. package/src/new-project/location-fields.ts +8 -5
  131. package/src/new-project/new-project-modal.ts +27 -34
  132. package/src/packages/ensure-deps.ts +14 -7
  133. package/src/packages/jxsuite-update.ts +11 -11
  134. package/src/page-params.ts +10 -46
  135. package/src/panels/activity-bar.ts +45 -16
  136. package/src/panels/ai-chat/attached-context.ts +6 -1
  137. package/src/panels/ai-chat/chat-view.ts +255 -13
  138. package/src/panels/ai-chat/composer.ts +31 -97
  139. package/src/panels/ai-panel.ts +116 -2
  140. package/src/panels/navigator-panels.ts +7 -6
  141. package/src/panels/settings-menu.ts +799 -0
  142. package/src/panels/toolbar.ts +135 -15
  143. package/src/platforms/cloud.ts +261 -70
  144. package/src/platforms/devserver.ts +67 -33
  145. package/src/preview/preview-overlay.ts +226 -0
  146. package/src/publish/publish-panel.ts +62 -2
  147. package/src/services/ai-ask.ts +247 -0
  148. package/src/services/ai-import-tools.ts +532 -0
  149. package/src/services/ai-models.ts +108 -1
  150. package/src/services/ai-project-tools.ts +20 -26
  151. package/src/services/ai-system-prompt.ts +88 -2
  152. package/src/services/ai-turn-signal.ts +70 -0
  153. package/src/services/connection.ts +74 -0
  154. package/src/services/context-manager.ts +101 -1
  155. package/src/services/document-assistant.ts +45 -4
  156. package/src/services/import-client.ts +33 -6
  157. package/src/services/import-run.ts +158 -0
  158. package/src/services/import-seed.ts +120 -0
  159. package/src/services/jx-validate.ts +58 -6
  160. package/src/services/project-adoption.ts +71 -0
  161. package/src/services/references.ts +0 -0
  162. package/src/services/settings/definitions.ts +17 -0
  163. package/src/services/tool-executor.ts +45 -2
  164. package/src/settings/contexts-section.ts +45 -3
  165. package/src/settings/dependencies-editor.ts +43 -23
  166. package/src/settings/general-settings.ts +30 -12
  167. package/src/settings/preferences-accounts.ts +193 -12
  168. package/src/settings/preferences-dialog.ts +93 -59
  169. package/src/settings/preferences-sections.ts +57 -0
  170. package/src/settings/settings-document.ts +65 -9
  171. package/src/studio.ts +33 -6
  172. package/src/tabs/transact.ts +42 -19
  173. package/src/types.ts +129 -7
  174. package/src/ui/ai-managed-connect.ts +62 -13
  175. package/src/ui/ai-model-picker.ts +189 -0
  176. package/src/ui/cf-account-picker.ts +200 -0
  177. package/src/ui/choice-field.ts +72 -0
  178. package/src/ui/layers.ts +83 -5
  179. package/src/utils/studio-utils.ts +22 -52
  180. package/styles/forced-colors.css +7 -0
  181. package/styles/overlays.css +256 -0
  182. package/styles/panels.css +13 -1
  183. package/styles/shell.css +147 -0
  184. package/dist/chunks/Overlay.dev-d2ajy6sj.js +0 -23
  185. package/dist/chunks/content-source-7fy9qrm0.js +0 -40
  186. package/dist/chunks/files-xcybanxe.js +0 -70
  187. package/dist/chunks/layers-panel-27essgkc.js +0 -60
  188. package/dist/chunks/publish-panel-09sy2bab.js +0 -22
  189. package/dist/chunks/quick-search-sefb61v8.js +0 -55
  190. package/dist/chunks/right-panel-9r32jd5j.js +0 -39
  191. package/dist/chunks/sp-overlay.dev-s18hymn9.js +0 -18
  192. package/dist/chunks/sp-popover.dev-dj9x7e7a.js +0 -9
  193. package/dist/chunks/sp-progress-circle.dev-0zxcyz3r.js +0 -15
  194. package/dist/chunks/sp-tooltip.dev-5c6d1kjz.js +0 -15
  195. package/dist/chunks/studio-bw5dz6km.js +0 -608
  196. package/dist/chunks/studio-c31yb4t6.js +0 -55
  197. package/src/canvas/site-style-css.ts +0 -117
@@ -3,74 +3,88 @@ import {
3
3
  measuredRowHeight,
4
4
  revealListRow,
5
5
  watchListWindow
6
- } from "./studio-p5dja96s.js";
6
+ } from "./studio-4r1pv6zf.js";
7
+ import {
8
+ formatBytes,
9
+ loadMediaMeta,
10
+ peekMediaMeta,
11
+ recordImageSize
12
+ } from "./studio-1wagb550.js";
7
13
  import {
8
14
  autoSyncProjectOnOpen,
9
15
  cleanupGitPanel,
10
16
  maybePromptJxsuiteUpdate,
11
17
  shouldInstallAutomation
12
- } from "./studio-7p35254k.js";
18
+ } from "./studio-gfgb8pe6.js";
19
+ import {
20
+ collectionOfPath
21
+ } from "./studio-6yw4y3my.js";
13
22
  import {
14
23
  openCsvGridTab,
15
24
  openPagesGrid
16
- } from "./studio-pkp61mh8.js";
25
+ } from "./studio-3bqpr0zh.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-dc8s8heb.js";
34
+ } from "./studio-cmb03bg5.js";
25
35
  import {
26
- invalidateUsages,
36
+ ensureIgnoreLayers,
37
+ isIgnoredEntry,
38
+ markLocalMutation,
39
+ resetIgnoreCache
40
+ } from "./studio-q43a5y9h.js";
41
+ import {
42
+ extensionOf,
27
43
  isImage,
44
+ isMediaFile,
45
+ mediaKind,
46
+ mediaSiteUrl,
47
+ previewFileSrc,
28
48
  uploadAccept,
29
49
  uploadAssets
30
- } from "./studio-a0yp7syn.js";
50
+ } from "./studio-qjy2z9bv.js";
51
+ import {
52
+ invalidateUsages
53
+ } from "./studio-cft9r4r7.js";
31
54
  import {
32
55
  showProgressModal
33
- } from "./studio-ry1vz5sv.js";
56
+ } from "./studio-4n2b5gzb.js";
34
57
  import {
35
58
  rectOf,
36
59
  registerPanel
37
- } from "./studio-j595e748.js";
60
+ } from "./studio-2b946z26.js";
61
+ import {
62
+ markSessionRestored,
63
+ persistedSession,
64
+ resetProjectShell,
65
+ restoreSession,
66
+ setActivityTab
67
+ } from "./studio-msd847zn.js";
38
68
  import {
39
69
  activateTab,
40
- activeRegistry,
41
- errorMessage,
42
- findContentTypeSchema,
70
+ collectionForDirectory,
71
+ collectionForFile,
43
72
  isCollabPath,
44
73
  localeLabel,
45
74
  localeOfPath,
46
- markSessionRestored,
47
75
  moveTabToPane,
48
76
  openTab,
49
77
  paneOfTab,
50
- persistedSession,
51
78
  receivingPane,
52
79
  renameTab,
53
- renderPopover,
54
80
  replaceAllTabs,
55
- resetProjectShell,
56
81
  resolveI18n,
57
- restoreSession,
58
- setActivityTab,
59
82
  setWorkspaceProject,
60
- showPromptDialog,
61
83
  workspace
62
- } from "./studio-r5xbtctf.js";
84
+ } from "./studio-trxrabqf.js";
63
85
  import {
64
86
  classMap
65
- } from "./studio-twsafr1e.js";
66
- import {
67
- ref,
68
- repeat
69
- } from "./studio-kvfx9z2c.js";
70
- import {
71
- html,
72
- nothing
73
- } from "./studio-5x5774ak.js";
87
+ } from "./studio-m3hwn7y1.js";
74
88
  import {
75
89
  combine,
76
90
  draggable,
@@ -79,46 +93,76 @@ import {
79
93
  } from "./studio-g1wdche0.js";
80
94
  import {
81
95
  loadComponentRegistry
82
- } from "./studio-s519zc99.js";
96
+ } from "./studio-bgxt93ga.js";
83
97
  import {
84
98
  projectState,
85
99
  requireProjectState,
86
100
  setProjectState
87
- } from "./studio-cjm7fdjf.js";
101
+ } from "./studio-p4ngc046.js";
88
102
  import {
103
+ activeRegistry,
104
+ paneRegion,
105
+ renderPopover,
106
+ showPromptDialog
107
+ } from "./studio-t7qb9db8.js";
108
+ import {
109
+ errorMessage
110
+ } from "./studio-9n4mrmnb.js";
111
+ import {
112
+ SETTINGS,
113
+ readStoredSetting,
114
+ setSetting
115
+ } from "./studio-92ab2jqe.js";
116
+ import {
117
+ effect,
118
+ effectScope,
89
119
  notify,
90
120
  now
91
- } from "./studio-qpfq7xhm.js";
121
+ } from "./studio-xrapxwwc.js";
122
+ import {
123
+ ref,
124
+ repeat
125
+ } from "./studio-4rzyrtjz.js";
126
+ import {
127
+ html,
128
+ nothing,
129
+ render
130
+ } from "./studio-kryvsw6j.js";
92
131
  import {
93
132
  documentExtensions,
133
+ formatByExtension,
94
134
  formatForPath,
135
+ getFormats,
95
136
  loadFormats,
96
137
  noFormatError,
97
138
  refreshExtensionUi,
98
139
  refreshFormats
99
- } from "./studio-hcxzyk3x.js";
140
+ } from "./studio-ahn3pdgx.js";
100
141
  import {
101
142
  getPlatform,
102
143
  hasPlatform
103
- } from "./studio-sas7mt3k.js";
144
+ } from "./studio-9ab8gqsm.js";
145
+ import {
146
+ __require
147
+ } from "./studio-a0tswy0w.js";
104
148
 
105
149
  // src/packages/ensure-deps.ts
106
150
  async function ensureDependenciesInstalled() {
107
151
  if (shouldInstallAutomation(location.search)) {
108
- return;
152
+ return false;
109
153
  }
110
154
  const platform = getPlatform();
111
155
  if (!platform.dependenciesNeedInstall || !platform.installDependencies) {
112
- return;
156
+ return false;
113
157
  }
114
158
  let needs = false;
115
159
  try {
116
160
  needs = await platform.dependenciesNeedInstall();
117
161
  } catch {
118
- return;
162
+ return false;
119
163
  }
120
164
  if (!needs) {
121
- return;
165
+ return false;
122
166
  }
123
167
  const progress = showProgressModal({
124
168
  status: "Running bun install…",
@@ -128,89 +172,287 @@ async function ensureDependenciesInstalled() {
128
172
  const result = await platform.installDependencies();
129
173
  if (result.ok) {
130
174
  progress.done();
131
- } else {
132
- progress.fail(result.log ?? "bun install failed");
175
+ return true;
133
176
  }
177
+ progress.fail(result.log ?? "bun install failed");
134
178
  } catch (error) {
135
179
  progress.fail(error instanceof Error ? error.message : String(error));
136
180
  }
181
+ return false;
137
182
  }
138
183
 
139
- // src/content/entry-model.ts
140
- function collectionNames() {
141
- return Object.keys(projectState?.projectConfig?.content ?? {});
142
- }
143
- function entryCollection(name) {
144
- const info = collectionInfo(name);
145
- if (!info) {
146
- 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;
147
201
  }
148
- if (/\.[a-z\d]+$/i.test(info.dir)) {
149
- 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
+ }
150
250
  }
151
- return {
152
- def: info.def,
153
- dir: info.dir,
154
- ext: info.ext,
155
- name: info.name,
156
- schema: info.def.schema ?? null
157
- };
158
251
  }
159
- function entryCollections() {
160
- 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>`;
161
264
  }
162
- function collectionOfPath(path) {
163
- const matched = findContentTypeSchema(path ?? null, projectState?.projectConfig);
164
- 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
+ `;
165
281
  }
166
- function cloneDefault(value) {
167
- if (value === null || typeof value !== "object") {
168
- return value;
282
+ function usageTpl(usage) {
283
+ const headline = mediaUsageHeadline(usage);
284
+ if (headline === null) {
285
+ return nothing;
169
286
  }
170
- 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
+ `;
171
308
  }
172
- function emptyForField(field) {
173
- if (typeof field.$ref === "string") {
174
- return "";
309
+ async function openDocument(path) {
310
+ const { openFileInTab } = await import("./files-3kfkwhk4.js");
311
+ await openFileInTab(path);
312
+ }
313
+ function renderMediaMode(surface, tab) {
314
+ const { paneId, wrap: canvasWrap } = surface;
315
+ if (mediaPaneMounted(paneId, tab)) {
316
+ return;
175
317
  }
176
- switch (field.type) {
177
- case "boolean": {
178
- 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();
179
326
  }
180
- case "integer":
181
- case "number": {
182
- 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);
183
333
  }
184
- case "array": {
185
- return [];
334
+ if (usage === null || usage.status === "pending") {
335
+ loadMediaUsages(path).then(redraw);
186
336
  }
187
- case "object": {
188
- 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;
189
392
  }
190
- default: {
191
- 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})` });
192
412
  }
193
413
  }
414
+ return rows;
194
415
  }
195
- function seedEntry(schema) {
196
- const properties = schema?.properties;
197
- if (!properties) {
198
- 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 [];
199
422
  }
200
- const required = new Set(Array.isArray(schema?.required) ? schema.required : []);
201
- const seed = {};
202
- for (const [name, field] of Object.entries(properties)) {
203
- if (field.default !== undefined) {
204
- seed[name] = cloneDefault(field.default);
205
- } else if (required.has(name)) {
206
- seed[name] = emptyForField(field);
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 [];
431
+ }
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;
207
440
  }
441
+ rows.push({ ext, label: `${writer.name} (${ext})` });
208
442
  }
209
- return seed;
443
+ return rows;
210
444
  }
211
- function missingRequired(schema, value) {
212
- const required = Array.isArray(schema?.required) ? schema.required : [];
213
- 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();
214
456
  }
215
457
 
216
458
  // src/recent-projects.ts
@@ -332,12 +574,21 @@ async function loadDirectory(dirPath) {
332
574
  }
333
575
  try {
334
576
  const platform = getPlatform();
335
- const entries = await platform.listDirectory(dirPath);
577
+ const [entries] = await Promise.all([
578
+ platform.listDirectory(dirPath),
579
+ ensureIgnoreLayers(dirPath)
580
+ ]);
336
581
  projectState.dirs.set(dirPath, entries);
337
582
  } catch {
338
583
  projectState.dirs.set(dirPath, []);
339
584
  }
340
585
  }
586
+ function showIgnoredFiles() {
587
+ return readStoredSetting(SETTINGS.showIgnoredFiles) === "true";
588
+ }
589
+ function setShowIgnoredFiles(show) {
590
+ setSetting(SETTINGS.showIgnoredFiles, show ? "true" : "");
591
+ }
341
592
  async function loadProject() {
342
593
  try {
343
594
  const platform = getPlatform();
@@ -349,6 +600,7 @@ async function loadProject() {
349
600
  refreshFormats();
350
601
  loadFormats();
351
602
  refreshExtensionUi(platform);
603
+ resetIgnoreCache();
352
604
  setProjectState({
353
605
  dirs: new Map,
354
606
  expanded: new Set,
@@ -367,7 +619,11 @@ async function loadProject() {
367
619
  if (info.isSiteProject) {
368
620
  addRecentProject(requireProjectState().name, meta.root);
369
621
  await autoSyncProjectOnOpen();
370
- await ensureDependenciesInstalled();
622
+ if (await ensureDependenciesInstalled()) {
623
+ refreshFormats();
624
+ await loadFormats();
625
+ refreshExtensionUi(platform);
626
+ }
371
627
  await loadDirectory(".");
372
628
  await loadComponentRegistry();
373
629
  await openLastSessionOrHome();
@@ -389,9 +645,11 @@ async function openProject({
389
645
  document: { children: [], tagName: "div" },
390
646
  id: "initial"
391
647
  });
648
+ disarmPreviewOverlay();
392
649
  refreshFormats();
393
650
  loadFormats();
394
651
  refreshExtensionUi(platform);
652
+ resetIgnoreCache();
395
653
  setProjectState({
396
654
  ...projectState,
397
655
  dirs: new Map,
@@ -405,7 +663,11 @@ async function openProject({
405
663
  });
406
664
  setWorkspaceProject(handle.root, config);
407
665
  await autoSyncProjectOnOpen();
408
- await ensureDependenciesInstalled();
666
+ if (await ensureDependenciesInstalled()) {
667
+ refreshFormats();
668
+ await loadFormats();
669
+ refreshExtensionUi(platform);
670
+ }
409
671
  await loadDirectory(".");
410
672
  await loadComponentRegistry();
411
673
  const conventionalDirs = new Set([
@@ -551,6 +813,7 @@ function renderFilesTemplate({
551
813
  <sp-button variant="accent" size="s" @click=${openProjectFn}>Open Project</sp-button>
552
814
  </div>`;
553
815
  }
816
+ const showingIgnored = showIgnoredFiles();
554
817
  return html`
555
818
  ${projectState.isSiteProject ? html`
556
819
  <div class="project-header">
@@ -573,6 +836,7 @@ function renderFilesTemplate({
573
836
  label="Refresh"
574
837
  @click=${async () => {
575
838
  requireProjectState().dirs.clear();
839
+ resetIgnoreCache();
576
840
  await loadDirectory(".");
577
841
  for (const dir of requireProjectState().expanded) {
578
842
  await loadDirectory(dir);
@@ -582,6 +846,17 @@ function renderFilesTemplate({
582
846
  >
583
847
  <sp-icon-refresh slot="icon"></sp-icon-refresh>
584
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>
585
860
  </sp-action-group>
586
861
  <sp-search
587
862
  size="s"
@@ -647,8 +922,9 @@ function collectFileRows(dirPath, depth, rows, ctx, i18n) {
647
922
  }
648
923
  return a.name.localeCompare(b.name);
649
924
  });
925
+ const visible = showIgnoredFiles() ? sorted : sorted.filter((e) => !isIgnoredEntry(dirPath, e.path, e.type === "directory"));
650
926
  const query = requireProjectState().searchQuery.toLowerCase();
651
- 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;
652
928
  for (const [index, entry] of filtered.entries()) {
653
929
  const isDir = entry.type === "directory";
654
930
  const isExpanded = isDir && requireProjectState().expanded.has(entry.path);
@@ -1025,6 +1301,7 @@ async function moveFileEntry(oldPath, newPath, renderLeftPanel) {
1025
1301
  markLocalMutation(oldPath, newPath);
1026
1302
  try {
1027
1303
  const report = await platform.renameFile(oldPath, newPath);
1304
+ invalidateUsages();
1028
1305
  for (const [id] of workspace.tabs.entries()) {
1029
1306
  if (id === oldPath) {
1030
1307
  renameTab(oldPath, newPath, newPath);
@@ -1044,7 +1321,7 @@ async function moveFileEntry(oldPath, newPath, renderLeftPanel) {
1044
1321
  }
1045
1322
  reloadRewrittenTabs(report, newPath);
1046
1323
  renderLeftPanel();
1047
- notify.success(`Moved to ${newPath}`);
1324
+ notifyMoveOutcome(`Moved to ${newPath}`, report, newPath);
1048
1325
  } catch (error) {
1049
1326
  notify.error(`Could not move ${oldPath}.`, {
1050
1327
  detail: errorMessage(error),
@@ -1068,13 +1345,18 @@ function dismissFileContextMenu() {
1068
1345
  function fileRowFacts(entry) {
1069
1346
  const facts = {};
1070
1347
  if (entry.type === "directory") {
1071
- const collection = collectionDirs().find(({ dir }) => entry.path === dir || entry.path.endsWith(`/${dir}`));
1072
- if (collection) {
1348
+ const collection = collectionForDirectory(entry.path);
1349
+ if (collection?.isSourceRoot === true && !collection.fileBacked) {
1073
1350
  facts.name = collection.name;
1074
1351
  }
1075
- } else if (collectionOfPath(entry.path)) {
1352
+ return facts;
1353
+ }
1354
+ if (collectionOfPath(entry.path)) {
1076
1355
  facts.path = entry.path;
1077
1356
  }
1357
+ if (convertTargets(entry.path).length > 0) {
1358
+ facts.source = entry.path;
1359
+ }
1078
1360
  return facts;
1079
1361
  }
1080
1362
  function placedFileItems(entry) {
@@ -1184,40 +1466,122 @@ function showFileContextMenu(e, entry, ctx) {
1184
1466
  });
1185
1467
  }
1186
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
+ }
1187
1500
  async function createFileIn(request) {
1188
- const { dir, ext, source = "Files" } = request;
1501
+ const { dir, format: choice, source = "Files" } = request;
1189
1502
  await loadFormats();
1190
1503
  let taken = new Set;
1191
1504
  try {
1192
1505
  const listing = await getPlatform().listDirectory(dir);
1193
- taken = new Set(listing.map((entry) => entry.name));
1506
+ taken = new Set(listing.map((entry) => entry.name.toLowerCase()));
1194
1507
  } catch {}
1195
- 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;
1196
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
+ },
1197
1567
  confirmLabel: "Create",
1198
1568
  message: dir === "." ? "Creating in the project root." : `Creating in ${dir}/`,
1199
1569
  ...request.suggestedName === undefined ? {} : { placeholder: request.suggestedName },
1200
- select: "stem",
1201
- validate: (value) => {
1202
- if (!value.trim()) {
1203
- return ext === undefined ? "Enter a file name." : "Enter a name.";
1204
- }
1205
- const candidate = fileNameFor(value);
1206
- if (!candidate || candidate === ext) {
1207
- return "Enter at least one letter or number.";
1208
- }
1209
- return taken.has(candidate) ? `${candidate} already exists in ${dir}/.` : "";
1210
- },
1570
+ select: rows === null ? "stem" : "all",
1571
+ validate,
1211
1572
  value: request.suggestedName ?? "untitled.json"
1212
1573
  });
1213
1574
  if (!entered) {
1214
1575
  return null;
1215
1576
  }
1216
- const fileName = fileNameFor(entered);
1577
+ const fileName = fileNameFor(entered, picked);
1217
1578
  const path = dir === "." ? fileName : `${dir}/${fileName}`;
1218
1579
  markLocalMutation(path);
1219
1580
  const format = formatForPath(fileName);
1220
- 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
+ ` : "");
1221
1585
  try {
1222
1586
  await getPlatform().writeFile(path, content);
1223
1587
  await loadDirectory(dir);
@@ -1233,7 +1597,32 @@ async function createFileIn(request) {
1233
1597
  }
1234
1598
  }
1235
1599
  async function createNewFile(dirPath, renderLeftPanel) {
1236
- 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-e63f1xc0.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
+ }
1237
1626
  if (created !== null) {
1238
1627
  renderLeftPanel();
1239
1628
  }
@@ -1259,6 +1648,20 @@ function renameStatus(newName, report) {
1259
1648
  }
1260
1649
  return `Renamed to ${newName}`;
1261
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
+ }
1262
1665
  function reloadRewrittenTabs(report, skipPath) {
1263
1666
  for (const f of report.references?.files ?? []) {
1264
1667
  if (f.path !== skipPath && workspace.tabs.has(f.path)) {
@@ -1266,29 +1669,40 @@ function reloadRewrittenTabs(report, skipPath) {
1266
1669
  }
1267
1670
  }
1268
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
+ }
1269
1689
  async function renameFile(entry, renderLeftPanel) {
1270
1690
  const newName = await showRenameFileDialog(entry.name, entry.path);
1271
1691
  if (!newName || newName === entry.name) {
1272
1692
  return;
1273
1693
  }
1274
- const entryPath = entry.path.replaceAll("\\", "/");
1275
- const parentDirPath = entryPath.includes("/") ? entryPath.slice(0, entryPath.lastIndexOf("/")) : ".";
1694
+ const parentDirPath = parentDirOf(entry.path);
1276
1695
  const newPath = parentDirPath === "." ? newName : `${parentDirPath}/${newName}`;
1277
1696
  markLocalMutation(entry.path, newPath);
1278
1697
  try {
1279
1698
  const platform = getPlatform();
1280
1699
  const report = await platform.renameFile(entry.path, newPath);
1281
- invalidateUsages();
1282
- await loadDirectory(parentDirPath);
1283
- if (requireProjectState().selectedPath === entry.path) {
1284
- requireProjectState().selectedPath = newPath;
1285
- }
1286
1700
  if (workspace.tabs.has(entry.path)) {
1287
1701
  renameTab(entry.path, newPath, newPath);
1288
1702
  }
1289
- reloadRewrittenTabs(report, newPath);
1703
+ await settleRename(entry.path, newPath, report);
1290
1704
  renderLeftPanel();
1291
- notify.success(renameStatus(newName, report));
1705
+ notifyMoveOutcome(renameStatus(newName, report), report, newPath);
1292
1706
  } catch (error) {
1293
1707
  notify.error(`Could not rename ${entry.name}.`, {
1294
1708
  detail: errorMessage(error),
@@ -1345,6 +1759,18 @@ async function openFileInTab(path, opts = {}) {
1345
1759
  return;
1346
1760
  }
1347
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
+ }
1348
1774
  if (path.toLowerCase().endsWith(".csv")) {
1349
1775
  try {
1350
1776
  await openCsvGridTab(path);
@@ -1367,6 +1793,10 @@ async function openFileInTab(path, opts = {}) {
1367
1793
  try {
1368
1794
  const content = await platform.readFile(path);
1369
1795
  if (!content) {
1796
+ await loadFormats();
1797
+ if (!formatForPath(path) && !path.toLowerCase().endsWith(".json")) {
1798
+ throw noFormatError(path);
1799
+ }
1370
1800
  return;
1371
1801
  }
1372
1802
  await loadFormats();
@@ -1467,7 +1897,7 @@ function registerFilesPanel() {
1467
1897
  });
1468
1898
  }
1469
1899
 
1470
- 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 };
1471
1901
 
1472
- //# debugId=C29DF2691E1E87E064756E2164756E21
1473
- //# sourceMappingURL=studio-rh1cnamv.js.map
1902
+ //# debugId=F11FCA2F7EB309F464756E2164756E21
1903
+ //# sourceMappingURL=studio-5myknm0a.js.map