@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.
- package/README.md +5 -5
- package/dist/chunks/{2020-xp8vnwma.js → 2020-n6tfwnda.js} +2 -2
- package/dist/chunks/Overlay.dev-etqjwyf9.js +23 -0
- package/dist/chunks/cf-account-picker-en86v7x7.js +163 -0
- package/dist/chunks/{cf-settings-zj2cj1ar.js → cf-settings-45fgyx8y.js} +6 -5
- package/dist/chunks/{components-z6cnkyy1.js → components-zmx10t62.js} +6 -5
- package/dist/chunks/content-source-48xg86wj.js +49 -0
- package/dist/chunks/{convert-to-repeater-6vvx96n2.js → convert-to-repeater-43tfn7cs.js} +33 -25
- package/dist/chunks/entry-commands-e63f1xc0.js +46 -0
- package/dist/chunks/{extension-sections-kwzvpemr.js → extension-sections-txv08xxg.js} +34 -26
- package/dist/chunks/file-ops-wbh4hhsx.js +48 -0
- package/dist/chunks/files-3kfkwhk4.js +93 -0
- package/dist/chunks/{focus-trap.esm-ggawf1hy.js → focus-trap.esm-9t2drp0g.js} +6 -5
- package/dist/chunks/{format-host-8fr3545e.js → format-host-w16q81tf.js} +3 -3
- package/dist/chunks/fs-events-hrrg328g.js +21 -0
- package/dist/chunks/{git-panel-g01d93vc.js → git-panel-532whr1y.js} +22 -16
- package/dist/chunks/{index-nj44jpzj.js → index-wcnt3r9g.js} +2 -2
- package/dist/chunks/{jx-validate-jj9rnt4n.js → jx-validate-8er6gbxy.js} +6 -4
- package/dist/chunks/layers-panel-07hm7g5j.js +67 -0
- package/dist/chunks/layers-q75tj2mf.js +45 -0
- package/dist/chunks/lit-html-j055mkh8.js +22 -0
- package/dist/chunks/monaco-buffer-dqw0xxpx.js +25 -0
- package/dist/chunks/{monaco-setup-a5wkmbgh.js → monaco-setup-vx9bhp1d.js} +4 -4
- package/dist/chunks/notify-yjm79n4s.js +30 -0
- package/dist/chunks/parse-24d0gay3.js +19 -0
- package/dist/chunks/platform-ba1j1gbh.js +16 -0
- package/dist/chunks/publish-panel-v9st96ja.js +29 -0
- package/dist/chunks/quick-search-zccjp6gv.js +66 -0
- package/dist/chunks/references-3qx8fkyp.js +29 -0
- package/dist/chunks/right-panel-vm3b6kwy.js +46 -0
- package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +18 -0
- package/dist/chunks/sp-popover.dev-n2etmqdp.js +9 -0
- package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +15 -0
- package/dist/chunks/sp-tooltip.dev-4sastdnw.js +15 -0
- package/dist/chunks/{sp-tray.dev-64v4pxw8.js → sp-tray.dev-tkbscm06.js} +5 -5
- package/dist/chunks/{studio-wdt9y92a.js → studio-0bq6eh7v.js} +2 -2
- package/dist/chunks/studio-1ngbejft.js +181 -0
- package/dist/chunks/studio-1wagb550.js +118 -0
- package/dist/chunks/{studio-j595e748.js → studio-2b946z26.js} +172 -95
- package/dist/chunks/{studio-pkp61mh8.js → studio-3bqpr0zh.js} +25 -17
- package/dist/chunks/{studio-k4jvjcfx.js → studio-4b5z1x43.js} +46 -12
- package/dist/chunks/{studio-ry1vz5sv.js → studio-4n2b5gzb.js} +15 -13
- package/dist/chunks/{studio-p5dja96s.js → studio-4r1pv6zf.js} +2 -2
- package/dist/chunks/{studio-kvfx9z2c.js → studio-4rzyrtjz.js} +70 -31
- package/dist/chunks/{studio-tzx3xmvx.js → studio-5fx3g9jg.js} +33 -52
- package/dist/chunks/{studio-rh1cnamv.js → studio-5myknm0a.js} +565 -135
- package/dist/chunks/studio-6yw4y3my.js +91 -0
- package/dist/chunks/{studio-0rqqhrdy.js → studio-7nte2hj4.js} +2 -2
- package/dist/chunks/studio-92ab2jqe.js +282 -0
- package/dist/chunks/{studio-6z5eyjwn.js → studio-950k0dx6.js} +72 -135
- package/dist/chunks/{studio-sas7mt3k.js → studio-9ab8gqsm.js} +2 -2
- package/dist/chunks/studio-9n4mrmnb.js +184 -0
- package/dist/chunks/{studio-hcxzyk3x.js → studio-ahn3pdgx.js} +6 -6
- package/dist/chunks/studio-b55y6ewx.js +498 -0
- package/dist/chunks/{studio-s519zc99.js → studio-bgxt93ga.js} +3 -3
- package/dist/chunks/studio-cft9r4r7.js +119 -0
- package/dist/chunks/{studio-dc8s8heb.js → studio-cmb03bg5.js} +6904 -8321
- package/dist/chunks/studio-dm19q43r.js +195 -0
- package/dist/chunks/{studio-2x9zbxmx.js → studio-ef49pnzx.js} +11 -9
- package/dist/chunks/{studio-ay8wcr2b.js → studio-eqq1w638.js} +7 -9
- package/dist/chunks/{studio-5efrtn1m.js → studio-ew0djmhg.js} +23 -19
- package/dist/chunks/{studio-tr20m8g8.js → studio-f3wqjtk5.js} +39 -3
- package/dist/chunks/{studio-xntfdy1x.js → studio-fbf92ckf.js} +36 -23
- package/dist/chunks/{studio-4q0wznzf.js → studio-gcye7qka.js} +442 -126
- package/dist/chunks/{studio-7p35254k.js → studio-gfgb8pe6.js} +36 -193
- package/dist/chunks/studio-jc994dnr.js +1066 -0
- package/dist/chunks/{studio-hj2se583.js → studio-jkftxyvz.js} +69 -3423
- package/dist/chunks/{studio-5x5774ak.js → studio-kryvsw6j.js} +3 -3
- package/dist/chunks/{studio-kfpek536.js → studio-kyh9avvv.js} +2 -2
- package/dist/chunks/{studio-twsafr1e.js → studio-m3hwn7y1.js} +3 -3
- package/dist/chunks/studio-msd847zn.js +1315 -0
- package/dist/chunks/{studio-g5gdqnyd.js → studio-mtfnaqs9.js} +19 -19
- package/dist/chunks/{studio-7ejemhej.js → studio-n6j9t10q.js} +6 -6
- package/dist/chunks/{studio-vhk5fmfv.js → studio-n8b9wqwy.js} +5 -5
- package/dist/chunks/{studio-cjm7fdjf.js → studio-p4ngc046.js} +8 -104
- package/dist/chunks/studio-q43a5y9h.js +394 -0
- package/dist/chunks/studio-q7v10cg2.js +106 -0
- package/dist/chunks/{studio-6kv3apa9.js → studio-q9jd1a60.js} +27 -11
- package/dist/chunks/studio-qcy05dp8.js +1743 -0
- package/dist/chunks/{studio-a0yp7syn.js → studio-qjy2z9bv.js} +29 -132
- package/dist/chunks/{studio-2w11grav.js → studio-qq6jym04.js} +3 -3
- package/dist/chunks/{studio-3fk1ac9z.js → studio-rdx8jcxy.js} +4 -4
- package/dist/chunks/{studio-4mqb9a0j.js → studio-rn9ta2mz.js} +4 -4
- package/dist/chunks/studio-t7qb9db8.js +665 -0
- package/dist/chunks/studio-tmc5emrw.js +8 -0
- package/dist/chunks/{studio-r5xbtctf.js → studio-trxrabqf.js} +292 -2297
- package/dist/chunks/studio-wcy8trfx.js +263 -0
- package/dist/chunks/{studio-3dkcd4c5.js → studio-x4qkdat5.js} +62 -57
- package/dist/chunks/studio-x813zp48.js +195 -0
- package/dist/chunks/{studio-qpfq7xhm.js → studio-xrapxwwc.js} +22 -285
- package/dist/chunks/{studio-xm3dbxvn.js → studio-zczps70q.js} +2 -2
- package/dist/chunks/{studio-8ezdegcx.js → studio-zqazsct2.js} +40 -36
- package/dist/chunks/workspace-fjx52ryv.js +89 -0
- package/dist/iframe-entry.js +141 -56
- package/dist/manifest.json +7 -0
- package/dist/studio.js +2605 -1950
- package/favicon.ico +0 -0
- package/index.html +1 -0
- package/package.json +20 -18
- package/src/about/about-modal.ts +4 -3
- package/src/browse/library-pane.ts +57 -3
- package/src/canvas/canvas-render.ts +23 -1
- package/src/canvas/iframe-patch.ts +47 -21
- package/src/canvas/iframe-render.ts +22 -6
- package/src/commands/app-commands.ts +4 -0
- package/src/commands/command-args.ts +29 -0
- package/src/commands/context.ts +15 -1
- package/src/commands/defaults.ts +29 -1
- package/src/commands/levels.ts +16 -0
- package/src/commands/live-context.ts +3 -0
- package/src/content/collection-match.ts +267 -0
- package/src/content/entry-commands.ts +38 -29
- package/src/content/entry-fields.ts +6 -0
- package/src/editor/shortcuts.ts +5 -0
- package/src/files/file-ops.ts +10 -42
- package/src/files/files.ts +487 -59
- package/src/files/fs-events.ts +9 -0
- package/src/files/gitignore.ts +432 -0
- package/src/files/media-paths.ts +13 -42
- package/src/files/media-usage.ts +41 -131
- package/src/files/serialize-document.ts +60 -0
- package/src/format/convert-file.ts +511 -0
- package/src/format/format-choices.ts +199 -0
- package/src/grid/grid-view.ts +16 -2
- package/src/hosting/document.ts +2 -0
- package/src/hosting/layout.ts +8 -0
- package/src/media/media-open.ts +58 -0
- package/src/media/media-pane.ts +310 -0
- package/src/new-project/import-tab.ts +357 -163
- package/src/new-project/location-fields.ts +8 -5
- package/src/new-project/new-project-modal.ts +27 -34
- package/src/packages/ensure-deps.ts +14 -7
- package/src/packages/jxsuite-update.ts +11 -11
- package/src/page-params.ts +10 -46
- package/src/panels/activity-bar.ts +45 -16
- package/src/panels/ai-chat/attached-context.ts +6 -1
- package/src/panels/ai-chat/chat-view.ts +255 -13
- package/src/panels/ai-chat/composer.ts +31 -97
- package/src/panels/ai-panel.ts +116 -2
- package/src/panels/navigator-panels.ts +7 -6
- package/src/panels/settings-menu.ts +799 -0
- package/src/panels/toolbar.ts +135 -15
- package/src/platforms/cloud.ts +261 -70
- package/src/platforms/devserver.ts +67 -33
- package/src/preview/preview-overlay.ts +226 -0
- package/src/publish/publish-panel.ts +62 -2
- package/src/services/ai-ask.ts +247 -0
- package/src/services/ai-import-tools.ts +532 -0
- package/src/services/ai-models.ts +108 -1
- package/src/services/ai-project-tools.ts +20 -26
- package/src/services/ai-system-prompt.ts +88 -2
- package/src/services/ai-turn-signal.ts +70 -0
- package/src/services/connection.ts +74 -0
- package/src/services/context-manager.ts +101 -1
- package/src/services/document-assistant.ts +45 -4
- package/src/services/import-client.ts +33 -6
- package/src/services/import-run.ts +158 -0
- package/src/services/import-seed.ts +120 -0
- package/src/services/jx-validate.ts +58 -6
- package/src/services/project-adoption.ts +71 -0
- package/src/services/references.ts +0 -0
- package/src/services/settings/definitions.ts +17 -0
- package/src/services/tool-executor.ts +45 -2
- package/src/settings/contexts-section.ts +45 -3
- package/src/settings/dependencies-editor.ts +43 -23
- package/src/settings/general-settings.ts +30 -12
- package/src/settings/preferences-accounts.ts +193 -12
- package/src/settings/preferences-dialog.ts +93 -59
- package/src/settings/preferences-sections.ts +57 -0
- package/src/settings/settings-document.ts +65 -9
- package/src/studio.ts +33 -6
- package/src/tabs/transact.ts +42 -19
- package/src/types.ts +129 -7
- package/src/ui/ai-managed-connect.ts +62 -13
- package/src/ui/ai-model-picker.ts +189 -0
- package/src/ui/cf-account-picker.ts +200 -0
- package/src/ui/choice-field.ts +72 -0
- package/src/ui/layers.ts +83 -5
- package/src/utils/studio-utils.ts +22 -52
- package/styles/forced-colors.css +7 -0
- package/styles/overlays.css +256 -0
- package/styles/panels.css +13 -1
- package/styles/shell.css +147 -0
- package/dist/chunks/Overlay.dev-d2ajy6sj.js +0 -23
- package/dist/chunks/content-source-7fy9qrm0.js +0 -40
- package/dist/chunks/files-xcybanxe.js +0 -70
- package/dist/chunks/layers-panel-27essgkc.js +0 -60
- package/dist/chunks/publish-panel-09sy2bab.js +0 -22
- package/dist/chunks/quick-search-sefb61v8.js +0 -55
- package/dist/chunks/right-panel-9r32jd5j.js +0 -39
- package/dist/chunks/sp-overlay.dev-s18hymn9.js +0 -18
- package/dist/chunks/sp-popover.dev-dj9x7e7a.js +0 -9
- package/dist/chunks/sp-progress-circle.dev-0zxcyz3r.js +0 -15
- package/dist/chunks/sp-tooltip.dev-5c6d1kjz.js +0 -15
- package/dist/chunks/studio-bw5dz6km.js +0 -608
- package/dist/chunks/studio-c31yb4t6.js +0 -55
- package/src/canvas/site-style-css.ts +0 -117
|
@@ -38,7 +38,15 @@ import { createManagedConnect } from "../ui/ai-managed-connect";
|
|
|
38
38
|
import { activeRegistry } from "../commands/active-registry";
|
|
39
39
|
import { shortcutReference, SCOPE_LABELS } from "../commands/reference";
|
|
40
40
|
import { chordFromEvent } from "../commands/keymap";
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
ensureCfConnection,
|
|
43
|
+
listAccounts,
|
|
44
|
+
notifyCredentialsChanged,
|
|
45
|
+
platformBrokersCf,
|
|
46
|
+
refreshCfConnection,
|
|
47
|
+
resetCfConnectionCache,
|
|
48
|
+
revokeAccount,
|
|
49
|
+
} from "./preferences-accounts";
|
|
42
50
|
import { applyKeybindingOverrides, rebindCommand, resetKeybinding } from "./preferences-keymap";
|
|
43
51
|
import { overlayRegion } from "../ui/regions";
|
|
44
52
|
|
|
@@ -48,47 +56,24 @@ import type { ShortcutRow } from "../commands/reference";
|
|
|
48
56
|
import type { RebindResult } from "./preferences-keymap";
|
|
49
57
|
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
blurb: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* The sections, in sheet order.
|
|
61
|
-
*
|
|
62
|
-
* §9.3 lists six for the finished surface (Editor behaviour and Updates/About are the other two).
|
|
63
|
-
* Four are built; a section is added here when it has something to configure, never before — an
|
|
64
|
-
* empty pane is the "declared but unbuilt" state the rail already refuses to render.
|
|
59
|
+
/*
|
|
60
|
+
* The sections moved to `./preferences-sections`, a leaf with no imports, so the rail's gear menu
|
|
61
|
+
* can enumerate `app.preferences`'s own `section` argument without pulling this module's graph
|
|
62
|
+
* (ai-models, github-auth, cf-settings, the settings kernel) into the rail. Re-exported here so
|
|
63
|
+
* every existing import site is unchanged.
|
|
65
64
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
id: "keyboard",
|
|
80
|
-
title: "Keyboard",
|
|
81
|
-
blurb: "Generated from the command registry — the app's own record of every chord.",
|
|
82
|
-
},
|
|
83
|
-
];
|
|
84
|
-
|
|
85
|
-
/** The section the sheet opens on when none is named. */
|
|
86
|
-
export const DEFAULT_PREFERENCES_SECTION = PREFERENCES_SECTIONS[0]!.id;
|
|
87
|
-
|
|
88
|
-
/** Whether a string names a section. */
|
|
89
|
-
export function isPreferencesSection(id: unknown): boolean {
|
|
90
|
-
return PREFERENCES_SECTIONS.some((section) => section.id === id);
|
|
91
|
-
}
|
|
65
|
+
import {
|
|
66
|
+
DEFAULT_PREFERENCES_SECTION,
|
|
67
|
+
isPreferencesSection,
|
|
68
|
+
PREFERENCES_SECTIONS,
|
|
69
|
+
} from "./preferences-sections";
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
DEFAULT_PREFERENCES_SECTION,
|
|
73
|
+
isPreferencesSection,
|
|
74
|
+
PREFERENCES_SECTIONS,
|
|
75
|
+
} from "./preferences-sections";
|
|
76
|
+
export type { PreferencesSection } from "./preferences-sections";
|
|
92
77
|
|
|
93
78
|
// ─── Open state ───────────────────────────────────────────────────────────────
|
|
94
79
|
|
|
@@ -120,6 +105,19 @@ function credentialsChanged(): void {
|
|
|
120
105
|
repaint();
|
|
121
106
|
}
|
|
122
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Showing a section is a chance to find out it is stale.
|
|
110
|
+
*
|
|
111
|
+
* Only Accounts has anything to re-read: the brokered Cloudflare connection lives on the server, so
|
|
112
|
+
* a grant that lapsed while Preferences was closed — or while the author was in Keyboard — would
|
|
113
|
+
* otherwise be reported from a cache taken before it did.
|
|
114
|
+
*/
|
|
115
|
+
function sectionShown(): void {
|
|
116
|
+
if (_section === "accounts" && platformBrokersCf()) {
|
|
117
|
+
void refreshCfConnection().then(repaint);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
123
121
|
/** The shared credentials form — draft state lives inside the form's closure. */
|
|
124
122
|
const credsForm = createAiCredentialsForm({
|
|
125
123
|
intro: html`
|
|
@@ -167,7 +165,52 @@ function assistantTpl(): TemplateResult {
|
|
|
167
165
|
return html`<div class="prefs-assistant">${managedConnect.render()} ${credsForm.render()}</div>`;
|
|
168
166
|
}
|
|
169
167
|
|
|
168
|
+
/**
|
|
169
|
+
* The buttons on one row.
|
|
170
|
+
*
|
|
171
|
+
* A record that declares `actions` decides its own verbs; everything else keeps the original rule —
|
|
172
|
+
* a connected credential offers Disconnect and a disconnected one offers nothing. The async ones
|
|
173
|
+
* repaint when they settle, because a hosted Reconnect is a round trip through a Cloudflare popup
|
|
174
|
+
* and the row must not still be reading "expired" when it comes back.
|
|
175
|
+
*/
|
|
176
|
+
function accountActionsTpl(account: ReturnType<typeof listAccounts>[number]) {
|
|
177
|
+
if (account.actions) {
|
|
178
|
+
return account.actions.map(
|
|
179
|
+
(action) => html`
|
|
180
|
+
<sp-button
|
|
181
|
+
size="s"
|
|
182
|
+
data-action=${action.id}
|
|
183
|
+
variant=${action.variant ?? nothing}
|
|
184
|
+
treatment=${action.variant === "negative" ? "outline" : nothing}
|
|
185
|
+
@click=${() => {
|
|
186
|
+
void Promise.resolve(action.run()).finally(repaint);
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
${action.label}
|
|
190
|
+
</sp-button>
|
|
191
|
+
`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return account.connected
|
|
195
|
+
? html`
|
|
196
|
+
<sp-button
|
|
197
|
+
size="s"
|
|
198
|
+
variant="negative"
|
|
199
|
+
treatment="outline"
|
|
200
|
+
@click=${() => {
|
|
201
|
+
revokeAccount(account.id);
|
|
202
|
+
repaint();
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
Disconnect
|
|
206
|
+
</sp-button>
|
|
207
|
+
`
|
|
208
|
+
: nothing;
|
|
209
|
+
}
|
|
210
|
+
|
|
170
211
|
function accountsTpl(): TemplateResult {
|
|
212
|
+
// First paint asks the broker what it holds; the answer arrives as a credentials-changed repaint.
|
|
213
|
+
ensureCfConnection();
|
|
171
214
|
return html`
|
|
172
215
|
<div class="prefs-accounts">
|
|
173
216
|
${listAccounts().map(
|
|
@@ -177,23 +220,7 @@ function accountsTpl(): TemplateResult {
|
|
|
177
220
|
<span class="prefs-account-label">${account.label}</span>
|
|
178
221
|
<span class="prefs-account-detail">${account.detail}</span>
|
|
179
222
|
</div>
|
|
180
|
-
|
|
181
|
-
account.connected
|
|
182
|
-
? html`
|
|
183
|
-
<sp-button
|
|
184
|
-
size="s"
|
|
185
|
-
variant="negative"
|
|
186
|
-
treatment="outline"
|
|
187
|
-
@click=${() => {
|
|
188
|
-
revokeAccount(account.id);
|
|
189
|
-
repaint();
|
|
190
|
-
}}
|
|
191
|
-
>
|
|
192
|
-
Disconnect
|
|
193
|
-
</sp-button>
|
|
194
|
-
`
|
|
195
|
-
: nothing
|
|
196
|
-
}
|
|
223
|
+
<div class="prefs-account-actions">${accountActionsTpl(account)}</div>
|
|
197
224
|
</div>
|
|
198
225
|
`,
|
|
199
226
|
)}
|
|
@@ -472,15 +499,21 @@ function sectionBodyTpl(): TemplateResult {
|
|
|
472
499
|
* @param section One of {@link PREFERENCES_SECTIONS}' ids. An unknown id falls back to Appearance.
|
|
473
500
|
*/
|
|
474
501
|
export function openPreferences(section?: string): Promise<null> {
|
|
502
|
+
/* Opening is a fresh look, so nothing may be reported from the last one: a brokered Cloudflare
|
|
503
|
+
grant can lapse between visits, and a row that repaints the previous answer first is stating
|
|
504
|
+
something it has no reason to believe. */
|
|
505
|
+
resetCfConnectionCache();
|
|
475
506
|
_section = section !== undefined && isPreferencesSection(section) ? section : _section;
|
|
476
507
|
if (_close) {
|
|
477
508
|
repaint();
|
|
509
|
+
sectionShown();
|
|
478
510
|
return Promise.resolve(null);
|
|
479
511
|
}
|
|
480
512
|
_section =
|
|
481
513
|
section !== undefined && isPreferencesSection(section) ? section : DEFAULT_PREFERENCES_SECTION;
|
|
482
514
|
credsForm.startEdit();
|
|
483
515
|
resetKeyboardState();
|
|
516
|
+
sectionShown();
|
|
484
517
|
return showDialog<null>(
|
|
485
518
|
(done) => {
|
|
486
519
|
let wrapperEl: HTMLElement | null = null;
|
|
@@ -523,6 +556,7 @@ export function openPreferences(section?: string): Promise<null> {
|
|
|
523
556
|
// First chord pressed on the author's NEXT visit.
|
|
524
557
|
resetKeyboardState();
|
|
525
558
|
repaint();
|
|
559
|
+
sectionShown();
|
|
526
560
|
}}
|
|
527
561
|
>
|
|
528
562
|
${candidate.title}
|
|
@@ -588,7 +622,7 @@ export function preferencesCommands(): AnyCommand[] {
|
|
|
588
622
|
id: "app.preferences",
|
|
589
623
|
level: "application",
|
|
590
624
|
keybinding: "mod+,",
|
|
591
|
-
menus: ["commandbar/overflow", "palette"],
|
|
625
|
+
menus: ["commandbar/overflow", "settings/menu", "palette"],
|
|
592
626
|
group: "7_settings",
|
|
593
627
|
run: (_ctx, args) => {
|
|
594
628
|
const section = (args as { section?: unknown } | undefined)?.section;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Preferences sections — the values `app.preferences { section }` accepts.
|
|
3
|
+
*
|
|
4
|
+
* A LEAF: it imports nothing, and that is the whole reason it is a file. `preferences-dialog.ts`
|
|
5
|
+
* reaches `ui/layers`, `ui/ai-credentials-form`, `ui/ai-managed-connect`, `preferences-accounts` (→
|
|
6
|
+
* github-auth, cf-settings, ai-settings, ai-models) and `preferences-keymap` (→
|
|
7
|
+
* services/settings/kernel) — a graph so wide that `tests/preferences-dialog.test.ts` mocks
|
|
8
|
+
* `ai-models` merely to import the module at all. The rail's gear menu needs four labels from it
|
|
9
|
+
* and nothing else, so it takes the labels rather than the graph.
|
|
10
|
+
*
|
|
11
|
+
* Same split, and the same stated reason, as `settings/section-registry.ts`: what is contributed to
|
|
12
|
+
* is not the surface that renders it, and keeping the two apart is what lets either be replaced.
|
|
13
|
+
*
|
|
14
|
+
* `preferences-dialog.ts` re-exports every symbol here, so no existing import site moved.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** One section of the sheet. The `id` is what `app.preferences { section }` accepts. */
|
|
18
|
+
export interface PreferencesSection {
|
|
19
|
+
id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
/** The one-line answer to "what is in here", shown under the heading. */
|
|
22
|
+
blurb: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The sections, in sheet order.
|
|
27
|
+
*
|
|
28
|
+
* §9.3 lists six for the finished surface (Editor behaviour and Updates/About are the other two).
|
|
29
|
+
* Four are built; a section is added here when it has something to configure, never before — an
|
|
30
|
+
* empty pane is the "declared but unbuilt" state the rail already refuses to render.
|
|
31
|
+
*/
|
|
32
|
+
export const PREFERENCES_SECTIONS: readonly PreferencesSection[] = [
|
|
33
|
+
{ id: "appearance", title: "Appearance", blurb: "How the Studio chrome looks." },
|
|
34
|
+
{
|
|
35
|
+
id: "assistant",
|
|
36
|
+
title: "Assistant",
|
|
37
|
+
blurb: "The AI provider the assistant talks to. Stored on this machine.",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "accounts",
|
|
41
|
+
title: "Accounts",
|
|
42
|
+
blurb: "Every credential Studio holds, and how to make it forget one.",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "keyboard",
|
|
46
|
+
title: "Keyboard",
|
|
47
|
+
blurb: "Generated from the command registry — the app's own record of every chord.",
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/** The section the sheet opens on when none is named. */
|
|
52
|
+
export const DEFAULT_PREFERENCES_SECTION = PREFERENCES_SECTIONS[0]!.id;
|
|
53
|
+
|
|
54
|
+
/** Whether a string names a section. */
|
|
55
|
+
export function isPreferencesSection(id: unknown): boolean {
|
|
56
|
+
return PREFERENCES_SECTIONS.some((section) => section.id === id);
|
|
57
|
+
}
|
|
@@ -52,6 +52,8 @@ import { renderGeneralSettings } from "./general-settings";
|
|
|
52
52
|
import { renderHeadEditor } from "./head-editor";
|
|
53
53
|
import { renderLocalesSection } from "./locales-section";
|
|
54
54
|
import { selectContributedEntry } from "./contributed-section";
|
|
55
|
+
// The NAME and the wire value, kept apart by that module on purpose — see its docstring.
|
|
56
|
+
import { PROJECT_STYLES_TITLE, PROJECT_STYLES_VIEW } from "../style/project-styles";
|
|
55
57
|
import type { Tab } from "../tabs/tab";
|
|
56
58
|
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
57
59
|
|
|
@@ -192,28 +194,37 @@ export function settingsDocumentOpen(): boolean {
|
|
|
192
194
|
*
|
|
193
195
|
* @returns {Tab | null} The tab, or null when no project is open
|
|
194
196
|
*/
|
|
195
|
-
export function showSettingsDocument(): Tab | null {
|
|
197
|
+
export function showSettingsDocument(mode: string = SETTINGS_MODE): Tab | null {
|
|
196
198
|
const state = requireProjectState();
|
|
197
199
|
if (!state) {
|
|
198
200
|
return null;
|
|
199
201
|
}
|
|
200
202
|
const existing = workspace.tabs.get(PROJECT_CONFIG_PATH);
|
|
201
203
|
if (existing) {
|
|
202
|
-
for (const
|
|
203
|
-
if (!existing.capabilities.modes.includes(
|
|
204
|
-
existing.capabilities.modes.unshift(
|
|
204
|
+
for (const candidate of SETTINGS_TAB_MODES) {
|
|
205
|
+
if (!existing.capabilities.modes.includes(candidate)) {
|
|
206
|
+
existing.capabilities.modes.unshift(candidate);
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
|
-
existing.session.ui.canvasMode =
|
|
209
|
+
existing.session.ui.canvasMode = mode;
|
|
208
210
|
existing.session.ui.preview = false;
|
|
209
211
|
return revealTab(existing as unknown as Tab);
|
|
210
212
|
}
|
|
211
|
-
|
|
213
|
+
const tab = openTab({
|
|
212
214
|
capabilities: { modes: [...SETTINGS_TAB_MODES] },
|
|
213
215
|
document: (state.projectConfig ?? {}) as unknown as Record<string, unknown>,
|
|
214
216
|
documentPath: PROJECT_CONFIG_PATH,
|
|
215
217
|
id: PROJECT_CONFIG_PATH,
|
|
216
218
|
});
|
|
219
|
+
/* The mode is written here rather than left to the capability list, because `createTab` seeds
|
|
220
|
+
`session.ui.canvasMode` from `resolvedModes.find(m => m !== "preview")` (`tabs/tab.ts`) — i.e.
|
|
221
|
+
from `SETTINGS_TAB_MODES[0]`, which is `settings`. A second door over the same document has to
|
|
222
|
+
SAY which editor it is, or `styles.open` from a cold workspace opens the settings form. The
|
|
223
|
+
capability list stays in its declared order for the same reason it always had one: the tab's
|
|
224
|
+
Editor control offers the modes in that order. */
|
|
225
|
+
tab.session.ui.canvasMode = mode;
|
|
226
|
+
tab.session.ui.preview = false;
|
|
227
|
+
return tab;
|
|
217
228
|
}
|
|
218
229
|
|
|
219
230
|
/**
|
|
@@ -328,10 +339,17 @@ export function settingsCommands(): AnyCommand[] {
|
|
|
328
339
|
// ⌘⇧, — the other half of §5.3's `⌘, / ⌘⇧,` pair. `app.preferences` shipped with its chord
|
|
329
340
|
// And this one did not, so the two halves of "settings" were a keystroke and a palette search.
|
|
330
341
|
keybinding: "mod+shift+,",
|
|
331
|
-
menus: ["commandbar/overflow", "palette"],
|
|
342
|
+
menus: ["commandbar/overflow", "settings/menu", "palette"],
|
|
332
343
|
group: "7_settings",
|
|
333
344
|
requires: "an open project",
|
|
334
|
-
when
|
|
345
|
+
/* `enablement`, not `when`: with no project open this row is DRAWN, greyed, carrying the
|
|
346
|
+
sentence above — §12.3's rule that a control which cannot act explains itself rather than
|
|
347
|
+
vanishing, and the reason the palette greys unavailable commands instead of hiding them.
|
|
348
|
+
It used to be `when`, so the rail's Settings menu held one row on the welcome screen and
|
|
349
|
+
said nothing about the other two; "why can't I" had no answer anywhere. The gate itself is
|
|
350
|
+
unchanged — `enabledWith` still refuses, so `registry.run` and the assistant's tool still
|
|
351
|
+
throw `CommandUnavailableError` exactly as before. */
|
|
352
|
+
enablement: (ctx) => ctx.project.open,
|
|
335
353
|
aiTool: {
|
|
336
354
|
description:
|
|
337
355
|
"Open the project's Settings, optionally on a named section (overview, contexts, head, " +
|
|
@@ -370,7 +388,45 @@ export function settingsCommands(): AnyCommand[] {
|
|
|
370
388
|
}
|
|
371
389
|
notifySettingsDocument();
|
|
372
390
|
},
|
|
373
|
-
title: "Open Settings",
|
|
391
|
+
title: "Open Project Settings",
|
|
392
|
+
},
|
|
393
|
+
/*
|
|
394
|
+
* The SECOND door over the same document. `project.json` has three editors — the settings form,
|
|
395
|
+
* Project Styles and the raw source — and until now only one of them had a name: Project Styles
|
|
396
|
+
* was reached by the pane's Editor control (which can only re-mode a tab that is ALREADY open)
|
|
397
|
+
* and by a hand-written `canvasMode` assignment inside Overview. `canvas.setMode` cannot stand
|
|
398
|
+
* in for this: it is level `document` and requires an open one, so from a cold workspace there
|
|
399
|
+
* is nothing to re-mode.
|
|
400
|
+
*
|
|
401
|
+
* It is a peer of `settings.open` and declares the same gate, byte for byte (§12.4: two verbs
|
|
402
|
+
* over one document declare ONE availability rule). `styles.*` is the project-level plural of
|
|
403
|
+
* the selection-level `style.*` verbs in `panels/style-panel.ts`; the ids are one letter apart
|
|
404
|
+
* and the levels are the reason.
|
|
405
|
+
*/
|
|
406
|
+
{
|
|
407
|
+
category: "Project",
|
|
408
|
+
id: "styles.open",
|
|
409
|
+
level: "project",
|
|
410
|
+
menus: ["settings/menu", "palette"],
|
|
411
|
+
/* A distinct ordinal inside the settings family, so the gear reads Settings → Styles.
|
|
412
|
+
§12.3's lever is `group`, never a per-menu sort; `styles.open` is not in the ⬢ menu, so
|
|
413
|
+
nothing else sees this string. */
|
|
414
|
+
group: "7_settings_styles",
|
|
415
|
+
requires: "an open project",
|
|
416
|
+
// §12.4: two verbs over ONE document declare ONE availability rule, byte-identical.
|
|
417
|
+
enablement: (ctx) => ctx.project.open,
|
|
418
|
+
aiTool: {
|
|
419
|
+
description:
|
|
420
|
+
"Open the project's configuration document in its Project Styles editor — the design " +
|
|
421
|
+
"tokens and the default element styles that apply across every page.",
|
|
422
|
+
name: "open_project_styles",
|
|
423
|
+
},
|
|
424
|
+
run: () => {
|
|
425
|
+
showSettingsDocument(PROJECT_STYLES_VIEW);
|
|
426
|
+
},
|
|
427
|
+
// Named, never spelled from the wire value: `stylebook` is shared with `dist/iframe-entry.js`
|
|
428
|
+
// And may never be renamed, so nothing a reader sees is derived from it (`style/project-styles`).
|
|
429
|
+
title: `Open ${PROJECT_STYLES_TITLE}`,
|
|
374
430
|
},
|
|
375
431
|
];
|
|
376
432
|
}
|
package/src/studio.ts
CHANGED
|
@@ -94,7 +94,8 @@ import { mountJumpBar } from "./panels/jump-bar";
|
|
|
94
94
|
import { cellForPane } from "./panels/pane-grid";
|
|
95
95
|
import { notify } from "./services/notify";
|
|
96
96
|
import { beginActivity } from "./panels/activity-panel";
|
|
97
|
-
import { exportFile, parseSourceForPath, saveFile
|
|
97
|
+
import { exportFile, parseSourceForPath, saveFile } from "./files/file-ops";
|
|
98
|
+
import { serializeDocument } from "./files/serialize-document";
|
|
98
99
|
import {
|
|
99
100
|
formatForPath,
|
|
100
101
|
loadFormats,
|
|
@@ -113,6 +114,7 @@ import {
|
|
|
113
114
|
registerFileTreeDnD,
|
|
114
115
|
reloadCleanTab,
|
|
115
116
|
} from "./files/files";
|
|
117
|
+
import { resetIgnoreCache } from "./files/gitignore";
|
|
116
118
|
import { startFsSync } from "./files/fs-events";
|
|
117
119
|
import { invalidateParamValues } from "./page-params";
|
|
118
120
|
import {
|
|
@@ -128,7 +130,13 @@ import { invalidateLibrary } from "./browse/library-pane";
|
|
|
128
130
|
import { invalidateMediaCache } from "./ui/media-picker";
|
|
129
131
|
import { setMediaChangedHandler } from "./files/media-upload";
|
|
130
132
|
import { applyFileDrop } from "./editor/file-drop-action";
|
|
131
|
-
import {
|
|
133
|
+
import {
|
|
134
|
+
assistantCommands,
|
|
135
|
+
isAssistantStreaming,
|
|
136
|
+
isAssistantWaiting,
|
|
137
|
+
revealImportHandoff,
|
|
138
|
+
seedAssistantMessages,
|
|
139
|
+
} from "./panels/ai-panel";
|
|
132
140
|
import { seedPublishConnected } from "./publish/publish-panel";
|
|
133
141
|
|
|
134
142
|
import { getPlatform, hasPlatform, registerPlatform } from "./platform";
|
|
@@ -180,6 +188,8 @@ import * as frontmatterPanelMod from "./panels/frontmatter-panel";
|
|
|
180
188
|
import * as rightPanelMod from "./panels/right-panel";
|
|
181
189
|
import * as chatPanelMod from "./panels/chat-panel";
|
|
182
190
|
import { setProjectAdopter } from "./services/project-adoption";
|
|
191
|
+
import { setImportHandoff } from "./services/import-seed";
|
|
192
|
+
import { watchConnection } from "./services/connection";
|
|
183
193
|
import { tabBufferUnsaved } from "./services/monaco-buffer";
|
|
184
194
|
import * as leftPanelMod from "./panels/left-panel";
|
|
185
195
|
import * as tabStrip from "./panels/tab-strip";
|
|
@@ -228,6 +238,7 @@ import { registerPreferencesCommands } from "./settings/preferences-dialog";
|
|
|
228
238
|
import { registerAboutCommands } from "./about/about-modal";
|
|
229
239
|
import { registerCollabCommands } from "./collab/collab-commands";
|
|
230
240
|
import { registerLibraryCommands } from "./browse/library-commands";
|
|
241
|
+
import { registerFileFormatCommands } from "./format/convert-file";
|
|
231
242
|
import { registerPublishCommands } from "./publish/publish-commands";
|
|
232
243
|
import { registerGridViewCommands } from "./grid/grid-panel";
|
|
233
244
|
import { registerRedirectsCommands } from "./grid/redirects-grid";
|
|
@@ -799,6 +810,11 @@ frontmatterPanelMod.mount();
|
|
|
799
810
|
// The assistant's create_project tool adopts freshly scaffolded projects through the same
|
|
800
811
|
// Flow as the recent-projects list.
|
|
801
812
|
setProjectAdopter(openRecentProject);
|
|
813
|
+
setImportHandoff(revealImportHandoff);
|
|
814
|
+
|
|
815
|
+
/* A launcher whose backend can go away says so. Installs nothing on a platform that cannot lose
|
|
816
|
+
one — see services/connection.ts for why only the chromium shell can. */
|
|
817
|
+
watchConnection();
|
|
802
818
|
|
|
803
819
|
leftPanelMod.mount({
|
|
804
820
|
cloneRepository: () => cloneRepository({ openRecentProject }),
|
|
@@ -971,6 +987,7 @@ if (_projectParam) {
|
|
|
971
987
|
}
|
|
972
988
|
}
|
|
973
989
|
|
|
990
|
+
resetIgnoreCache();
|
|
974
991
|
setProjectState({
|
|
975
992
|
dirs: new Map(),
|
|
976
993
|
expanded: new Set(),
|
|
@@ -1000,15 +1017,18 @@ if (_projectParam) {
|
|
|
1000
1017
|
"public",
|
|
1001
1018
|
"styles",
|
|
1002
1019
|
]);
|
|
1003
|
-
|
|
1004
|
-
|
|
1020
|
+
/* Through `loadDirectory`, not `platform.listDirectory`: this is the second copy of the
|
|
1021
|
+
conventional-dir walk (the first is `openProject` in files/files.ts), and the loader is
|
|
1022
|
+
what reads the `.gitignore` chain governing each directory. Listing around it left the
|
|
1023
|
+
`?project=` boot as the one door into a project whose tree knew no ignore rules. */
|
|
1024
|
+
await loadDirectory(".");
|
|
1025
|
+
const dirEntries = requireProjectState().dirs.get(".") ?? [];
|
|
1005
1026
|
const foundDirs = [];
|
|
1006
1027
|
for (const e of dirEntries) {
|
|
1007
1028
|
if (e.type === "directory" && conventionalDirs.has(e.name)) {
|
|
1008
1029
|
foundDirs.push(e.name);
|
|
1009
1030
|
requireProjectState().expanded.add(e.path || e.name);
|
|
1010
|
-
|
|
1011
|
-
requireProjectState().dirs.set(e.path || e.name, sub);
|
|
1031
|
+
await loadDirectory(e.path || e.name);
|
|
1012
1032
|
}
|
|
1013
1033
|
}
|
|
1014
1034
|
requireProjectState().projectDirs = foundDirs;
|
|
@@ -1297,6 +1317,7 @@ async function openRecentProject(root: string) {
|
|
|
1297
1317
|
const config = JSON.parse(content) as ProjectConfig;
|
|
1298
1318
|
|
|
1299
1319
|
closeAllTabs();
|
|
1320
|
+
resetIgnoreCache();
|
|
1300
1321
|
|
|
1301
1322
|
setProjectState({
|
|
1302
1323
|
...projectState,
|
|
@@ -1402,6 +1423,7 @@ const commandRegistry = createCommandRegistry({
|
|
|
1402
1423
|
// The probe `live-context.ts` declared optional and nobody ever passed, so `ctx.ai.streaming`
|
|
1403
1424
|
// Read false forever. `assistant.stop` is gated on it.
|
|
1404
1425
|
aiStreaming: isAssistantStreaming,
|
|
1426
|
+
aiWaiting: isAssistantWaiting,
|
|
1405
1427
|
canvasMode: getCanvasMode,
|
|
1406
1428
|
isCaretActive,
|
|
1407
1429
|
isModalOpen,
|
|
@@ -1416,6 +1438,10 @@ registerStudioCommands(
|
|
|
1416
1438
|
// Hands the URL to the launcher's preview-navigate handler — the OS browser — where a bare
|
|
1417
1439
|
// `window.open` would open a webview with no address bar. The browser build falls back to a
|
|
1418
1440
|
// New tab either way.
|
|
1441
|
+
// `View: Open in Browser` renders the working tree; `Build Site` runs the compiler. Two
|
|
1442
|
+
// Verbs, because "what does this page look like?" and "does my site build?" are two questions
|
|
1443
|
+
// And only one of them is worth waiting for a bundler and an image pipeline.
|
|
1444
|
+
buildSite: () => toolbarPanel.runBuildSite(),
|
|
1419
1445
|
openInBrowser: () => toolbarPanel.runOpenInBrowser(),
|
|
1420
1446
|
openProject,
|
|
1421
1447
|
// Wrapped rather than passed by reference: `saveFile` takes an optional tab and reports
|
|
@@ -1483,6 +1509,7 @@ registerGridCommands(commandRegistry);
|
|
|
1483
1509
|
registerSettingsCommands(commandRegistry);
|
|
1484
1510
|
registerPreferencesCommands(commandRegistry);
|
|
1485
1511
|
registerLibraryCommands(commandRegistry);
|
|
1512
|
+
registerFileFormatCommands(commandRegistry);
|
|
1486
1513
|
registerContentCommands(commandRegistry);
|
|
1487
1514
|
/* The four translation verbs: open a sibling translation, create the missing one, show the
|
|
1488
1515
|
Languages panel, declare a language. `i18n.switchLocale` is deliberately not among them — it sets
|
package/src/tabs/transact.ts
CHANGED
|
@@ -1135,6 +1135,46 @@ export function mutateUpdateMediaNestedStyle(
|
|
|
1135
1135
|
recordPatch({ op: "set-style", path });
|
|
1136
1136
|
}
|
|
1137
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* Drop every level of `stylePath` a deletion has just left empty, DEEPEST FIRST.
|
|
1140
|
+
*
|
|
1141
|
+
* Emptying the leaf usually empties its parent with it — `["table", "th"]` losing its last
|
|
1142
|
+
* declaration leaves `table` holding nothing — and an empty object still serialises, so it reaches
|
|
1143
|
+
* the file, the diff, and every equality check the collab layer makes. Every other style writer in
|
|
1144
|
+
* this module is careful about that; these two were not.
|
|
1145
|
+
*
|
|
1146
|
+
* The walk this replaces went top-down and stopped at the first empty level it found, which is the
|
|
1147
|
+
* one order that cannot work: on the way down the shallow levels are still non-empty _because_ the
|
|
1148
|
+
* leaf it is looking for is inside them, so it deleted the leaf and returned with every ancestor
|
|
1149
|
+
* left behind. Writing then deleting `["table", "th"]` on a node with no style at all produced `{
|
|
1150
|
+
* table: {} }` — a style the author never wrote, on a node that should have had none, because
|
|
1151
|
+
* `ensureNestedStyle` creates the intermediates on the way in.
|
|
1152
|
+
*
|
|
1153
|
+
* @param {JxStyle} root - The object `stylePath[0]` is a key of: a node's `style`, or a media
|
|
1154
|
+
* block.
|
|
1155
|
+
* @param {string[]} stylePath
|
|
1156
|
+
*/
|
|
1157
|
+
function pruneEmptyStylePath(root: JxStyle, stylePath: string[]) {
|
|
1158
|
+
// Each entry holds the segment at its own index; an absent (or scalar) one ends the descent.
|
|
1159
|
+
const owners: JxStyle[] = [root];
|
|
1160
|
+
for (const seg of stylePath) {
|
|
1161
|
+
const next = getNestedStyle(owners.at(-1), seg);
|
|
1162
|
+
if (!next) {
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
owners.push(next);
|
|
1166
|
+
}
|
|
1167
|
+
for (let i = owners.length - 2; i >= 0; i--) {
|
|
1168
|
+
const owner = owners[i]!;
|
|
1169
|
+
const seg = stylePath[i]!;
|
|
1170
|
+
const block = getNestedStyle(owner, seg);
|
|
1171
|
+
// Re-read rather than reuse `owners[i + 1]`: the level below may have gone this same loop.
|
|
1172
|
+
if (block && Object.keys(block).length === 0) {
|
|
1173
|
+
delete owner[seg];
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1138
1178
|
/**
|
|
1139
1179
|
* Update a style property at a nested style path (e.g., ["table", "th"]). Creates intermediate
|
|
1140
1180
|
* objects as needed.
|
|
@@ -1163,16 +1203,7 @@ export function mutateUpdateNestedStylePath(
|
|
|
1163
1203
|
}
|
|
1164
1204
|
if (value === undefined || value === "") {
|
|
1165
1205
|
delete obj[prop];
|
|
1166
|
-
|
|
1167
|
-
let cur: JxStyle | undefined = node.style;
|
|
1168
|
-
for (let i = 0; i < stylePath.length && cur; i++) {
|
|
1169
|
-
const child = getNestedStyle(cur, stylePath[i]!);
|
|
1170
|
-
if (child && Object.keys(child).length === 0) {
|
|
1171
|
-
delete cur[stylePath[i]!];
|
|
1172
|
-
break;
|
|
1173
|
-
}
|
|
1174
|
-
cur = child;
|
|
1175
|
-
}
|
|
1206
|
+
pruneEmptyStylePath(node.style, stylePath);
|
|
1176
1207
|
} else {
|
|
1177
1208
|
obj[prop] = value;
|
|
1178
1209
|
}
|
|
@@ -1214,15 +1245,7 @@ export function mutateUpdateMediaNestedStylePath(
|
|
|
1214
1245
|
}
|
|
1215
1246
|
if (value === undefined || value === "") {
|
|
1216
1247
|
delete obj[prop];
|
|
1217
|
-
|
|
1218
|
-
for (let i = 0; i < stylePath.length && cur; i++) {
|
|
1219
|
-
const child = getNestedStyle(cur, stylePath[i]!);
|
|
1220
|
-
if (child && Object.keys(child).length === 0) {
|
|
1221
|
-
delete cur[stylePath[i]!];
|
|
1222
|
-
break;
|
|
1223
|
-
}
|
|
1224
|
-
cur = child;
|
|
1225
|
-
}
|
|
1248
|
+
pruneEmptyStylePath(media, stylePath);
|
|
1226
1249
|
if (Object.keys(media).length === 0) {
|
|
1227
1250
|
delete node.style[key];
|
|
1228
1251
|
}
|