@jxsuite/studio 10.4.0 → 10.6.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/data/locales/en/elements.json +434 -0
- package/data/webdata.json +116 -139
- package/dist/chunks/2020-rqm7qrje.js +4 -0
- package/dist/chunks/adapter-cyy8vbcx.js +4 -0
- package/dist/chunks/add-new-flow-8cgtejv2.js +4 -0
- package/dist/chunks/{cf-account-picker-xb8vvt2h.js → cf-account-picker-v3qd2dhw.js} +3 -3
- package/dist/chunks/cf-settings-1fnw874z.js +4 -0
- package/dist/chunks/{color-area-h8dbasp1.js → color-area-frd9y9q6.js} +2 -2
- package/dist/chunks/color-field-rebpbe33.js +4 -0
- package/dist/chunks/{color-slider-nz2mrzky.js → color-slider-6bzmk3s7.js} +2 -2
- package/dist/chunks/color-t2bnnj8y.js +4 -0
- package/dist/chunks/{combobox-6hs00996.js → combobox-c49x7gf1.js} +3 -3
- package/dist/chunks/components-y8yhdd7d.js +4 -0
- package/dist/chunks/content-source-9e7qvmsf.js +4 -0
- package/dist/chunks/convert-to-repeater-pd2w4z21.js +4 -0
- package/dist/chunks/editor-mb9jzdrw.js +4 -0
- package/dist/chunks/entry-commands-at0wnx3f.js +4 -0
- package/dist/chunks/extension-sections-4z9qq0gh.js +4 -0
- package/dist/chunks/file-ops-dtdvjmza.js +4 -0
- package/dist/chunks/files-8pf70854.js +4 -0
- package/dist/chunks/format-host-fd3m2y2a.js +4 -0
- package/dist/chunks/fs-events-hrk29vcv.js +4 -0
- package/dist/chunks/grid-open-bh74v82z.js +4 -0
- package/dist/chunks/{grid-view-2f03wcgw.js → grid-view-a51mgzmq.js} +17 -17
- package/dist/chunks/{icons-gb967qc8.js → icons-qp8fn1k8.js} +2 -2
- package/dist/chunks/{iframe-entry-btck0e9s.js → iframe-entry-mpwe5pwk.js} +4 -4
- package/dist/chunks/{iframe-entry-2x9bn5ng.js → iframe-entry-msd43x9h.js} +4 -4
- package/dist/chunks/iframe-entry-rt8jz5t8.js +5 -0
- package/dist/chunks/{iframe-entry-b5qgggyp.js → iframe-entry-v40cr813.js} +4 -4
- package/dist/chunks/iframe-host-q0s0wwy3.js +4 -0
- package/dist/chunks/{image-bake-4afr4y6a.js → image-bake-q9ajdjsj.js} +3 -3
- package/dist/chunks/{image-editor-x7t8jbjn.js → image-editor-ke9std9b.js} +3 -3
- package/dist/chunks/index-57ang7ma.js +4 -0
- package/dist/chunks/index-eytr7av4.js +17 -0
- package/dist/chunks/{index-yjrfeks8.js → index-x495reff.js} +3 -3
- package/dist/chunks/{javascript-hy2mws8v.js → javascript-qe34bvaz.js} +3 -3
- package/dist/chunks/jsonMode-m8rseh31.js +13 -0
- package/dist/chunks/jx-validate-bbd1mary.js +4 -0
- package/dist/chunks/layers-panel-wcjtdqsa.js +4 -0
- package/dist/chunks/layers-yscpys6q.js +4 -0
- package/dist/chunks/{listbox-7gka1h86.js → listbox-tktzq5xj.js} +2 -2
- package/dist/chunks/lit-html-qm5wbhqz.js +4 -0
- package/dist/chunks/media-library-2hfff88g.js +4 -0
- package/dist/chunks/media-open-4pb05cne.js +4 -0
- package/dist/chunks/media-picker-bzktmmwr.js +4 -0
- package/dist/chunks/menu-xc3cbgsd.js +4 -0
- package/dist/chunks/{monaco-binding-a51py33c.js → monaco-binding-7t38arh9.js} +3 -3
- package/dist/chunks/monaco-buffer-3vz94c9f.js +4 -0
- package/dist/chunks/monaco-setup-fstdew02.js +4 -0
- package/dist/chunks/notify-gbdcckrh.js +4 -0
- package/dist/chunks/parse-fpdzgk4c.js +4 -0
- package/dist/chunks/platform-9g72gdqr.js +4 -0
- package/dist/chunks/popover-cnmsav6b.js +4 -0
- package/dist/chunks/publish-panel-91x1cb5d.js +4 -0
- package/dist/chunks/publish-quick-e2dmf7eg.js +4 -0
- package/dist/chunks/publish-workspace-1x8mnzrj.js +4 -0
- package/dist/chunks/quick-search-2m4y6p1x.js +4 -0
- package/dist/chunks/references-yg1g9rwx.js +4 -0
- package/dist/chunks/right-panel-7mfyhzrk.js +4 -0
- package/dist/chunks/source-control-rrc4jgp5.js +4 -0
- package/dist/chunks/{split-m5rwn31z.js → split-d15yencd.js} +2 -2
- package/dist/chunks/studio-0jhhznxy.js +9 -0
- package/dist/chunks/studio-0nb57pjg.js +7 -0
- package/dist/chunks/{studio-s24mhq0z.js → studio-0wa4wv4s.js} +4 -4
- package/dist/chunks/studio-0z8kp4xk.js +5 -0
- package/dist/chunks/{studio-g0hxtcga.js → studio-18gc5t3v.js} +4 -4
- package/dist/chunks/{studio-4fnb5648.js → studio-1b79rpcb.js} +4 -4
- package/dist/chunks/studio-211q5j7m.js +4 -0
- package/dist/chunks/studio-213pyfxh.js +16 -0
- package/dist/chunks/studio-2b1smjvp.js +5 -0
- package/dist/chunks/{studio-nwf0c32p.js → studio-2v6tc9jp.js} +4 -4
- package/dist/chunks/studio-30j0wz99.js +5 -0
- package/dist/chunks/studio-3kmqhwtt.js +5 -0
- package/dist/chunks/studio-44drsmhw.js +5 -0
- package/dist/chunks/{studio-2pk7kc7z.js → studio-495b6f2r.js} +4 -4
- package/dist/chunks/{studio-jma2q8r9.js → studio-4gthb2fd.js} +4 -4
- package/dist/chunks/{studio-0gvvqj3h.js → studio-4xadhxj1.js} +4 -4
- package/dist/chunks/{studio-nvcn2ykc.js → studio-52mzbxqm.js} +4 -4
- package/dist/chunks/{studio-v7a7yb5w.js → studio-56mmnsw8.js} +7 -7
- package/dist/chunks/studio-59hz8f4r.js +5 -0
- package/dist/chunks/{studio-sce30r7b.js → studio-5k9mfafh.js} +3 -3
- package/dist/chunks/{studio-rr00ex23.js → studio-5qez462c.js} +6 -6
- package/dist/chunks/studio-6tnpg0vx.js +5 -0
- package/dist/chunks/studio-6w8mt734.js +6 -0
- package/dist/chunks/studio-7hm04x5z.js +5 -0
- package/dist/chunks/studio-8atmfjjm.js +7 -0
- package/dist/chunks/{studio-wmjmsw0p.js → studio-8n99z71s.js} +4 -4
- package/dist/chunks/studio-8vmad6x2.js +5 -0
- package/dist/chunks/studio-93p5k4r4.js +5 -0
- package/dist/chunks/{studio-2x972v58.js → studio-9gb0m81w.js} +4 -4
- package/dist/chunks/studio-a4ymg4vd.js +5 -0
- package/dist/chunks/studio-anc8067x.js +5 -0
- package/dist/chunks/{studio-2e4hry72.js → studio-dbdt67kr.js} +5 -5
- package/dist/chunks/studio-dbex091t.js +5 -0
- package/dist/chunks/{studio-amaz545p.js → studio-dj8ctxey.js} +4 -4
- package/dist/chunks/{studio-k2evngrj.js → studio-ed9bz68j.js} +5 -5
- package/dist/chunks/{studio-4bgvvzhv.js → studio-fe2d1fp5.js} +4 -4
- package/dist/chunks/{studio-45mj4jqe.js → studio-fgp4cyy3.js} +4 -4
- package/dist/chunks/studio-ft706q02.js +5 -0
- package/dist/chunks/studio-g79s1dyg.js +6 -0
- package/dist/chunks/{studio-pj216fmn.js → studio-gahswxp4.js} +4 -4
- package/dist/chunks/studio-gbqc12bk.js +5 -0
- package/dist/chunks/studio-gxphbetb.js +6 -0
- package/dist/chunks/studio-hdf01j7q.js +7 -0
- package/dist/chunks/studio-hf259xzd.js +48 -0
- package/dist/chunks/studio-jbva4wxw.js +5 -0
- package/dist/chunks/studio-jj9gf1qc.js +5 -0
- package/dist/chunks/studio-jsjg3mjv.js +5 -0
- package/dist/chunks/studio-jtvb8hd7.js +5 -0
- package/dist/chunks/{studio-g9brbewz.js → studio-kp4vzrct.js} +4 -4
- package/dist/chunks/studio-kpx02gaa.js +7 -0
- package/dist/chunks/{studio-3na1r48j.js → studio-kv1728ex.js} +8 -8
- package/dist/chunks/studio-m25ry0mf.js +5 -0
- package/dist/chunks/studio-mcbwmkyh.js +5 -0
- package/dist/chunks/studio-meajkr51.js +5 -0
- package/dist/chunks/studio-mkjrjpr2.js +6 -0
- package/dist/chunks/{studio-eqcddvc2.js → studio-mr084886.js} +201 -201
- package/dist/chunks/studio-mrb10hv3.js +5 -0
- package/dist/chunks/{studio-0939t1q1.js → studio-nhpg405j.js} +4 -4
- package/dist/chunks/studio-nsec0hxm.js +64 -0
- package/dist/chunks/{studio-xx3r3sb0.js → studio-nv0mh0p5.js} +4 -4
- package/dist/chunks/studio-pafmb06w.js +5 -0
- package/dist/chunks/{studio-pm17wdc4.js → studio-pgftd79b.js} +4 -4
- package/dist/chunks/{studio-4zd6an2b.js → studio-pk961xgz.js} +4 -4
- package/dist/chunks/studio-pzbr7g05.js +5 -0
- package/dist/chunks/studio-qcc5hsw2.js +10 -0
- package/dist/chunks/studio-qfq58c8n.js +5 -0
- package/dist/chunks/studio-qvb2jr83.js +5 -0
- package/dist/chunks/studio-r85zw6ay.js +5 -0
- package/dist/chunks/studio-s0kwce5w.js +9 -0
- package/dist/chunks/studio-s6ymkf3q.js +14 -0
- package/dist/chunks/{studio-0x6f3nk9.js → studio-s7p5ccxa.js} +4 -4
- package/dist/chunks/{studio-1410q5dw.js → studio-s9jjap3x.js} +4 -4
- package/dist/chunks/{studio-evq47j2z.js → studio-t5hde1ws.js} +4 -4
- package/dist/chunks/studio-vj9yv897.js +5 -0
- package/dist/chunks/studio-wmak8xjw.js +5 -0
- package/dist/chunks/{studio-bnc50f9h.js → studio-wqsmvtqj.js} +4 -4
- package/dist/chunks/studio-x3z3rhej.js +5 -0
- package/dist/chunks/{studio-bg8pzprg.js → studio-xb55kpmy.js} +4 -4
- package/dist/chunks/studio-xxtdajmv.js +12 -0
- package/dist/chunks/studio-xxttcym1.js +5 -0
- package/dist/chunks/{studio-90zkgne5.js → studio-y2zsnzye.js} +7 -7
- package/dist/chunks/{studio-3qr08sxq.js → studio-y3tjh2xb.js} +3 -3
- package/dist/chunks/studio-y4f1rdsj.js +5 -0
- package/dist/chunks/studio-ybkd5w8k.js +5 -0
- package/dist/chunks/{studio-tpxcryf2.js → studio-yetr4gg1.js} +4 -4
- package/dist/chunks/studio-z295anhs.js +6 -0
- package/dist/chunks/studio-z89e6wpg.js +9 -0
- package/dist/chunks/{studio-5tet77de.js → studio-zbwr6prc.js} +3 -3
- package/dist/chunks/{studio-daksw8kh.js → studio-zdbsv5q2.js} +4 -4
- package/dist/chunks/{studio-abh9kfdh.js → studio-zvthj8hg.js} +4 -4
- package/dist/chunks/toast-2kn2vrn4.js +4 -0
- package/dist/chunks/{toast-host-g3r2s0kn.js → toast-host-3rxeyj3d.js} +3 -3
- package/dist/chunks/{tooltip-dj1pvgz7.js → tooltip-ghkxkasp.js} +3 -3
- package/dist/chunks/{tree-j0ebwr5s.js → tree-vw4nc63s.js} +2 -2
- package/dist/chunks/tsMode-7gngmamr.js +14 -0
- package/dist/chunks/workspace-gv0hxgsm.js +4 -0
- package/dist/chunks/workspace-home-j7e0mjy5.js +4 -0
- package/dist/chunks/{ws-client-arz2qsy9.js → ws-client-vs11sxc5.js} +3 -3
- package/dist/iframe-entry.js +14 -14
- package/dist/manifest.json +84 -83
- package/dist/studio.js +99 -98
- package/dist/workers/ts.worker.js +3 -3
- package/package.json +12 -12
- 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/browse/preview-style.ts +2 -1
- package/src/canvas/canvas-live-render.ts +2 -5
- package/src/canvas/canvas-page-context.ts +5 -0
- package/src/canvas/canvas-render.ts +6 -1
- package/src/canvas/iframe-drop.ts +84 -1
- package/src/canvas/iframe-entry.ts +9 -6
- package/src/canvas/iframe-render.ts +13 -0
- package/src/collab/collab-session.ts +2 -2
- package/src/commands/levels.ts +1 -1
- 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/create/add-new-flow.ts +2 -1
- package/src/editor/convert-targets.ts +2 -23
- package/src/editor/inline-edit.ts +6 -1
- package/src/editor/repeater-scope.ts +333 -74
- package/src/editor/slash-menu.ts +35 -17
- package/src/files/files.ts +18 -0
- package/src/format/format-choices.ts +5 -5
- package/src/format/format-host.ts +35 -3
- package/src/grid/redirects-grid.ts +4 -4
- package/src/grid/sources/content-source.ts +42 -66
- package/src/locale/element-names.ts +131 -0
- package/src/panels/block-action-bar.ts +3 -4
- package/src/panels/dnd.ts +3 -1
- package/src/panels/elements-panel.ts +93 -36
- package/src/panels/events-panel.ts +6 -2
- package/src/panels/formula-workspace.ts +9 -0
- package/src/panels/imports-panel.ts +3 -2
- package/src/panels/navigator-panels.ts +10 -10
- package/src/panels/panel-registry.ts +3 -3
- package/src/panels/properties-panel.ts +127 -35
- package/src/panels/site-panel.ts +2 -1
- package/src/panels/statement-editor.ts +4 -0
- package/src/panels/style-panel.ts +2 -1
- package/src/panels/styles-panel.ts +2 -2
- package/src/panels/tab-strip.ts +29 -26
- package/src/platforms/cloud.ts +5 -2
- package/src/services/ai-command-tools.ts +2 -1
- package/src/services/ai-project-tools.ts +21 -8
- 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/jxn-language.ts +110 -0
- package/src/services/model-uri.ts +3 -0
- package/src/services/monaco-setup.ts +6 -2
- package/src/services/render-critic.ts +234 -12
- package/src/services/tool-executor.ts +16 -8
- package/src/settings/contributed-section.ts +2 -1
- package/src/settings/extension-commands.ts +2 -1
- package/src/settings/general-settings.ts +2 -2
- package/src/settings/settings-document.ts +5 -5
- package/src/shell.ts +1 -1
- package/src/studio.ts +28 -11
- package/src/style/variables-view.ts +2 -2
- 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/panel-elements.json +466 -206
- package/src/surfaces/panel-elements.ts +61 -25
- package/src/surfaces/tab-strip.json +5 -5
- package/src/surfaces/tab-strip.ts +2 -2
- package/src/tabs/project-config.ts +13 -13
- package/src/tabs/tab.ts +7 -2
- package/src/types.ts +13 -2
- 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/src/view.ts +9 -2
- package/src/workspace/tab-workspace.ts +2 -2
- package/src/workspace/workspace-back.ts +2 -2
- package/dist/chunks/2020-906vx2dq.js +0 -4
- package/dist/chunks/adapter-hnvad61x.js +0 -4
- package/dist/chunks/add-new-flow-v0pm96mr.js +0 -4
- package/dist/chunks/cf-settings-mkbcz4z2.js +0 -4
- package/dist/chunks/color-04361ttk.js +0 -4
- package/dist/chunks/color-field-qv5r2b16.js +0 -4
- package/dist/chunks/components-w5zqwva1.js +0 -4
- package/dist/chunks/content-source-g38w0d3f.js +0 -4
- package/dist/chunks/convert-to-repeater-r8sfxjjm.js +0 -4
- package/dist/chunks/editor-wvf5e6j9.js +0 -4
- package/dist/chunks/entry-commands-t13dyw3c.js +0 -4
- package/dist/chunks/extension-sections-vjzw46xb.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-entry-42hpmwz5.js +0 -5
- 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/parse-hee7ne9b.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-quick-4fwngwrw.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-61rg2cz5.js +0 -5
- package/dist/chunks/studio-7rrdbvex.js +0 -5
- package/dist/chunks/studio-9c6cbq2c.js +0 -4
- package/dist/chunks/studio-9dq7rwc7.js +0 -5
- package/dist/chunks/studio-9gr7xs9r.js +0 -6
- package/dist/chunks/studio-a88ej93p.js +0 -7
- package/dist/chunks/studio-acer0g8w.js +0 -5
- package/dist/chunks/studio-az3wvyh2.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-gnazwtv3.js +0 -5
- package/dist/chunks/studio-hfqpfmpk.js +0 -5
- package/dist/chunks/studio-hh394phe.js +0 -5
- 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-k0xzrse2.js +0 -64
- 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-r6mx7vm5.js +0 -7
- package/dist/chunks/studio-swedpd82.js +0 -5
- package/dist/chunks/studio-tmfcn347.js +0 -6
- package/dist/chunks/studio-v5wktj6f.js +0 -5
- package/dist/chunks/studio-vpevkfxw.js +0 -5
- 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/toast-w11qpkyz.js +0 -4
- 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
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
setCanvasDelinkCommands,
|
|
25
25
|
setCanvasDelinkPopovers,
|
|
26
26
|
setCanvasViewportTranspose,
|
|
27
|
+
setDocumentParser,
|
|
27
28
|
setRootMedia,
|
|
28
29
|
setSkipAutoRequests,
|
|
29
30
|
setSkipServerFunctions,
|
|
@@ -43,6 +44,17 @@ import { isNonNullObject, isString } from "@jxsuite/schema/guards";
|
|
|
43
44
|
import type { JxDocument, JxElement, JxStyle } from "@jxsuite/schema/types";
|
|
44
45
|
import type { PathMapCtx } from "./path-mapping";
|
|
45
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Read a `.jxn` `$ref`, which the editing server answers as its text.
|
|
49
|
+
*
|
|
50
|
+
* Loaded when one turns up, because the parser is a third of this entry's size budget and most
|
|
51
|
+
* canvases never see a `.jxn` reference.
|
|
52
|
+
*/
|
|
53
|
+
async function parseDocumentText(text: string, source: string): Promise<JxDocument> {
|
|
54
|
+
const { parseJxn } = await import("@jxsuite/schema/jxn");
|
|
55
|
+
return parseJxn(text, source);
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
/**
|
|
47
59
|
* The retained render context a full render leaves behind so the surgical patcher can re-render an
|
|
48
60
|
* individual subtree (insert/replace/attr edits) with the SAME scope, doc base, path mapping, and
|
|
@@ -1070,6 +1082,7 @@ export async function renderResolvedDocument(opts: {
|
|
|
1070
1082
|
const assets = opts.assets ?? null;
|
|
1071
1083
|
setCanvasAssetResolver(assets ? (value) => resolveAssetRef(value, assets) : null);
|
|
1072
1084
|
setSkipServerFunctions(opts.mode !== "preview");
|
|
1085
|
+
setDocumentParser(parseDocumentText);
|
|
1073
1086
|
// Same gate for automatic `$prototype: "Request"` state entries. `buildScope` re-resolves every
|
|
1074
1087
|
// State entry on each full render, so without this an escalating authoring action (a signals-panel
|
|
1075
1088
|
// Edit, or Enter inside component-wrapped content) issued an HTTP request per render. Edit/design
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
setTransactObserver,
|
|
34
34
|
transactDoc,
|
|
35
35
|
} from "../tabs/transact";
|
|
36
|
-
import {
|
|
36
|
+
import { isProjectConfigPath } from "../tabs/tab";
|
|
37
37
|
import type { TransactOrigin } from "../tabs/transact";
|
|
38
38
|
import type { TransactionRecord } from "../tabs/patch-ops";
|
|
39
39
|
import type { Tab } from "../tabs/tab";
|
|
@@ -672,7 +672,7 @@ const liveTabs = new Set<Tab>();
|
|
|
672
672
|
*/
|
|
673
673
|
export function ensureCollab(tab: Tab): void {
|
|
674
674
|
const platform = maybePlatform();
|
|
675
|
-
if (!platform?.collab || !tab.documentPath || tab.documentPath
|
|
675
|
+
if (!platform?.collab || !tab.documentPath || isProjectConfigPath(tab.documentPath)) {
|
|
676
676
|
return;
|
|
677
677
|
}
|
|
678
678
|
const runtime = runtimeFor(tab);
|
package/src/commands/levels.ts
CHANGED
|
@@ -180,7 +180,7 @@ export const PANEL_PLACEMENT_MATRIX: Readonly<Record<PanelPlacement, PlacementRu
|
|
|
180
180
|
},
|
|
181
181
|
"rail/document": {
|
|
182
182
|
admits: ["document"],
|
|
183
|
-
note: "the rail's lower group — Outline, Data, and Packages with the developer panels on",
|
|
183
|
+
note: "the rail's lower group — Insert, Outline, Data, and Packages with the developer panels on",
|
|
184
184
|
},
|
|
185
185
|
navigator: {
|
|
186
186
|
admits: ["project", "document"],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The content-entry verbs: open one where it is written,
|
|
3
|
-
*
|
|
2
|
+
* The content-entry verbs: open one where it is written, hold one back, and choose whether held
|
|
3
|
+
* entries are listed at all — and {@link createEntry}, the New File tree's route into a collection
|
|
4
4
|
* folder.
|
|
5
5
|
*
|
|
6
6
|
* Creating an entry from anywhere else is Add new's (`file.new { kind: "entry", collection }`,
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Each is a record in its owning module, so the palette row, the assistant's tool, the automation
|
|
11
11
|
* step and (for the two that declare one) the context-menu item are renderings of the same
|
|
12
|
-
* declaration. Two of them are **setters and not toggles** — `content.
|
|
13
|
-
* `content.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* declaration. Two of them are **setters and not toggles** — `content.setHold {hold}` and
|
|
13
|
+
* `content.setIncludeHeld {include}` name the state they reach, because "toggle the hold" run twice
|
|
14
|
+
* by a script leaves the entry exactly where it started and run once against an unknown default
|
|
15
|
+
* publishes something private.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import {
|
|
@@ -33,13 +33,13 @@ import { notify } from "../services/notify";
|
|
|
33
33
|
import { activeTab } from "../workspace/workspace";
|
|
34
34
|
import { serializeJson } from "@jxsuite/schema/json-layout";
|
|
35
35
|
import { invalidateReferenceEntries } from "../ui/form-controls";
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
36
|
+
import { reloadHoldAwareGrids } from "../grid/sources/content-source";
|
|
37
|
+
import { holdSwitch, setIncludeHeld } from "./hold-state";
|
|
38
38
|
import { collectionOfPath, entryCollection } from "./entry-model";
|
|
39
39
|
import { entryBody } from "../create/add-new";
|
|
40
40
|
import { collectionForDirectory } from "./collection-match";
|
|
41
41
|
import { LOCALE_PLACEHOLDER } from "../canvas/asset-resolve";
|
|
42
|
-
import {
|
|
42
|
+
import { setEntryHold } from "./entry-editor";
|
|
43
43
|
import { openEntry } from "./entry-open";
|
|
44
44
|
import { errorMessage } from "@jxsuite/schema/parse";
|
|
45
45
|
import type { EntryCollection } from "./entry-model";
|
|
@@ -188,6 +188,11 @@ function activeEntryTab(): Tab | null {
|
|
|
188
188
|
return tab && collectionOfPath(tab.documentPath) ? tab : null;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/** The collection of the entry in front of the author, or null. */
|
|
192
|
+
function activeEntryCollection(): EntryCollection | null {
|
|
193
|
+
return collectionOfPath(activeEntryTab()?.documentPath ?? null);
|
|
194
|
+
}
|
|
195
|
+
|
|
191
196
|
/** The content-entry command records. */
|
|
192
197
|
export function contentCommands(): AnyCommand[] {
|
|
193
198
|
return [
|
|
@@ -212,41 +217,41 @@ export function contentCommands(): AnyCommand[] {
|
|
|
212
217
|
},
|
|
213
218
|
{
|
|
214
219
|
args: argsSchema({
|
|
215
|
-
|
|
216
|
-
"True
|
|
217
|
-
"field
|
|
220
|
+
hold: booleanProperty(
|
|
221
|
+
"True holds the open entry back from production; false releases it. Written to the " +
|
|
222
|
+
"one-field `hold` rule its collection declares.",
|
|
218
223
|
),
|
|
219
224
|
}),
|
|
220
225
|
category: "Document",
|
|
221
|
-
id: "content.
|
|
226
|
+
id: "content.setHold",
|
|
222
227
|
level: "document",
|
|
223
228
|
menus: ["context/tab", "palette"],
|
|
224
229
|
group: "3_document",
|
|
225
|
-
requires: "a content entry
|
|
230
|
+
requires: "a content entry whose collection has a one-field hold rule",
|
|
226
231
|
when: (ctx) => ctx.document.open,
|
|
227
|
-
enablement: () =>
|
|
232
|
+
enablement: () => holdSwitch(activeEntryCollection()?.def) !== null,
|
|
228
233
|
undo: "document",
|
|
229
|
-
/* No `aiTool`, by studio-ui-guidelines.md §12.4's fourth deletion rule: the
|
|
234
|
+
/* No `aiTool`, by studio-ui-guidelines.md §12.4's fourth deletion rule: the hold switch is ONE
|
|
230
235
|
FIELD on the entry, and the model already writes fields — `set_property` at the root path
|
|
231
236
|
for a JSON entry (`mutateDocumentField` is the same `set-key` at `[]`), `write_file` for a
|
|
232
237
|
frontmatter one. The state is reachable — `open_document` opens a collection file as
|
|
233
238
|
readily as `content.openEntry` does — so the reason is redundancy, not reach. Six lines to
|
|
234
|
-
re-enter should a task want the
|
|
239
|
+
re-enter should a task want the state by name rather than by key. */
|
|
235
240
|
run: (_ctx, args) => {
|
|
236
241
|
const tab = activeEntryTab();
|
|
237
242
|
if (!tab) {
|
|
238
|
-
throw new Error("content.
|
|
243
|
+
throw new Error("content.setHold: the active document is not a content entry");
|
|
239
244
|
}
|
|
240
|
-
|
|
245
|
+
setEntryHold(tab, booleanArg("content.setHold", args, "hold"));
|
|
241
246
|
},
|
|
242
|
-
title: "Set
|
|
247
|
+
title: "Set Hold",
|
|
243
248
|
},
|
|
244
249
|
{
|
|
245
250
|
args: argsSchema({
|
|
246
|
-
include: booleanProperty("True lists entries
|
|
251
|
+
include: booleanProperty("True lists entries a `hold` rule matches alongside the rest."),
|
|
247
252
|
}),
|
|
248
253
|
category: "View",
|
|
249
|
-
id: "content.
|
|
254
|
+
id: "content.setIncludeHeld",
|
|
250
255
|
level: "project",
|
|
251
256
|
menus: ["palette"],
|
|
252
257
|
group: "2_view",
|
|
@@ -258,16 +263,16 @@ export function contentCommands(): AnyCommand[] {
|
|
|
258
263
|
* Set the flag, then repaint what reads it.
|
|
259
264
|
*
|
|
260
265
|
* The second half is the wiring, and without it this command sets a value nobody looks at
|
|
261
|
-
* again: a collection grid holds the rows it loaded once, so an author who asks to see
|
|
262
|
-
*
|
|
266
|
+
* again: a collection grid holds the rows it loaded once, so an author who asks to see held
|
|
267
|
+
* entries watches an unchanged table. `hold-state.ts` cannot do it — it is imported BY the
|
|
263
268
|
* listings and reaching back into the workspace from there is the cycle. The command can,
|
|
264
269
|
* because a command is exactly the place where "the state changed" becomes "the app changed".
|
|
265
270
|
*/
|
|
266
271
|
run: async (_ctx, args) => {
|
|
267
|
-
|
|
268
|
-
await
|
|
272
|
+
setIncludeHeld(booleanArg("content.setIncludeHeld", args, "include"));
|
|
273
|
+
await reloadHoldAwareGrids();
|
|
269
274
|
},
|
|
270
|
-
title: "Include
|
|
275
|
+
title: "Include Held Entries",
|
|
271
276
|
},
|
|
272
277
|
];
|
|
273
278
|
}
|
|
@@ -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
|
+
}
|