@jxsuite/studio 10.4.0 → 10.5.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/dist/chunks/2020-7sx2vexa.js +4 -0
- package/dist/chunks/adapter-12dxr3z7.js +4 -0
- package/dist/chunks/{add-new-flow-v0pm96mr.js → add-new-flow-any4pw2q.js} +3 -3
- package/dist/chunks/{cf-account-picker-xb8vvt2h.js → cf-account-picker-ajh60k54.js} +3 -3
- package/dist/chunks/cf-settings-hbj16kh3.js +4 -0
- package/dist/chunks/{color-area-h8dbasp1.js → color-area-gbcftzh3.js} +2 -2
- package/dist/chunks/{color-field-qv5r2b16.js → color-field-85136598.js} +3 -3
- package/dist/chunks/{color-slider-nz2mrzky.js → color-slider-w1s3ndd8.js} +2 -2
- package/dist/chunks/color-thzpppyc.js +4 -0
- package/dist/chunks/{combobox-6hs00996.js → combobox-dk98wc5x.js} +3 -3
- package/dist/chunks/components-a98hzqw9.js +4 -0
- package/dist/chunks/content-source-jq3fjs3w.js +4 -0
- package/dist/chunks/{convert-to-repeater-r8sfxjjm.js → convert-to-repeater-g66r6qp1.js} +3 -3
- package/dist/chunks/editor-rth1rzj8.js +4 -0
- package/dist/chunks/entry-commands-02j0s19w.js +4 -0
- package/dist/chunks/{extension-sections-vjzw46xb.js → extension-sections-rqrs7dr0.js} +3 -3
- package/dist/chunks/file-ops-6a49er4c.js +4 -0
- package/dist/chunks/files-b2p9y0yz.js +4 -0
- package/dist/chunks/format-host-7xe57k1s.js +4 -0
- package/dist/chunks/fs-events-0xsy168g.js +4 -0
- package/dist/chunks/grid-open-h3q4fjy5.js +4 -0
- package/dist/chunks/{grid-view-2f03wcgw.js → grid-view-0v4hjspb.js} +17 -17
- package/dist/chunks/{icons-gb967qc8.js → icons-j9eksgew.js} +2 -2
- package/dist/chunks/{iframe-entry-btck0e9s.js → iframe-entry-4zqtn3ry.js} +4 -4
- package/dist/chunks/{iframe-entry-b5qgggyp.js → iframe-entry-54kwwhmg.js} +4 -4
- package/dist/chunks/{iframe-entry-42hpmwz5.js → iframe-entry-m24fza30.js} +4 -4
- package/dist/chunks/iframe-host-8jcmaktg.js +4 -0
- package/dist/chunks/{image-bake-4afr4y6a.js → image-bake-m8gzaan9.js} +3 -3
- package/dist/chunks/{image-editor-x7t8jbjn.js → image-editor-0afyws7c.js} +3 -3
- package/dist/chunks/{index-yjrfeks8.js → index-2q88g57p.js} +3 -3
- package/dist/chunks/index-7nj2375a.js +4 -0
- package/dist/chunks/{javascript-hy2mws8v.js → javascript-5sxtr1g7.js} +3 -3
- package/dist/chunks/jsonMode-m6sf47hq.js +13 -0
- package/dist/chunks/jx-validate-808zgbgy.js +4 -0
- package/dist/chunks/layers-0ffdtqjn.js +4 -0
- package/dist/chunks/layers-panel-rw62wtvf.js +4 -0
- package/dist/chunks/{listbox-7gka1h86.js → listbox-k5p41049.js} +2 -2
- package/dist/chunks/lit-html-9bz9rcfv.js +4 -0
- package/dist/chunks/media-library-2a5hdbek.js +4 -0
- package/dist/chunks/media-open-069kzhj4.js +4 -0
- package/dist/chunks/media-picker-4p4pd28y.js +4 -0
- package/dist/chunks/menu-26nez152.js +4 -0
- package/dist/chunks/{monaco-binding-a51py33c.js → monaco-binding-1s2b8snq.js} +3 -3
- package/dist/chunks/monaco-buffer-m9fd2dka.js +4 -0
- package/dist/chunks/monaco-setup-e0crh3v6.js +4 -0
- package/dist/chunks/notify-re3js157.js +4 -0
- package/dist/chunks/platform-rywy3tkg.js +4 -0
- package/dist/chunks/popover-fhdnt4be.js +4 -0
- package/dist/chunks/publish-panel-czkpg8m4.js +4 -0
- package/dist/chunks/{publish-quick-4fwngwrw.js → publish-quick-b9wbhqj6.js} +3 -3
- package/dist/chunks/publish-workspace-yy6d23b7.js +4 -0
- package/dist/chunks/quick-search-tp7pjnmv.js +4 -0
- package/dist/chunks/references-wd2yj8s1.js +4 -0
- package/dist/chunks/right-panel-x971k0zc.js +4 -0
- package/dist/chunks/source-control-k4p00hpe.js +4 -0
- package/dist/chunks/{split-m5rwn31z.js → split-qh21j2b0.js} +2 -2
- package/dist/chunks/{studio-v5wktj6f.js → studio-151kgn69.js} +4 -4
- package/dist/chunks/studio-15mfah02.js +10 -0
- package/dist/chunks/studio-1a7zshnv.js +5 -0
- package/dist/chunks/studio-1d8m5s38.js +16 -0
- package/dist/chunks/studio-35pmfjtx.js +5 -0
- package/dist/chunks/{studio-3na1r48j.js → studio-3yr1zdp9.js} +8 -8
- package/dist/chunks/studio-43pynnhz.js +5 -0
- package/dist/chunks/studio-44xqe0b7.js +4 -0
- package/dist/chunks/{studio-hh394phe.js → studio-48yzqzqr.js} +4 -4
- package/dist/chunks/{studio-9gr7xs9r.js → studio-4jy967rj.js} +5 -5
- package/dist/chunks/{studio-4bgvvzhv.js → studio-4ncxzw5m.js} +4 -4
- package/dist/chunks/{studio-pm17wdc4.js → studio-4s5bka8t.js} +4 -4
- package/dist/chunks/{studio-5tet77de.js → studio-4z0cdh16.js} +3 -3
- package/dist/chunks/studio-5rq3e4q5.js +5 -0
- package/dist/chunks/{studio-wmjmsw0p.js → studio-69g6a0jn.js} +4 -4
- package/dist/chunks/studio-6hcfm9q3.js +5 -0
- package/dist/chunks/studio-6mf31e16.js +5 -0
- package/dist/chunks/studio-6sxnqqw8.js +5 -0
- package/dist/chunks/{studio-0939t1q1.js → studio-7qqb7hb8.js} +4 -4
- package/dist/chunks/{studio-bg8pzprg.js → studio-7qr8k0v2.js} +4 -4
- package/dist/chunks/{studio-0x6f3nk9.js → studio-8ncm9zwx.js} +4 -4
- package/dist/chunks/studio-8w5pa88p.js +7 -0
- package/dist/chunks/{studio-3qr08sxq.js → studio-9f5vcb4w.js} +3 -3
- package/dist/chunks/studio-aaw7wxv8.js +5 -0
- package/dist/chunks/studio-afx9brsc.js +5 -0
- package/dist/chunks/studio-ajfq4fmr.js +5 -0
- package/dist/chunks/{studio-evq47j2z.js → studio-ber1dgte.js} +4 -4
- package/dist/chunks/{studio-sce30r7b.js → studio-bk0anq3b.js} +3 -3
- package/dist/chunks/{studio-0gvvqj3h.js → studio-bmwgj8ez.js} +4 -4
- package/dist/chunks/{studio-s24mhq0z.js → studio-bpzwhn88.js} +4 -4
- package/dist/chunks/{studio-g9brbewz.js → studio-c72v1kwc.js} +4 -4
- package/dist/chunks/{studio-tpxcryf2.js → studio-cv6gnjqd.js} +4 -4
- package/dist/chunks/{studio-90zkgne5.js → studio-czpmzs9r.js} +7 -7
- package/dist/chunks/{studio-2pk7kc7z.js → studio-d5f7sskn.js} +4 -4
- package/dist/chunks/studio-dmgh2rrj.js +5 -0
- package/dist/chunks/{studio-nwf0c32p.js → studio-dst8y8vh.js} +4 -4
- package/dist/chunks/studio-ehj3csbm.js +6 -0
- package/dist/chunks/{studio-hfqpfmpk.js → studio-ek4fb4eb.js} +4 -4
- package/dist/chunks/studio-ep1384t9.js +5 -0
- package/dist/chunks/{studio-abh9kfdh.js → studio-eq5vqn1q.js} +4 -4
- package/dist/chunks/studio-erjccagy.js +5 -0
- package/dist/chunks/studio-ex1644g2.js +5 -0
- package/dist/chunks/studio-f3awr5nc.js +12 -0
- package/dist/chunks/studio-f69wcajk.js +9 -0
- package/dist/chunks/{studio-jma2q8r9.js → studio-g65t6svn.js} +4 -4
- package/dist/chunks/{studio-r6mx7vm5.js → studio-gw83z798.js} +5 -5
- package/dist/chunks/{studio-2x972v58.js → studio-hy936516.js} +4 -4
- package/dist/chunks/studio-j3bq30yz.js +5 -0
- package/dist/chunks/studio-j52ks8q7.js +5 -0
- package/dist/chunks/{studio-g0hxtcga.js → studio-jqvke4ac.js} +4 -4
- package/dist/chunks/{studio-bnc50f9h.js → studio-k05g7efh.js} +4 -4
- package/dist/chunks/studio-kefs4sz4.js +5 -0
- package/dist/chunks/{studio-2e4hry72.js → studio-ketrgtph.js} +5 -5
- package/dist/chunks/studio-m3jrjfhy.js +14 -0
- package/dist/chunks/{studio-45mj4jqe.js → studio-m8hesdrw.js} +4 -4
- package/dist/chunks/{studio-k0xzrse2.js → studio-n271vdsg.js} +36 -36
- package/dist/chunks/studio-n4pnf0sw.js +5 -0
- package/dist/chunks/{studio-gnazwtv3.js → studio-ncwx602y.js} +4 -4
- package/dist/chunks/{studio-amaz545p.js → studio-nngc45ct.js} +4 -4
- package/dist/chunks/{studio-eqcddvc2.js → studio-nv5eq0f7.js} +173 -173
- package/dist/chunks/{studio-az3wvyh2.js → studio-nzp0sqjg.js} +4 -4
- package/dist/chunks/{studio-k2evngrj.js → studio-pgze9p34.js} +5 -5
- package/dist/chunks/studio-ph17an19.js +5 -0
- package/dist/chunks/studio-pte5w7rf.js +9 -0
- package/dist/chunks/studio-pv1ts08p.js +6 -0
- package/dist/chunks/studio-q509sqt8.js +5 -0
- package/dist/chunks/{studio-rr00ex23.js → studio-qarg1xf9.js} +6 -6
- package/dist/chunks/studio-r5g7fx7q.js +9 -0
- package/dist/chunks/studio-s49y6m5b.js +5 -0
- package/dist/chunks/studio-s4k1v7dt.js +7 -0
- package/dist/chunks/studio-t46rt6gx.js +5 -0
- package/dist/chunks/{studio-61rg2cz5.js → studio-td3h1644.js} +4 -4
- package/dist/chunks/{studio-v7a7yb5w.js → studio-thgexj3w.js} +7 -7
- package/dist/chunks/{studio-vpevkfxw.js → studio-veh91hjv.js} +4 -4
- package/dist/chunks/studio-vswmz40d.js +6 -0
- package/dist/chunks/{studio-daksw8kh.js → studio-w3aafbpk.js} +4 -4
- package/dist/chunks/studio-w4mfaaam.js +5 -0
- package/dist/chunks/{studio-4zd6an2b.js → studio-wn2cmf9q.js} +4 -4
- package/dist/chunks/studio-x5ffyssw.js +5 -0
- package/dist/chunks/{studio-swedpd82.js → studio-x5py815g.js} +4 -4
- package/dist/chunks/{studio-4fnb5648.js → studio-xh6rf05g.js} +4 -4
- package/dist/chunks/studio-z6s56dzz.js +5 -0
- package/dist/chunks/{studio-nvcn2ykc.js → studio-zav9hgb3.js} +4 -4
- package/dist/chunks/{studio-a88ej93p.js → studio-ze1sm147.js} +6 -6
- package/dist/chunks/{studio-pj216fmn.js → studio-zkkt13qd.js} +4 -4
- package/dist/chunks/studio-zn7ysyzb.js +5 -0
- package/dist/chunks/{studio-xx3r3sb0.js → studio-ztzf8mce.js} +4 -4
- package/dist/chunks/{studio-1410q5dw.js → studio-zzgp5yka.js} +4 -4
- package/dist/chunks/{tooltip-dj1pvgz7.js → tooltip-gtx40bjg.js} +3 -3
- package/dist/chunks/{tree-j0ebwr5s.js → tree-a19s1c05.js} +2 -2
- package/dist/chunks/tsMode-3f116hth.js +14 -0
- package/dist/chunks/workspace-home-61n1jz3q.js +4 -0
- package/dist/chunks/workspace-sheek6zw.js +4 -0
- package/dist/chunks/{ws-client-arz2qsy9.js → ws-client-cb49m87s.js} +3 -3
- package/dist/iframe-entry.js +14 -14
- package/dist/manifest.json +82 -82
- package/dist/studio.js +97 -96
- package/dist/workers/ts.worker.js +3 -3
- package/package.json +11 -11
- package/src/browse/library-layouts.ts +9 -12
- package/src/browse/library-pane.ts +22 -16
- package/src/browse/library-source.ts +3 -4
- package/src/canvas/canvas-live-render.ts +2 -5
- package/src/canvas/canvas-page-context.ts +5 -0
- package/src/content/entry-commands.ts +32 -27
- package/src/content/entry-editor.ts +46 -44
- package/src/content/entry-inspector.ts +15 -12
- package/src/content/entry-model.ts +3 -3
- package/src/content/entry-summaries.ts +5 -5
- package/src/content/hold-state.ts +172 -0
- package/src/content/page-summaries.ts +10 -14
- package/src/editor/inline-edit.ts +6 -1
- package/src/editor/repeater-scope.ts +333 -74
- package/src/grid/sources/content-source.ts +42 -66
- package/src/panels/block-action-bar.ts +3 -4
- package/src/panels/events-panel.ts +6 -2
- package/src/panels/formula-workspace.ts +9 -0
- package/src/panels/properties-panel.ts +127 -35
- package/src/panels/statement-editor.ts +4 -0
- package/src/panels/tab-strip.ts +29 -26
- package/src/services/ai-project-tools.ts +3 -3
- package/src/services/ai-tools.ts +5 -5
- package/src/services/ai-write-report.ts +22 -4
- package/src/services/context-manager.ts +110 -2
- package/src/services/document-assistant.ts +2 -2
- package/src/services/harness/chat-reducer.ts +9 -4
- package/src/services/render-critic.ts +234 -12
- package/src/services/tool-executor.ts +16 -8
- package/src/surfaces/collections-panel.json +1 -1
- package/src/surfaces/collections-panel.ts +1 -1
- package/src/surfaces/entry-editor.json +10 -10
- package/src/surfaces/entry-editor.ts +22 -19
- package/src/surfaces/entry-inspector.json +11 -11
- package/src/surfaces/entry-inspector.ts +17 -10
- package/src/surfaces/tab-strip.json +5 -5
- package/src/surfaces/tab-strip.ts +2 -2
- package/src/types.ts +2 -0
- package/src/ui/expression-editor.ts +10 -5
- package/src/ui/schema-form.ts +18 -1
- package/src/utils/edit-display.ts +27 -1
- package/dist/chunks/2020-906vx2dq.js +0 -4
- package/dist/chunks/adapter-hnvad61x.js +0 -4
- package/dist/chunks/cf-settings-mkbcz4z2.js +0 -4
- package/dist/chunks/color-04361ttk.js +0 -4
- package/dist/chunks/components-w5zqwva1.js +0 -4
- package/dist/chunks/content-source-g38w0d3f.js +0 -4
- package/dist/chunks/editor-wvf5e6j9.js +0 -4
- package/dist/chunks/entry-commands-t13dyw3c.js +0 -4
- package/dist/chunks/file-ops-1sghzgcr.js +0 -4
- package/dist/chunks/files-zrqtgt41.js +0 -4
- package/dist/chunks/format-host-9gy76erh.js +0 -4
- package/dist/chunks/fs-events-45rhg3me.js +0 -4
- package/dist/chunks/grid-open-t4fq6bbm.js +0 -4
- package/dist/chunks/iframe-host-9aq8ze3q.js +0 -4
- package/dist/chunks/index-f9qr7kzh.js +0 -4
- package/dist/chunks/jsonMode-vj30te2h.js +0 -13
- package/dist/chunks/jx-validate-r4ax11e3.js +0 -4
- package/dist/chunks/layers-m49nmwvx.js +0 -4
- package/dist/chunks/layers-panel-zr92pwdp.js +0 -4
- package/dist/chunks/lit-html-k2mb9k0r.js +0 -4
- package/dist/chunks/media-library-j74rt0w4.js +0 -4
- package/dist/chunks/media-open-mypr0k6b.js +0 -4
- package/dist/chunks/media-picker-pemtbsjv.js +0 -4
- package/dist/chunks/menu-d2jxpgma.js +0 -4
- package/dist/chunks/monaco-buffer-pa0g6ax7.js +0 -4
- package/dist/chunks/monaco-setup-0hw9f835.js +0 -4
- package/dist/chunks/notify-bwehrp7q.js +0 -4
- package/dist/chunks/platform-kv0k65cy.js +0 -4
- package/dist/chunks/popover-9tf4cjrk.js +0 -4
- package/dist/chunks/publish-panel-vg1n880b.js +0 -4
- package/dist/chunks/publish-workspace-jv4w2bx2.js +0 -4
- package/dist/chunks/quick-search-nh29rt8a.js +0 -4
- package/dist/chunks/references-vqyr5s9x.js +0 -4
- package/dist/chunks/right-panel-3ws9f2w0.js +0 -4
- package/dist/chunks/source-control-h3hcs5gy.js +0 -4
- package/dist/chunks/studio-0khd8xtx.js +0 -4
- package/dist/chunks/studio-1ctnw3xv.js +0 -5
- package/dist/chunks/studio-1ks5d9y4.js +0 -5
- package/dist/chunks/studio-1y9mab9m.js +0 -5
- package/dist/chunks/studio-2kpxm0eh.js +0 -7
- package/dist/chunks/studio-2zz4qbv9.js +0 -5
- package/dist/chunks/studio-3pz4t5a2.js +0 -5
- package/dist/chunks/studio-4f7thq0p.js +0 -5
- package/dist/chunks/studio-7rrdbvex.js +0 -5
- package/dist/chunks/studio-9dq7rwc7.js +0 -5
- package/dist/chunks/studio-acer0g8w.js +0 -5
- package/dist/chunks/studio-brh8d2pr.js +0 -5
- package/dist/chunks/studio-c2fa783p.js +0 -9
- package/dist/chunks/studio-cfnn70h1.js +0 -5
- package/dist/chunks/studio-cqxj4fcy.js +0 -5
- package/dist/chunks/studio-cw11h078.js +0 -5
- package/dist/chunks/studio-dn56z7sn.js +0 -5
- package/dist/chunks/studio-egnjpyde.js +0 -9
- package/dist/chunks/studio-ejrypvmq.js +0 -5
- package/dist/chunks/studio-frc16j8v.js +0 -10
- package/dist/chunks/studio-j0zf5x2x.js +0 -5
- package/dist/chunks/studio-j5fcp6gj.js +0 -5
- package/dist/chunks/studio-jd1qb1st.js +0 -7
- package/dist/chunks/studio-k1xqrgm5.js +0 -5
- package/dist/chunks/studio-k3we3kqb.js +0 -5
- package/dist/chunks/studio-knsjyx7n.js +0 -5
- package/dist/chunks/studio-mrjtnm62.js +0 -5
- package/dist/chunks/studio-n9qba0gx.js +0 -5
- package/dist/chunks/studio-nzemfbkk.js +0 -14
- package/dist/chunks/studio-pwctzka3.js +0 -16
- package/dist/chunks/studio-q80fg1d6.js +0 -12
- package/dist/chunks/studio-q9hx2qp0.js +0 -5
- package/dist/chunks/studio-qrzmfaf6.js +0 -5
- package/dist/chunks/studio-tmfcn347.js +0 -6
- package/dist/chunks/studio-wqfaqw4m.js +0 -5
- package/dist/chunks/studio-xpx91fby.js +0 -6
- package/dist/chunks/studio-xva3hzkv.js +0 -5
- package/dist/chunks/studio-z3fztxrp.js +0 -9
- package/dist/chunks/studio-z9453ypn.js +0 -6
- package/dist/chunks/tsMode-jg1dtphj.js +0 -14
- package/dist/chunks/workspace-6ztqcrsh.js +0 -4
- package/dist/chunks/workspace-home-rse9h8a0.js +0 -4
- package/src/content/draft-state.ts +0 -108
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* **This module is the FLOW; `surfaces/entry-editor.json` is the markup.** Everything below decides
|
|
18
18
|
* something — which pane holds which tab, which collection a path belongs to, which record the
|
|
19
|
-
* fields live in, which required keys are absent, what a
|
|
19
|
+
* fields live in, which required keys are absent, what a hold switch commits into — and hands the
|
|
20
20
|
* surface a projection with no decisions left in it. The form itself is an island the surface
|
|
21
21
|
* renders empty: `ui/schema-form.ts` hands back a host ELEMENT, and {@link renderEntryMode} places
|
|
22
22
|
* it in the node the document announces.
|
|
@@ -51,7 +51,7 @@ import { paneRegion } from "../ui/regions";
|
|
|
51
51
|
import { activateTab, workspace } from "../workspace/workspace";
|
|
52
52
|
import { commitEntryFields, entryFields, mutateEntryField } from "./entry-fields";
|
|
53
53
|
import type { EntryFields } from "./entry-fields";
|
|
54
|
-
import {
|
|
54
|
+
import { hasHold, holdMeaning, holdOf, holdStatus, holdSwitch } from "./hold-state";
|
|
55
55
|
import { collectionOfPath, missingRequired, recordCollectionOfPath } from "./entry-model";
|
|
56
56
|
import { ENTRY_MODE, isRecordTab, recordPlace, showRecord } from "./record-entry";
|
|
57
57
|
import { requestGridReveal } from "../grid/grid-reveal";
|
|
@@ -115,56 +115,55 @@ export function detachEntryPane(paneId: string): void {
|
|
|
115
115
|
_active.delete(paneId);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
// ───
|
|
118
|
+
// ─── Holds ───────────────────────────────────────────────────────────────────
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
|
-
*
|
|
122
|
-
* all reach the same state whatever it was before.
|
|
121
|
+
* Flip the switch this entry's collection offers (`holdSwitch`). A setter, never a toggle: the
|
|
122
|
+
* pill, the command and the assistant all reach the same state whatever it was before. Does nothing
|
|
123
|
+
* on a collection whose rules offer no switch.
|
|
123
124
|
*
|
|
124
|
-
* `
|
|
125
|
-
*
|
|
126
|
-
*
|
|
125
|
+
* `false` is written rather than removed. The absence of the key and the value `false` mean the
|
|
126
|
+
* same thing to a reader, but only one of them survives a glance at the file, and an author who has
|
|
127
|
+
* explicitly released something should be able to see that they did.
|
|
127
128
|
*/
|
|
128
|
-
export function
|
|
129
|
-
|
|
129
|
+
export function setEntryHold(tab: Tab, on: boolean): void {
|
|
130
|
+
const toggle = holdSwitch(entryCollectionOf(tab)?.def);
|
|
131
|
+
if (toggle) {
|
|
132
|
+
transactDoc(tab, (t) => mutateEntryField(t, toggle.field, on));
|
|
133
|
+
}
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
/** What the tab chip's
|
|
133
|
-
export interface
|
|
134
|
-
/** The word on the pill:
|
|
136
|
+
/** What the tab chip's pill says. A value, drawn by `surfaces/tab-strip.json`. */
|
|
137
|
+
export interface HoldPill {
|
|
138
|
+
/** The word on the pill: the rule's label, or "Published". */
|
|
135
139
|
text: string;
|
|
136
|
-
/** Its tooltip: what being
|
|
140
|
+
/** Its tooltip: what being held actually does, or that it is not. */
|
|
137
141
|
title: string;
|
|
138
|
-
|
|
142
|
+
held: boolean;
|
|
139
143
|
}
|
|
140
144
|
|
|
141
145
|
/**
|
|
142
|
-
* The
|
|
146
|
+
* The pill for a tab, or `null` when its collection states no rules.
|
|
143
147
|
*
|
|
144
148
|
* Drawn on the pane's tab chip (`panels/tab-strip.ts`) rather than only inside the editor, because
|
|
145
|
-
* the failure this exists to prevent is publishing something you thought was private
|
|
146
|
-
* mistake is made while looking at a tab, not while looking at a form. It states its
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
+
* the failure this exists to prevent is publishing something you thought was private, and that
|
|
150
|
+
* mistake is made while looking at a tab, not while looking at a form. It states its state for any
|
|
151
|
+
* entry whose collection has rules, so "Published" is as visible as "Draft"; a collection with none
|
|
152
|
+
* states neither, which is what `null` means.
|
|
149
153
|
*
|
|
150
154
|
* **It was the ONE piece of markup left in this file, and it was not this surface's.** It was a
|
|
151
|
-
* fragment of the tab CHIP
|
|
152
|
-
* strip repaint
|
|
153
|
-
*
|
|
154
|
-
* chip's `status` slot, and `.entry-pill`'s rules in `styles/overlays.css` are dead with it.
|
|
155
|
+
* fragment of the tab CHIP, a `<span>` lit interpolated into a `repeat()` that rebuilt on every
|
|
156
|
+
* strip repaint. The strip is a Jx document now, so this is a projection: two strings and a flag,
|
|
157
|
+
* drawn by the chip's `status` slot.
|
|
155
158
|
*/
|
|
156
|
-
export function
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
if (!collection || !hasDraftAxis(collection.schema, fields)) {
|
|
159
|
+
export function entryHoldPill(tab: Tab): HoldPill | null {
|
|
160
|
+
const def = entryCollectionOf(tab)?.def;
|
|
161
|
+
if (!hasHold(def)) {
|
|
160
162
|
return null;
|
|
161
163
|
}
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
text: draft ? "Draft" : "Published",
|
|
166
|
-
title: draft ? DRAFT_MEANING : "Not marked a draft.",
|
|
167
|
-
};
|
|
164
|
+
const fields = entryFields(tab);
|
|
165
|
+
const label = holdOf(def, fields);
|
|
166
|
+
return { held: label !== null, text: holdStatus(def, fields), title: holdMeaning(label) };
|
|
168
167
|
}
|
|
169
168
|
|
|
170
169
|
// ─── The form ────────────────────────────────────────────────────────────────
|
|
@@ -255,11 +254,12 @@ function entryView(paneId: string, tab: Tab, collection: EntryCollection | null)
|
|
|
255
254
|
if (!collection) {
|
|
256
255
|
return {
|
|
257
256
|
collection: "",
|
|
258
|
-
draft: false,
|
|
259
|
-
draftAxis: "hidden",
|
|
260
|
-
draftHint: DRAFT_MEANING,
|
|
261
257
|
emptyLine: emptyLine(tab),
|
|
262
258
|
fieldsRegion,
|
|
259
|
+
holdHint: holdMeaning(null),
|
|
260
|
+
holdLabel: "",
|
|
261
|
+
holdOn: false,
|
|
262
|
+
holdSwitch: "hidden",
|
|
263
263
|
name: "",
|
|
264
264
|
nextDisabled: true,
|
|
265
265
|
note: "",
|
|
@@ -272,23 +272,25 @@ function entryView(paneId: string, tab: Tab, collection: EntryCollection | null)
|
|
|
272
272
|
};
|
|
273
273
|
}
|
|
274
274
|
const fields = entryFields(tab);
|
|
275
|
-
const
|
|
275
|
+
const label = holdOf(collection.def, fields);
|
|
276
|
+
const toggle = holdSwitch(collection.def);
|
|
276
277
|
const record = isRecordTab(tab);
|
|
277
278
|
const place = recordPlace(tab);
|
|
278
279
|
return {
|
|
279
280
|
collection: collection.name,
|
|
280
|
-
draft,
|
|
281
|
-
draftAxis: hasDraftAxis(collection.schema, fields) ? "shown" : "hidden",
|
|
282
|
-
draftHint: DRAFT_MEANING,
|
|
283
281
|
emptyLine: "",
|
|
284
282
|
fieldsRegion,
|
|
283
|
+
holdHint: holdMeaning(label),
|
|
284
|
+
holdLabel: toggle?.label ?? "",
|
|
285
|
+
holdOn: toggle !== null && fields[toggle.field] === true,
|
|
286
|
+
holdSwitch: toggle ? "shown" : "hidden",
|
|
285
287
|
// A record is called what the Content home calls it: its title field, else its row.
|
|
286
288
|
name: record
|
|
287
289
|
? recordName(collection, fields, place.index)
|
|
288
290
|
: (tab.documentPath?.split("/").pop() ?? "Untitled"),
|
|
289
291
|
nextDisabled: place.next === null,
|
|
290
|
-
note:
|
|
291
|
-
noteState:
|
|
292
|
+
note: holdMeaning(label),
|
|
293
|
+
noteState: label === null ? "hidden" : "shown",
|
|
292
294
|
place: place.index === -1 ? "" : `${place.index + 1} of ${place.total}`,
|
|
293
295
|
previousDisabled: place.previous === null,
|
|
294
296
|
recordNav: record ? "shown" : "hidden",
|
|
@@ -401,7 +403,7 @@ export function renderEntryMode(surface: CanvasSurface, tab: Tab): void {
|
|
|
401
403
|
}
|
|
402
404
|
tab.session.ui.canvasMode = "grid";
|
|
403
405
|
},
|
|
404
|
-
|
|
406
|
+
setHold: (on) => setEntryHold(tab, on),
|
|
405
407
|
showNext: () => {
|
|
406
408
|
const { next } = recordPlace(tab);
|
|
407
409
|
if (next !== null) {
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* The Inspector's Entry tab: what the Page tab draws while a collection entry is the focused
|
|
4
4
|
* document (`specs/studio.md` §6, §22.5; the Content-Entry board's sidebar).
|
|
5
5
|
*
|
|
6
|
-
* Four things, in the board's order: the entry's **status** (its
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Four things, in the board's order: the entry's **status** (what its collection's `hold` rules
|
|
7
|
+
* say, `content/hold-state.ts`), its **fields** as the collection's schema types them
|
|
8
|
+
* (`ui/schema-form.ts` over `content/entry-fields.ts`, the same form and the same transacted
|
|
9
9
|
* commits as the form-only editor, so ⌘Z undoes a field here exactly as it does there), the **URL**
|
|
10
10
|
* its collection page publishes it at (`content/collection-pages.ts`), and the **file** it is
|
|
11
11
|
* stored in, with what that file holds.
|
|
@@ -30,13 +30,13 @@ import {
|
|
|
30
30
|
peekCollectionPage,
|
|
31
31
|
} from "./collection-pages";
|
|
32
32
|
import { commitEntryFields, entryFields } from "./entry-fields";
|
|
33
|
-
import {
|
|
33
|
+
import { holdMeaning, holdOf, holdStatus, holdSwitch } from "./hold-state";
|
|
34
34
|
import { entryCollection } from "./entry-model";
|
|
35
35
|
import {
|
|
36
36
|
absentRequiredErrors,
|
|
37
37
|
fieldLabels,
|
|
38
38
|
formContext,
|
|
39
|
-
|
|
39
|
+
setEntryHold,
|
|
40
40
|
titleFirst,
|
|
41
41
|
} from "./entry-editor";
|
|
42
42
|
import { mountEntryInspectorSurface } from "../surfaces/entry-inspector";
|
|
@@ -64,20 +64,23 @@ export function entryInspectorView(tab: Tab): EntryInspectorView {
|
|
|
64
64
|
const name = match?.name ?? "";
|
|
65
65
|
const collection = entryCollection(name);
|
|
66
66
|
const fields = entryFields(tab);
|
|
67
|
-
const
|
|
68
|
-
const
|
|
67
|
+
const def = collection?.def;
|
|
68
|
+
const label = holdOf(def, fields);
|
|
69
|
+
const toggle = holdSwitch(def);
|
|
69
70
|
const page = name === "" ? null : peekCollectionPage(name, entryLocale(path));
|
|
70
71
|
const url = page ? entryUrl(page, path, fields) : null;
|
|
71
72
|
const isJson = path.endsWith(".json");
|
|
72
73
|
return {
|
|
73
|
-
draft,
|
|
74
|
-
draftAxis: draftAxis ? "shown" : "hidden",
|
|
75
|
-
draftHint: DRAFT_MEANING,
|
|
76
74
|
fieldsState: collection?.schema ? "form" : "none",
|
|
75
|
+
held: label !== null,
|
|
76
|
+
holdHint: holdMeaning(label),
|
|
77
|
+
holdLabel: toggle?.label ?? "",
|
|
78
|
+
holdOn: toggle !== null && fields[toggle.field] === true,
|
|
79
|
+
holdSwitch: toggle ? "shown" : "hidden",
|
|
77
80
|
kind: capitalise(contentCollection(name)?.item ?? "entry"),
|
|
78
81
|
path,
|
|
79
82
|
region: ENTRY_INSPECTOR_REGION,
|
|
80
|
-
status:
|
|
83
|
+
status: holdStatus(def, fields),
|
|
81
84
|
storage: isJson
|
|
82
85
|
? ". Its fields are the file itself."
|
|
83
86
|
: ". Fields are its frontmatter, and the body is its Markdown.",
|
|
@@ -194,7 +197,7 @@ export function renderEntryInspector(host: HTMLElement, tab: Tab): void {
|
|
|
194
197
|
openSeo: () => {
|
|
195
198
|
void runActiveReported("document.openSeo", undefined, "Entry");
|
|
196
199
|
},
|
|
197
|
-
|
|
200
|
+
setHold: (on) => setEntryHold(tab, on),
|
|
198
201
|
},
|
|
199
202
|
{
|
|
200
203
|
fieldsSlot: (element) => {
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
* `grid/sources/content-source.ts`'s `collectionInfo`, and `browse/library-model.ts`'s
|
|
19
19
|
* `contentTypeFor`, since deleted); this module adds no fourth and calls the exported ones.
|
|
20
20
|
*
|
|
21
|
-
* **The
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* **The hold axis is NOT here.** `content/hold-state.ts` owns what a hold is and whether hrafts are
|
|
22
|
+
* listed, because this module imports `content-source.ts` and the grid source must be able to ask
|
|
23
|
+
* what a draft is — the reverse edge would be a cycle, and `import/no-cycle` is an error here.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { collectionInfo } from "../grid/sources/content-source";
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import { getPlatform, hasPlatform } from "../platform";
|
|
26
26
|
import { isImage } from "../files/media-upload";
|
|
27
27
|
import { previewFileSrc } from "../files/media-paths";
|
|
28
|
-
import {
|
|
28
|
+
import { holdStatus } from "./hold-state";
|
|
29
29
|
import { entryCollection } from "./entry-model";
|
|
30
30
|
import { PAGES_COLLECTION, collectionKeyOfPath } from "./content-collections";
|
|
31
31
|
import type { EntryFields } from "./entry-fields";
|
|
@@ -36,7 +36,7 @@ export interface EntrySummary {
|
|
|
36
36
|
path: string;
|
|
37
37
|
/** The entry's `title`, or its file name without the extension. */
|
|
38
38
|
title: string;
|
|
39
|
-
/**
|
|
39
|
+
/** A `hold` rule's label, "Published", or "" for a collection with no rules. */
|
|
40
40
|
status: string;
|
|
41
41
|
/** A preview URL for the entry's first image field, or "" for none. */
|
|
42
42
|
thumb: string;
|
|
@@ -278,16 +278,16 @@ export function entryThumb(
|
|
|
278
278
|
|
|
279
279
|
/** One entry's summary, from its fields. Pure. */
|
|
280
280
|
export function summarize(path: string, collection: string, fields: EntryFields): EntrySummary {
|
|
281
|
-
const
|
|
281
|
+
const definition = entryCollection(collection);
|
|
282
|
+
const schema = definition?.schema ?? null;
|
|
282
283
|
const titleField = titleFieldOf(schema, fields);
|
|
283
284
|
const title = titleField === null ? undefined : fields[titleField];
|
|
284
|
-
const draft = isDraftEntry(fields);
|
|
285
285
|
const dateField = dateFieldOf(schema, fields);
|
|
286
286
|
return {
|
|
287
287
|
date: dateField === null ? "" : entryDay(fields[dateField]),
|
|
288
288
|
fields: fieldTexts(schema, fields),
|
|
289
289
|
path,
|
|
290
|
-
status:
|
|
290
|
+
status: holdStatus(definition?.def, fields),
|
|
291
291
|
thumb: entryThumb(path, fields, schema),
|
|
292
292
|
title: isString(title) && title.trim() !== "" ? title : entryStem(path),
|
|
293
293
|
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The hold axis (site-architecture.md §7.6): what a project says keeps an entry back, what state an
|
|
3
|
+
* entry is in by those rules, and whether held entries are listed. One place, every list.
|
|
4
|
+
*
|
|
5
|
+
* Jx gives no field a meaning of its own. A project names its fields and writes `hold` rules on a
|
|
6
|
+
* content type, so everything here reads those rules and nothing here knows the word "draft". The
|
|
7
|
+
* loader answers the same rules with the same code (`@jxsuite/schema/content-query`), which is what
|
|
8
|
+
* lets a badge here say what the build will actually do.
|
|
9
|
+
*
|
|
10
|
+
* It is deliberately its own module rather than a field on a pane's state and rather than a section
|
|
11
|
+
* of `entry-model.ts`. Two reasons, and the second is why the definitions live here beside the
|
|
12
|
+
* flag:
|
|
13
|
+
*
|
|
14
|
+
* 1. **The perspective is a fact about the AUTHOR, not the surface.** A filter that belonged to the
|
|
15
|
+
* Library would answer for the Library only, and the same author would then be shown a held
|
|
16
|
+
* entry in the collection grid, in the pages grid and in Quick Open a second later.
|
|
17
|
+
* 2. **Every list that must obey it has to be able to import it.** `entry-model.ts` asks
|
|
18
|
+
* `grid/sources/content-source.ts` where a collection's entries live, so the grid source cannot
|
|
19
|
+
* ask `entry-model.ts` back what a hold is: `import/no-cycle` is an error in this repo, and the
|
|
20
|
+
* cycle would be real.
|
|
21
|
+
*
|
|
22
|
+
* @docs studio/projects/content-types
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { compileHold } from "@jxsuite/schema/content-query";
|
|
26
|
+
import type { HoldPredicate } from "@jxsuite/schema/content-query";
|
|
27
|
+
import { isJsonObject, isString } from "@jxsuite/schema/guards";
|
|
28
|
+
import type { JsonValue } from "@jxsuite/schema/types";
|
|
29
|
+
import { reactive } from "../reactivity";
|
|
30
|
+
import type { ContentSectionEntry } from "../types";
|
|
31
|
+
import type { EntryFields } from "./entry-fields";
|
|
32
|
+
|
|
33
|
+
/** What this module reads off a content type's definition. */
|
|
34
|
+
export type HoldBearer = Pick<ContentSectionEntry, "hold">;
|
|
35
|
+
|
|
36
|
+
/** The word for an entry no rule holds, on a collection that has rules. */
|
|
37
|
+
export const NOT_HELD = "Published";
|
|
38
|
+
|
|
39
|
+
/** The rules a definition states, or none: a value that is not a list has no rules to read. */
|
|
40
|
+
function rulesOf(def: HoldBearer | null | undefined): JsonValue[] {
|
|
41
|
+
/* SAFETY: `hold` is read straight out of `project.json`, so whatever it holds is JSON. */
|
|
42
|
+
return Array.isArray(def?.hold) ? (def.hold as JsonValue[]) : [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Each rules list's compiled answer, kept for as long as the list itself is. */
|
|
46
|
+
const compiled = new WeakMap<object, HoldPredicate>();
|
|
47
|
+
|
|
48
|
+
/** The compiled rules, answering nothing if they cannot compile: the build is who reports those. */
|
|
49
|
+
function predicateOf(def: HoldBearer | null | undefined): HoldPredicate {
|
|
50
|
+
const rules = rulesOf(def);
|
|
51
|
+
const known = compiled.get(rules);
|
|
52
|
+
if (known) {
|
|
53
|
+
return known;
|
|
54
|
+
}
|
|
55
|
+
let predicate: HoldPredicate;
|
|
56
|
+
try {
|
|
57
|
+
predicate = compileHold(rules);
|
|
58
|
+
} catch {
|
|
59
|
+
predicate = () => {};
|
|
60
|
+
}
|
|
61
|
+
compiled.set(rules, predicate);
|
|
62
|
+
return predicate;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Whether this collection has rules at all. With none, it has no state to show. */
|
|
66
|
+
export function hasHold(def: HoldBearer | null | undefined): boolean {
|
|
67
|
+
return rulesOf(def).length > 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The label of the rule holding this entry, or null. Takes the entry's FIELDS, whichever record
|
|
72
|
+
* they live in: `content/entry-fields.ts` answers that, because a JSON collection's entry has no
|
|
73
|
+
* frontmatter and its fields are the document.
|
|
74
|
+
*/
|
|
75
|
+
export function holdOf(
|
|
76
|
+
def: HoldBearer | null | undefined,
|
|
77
|
+
fields: EntryFields | null | undefined,
|
|
78
|
+
): string | null {
|
|
79
|
+
if (!hasHold(def)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/* SAFETY: an entry's fields are its parsed frontmatter or its JSON, which is what a query reads
|
|
83
|
+
from the loader too. */
|
|
84
|
+
return predicateOf(def)((fields ?? {}) as Parameters<HoldPredicate>[0]) ?? null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** What an entry's status reads: its rule's label, {@link NOT_HELD}, or "" with no rules. */
|
|
88
|
+
export function holdStatus(
|
|
89
|
+
def: HoldBearer | null | undefined,
|
|
90
|
+
fields: EntryFields | null | undefined,
|
|
91
|
+
): string {
|
|
92
|
+
return hasHold(def) ? (holdOf(def, fields) ?? NOT_HELD) : "";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A rule an author can flip: one field, equal to `true`. */
|
|
96
|
+
export interface HoldSwitch {
|
|
97
|
+
field: string;
|
|
98
|
+
label: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The switch a collection's rules offer, or null. A rule on one boolean field is a thing an author
|
|
103
|
+
* can flip. A rule on a date or a list of values has no single other state to flip to, so it is
|
|
104
|
+
* shown as a status and set by editing its field.
|
|
105
|
+
*/
|
|
106
|
+
export function holdSwitch(def: HoldBearer | null | undefined): HoldSwitch | null {
|
|
107
|
+
for (const rule of rulesOf(def)) {
|
|
108
|
+
if (isJsonObject(rule) && isString(rule.label) && isJsonObject(rule.when)) {
|
|
109
|
+
const fields = Object.entries(rule.when);
|
|
110
|
+
const [first] = fields;
|
|
111
|
+
if (fields.length === 1 && first?.[1] === true) {
|
|
112
|
+
return { field: first[0], label: rule.label };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The fields a collection's rules read, which is what a surface must repaint on. */
|
|
120
|
+
export function holdFields(def: HoldBearer | null | undefined): string[] {
|
|
121
|
+
return rulesOf(def).flatMap((rule) =>
|
|
122
|
+
isJsonObject(rule) && isJsonObject(rule.when) ? Object.keys(rule.when) : [],
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** What being held means, in one sentence, for every surface that shows the state. */
|
|
127
|
+
export function holdMeaning(label: string | null): string {
|
|
128
|
+
return label === null
|
|
129
|
+
? "No hold rule matches this entry, so a production build publishes it."
|
|
130
|
+
: `Held: ${label}. A production build leaves it out; jx dev, Preview and preview builds keep it.`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── The perspective ─────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
/** The perspective. `includeHeld` false, the default, hides entries a rule holds. */
|
|
136
|
+
export const holdView = reactive({ includeHeld: false });
|
|
137
|
+
|
|
138
|
+
/** Whether held entries are currently listed. */
|
|
139
|
+
export function includingHeld(): boolean {
|
|
140
|
+
return holdView.includeHeld;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Set the perspective.
|
|
145
|
+
*
|
|
146
|
+
* A setter, never a toggle: `content.setIncludeHeld` names the STATE it reaches, so a manifest
|
|
147
|
+
* step, an assistant call and a checkbox all land on the same value however many times they run
|
|
148
|
+
* (§13.5's idempotence rule, which `tests/app-commands.test.ts` enforces).
|
|
149
|
+
*
|
|
150
|
+
* Setting the flag is all this does. Repainting the lists that read it belongs to the command, in
|
|
151
|
+
* `content/entry-commands.ts`, because a data module may not reach into the workspace.
|
|
152
|
+
*/
|
|
153
|
+
export function setIncludeHeld(include: boolean): void {
|
|
154
|
+
holdView.includeHeld = include;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Apply the perspective to a list.
|
|
159
|
+
*
|
|
160
|
+
* `includeHeld` true, or a collection with no rules, returns the input array unchanged (identity,
|
|
161
|
+
* not a copy), so a caller can cheaply tell that nothing was filtered.
|
|
162
|
+
*/
|
|
163
|
+
export function applyHoldFilter<T>(
|
|
164
|
+
items: readonly T[],
|
|
165
|
+
def: HoldBearer | null | undefined,
|
|
166
|
+
fieldsOf: (item: T) => EntryFields | null | undefined,
|
|
167
|
+
includeHeld: boolean,
|
|
168
|
+
): readonly T[] {
|
|
169
|
+
return includeHeld || !hasHold(def)
|
|
170
|
+
? items
|
|
171
|
+
: items.filter((item) => holdOf(def, fieldsOf(item)) === null);
|
|
172
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What the Content workspace's Pages list says about each page beyond its file: the template it is
|
|
3
|
-
* drawn in, the collection a dynamic page expands over
|
|
4
|
-
*
|
|
3
|
+
* drawn in, the collection a dynamic page expands over (`specs/studio.md` §22.5, the Content-Pages
|
|
4
|
+
* board: Title, URL, Template, Status, Edited).
|
|
5
5
|
*
|
|
6
|
-
* The scan behind the Content home knows each page's FILE; its `$layout
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* The scan behind the Content home knows each page's FILE; its `$layout` and `$paths` are inside
|
|
7
|
+
* it. So this reads each page once, through the same parse the editor uses, and keeps the answer
|
|
8
|
+
* until a file event drops it ({@link invalidatePageSummaries}, wired beside the template-page
|
|
9
|
+
* index in `studio.ts`). The read never rejects: a page that cannot be read states nothing, and is
|
|
10
|
+
* listed by its route alone.
|
|
11
11
|
*
|
|
12
12
|
* @docs studio/projects/browse
|
|
13
13
|
*/
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
import { isNonNullObject } from "@jxsuite/schema/guards";
|
|
16
16
|
import { getPlatform, hasPlatform } from "../platform";
|
|
17
17
|
import { pageFactsOf } from "./collection-pages";
|
|
18
|
-
import { DRAFT_FIELD } from "./draft-state";
|
|
19
18
|
import type { FormatFrontmatter } from "../format/format-host";
|
|
20
19
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
21
20
|
|
|
@@ -26,13 +25,11 @@ export interface PageSummary {
|
|
|
26
25
|
layout: string | false | undefined;
|
|
27
26
|
/** The collection its `$paths` expands over, or `null` for a page of its own. */
|
|
28
27
|
contentType: string | null;
|
|
29
|
-
/** Whether the page carries `draft: true`, the one draft marker a page can have. */
|
|
30
|
-
draft: boolean;
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
/** What a page that could not be read states: nothing. */
|
|
34
31
|
function unread(path: string): PageSummary {
|
|
35
|
-
return { contentType: null,
|
|
32
|
+
return { contentType: null, layout: undefined, path };
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
/** The summary a parsed page states. Exported so the rule is assertable without a platform. */
|
|
@@ -42,8 +39,7 @@ export function pageSummaryOf(
|
|
|
42
39
|
frontmatter: FormatFrontmatter | null,
|
|
43
40
|
): PageSummary {
|
|
44
41
|
const { contentType, layout } = pageFactsOf(document, frontmatter);
|
|
45
|
-
|
|
46
|
-
return { contentType, draft, layout, path };
|
|
42
|
+
return { contentType, layout, path };
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
/** Read one page. */
|
|
@@ -61,7 +57,7 @@ async function readSummary(path: string): Promise<PageSummary> {
|
|
|
61
57
|
const { parseSourceForPath } = await import("../files/file-ops");
|
|
62
58
|
const parsed = await parseSourceForPath(path, source);
|
|
63
59
|
/* SAFETY: a format class's parser returns its file as a Jx element tree (the format contract);
|
|
64
|
-
only `$layout
|
|
60
|
+
only `$layout` and `$paths` are read off it. */
|
|
65
61
|
return pageSummaryOf(path, parsed.document as JxMutableNode, parsed.frontmatter);
|
|
66
62
|
} catch {
|
|
67
63
|
return unread(path);
|
|
@@ -234,7 +234,12 @@ export function setEditableVerdicts(verdicts: EditableVerdicts): void {
|
|
|
234
234
|
* {@link file://./editable-tags.ts}.
|
|
235
235
|
*/
|
|
236
236
|
export function isEditableBlock(el: HTMLElement) {
|
|
237
|
-
|
|
237
|
+
/* Closed explicitly, whatever its tag. Edit mode closes the label it draws for computed children
|
|
238
|
+
(`prepareForEditMode`), because activating that label commits its words as the element's
|
|
239
|
+
`textContent` when the caret leaves, and the binding is gone without a key being pressed. */
|
|
240
|
+
return (
|
|
241
|
+
el.getAttribute("contenteditable") !== "false" && isEditableTag(el.tagName, editableVerdicts)
|
|
242
|
+
);
|
|
238
243
|
}
|
|
239
244
|
|
|
240
245
|
/**
|