@forsion/unit 2.13.4 → 2.13.5
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/main.mjs +112 -9
- package/package.json +1 -1
- package/release.json +5 -5
- package/web/assets/ArtificialView-BD1yHz1N.js +1 -0
- package/web/assets/AutomationBuilder-B09c-5Bi.js +1 -0
- package/web/assets/CalendarConfigView-Bd-ZDGPO.js +1 -0
- package/web/assets/CalendarSide-B0RVbOsg.js +1 -0
- package/web/assets/CalendarView-BHio-n_r.js +3 -0
- package/web/assets/CalendarView-t91KpUG7.css +1 -0
- package/web/assets/CapabilityMenu-DyHTEYLr.js +1 -0
- package/web/assets/Chrome-C7F6fdDp.js +1 -0
- package/web/assets/{CodeView-GZ0BAgoW.js → CodeView-BGOnrGhB.js} +3 -3
- package/web/assets/CtxMenuButtons-BYjrbUKS.js +1 -0
- package/web/assets/DashboardGridView-CR-R_qV8.js +25 -0
- package/web/assets/DashboardGridView-RoVFSkNO.css +1 -0
- package/web/assets/EventCard-QyYorE1_.js +1 -0
- package/web/assets/ExcalidrawCanvas-BgcFPMpU.css +1 -0
- package/web/assets/ExcalidrawCanvas-lwJCN6uB.js +1 -0
- package/web/assets/FloatingToc-DO7SH8Ku.js +1 -0
- package/web/assets/GitCloneDialog-CbW-RJak.js +1 -0
- package/web/assets/{GitCloneDialog-Bj9G3o-n.css → GitCloneDialog-DOxZk2K0.css} +1 -1
- package/web/assets/HomepageView-Bx8FbWlY.css +1 -0
- package/web/assets/HomepageView-CHJEhf9c.js +4 -0
- package/web/assets/ImageStudioPanels-CgVf0wiq.js +3 -0
- package/web/assets/ImageStudioView-4WcX0_V4.js +1 -0
- package/web/assets/InlineAiPanel-CdIPINsA.js +1 -0
- package/web/assets/InlineFiles-CAIyTk1n.js +1 -0
- package/web/assets/{MarkdownBlock-DQTuoen3.css → MarkdownBlock-Cqfk5DhC.css} +1 -1
- package/web/assets/MarkdownBlock-DSItkLhu.js +58 -0
- package/web/assets/ModelPill--CU3XTIQ.js +2 -0
- package/web/assets/{ModelPill-CzdNh7Mt.css → ModelPill-BiUsuhy3.css} +1 -1
- package/web/assets/MuseLibraryView-PlS7HAlV.js +2 -0
- package/web/assets/MuseView-CvsO62Fo.js +7 -0
- package/web/assets/{PageHistoryPanel-Bn4-XmdK.js → PageHistoryPanel-CDh7nm78.js} +2 -2
- package/web/assets/{PdfAnnotator-Cq8cklTz.js → PdfAnnotator-DafvjP3U.js} +3 -3
- package/web/assets/PenRow-CEy-s6SF.js +1 -0
- package/web/assets/PluginToolCard-BmTRr5JT.css +1 -0
- package/web/assets/PluginToolCard-DCzLhsCA.js +2 -0
- package/web/assets/ProductView-AUqnTwmQ.js +1 -0
- package/web/assets/SettingsPage-B9mmkWaM.js +3 -0
- package/web/assets/StudioGuestSurface-OOhRA8nt.js +1 -0
- package/web/assets/TodoListView-BkgMe44I.js +1 -0
- package/web/assets/Tooltip-DOYi44eY.js +1 -0
- package/web/assets/UnifiedPage-CTmQpf2T.css +1 -0
- package/web/assets/UnifiedPage-s7-tDSwG.js +712 -0
- package/web/assets/ViewSelectionBar-WmSWMFsR.js +2 -0
- package/web/assets/appStore-DDOkzWDA.js +456 -0
- package/web/assets/appStore-iy8_Tmac.css +1 -0
- package/web/assets/arrow-up-right-BptN64d-.js +1 -0
- package/web/assets/{artificial-BzLFkChP.css → artificial-BubVxExB.css} +1 -1
- package/web/assets/astryxBridge-BL56QfEv.js +69 -0
- package/web/assets/astryxBridge-djzCiy4v.css +1 -0
- package/web/assets/automationListSource-B1jz6q0t.js +1 -0
- package/web/assets/boardSurface-CP4HNmcm.js +1 -0
- package/web/assets/calendarNavStore-DCi7VBmM.js +1 -0
- package/web/assets/canvasPrefs-ArsX3NFO.js +1 -0
- package/web/assets/chatBoxSurface-DRsDr-MT.js +1 -0
- package/web/assets/chevron-up-ChXNh14u.js +1 -0
- package/web/assets/{client-CaivGZFR.js → client-fKy9CWk0.js} +5 -5
- package/web/assets/code-xml-DCCD9eCS.js +1 -0
- package/web/assets/{codeStudioStore-DhyMazGT.js → codeStudioStore-BgLfCDK2.js} +8 -8
- package/web/assets/codingNavigation-BrkLENBZ.css +1 -0
- package/web/assets/contextMenuSurface-BF593l2M.js +1 -0
- package/web/assets/copy-plus-ClKrSgM7.js +1 -0
- package/web/assets/create-DYmVT68X.js +14 -0
- package/web/assets/dashboardSurface-C6cbR3oG.js +1 -0
- package/web/assets/dbStore-BIG5Mfh2.js +1 -0
- package/web/assets/deskCapture-q-jRDIcc.js +1 -0
- package/web/assets/{diff2html-Daje9Kkm.js → diff2html-Di8qIz6O.js} +2 -2
- package/web/assets/{docx-preview-CABR7n30.js → docx-preview-B-URZXdP.js} +1 -1
- package/web/assets/draft-CBSW5tHx.js +2 -0
- package/web/assets/drawingStore-CGvTqqYW.js +7 -0
- package/web/assets/file-down-Z913cCyQ.js +1 -0
- package/web/assets/floatingTocSurface-DGwTiqvU.js +1 -0
- package/web/assets/foldActions-DGIPTmE1.js +1 -0
- package/web/assets/folder-1OJQeDI1.js +1 -0
- package/web/assets/folder-minus-D_j1SwHu.js +1 -0
- package/web/assets/folder-plus-DP9sFr26.js +1 -0
- package/web/assets/gestures-ZhFAENNZ.js +1 -0
- package/web/assets/git-branch-BMajF2cu.js +1 -0
- package/web/assets/homeCardStatus-5R7XkxFL.js +1 -0
- package/web/assets/i18n-CAf4PT1o.css +1 -0
- package/web/assets/i18n.generated-DW_l6CkR.js +15 -0
- package/web/assets/imageStudio-CwG21Bat.css +1 -0
- package/web/assets/imageStudio-DC_syDtx.js +4 -0
- package/web/assets/index-B8A3nKeL.js +1 -0
- package/web/assets/{index-BGfvNj7R.js → index-BDZIQnEx.js} +2 -2
- package/web/assets/index-BYKR7EVg.js +1 -0
- package/web/assets/index-BeW45J9J.js +7 -0
- package/web/assets/index-BnJguNqI.js +1 -0
- package/web/assets/index-BniT-A2n.js +1 -0
- package/web/assets/index-C--E96Ul.js +1 -0
- package/web/assets/index-CI2esW1X.js +1 -0
- package/web/assets/index-CIKPZc3Q.js +1 -0
- package/web/assets/index-COzdD7mZ.js +13 -0
- package/web/assets/index-CPfmhZ3Z.js +1 -0
- package/web/assets/index-CVLDDD5Z.js +1 -0
- package/web/assets/index-CWXpOSI2.js +1 -0
- package/web/assets/index-Cg1FWM_p.js +1 -0
- package/web/assets/index-CmsN2Zm2.js +1 -0
- package/web/assets/index-CtCYv9bF.js +1 -0
- package/web/assets/index-CtRdzpNh.js +1 -0
- package/web/assets/index-D-ClOPbg.js +1 -0
- package/web/assets/{index-gt5JBIFf.js → index-DfhEDV2T.js} +2 -2
- package/web/assets/index-Dr6YmrrT.js +1 -0
- package/web/assets/index-SpegRFQV.js +1 -0
- package/web/assets/index-bF04pF8q.js +3 -0
- package/web/assets/index-fDB1l4d-.js +3398 -0
- package/web/assets/index-jzlU5luC.js +1 -0
- package/web/assets/{index-O05r2Ty6.js → index-uhvJ8AiJ.js} +1 -1
- package/web/assets/{invitePage-Bgveo6Hj.js → invitePage-iOT1zBvp.js} +1 -1
- package/web/assets/{ipc-B6hkgxQL.js → ipc-SAPDwyQ4.js} +1 -1
- package/web/assets/{jszip.min-eU-k-vEy.js → jszip.min-B3n1FGCb.js} +1 -1
- package/web/assets/launchpad-C9U9hpWJ.css +1 -0
- package/web/assets/launchpad-CcmSwlJl.js +2 -0
- package/web/assets/layout-grid-oIyIGbc5.js +1 -0
- package/web/assets/linkFollow-uke1Xtit.js +8 -0
- package/web/assets/listFold-BeNv2v6M.js +1 -0
- package/web/assets/lock-open-D2nMg0ga.js +1 -0
- package/web/assets/log-out-D70NCXC4.js +1 -0
- package/web/assets/magnet-Cqmm97W8.js +1 -0
- package/web/assets/main-B9aT0WXG.js +15 -0
- package/web/assets/main-BlVApHpj.css +1 -0
- package/web/assets/markdownEditorSurface-BiDH0__r.css +1 -0
- package/web/assets/markdownEditorSurface-zUzFity2.js +3 -0
- package/web/assets/mediaPlayer-DB4KUvua.js +1 -0
- package/web/assets/message-square-plus-BvUfAAZm.js +1 -0
- package/web/assets/{messages-CKtabOqy.js → messages-GQUjmbwW.js} +1 -1
- package/web/assets/minimize-2-CMhzL6ZW.js +1 -0
- package/web/assets/mobileEntry-6u7TYC4s.css +1 -0
- package/web/assets/mobileEntry-BnHzrdoC.js +13 -0
- package/web/assets/modelPickerSurface-Dtz40Ae2.js +1 -0
- package/web/assets/nativeDownload-DsGyUSJ5.js +1 -0
- package/web/assets/nativeSheetMenu--iihXNVd.js +1 -0
- package/web/assets/pageStore-CyvNZEYX.js +13 -0
- package/web/assets/pageStore-ys8L4rjF.css +1 -0
- package/web/assets/{pageTree-BYYPgZm_.js → pageTree-Ci2qzrs1.js} +3 -3
- package/web/assets/pen-line-DC3lbEY1.js +1 -0
- package/web/assets/pen-tool-DD5_N4lZ.js +1 -0
- package/web/assets/pencil--sWwxHdP.js +1 -0
- package/web/assets/phoneList-BpLxxdq6.css +1 -0
- package/web/assets/phoneList-DKRMxQHf.js +1 -0
- package/web/assets/pluginChat-82zZt47Y.js +2 -0
- package/web/assets/react-b7FrqRQR.js +1 -0
- package/web/assets/recordSurface-vUX25Z8R.js +1 -0
- package/web/assets/redo-2-CqlqhWrI.js +1 -0
- package/web/assets/rotate-ccw-D8kni6Ni.js +1 -0
- package/web/assets/selectionActions-DIq0xDm8.js +2 -0
- package/web/assets/selectionBarSurface-B94e5JW_.js +1 -0
- package/web/assets/sessionTitle-Ctp4iNKu.js +1 -0
- package/web/assets/settingsPageSurface-C9TA3mJg.js +1 -0
- package/web/assets/sharePage-CY04XCAf.js +2 -0
- package/web/assets/{shareViewer-BiMqIBxP.js → shareViewer-DweNF8Ch.js} +2 -2
- package/web/assets/star-B0sZEmbj.js +1 -0
- package/web/assets/tableSurface-CSBYBw5P.js +1 -0
- package/web/assets/tanguProbe-B8Qv2vcm.js +6 -0
- package/web/assets/{terminalView-lbcBgG0c.js → terminalView-BLGdmdl7.js} +3 -3
- package/web/assets/type-Dt7RNB9g.js +1 -0
- package/web/assets/undo-2-QimgpLBn.js +1 -0
- package/web/assets/{unitAccount-DGwGsmC3.js → unitAccount-DL3jfd84.js} +1 -1
- package/web/assets/{unitBridge-CE2yAvm0.js → unitBridge-BSsCy1zP.js} +1 -1
- package/web/assets/{unitCloudTransport-BGFwkaDM.js → unitCloudTransport-CI_6H0TU.js} +1 -1
- package/web/assets/unitOwnerAccess-CiQ7nD7E.js +1 -0
- package/web/assets/unitShim-Dl2ksNp-.js +5 -0
- package/web/assets/upload-Dy7svciS.js +1 -0
- package/web/assets/userSpaces-oMHIWgYe.js +1 -0
- package/web/assets/viewportLock-B0l1GU_k.css +1 -0
- package/web/assets/viewportLock-BZq5PTeN.js +738 -0
- package/web/assets/zoom-in-DDEtY_Go.js +1 -0
- package/web/excalidraw/excalidraw.js +4 -2
- package/web/index.html +12 -13
- package/web/assets/ArtificialView-DLh75dAI.js +0 -1
- package/web/assets/AutomationBuilder-DNWWGnFr.js +0 -1
- package/web/assets/CalendarConfigView-G7je7k9-.js +0 -1
- package/web/assets/CalendarView-B2MtaOJk.js +0 -1
- package/web/assets/CapabilityMenu-DJaZ_Cyt.js +0 -1
- package/web/assets/CheckboxInput-FYXLaZke.js +0 -1
- package/web/assets/Chrome-CFonajM4.js +0 -1
- package/web/assets/DashboardGridView-D9s9pez_.css +0 -1
- package/web/assets/DashboardGridView-Xgw4wa0_.js +0 -17
- package/web/assets/EventCard-_39F_I5M.js +0 -1
- package/web/assets/ExcalidrawCanvas-4uYrLYin.css +0 -1
- package/web/assets/ExcalidrawCanvas-rXujil36.js +0 -1
- package/web/assets/FloatingToc-DNqAkkI0.js +0 -1
- package/web/assets/GitCloneDialog-CQx-O4j1.js +0 -1
- package/web/assets/HomepageView-DcWmTPQO.css +0 -1
- package/web/assets/HomepageView-MYEk3RCs.js +0 -3
- package/web/assets/ImageStudioPanels-4wnb2XF4.js +0 -3
- package/web/assets/ImageStudioView-qrmW8pfx.js +0 -1
- package/web/assets/InlineAiPanel-B3kd1ob2.js +0 -1
- package/web/assets/InlineFiles-Clnqh9jD.js +0 -1
- package/web/assets/MarkdownBlock-DT_wsaja.js +0 -58
- package/web/assets/ModelPill-JaIH3ffZ.js +0 -2
- package/web/assets/MuseLibraryView-DOJVVeU6.js +0 -2
- package/web/assets/MuseView-uxTmjVQa.js +0 -6
- package/web/assets/PenRow-COOYAaxx.js +0 -1
- package/web/assets/ProductView-DPTveulg.js +0 -1
- package/web/assets/RightPanel-BJUacrsf.js +0 -4
- package/web/assets/RightPanel-D0zk1g3I.css +0 -1
- package/web/assets/SketchCard-B_UU5sxV.js +0 -641
- package/web/assets/StudioGuestSurface-yBa4QKfX.js +0 -1
- package/web/assets/TargetHealthNotice-BLzDB-Xl.css +0 -1
- package/web/assets/TargetHealthNotice-CgkdTdhB.js +0 -47
- package/web/assets/TodoListView-CricdCfW.js +0 -1
- package/web/assets/Tooltip-o8ZqIb3L.js +0 -1
- package/web/assets/UnifiedPage-BWAyqj65.js +0 -69
- package/web/assets/UnifiedPage-CWXc_mo1.css +0 -1
- package/web/assets/ViewSelectionBar-35x8TiLA.js +0 -2
- package/web/assets/appStore-CI1_L5OS.js +0 -3357
- package/web/assets/appStore-DQc2marq.css +0 -1
- package/web/assets/approvalText-Cy2UAUAQ.js +0 -2
- package/web/assets/arrow-up-right-BDsMFoNQ.js +0 -1
- package/web/assets/astryxBridge-CF_hpqC2.css +0 -1
- package/web/assets/astryxBridge-CH_c5xjo.js +0 -70
- package/web/assets/automationListSource-CB6AWkL_.js +0 -1
- package/web/assets/boardSurface-CLvEUjbF.js +0 -1
- package/web/assets/book-open-BLzEdGfu.js +0 -1
- package/web/assets/calendarNavStore-BHUNxDx8.js +0 -1
- package/web/assets/canvasPrefs-CvE7mgyq.js +0 -1
- package/web/assets/chatBoxSurface-CuuHLzs5.js +0 -1
- package/web/assets/circle-question-mark-BM4ZRxQS.js +0 -1
- package/web/assets/circle-x-DTcNGZDT.js +0 -1
- package/web/assets/code-xml-AE3mT4T5.js +0 -1
- package/web/assets/codingNavigation-B0kVsy4x.js +0 -53
- package/web/assets/codingNavigation-DHCidM4N.css +0 -1
- package/web/assets/create-1JI2I47p.js +0 -14
- package/web/assets/dashboardSurface-CIE5HybN.js +0 -1
- package/web/assets/deskCapture-COW7aG5Y.js +0 -1
- package/web/assets/emoji-DZ9CA_3l.css +0 -1
- package/web/assets/emoji-DphwSxPx.js +0 -1
- package/web/assets/floatingTocSurface-BOm2QYBK.js +0 -1
- package/web/assets/foldActions-DNbJPlPl.js +0 -1
- package/web/assets/folder-DotgUhYD.js +0 -1
- package/web/assets/folder-plus-aJgL9NeP.js +0 -1
- package/web/assets/gestures-dqncPOwo.js +0 -1
- package/web/assets/git-branch-CwR2qic6.js +0 -1
- package/web/assets/hack2gate-dark-Dx2RtDRI.jpg +0 -0
- package/web/assets/hack2gate-light-DRKq9tyS.jpg +0 -0
- package/web/assets/hand-XNkzNiW2.js +0 -1
- package/web/assets/hard-drive-CfWKRoAE.js +0 -1
- package/web/assets/i18n-CykmULlm.css +0 -1
- package/web/assets/i18n.generated-BwynSSMk.js +0 -15
- package/web/assets/imageStudio-CWgLpC_d.js +0 -2
- package/web/assets/imageStudio-DZ0qGU2s.css +0 -1
- package/web/assets/index-B65ULikK.js +0 -1
- package/web/assets/index-BIIeiIap.js +0 -1
- package/web/assets/index-BOzsniE1.js +0 -297
- package/web/assets/index-BfrjVzia.js +0 -13
- package/web/assets/index-Bi9UtjVz.js +0 -1
- package/web/assets/index-C1uilc9i.js +0 -1
- package/web/assets/index-C3Dc3Ivz.js +0 -1
- package/web/assets/index-CJF_Ld1Y.js +0 -1
- package/web/assets/index-CMARP4oV.js +0 -1
- package/web/assets/index-C_RsQVNm.js +0 -7
- package/web/assets/index-ChyxZMm8.js +0 -1
- package/web/assets/index-CoKcOaZg.js +0 -1
- package/web/assets/index-CuviSCbk.js +0 -1
- package/web/assets/index-Cw43kua8.js +0 -1
- package/web/assets/index-D1fu6XQN.js +0 -3
- package/web/assets/index-DG-hXb63.js +0 -1
- package/web/assets/index-DjnTfJpN.js +0 -1
- package/web/assets/index-Dtw0abst.js +0 -1
- package/web/assets/index-W3ErfZiX.js +0 -1
- package/web/assets/index-Yc56b3XX.js +0 -1
- package/web/assets/index-d4jCdYJH.js +0 -1
- package/web/assets/index-siHtlYTM.js +0 -1
- package/web/assets/launchpad-C2c1MSVX.css +0 -1
- package/web/assets/layout-grid-CraV7nhh.js +0 -1
- package/web/assets/linkFollow-BpvDWzUx.js +0 -8
- package/web/assets/list-tree-cLJw-jYQ.js +0 -1
- package/web/assets/listFold-M8s5JYEC.js +0 -1
- package/web/assets/lock-DBkXXwaI.js +0 -1
- package/web/assets/lock-open-C-9-AWct.js +0 -1
- package/web/assets/log-out-C8bDXj9P.js +0 -1
- package/web/assets/main-BiS_5wrB.css +0 -1
- package/web/assets/main-CMk6twzA.js +0 -20
- package/web/assets/markdownEditorSurface-Bql20IAf.css +0 -1
- package/web/assets/markdownEditorSurface-DgQ0TUWH.js +0 -3
- package/web/assets/message-square-plus-8pfimvem.js +0 -1
- package/web/assets/minus-CfA9fAN3.js +0 -1
- package/web/assets/mobileEntry-DS1vohuL.css +0 -1
- package/web/assets/mobileEntry-NcMcK0LV.js +0 -13
- package/web/assets/nativeDownload-CNtwSCy0.js +0 -1
- package/web/assets/pageChrome-BR4cfIaA.js +0 -1
- package/web/assets/pageChrome-BbUhILy0.css +0 -1
- package/web/assets/pen-tool-C5ftHCnm.js +0 -1
- package/web/assets/pencil-ehhM9_9k.js +0 -1
- package/web/assets/phoneList-B6gwUJZF.js +0 -9
- package/web/assets/phoneList-iRdfiKA8.css +0 -1
- package/web/assets/pluginChat-DTTWWiUS.js +0 -2
- package/web/assets/redo-2-DCEAgQlN.js +0 -1
- package/web/assets/rotate-ccw-DdkrbRAB.js +0 -1
- package/web/assets/selectionActions-BQD2kox1.js +0 -2
- package/web/assets/selectionBarSurface-CzUZmJDJ.js +0 -1
- package/web/assets/server-Ddu9f1k3.js +0 -1
- package/web/assets/sharePage-CNrm2xTC.js +0 -2
- package/web/assets/star-Dxv7XANS.js +0 -1
- package/web/assets/tableSurface-Bly1iCXc.js +0 -1
- package/web/assets/terminal-CAmmC9cV.js +0 -1
- package/web/assets/type-Dz6rnTvG.js +0 -1
- package/web/assets/undo-2-B7izWNb_.js +0 -1
- package/web/assets/unitOwnerAccess-D4nKS6Gg.js +0 -1
- package/web/assets/unitShim-2_2hhGxS.js +0 -5
- package/web/assets/upload-DqpRRAhG.js +0 -1
- package/web/assets/userSpaces-VFu4s05v.js +0 -1
- package/web/assets/viewportLock-3AOdDmwi.js +0 -631
- package/web/assets/viewportLock-djvL8fUQ.css +0 -1
- package/web/assets/zoom-in-Bf-eKTnH.js +0 -1
|
@@ -1,641 +0,0 @@
|
|
|
1
|
-
import{J as V}from"./appStore-CI1_L5OS.js";import{j as l,u as U,d as a}from"./index-BOzsniE1.js";const rt=[["path",{d:"M4 11V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-1",key:"1q9hii"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M2 15h10",key:"jfw4w8"}],["path",{d:"m9 18 3-3-3-3",key:"112psh"}]],Pt=V("file-input",rt);const it=[["path",{d:"M15 12h-5",key:"r7krc0"}],["path",{d:"M15 8h-5",key:"1khuty"}],["path",{d:"M19 17V5a2 2 0 0 0-2-2H4",key:"zz82l3"}],["path",{d:"M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3",key:"1ph1d7"}]],Kt=V("scroll-text",it),ot="forsion-sketch";function Jt(t){const e=Math.max(0,...(t.match(/`+/g)??[]).map(s=>s.length)),n="`".repeat(Math.max(3,e+1));return`${n}${ot}
|
|
2
|
-
${t.replace(/\s+$/,"")}
|
|
3
|
-
${n}`}const ct=`/* The sketch document is isolated. All colors inherit Forsion's current theme. */
|
|
4
|
-
html, body { margin: 0; padding: 0; }
|
|
5
|
-
*, *::before, *::after { box-sizing: border-box; }
|
|
6
|
-
body {
|
|
7
|
-
font-family: var(--fs-font); background: var(--fs-bg); color: var(--fs-text);
|
|
8
|
-
font-size: 14px; line-height: 1.5; padding: 16px 20px;
|
|
9
|
-
overflow-wrap: anywhere; -webkit-font-smoothing: antialiased;
|
|
10
|
-
}
|
|
11
|
-
[hidden] { display: none !important; }
|
|
12
|
-
header, figure, figcaption, footer { margin: 0; }
|
|
13
|
-
h1, h2, h3, h4 { margin: 0 0 .5em; font-weight: 500; line-height: 1.3; text-wrap: balance; }
|
|
14
|
-
h1 { font-size: 20px; } h2 { font-size: 18px; } h3 { font-size: 15px; } h4 { font-size: 14px; }
|
|
15
|
-
p { margin: 0 0 .7em; } p:last-child { margin-bottom: 0; }
|
|
16
|
-
small { font-size: 12px; color: var(--fs-muted); }
|
|
17
|
-
a { color: var(--fs-accent); }
|
|
18
|
-
hr { border: 0; border-top: 1px solid var(--fs-rule); margin: 16px 0; }
|
|
19
|
-
code, pre, kbd { font-family: var(--fs-mono); font-size: 12px; }
|
|
20
|
-
pre { overflow-x: auto; background: var(--fs-surface); border-radius: var(--fs-radius); padding: 10px; }
|
|
21
|
-
table { border-collapse: collapse; width: 100%; font-size: 13px; font-variant-numeric: tabular-nums; }
|
|
22
|
-
th, td { text-align: left; padding: 8px 12px 8px 0; border-bottom: 1px solid var(--fs-rule); vertical-align: top; }
|
|
23
|
-
th { font-weight: 500; color: var(--fs-muted); }
|
|
24
|
-
th:last-child, td:last-child { padding-right: 0; }
|
|
25
|
-
tr:last-child td { border-bottom: 0; }
|
|
26
|
-
svg { display: block; width: 100%; max-width: 100%; height: auto; }
|
|
27
|
-
svg text { font-family: var(--fs-font); fill: var(--fs-text); }
|
|
28
|
-
img, canvas { display: block; max-width: 100%; }
|
|
29
|
-
img { height: auto; }
|
|
30
|
-
button, input, select, textarea { font: inherit; color: inherit; max-width: 100%; }
|
|
31
|
-
button {
|
|
32
|
-
border: 1px solid var(--fs-border); border-radius: var(--fs-radius);
|
|
33
|
-
background: transparent; padding: 6px 10px; cursor: pointer;
|
|
34
|
-
}
|
|
35
|
-
button:hover { background: var(--fs-surface); }
|
|
36
|
-
button:disabled { opacity: .5; cursor: default; }
|
|
37
|
-
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
|
|
38
|
-
outline: 2px solid var(--fs-accent); outline-offset: 2px;
|
|
39
|
-
}
|
|
40
|
-
input, select, textarea { accent-color: var(--fs-accent); }
|
|
41
|
-
.fs-header { display: grid; gap: 4px; margin: 0 0 16px; }
|
|
42
|
-
.fs-title { margin: 0; font-size: 20px; font-weight: 500; line-height: 1.3; text-wrap: balance; }
|
|
43
|
-
.fs-eyebrow, .fs-subtitle, .fs-caption, .fs-source, .fs-label { font-size: 12px; color: var(--fs-muted); }
|
|
44
|
-
.fs-eyebrow { font-weight: 500; }
|
|
45
|
-
.fs-subtitle { max-width: 62ch; }
|
|
46
|
-
.fs-plot { position: relative; min-width: 0; margin: 0; }
|
|
47
|
-
.fs-caption, .fs-source { margin-top: 10px; }
|
|
48
|
-
.fs-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(130px, 100%), 1fr)); gap: 12px; margin: 4px 0 16px; }
|
|
49
|
-
.fs-stat { display: grid; align-content: start; gap: 3px; min-width: 0; }
|
|
50
|
-
.fs-value { font-size: 26px; font-weight: 500; line-height: 1.2; font-variant-numeric: tabular-nums; }
|
|
51
|
-
.fs-panel { padding: 12px 14px; border-radius: var(--fs-radius); background: var(--fs-surface); }
|
|
52
|
-
.fs-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; min-width: 0; }
|
|
53
|
-
.fs-chip { display: inline-flex; align-items: center; padding: 3px 8px; border-radius: 999px; background: var(--fs-surface); font-size: 12px; color: var(--fs-muted); }
|
|
54
|
-
.fs-bar-track { height: 9px; overflow: hidden; border-radius: 999px; background: var(--fs-surface); }
|
|
55
|
-
.fs-bar-fill { height: 100%; border-radius: inherit; background: var(--fs-accent); transform-origin: left center; }
|
|
56
|
-
.fs-controls { display: flex; flex-wrap: wrap; align-items: end; gap: 12px 16px; margin-bottom: 16px; }
|
|
57
|
-
.fs-field { display: grid; gap: 6px; flex: 1 1 200px; min-width: 0; }
|
|
58
|
-
.fs-field > span { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6px; }
|
|
59
|
-
.fs-field output { font-variant-numeric: tabular-nums; }
|
|
60
|
-
.fs-input, .fs-select { width: 100%; min-width: 0; border: 1px solid var(--fs-border); border-radius: var(--fs-radius); padding: 6px 9px; background: var(--fs-surface); }
|
|
61
|
-
.fs-range { width: 100%; min-width: 0; margin: 4px 0; }
|
|
62
|
-
.fs-check { display: inline-flex; align-items: center; gap: 8px; }
|
|
63
|
-
.fs-check input { margin: 0; }
|
|
64
|
-
.fs-button[aria-pressed="true"], .fs-tabs [aria-selected="true"] { background: var(--fs-accent-soft); color: var(--fs-accent); border-color: transparent; }
|
|
65
|
-
.fs-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 12px; }
|
|
66
|
-
.fs-tabs button { border-color: transparent; }
|
|
67
|
-
.fs-number { font-variant-numeric: tabular-nums; }
|
|
68
|
-
.fs-muted { color: var(--fs-muted); }
|
|
69
|
-
.fs-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
70
|
-
/* Figure primitives: direct labels, quiet structure and one focus accent. */
|
|
71
|
-
fs-chart, fs-flow { display: block; min-width: 0; }
|
|
72
|
-
fs-chart + fs-flow, fs-flow + fs-chart, fs-chart + fs-chart { margin-top: 28px; }
|
|
73
|
-
.fs-figure-title { margin: 0 0 18px; font-size: 16px; font-weight: 500; }
|
|
74
|
-
.fs-chart-bars { display: grid; gap: 14px; }
|
|
75
|
-
.fs-chart-bar-row { display: grid; grid-template-columns: minmax(72px, 1fr) minmax(0, 3fr) minmax(56px, 1fr); gap: 16px; align-items: center; }
|
|
76
|
-
.fs-chart-bar-label { font-size: 13px; }
|
|
77
|
-
.fs-chart-bar-value { text-align: end; font-size: 13px; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
78
|
-
.fs-chart-track { position: relative; height: 12px; background: var(--fs-surface); border-radius: 3px; }
|
|
79
|
-
.fs-chart-track::after { content: ''; position: absolute; left: var(--fs-zero); top: -4px; bottom: -4px; width: 1px; background: var(--fs-rule); }
|
|
80
|
-
.fs-chart-bar { position: absolute; top: 0; height: 100%; background: var(--fs-s1); border-radius: 3px; }
|
|
81
|
-
.fs-chart-bar--context { background: var(--fs-s2); }
|
|
82
|
-
.fs-chart-bars--narrow .fs-chart-bar-row { grid-template-columns: minmax(0, 1fr) fit-content(50%); gap: 7px 12px; }
|
|
83
|
-
.fs-chart-bars--narrow .fs-chart-track { grid-column: 1 / -1; grid-row: 2; }
|
|
84
|
-
.fs-chart-bars--narrow .fs-chart-bar-value { grid-column: 2; grid-row: 1; }
|
|
85
|
-
.fs-chart-field { position: relative; }
|
|
86
|
-
.fs-chart-grid { fill: none; stroke: var(--fs-rule); stroke-width: 1; }
|
|
87
|
-
.fs-chart-tick, .fs-chart-unit { font-size: 12px; fill: var(--fs-muted); }
|
|
88
|
-
.fs-chart-line { fill: none; stroke: var(--fs-s1); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
|
|
89
|
-
.fs-chart-point { fill: var(--fs-s1); }
|
|
90
|
-
.fs-chart-guide { fill: none; stroke: var(--fs-muted); stroke-width: 1; stroke-dasharray: 3 4; }
|
|
91
|
-
.fs-chart-hit { position: absolute; border: 0; border-radius: 0; padding: 0; background: transparent; cursor: crosshair; touch-action: pan-y; }
|
|
92
|
-
.fs-chart-hit:hover { background: transparent; }
|
|
93
|
-
.fs-chart-detail { display: block; min-height: 22px; font-size: 12px; color: var(--fs-muted); font-variant-numeric: tabular-nums; }
|
|
94
|
-
.fs-flow-steps { display: grid; grid-template-columns: repeat(var(--fs-step-count), minmax(0, 1fr)); gap: 20px; margin: 0; padding: 0; list-style: none; }
|
|
95
|
-
.fs-flow-step { position: relative; min-width: 0; }
|
|
96
|
-
.fs-flow-marker { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 50%; background: var(--fs-surface); color: var(--fs-muted); font-size: 11px; font-variant-numeric: tabular-nums; }
|
|
97
|
-
.fs-flow-step[data-status="done"] .fs-flow-marker { color: var(--fs-accent); }
|
|
98
|
-
.fs-flow-step[data-status="active"] .fs-flow-marker { background: var(--fs-accent-soft); color: var(--fs-accent); outline: 1px solid var(--fs-accent); outline-offset: 2px; }
|
|
99
|
-
.fs-flow-step:not(:last-child)::after { content: ''; position: absolute; height: 1px; top: 15px; left: 40px; width: calc(100% - 28px); background: var(--fs-rule); }
|
|
100
|
-
.fs-flow-step:not(:last-child)::before { content: ''; position: absolute; top: 12px; left: calc(100% + 7px); width: 6px; height: 6px; border-top: 1px solid var(--fs-muted); border-right: 1px solid var(--fs-muted); transform: rotate(45deg); }
|
|
101
|
-
.fs-flow-content { display: grid; gap: 6px; margin-top: 12px; }
|
|
102
|
-
.fs-flow-label { display: block; font-size: 13px; font-weight: 500; }
|
|
103
|
-
.fs-flow-description { display: block; font-size: 12px; color: var(--fs-muted); }
|
|
104
|
-
.fs-flow-branches { display: grid; gap: 12px; list-style: none; padding: 0 0 0 12px; margin: 8px 0 0; border-left: 1px solid var(--fs-rule); }
|
|
105
|
-
.fs-flow-branch { position: relative; display: grid; gap: 3px; }
|
|
106
|
-
.fs-flow-branch::before { content: ''; position: absolute; left: -12px; top: 9px; width: 7px; height: 1px; background: var(--fs-rule); }
|
|
107
|
-
.fs-flow-steps--vertical { grid-template-columns: minmax(0, 1fr); gap: 22px; }
|
|
108
|
-
.fs-flow-steps--vertical .fs-flow-step { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 14px; }
|
|
109
|
-
.fs-flow-steps--vertical .fs-flow-content { margin-top: 3px; }
|
|
110
|
-
.fs-flow-steps--vertical .fs-flow-step:not(:last-child)::after { left: 15px; top: 39px; width: 1px; height: calc(100% - 23px); }
|
|
111
|
-
.fs-flow-steps--vertical .fs-flow-step:not(:last-child)::before { top: calc(100% + 11px); left: 12px; transform: rotate(135deg); }
|
|
112
|
-
/* 直播补丁(10-09):新来的部件淡入;文字原地续写不动画。 */
|
|
113
|
-
[data-fs-new] { animation: fs-in .28s ease-out both; }
|
|
114
|
-
@keyframes fs-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
|
|
115
|
-
/* 统一拼装部件(10-09,对标 Intelligent UI 的固定组件库):网格 / 选项 / 按钮行 / 提示块 / 对比 / 追问选择 / 勾选清单。 */
|
|
116
|
-
.fs-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(128px, 100%), 1fr)); gap: 12px; margin: 4px 0 12px; } /* 128:1200 宽的窗口里聊天栏卡内只有 ≈430px,要并排得下三个选项 */
|
|
117
|
-
.fs-option { display: grid; align-content: start; gap: 6px; min-width: 0; padding: 12px 14px; border-radius: var(--fs-radius); background: var(--fs-surface); }
|
|
118
|
-
.fs-option > h4 { margin: 0; font-size: 14px; }
|
|
119
|
-
.fs-points { margin: 0; padding-left: 18px; font-size: 13px; display: grid; gap: 3px; }
|
|
120
|
-
.fs-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
121
|
-
.fs-callout { padding: 10px 14px; border-left: 2px solid var(--fs-accent); background: var(--fs-surface); border-radius: 0 var(--fs-radius) var(--fs-radius) 0; font-size: 13px; }
|
|
122
|
-
fs-compare, fs-choice, fs-checklist { display: block; min-width: 0; }
|
|
123
|
-
fs-compare + fs-choice, fs-choice + fs-compare, fs-checklist + fs-choice, fs-chart + fs-compare, fs-compare + fs-chart, fs-flow + fs-checklist, fs-checklist + fs-flow { margin-top: 24px; }
|
|
124
|
-
.fs-choice-question { margin: 0 0 10px; font-size: 14px; font-weight: 500; }
|
|
125
|
-
.fs-choice-options { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
126
|
-
.fs-checklist-items { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
|
|
127
|
-
.fs-checklist-items .fs-check { align-items: start; }
|
|
128
|
-
.fs-checklist-items .fs-check input { margin-top: 4px; }
|
|
129
|
-
.fs-checklist-items .fs-check input:checked + span { color: var(--fs-muted); text-decoration: line-through; }
|
|
130
|
-
::selection { background: var(--fs-accent-soft); }
|
|
131
|
-
@media(max-width:480px) {
|
|
132
|
-
body { padding: 14px 12px; }
|
|
133
|
-
.fs-controls { gap: 12px; }
|
|
134
|
-
}
|
|
135
|
-
@media(pointer:coarse) {
|
|
136
|
-
button, .fs-check { min-height: 44px; }
|
|
137
|
-
.fs-input, .fs-select { font-size: 16px; min-height: 44px; }
|
|
138
|
-
.fs-range { min-height: 36px; }
|
|
139
|
-
}
|
|
140
|
-
@media(prefers-reduced-motion:reduce) {
|
|
141
|
-
*, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
|
|
142
|
-
}
|
|
143
|
-
`,lt=`/* Runs only inside the opaque-origin sandbox. No host capabilities or network. */
|
|
144
|
-
(() => {
|
|
145
|
-
let state = __SKETCH_INITIAL_STATE__
|
|
146
|
-
// 授权令牌(Codex 10-09 二轮 P1):宿主只认带这个 nonce 的 ask / copy。它只活在这个闭包里 —— 运行时跑完就把自己的 script 节点删掉
|
|
147
|
-
// (模型脚本读 outerHTML 也拿不到),模型 HTML 绕过运行时直接 postMessage 就没有它。真正的手势判定因此可以放在卡内:
|
|
148
|
-
// navigator.userActivation 是每个 window 各自的,父文档里敲键不会传进子帧,子帧里的真点击才会让它为真。getter 在模型脚本
|
|
149
|
-
// 跑之前就抓好,模型脚本改不了。
|
|
150
|
-
const NONCE = '__SKETCH_NONCE__'
|
|
151
|
-
const uaGet = typeof Navigator !== 'undefined' ? Object.getOwnPropertyDescriptor(Navigator.prototype, 'userActivation')?.get : undefined
|
|
152
|
-
const activeGet = typeof UserActivation !== 'undefined' ? Object.getOwnPropertyDescriptor(UserActivation.prototype, 'isActive')?.get : undefined
|
|
153
|
-
const gestureActive = () => { try { const ua = uaGet && uaGet.call(navigator); return !!(ua && activeGet && activeGet.call(ua)) } catch { return false } }
|
|
154
|
-
const send = (msg) => parent.postMessage({ ...msg, nonce: NONCE }, '*')
|
|
155
|
-
window.forsionSketch = Object.freeze({
|
|
156
|
-
get state() { return state },
|
|
157
|
-
setState(value) {
|
|
158
|
-
const json = JSON.stringify(value)
|
|
159
|
-
if (!json || new TextEncoder().encode(json).length > 16384) throw new Error('Sketch state exceeds 16 KiB')
|
|
160
|
-
state = JSON.parse(json)
|
|
161
|
-
parent.postMessage({ type: 'sketch-state', state }, '*')
|
|
162
|
-
},
|
|
163
|
-
// 卡内按钮回头改答案:把这句话当用户的下一条消息发出去(宿主侧再核 nonce + 焦点、限频、限长,只在对话里接)。
|
|
164
|
-
// 手势检查在这里是**真闸**(见上):模型写的卡不能一加载 / 定时就替用户发话。
|
|
165
|
-
ask(text) {
|
|
166
|
-
if (!gestureActive()) throw new Error('forsionSketch.ask() must be called from a user gesture')
|
|
167
|
-
const t = String(text ?? '').trim().slice(0, 400)
|
|
168
|
-
if (!t) throw new Error('forsionSketch.ask() needs a non-empty text')
|
|
169
|
-
send({ type: 'sketch-ask', text: t })
|
|
170
|
-
},
|
|
171
|
-
// 复制到剪贴板(沙箱帧自己没有剪贴板权限,由宿主代写;同样要用户手势)。
|
|
172
|
-
copy(text) {
|
|
173
|
-
if (!gestureActive()) throw new Error('forsionSketch.copy() must be called from a user gesture')
|
|
174
|
-
const t = String(text ?? '')
|
|
175
|
-
if (!t) return
|
|
176
|
-
send({ type: 'sketch-copy', text: t.slice(0, 20000) })
|
|
177
|
-
},
|
|
178
|
-
})
|
|
179
|
-
document.currentScript?.remove()
|
|
180
|
-
|
|
181
|
-
// An href must never turn a sandbox frame into an external navigation surface.
|
|
182
|
-
document.addEventListener('click', (event) => {
|
|
183
|
-
if (event.target.closest?.('a[href]')) event.preventDefault()
|
|
184
|
-
}, true)
|
|
185
|
-
document.addEventListener('submit', (event) => event.preventDefault(), true)
|
|
186
|
-
|
|
187
|
-
// 直播补丁(10-09,对标 ChatGPT Intelligent UI 的流式组件):宿主把最新 html 发进来,这里**原地**打补丁 —— 不换 srcdoc、不重载。
|
|
188
|
-
// 同位置同标签的元素保留(只同步属性、递归子节点),文字节点原地续写,新节点标 data-fs-new 淡入;自定义元素(fs-*)内容一变整个换掉
|
|
189
|
-
// (它们从 JSON 子节点取数据,只在接入时解析)。终稿(sketch-final)再把惰性的 script 节点换成会执行的,并补发 DOMContentLoaded / load,
|
|
190
|
-
// 让「等 DOM 就绪再初始化」的两种写法都能跑。
|
|
191
|
-
function syncAttrs(live, next) {
|
|
192
|
-
for (const a of [...live.attributes]) if (a.name !== 'data-fs-new' && !next.hasAttribute(a.name)) live.removeAttribute(a.name)
|
|
193
|
-
for (const a of [...next.attributes]) if (live.getAttribute(a.name) !== a.value) live.setAttribute(a.name, a.value)
|
|
194
|
-
}
|
|
195
|
-
const markNew = (n) => {
|
|
196
|
-
if (n.nodeType !== 1) return
|
|
197
|
-
n.setAttribute('data-fs-new', '')
|
|
198
|
-
n.addEventListener('animationend', () => n.removeAttribute('data-fs-new'), { once: true })
|
|
199
|
-
}
|
|
200
|
-
function morph(live, next) {
|
|
201
|
-
if (live.nodeType === 1 && next.nodeType === 1) syncAttrs(live, next)
|
|
202
|
-
const a = [...live.childNodes], b = [...next.childNodes]
|
|
203
|
-
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
204
|
-
const x = a[i], y = b[i]
|
|
205
|
-
if (!y) { x.remove(); continue }
|
|
206
|
-
if (!x) { const n = document.importNode(y, true); markNew(n); live.appendChild(n); continue }
|
|
207
|
-
if (x.nodeType === 3 && y.nodeType === 3) { if (x.nodeValue !== y.nodeValue) x.nodeValue = y.nodeValue; continue }
|
|
208
|
-
const sameTag = x.nodeType === 1 && y.nodeType === 1 && x.tagName === y.tagName
|
|
209
|
-
// 自定义元素渲染后 innerHTML 已是画好的 DOM,比的是它接入时记下的原始源码(_fsSrc),不然每一拍都会被换掉重画。
|
|
210
|
-
if (sameTag && x.tagName.includes('-')) {
|
|
211
|
-
if ((x._fsSrc ?? x.innerHTML) !== y.innerHTML) { const n = document.importNode(y, true); markNew(n); x.replaceWith(n) }
|
|
212
|
-
else syncAttrs(x, y)
|
|
213
|
-
continue
|
|
214
|
-
}
|
|
215
|
-
if (sameTag) { morph(x, y); continue }
|
|
216
|
-
const n = document.importNode(y, true); markNew(n); x.replaceWith(n)
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
// 会执行的 script type(HTML 标准的 JavaScript MIME 类型表 + module);其余(application/json、importmap …)是数据,原样留着
|
|
220
|
-
const JS_TYPES = /^(?:module|(?:text|application)\\/(?:x-)?(?:ecma|java)script|text\\/(?:javascript1\\.[0-5]|jscript|livescript))$/
|
|
221
|
-
function applyHtml(html, final) {
|
|
222
|
-
const t = document.createElement('template')
|
|
223
|
-
t.innerHTML = html
|
|
224
|
-
morph(document.body, t.content)
|
|
225
|
-
if (final) {
|
|
226
|
-
const pending = [] // module 脚本是异步执行的,等它们跑完再发就绪事件(Codex 10-09 P2)
|
|
227
|
-
for (const s of [...document.body.querySelectorAll('script')]) {
|
|
228
|
-
const type = (s.getAttribute('type') || '').trim().toLowerCase()
|
|
229
|
-
if (type && !JS_TYPES.test(type)) continue
|
|
230
|
-
const n = document.createElement('script')
|
|
231
|
-
for (const attr of [...s.attributes]) n.setAttribute(attr.name, attr.value)
|
|
232
|
-
n.textContent = s.textContent
|
|
233
|
-
if (type === 'module') pending.push(new Promise((r) => { n.addEventListener('load', r, { once: true }); n.addEventListener('error', r, { once: true }) }))
|
|
234
|
-
s.replaceWith(n)
|
|
235
|
-
}
|
|
236
|
-
const ready = () => { document.dispatchEvent(new Event('DOMContentLoaded')); window.dispatchEvent(new Event('load')); measure() }
|
|
237
|
-
if (pending.length) Promise.all(pending).then(ready)
|
|
238
|
-
else ready()
|
|
239
|
-
}
|
|
240
|
-
measure()
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
window.addEventListener('message', (event) => {
|
|
244
|
-
if (event.source !== parent) return
|
|
245
|
-
const data = event.data
|
|
246
|
-
if (!data) return
|
|
247
|
-
if (data.type === 'sketch-draft' || data.type === 'sketch-final') { applyHtml(String(data.html ?? ''), data.type === 'sketch-final'); return }
|
|
248
|
-
if (data.type !== 'sketch-theme' || !data.vars) return
|
|
249
|
-
for (const [key, value] of Object.entries(data.vars)) {
|
|
250
|
-
if (key.startsWith('--fs-')) document.documentElement.style.setProperty(key, String(value))
|
|
251
|
-
else if (key === 'color-scheme') document.documentElement.style.colorScheme = String(value)
|
|
252
|
-
}
|
|
253
|
-
window.dispatchEvent(new Event('forsion:themechange'))
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
let lastHeight = 0
|
|
257
|
-
function measure() {
|
|
258
|
-
const height = Math.ceil(document.body?.getBoundingClientRect().height || 0)
|
|
259
|
-
if (height && height !== lastHeight) {
|
|
260
|
-
lastHeight = height
|
|
261
|
-
parent.postMessage({ type: 'sketch-height', height }, '*')
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function selectTab(tab) {
|
|
266
|
-
const list = tab.closest('.fs-tabs[role="tablist"]')
|
|
267
|
-
if (!list || tab.disabled || tab.getAttribute('aria-disabled') === 'true') return
|
|
268
|
-
for (const peer of list.querySelectorAll('[role="tab"]')) {
|
|
269
|
-
const selected = peer === tab
|
|
270
|
-
peer.setAttribute('aria-selected', String(selected))
|
|
271
|
-
const panel = document.getElementById(peer.getAttribute('aria-controls'))
|
|
272
|
-
if (panel?.getAttribute('role') === 'tabpanel') panel.hidden = !selected
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
document.addEventListener('click', (event) => {
|
|
276
|
-
const tab = event.target.closest?.('.fs-tabs [role="tab"]')
|
|
277
|
-
if (tab) selectTab(tab)
|
|
278
|
-
})
|
|
279
|
-
document.addEventListener('keydown', (event) => {
|
|
280
|
-
const tab = event.target.closest?.('.fs-tabs [role="tab"]')
|
|
281
|
-
if (!tab || !['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return
|
|
282
|
-
const tabs = [...tab.closest('.fs-tabs').querySelectorAll('[role="tab"]')]
|
|
283
|
-
.filter((node) => !node.disabled && node.getAttribute('aria-disabled') !== 'true')
|
|
284
|
-
const index = tabs.indexOf(tab)
|
|
285
|
-
const next = event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1
|
|
286
|
-
: (index + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length
|
|
287
|
-
event.preventDefault()
|
|
288
|
-
tabs[next]?.focus()
|
|
289
|
-
if (tabs[next]) selectTab(tabs[next])
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
function ready() {
|
|
293
|
-
window.__fsLoadedAt = Date.now() // 台架用:整张卡只加载一次(草稿 → 终稿不重载)
|
|
294
|
-
if (window.ResizeObserver) new ResizeObserver(measure).observe(document.body)
|
|
295
|
-
measure()
|
|
296
|
-
}
|
|
297
|
-
// once:终稿(sketch-final)会补发一次合成的 DOMContentLoaded 给模型脚本,运行时自己的就绪只跑一次
|
|
298
|
-
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ready, { once: true })
|
|
299
|
-
else ready()
|
|
300
|
-
window.addEventListener('load', measure)
|
|
301
|
-
})()
|
|
302
|
-
`,dt=`/* Small, offline figure primitives. Text and data are never interpreted as HTML. */
|
|
303
|
-
(() => {
|
|
304
|
-
const svgNS = 'http://www.w3.org/2000/svg'
|
|
305
|
-
const number = (value) => new Intl.NumberFormat(undefined, {
|
|
306
|
-
maximumSignificantDigits: 6, notation: Math.abs(value) >= 1e15 || (value !== 0 && Math.abs(value) < 1e-4) ? 'scientific' : Math.abs(value) >= 1e6 ? 'compact' : 'standard',
|
|
307
|
-
}).format(value)
|
|
308
|
-
const node = (tag, cls, text) => {
|
|
309
|
-
const el = document.createElement(tag)
|
|
310
|
-
if (cls) el.className = cls
|
|
311
|
-
if (text !== undefined) el.textContent = String(text)
|
|
312
|
-
return el
|
|
313
|
-
}
|
|
314
|
-
const mark = (tag, attrs, text) => {
|
|
315
|
-
const el = document.createElementNS(svgNS, tag)
|
|
316
|
-
for (const [key, value] of Object.entries(attrs)) el.setAttribute(key, String(value))
|
|
317
|
-
if (text !== undefined) el.textContent = String(text)
|
|
318
|
-
return el
|
|
319
|
-
}
|
|
320
|
-
function heading(root, data) {
|
|
321
|
-
if (data.title) root.append(node('h3', 'fs-figure-title', data.title))
|
|
322
|
-
}
|
|
323
|
-
function caption(root, data) {
|
|
324
|
-
if (data.caption) root.append(node('p', 'fs-caption', data.caption))
|
|
325
|
-
}
|
|
326
|
-
class Figure extends HTMLElement {
|
|
327
|
-
connectedCallback() {
|
|
328
|
-
queueMicrotask(() => {
|
|
329
|
-
if (!this.isConnected) return
|
|
330
|
-
if (!this.figureData) {
|
|
331
|
-
this._fsSrc = this.innerHTML // 直播补丁(sketchRuntime morph)拿它和新来的源码比,渲染后 innerHTML 就不是源码了
|
|
332
|
-
try { this.figureData = JSON.parse(this.querySelector('script[type="application/json"]')?.textContent || '{}') }
|
|
333
|
-
catch { this.figureData = {} }
|
|
334
|
-
}
|
|
335
|
-
this.render()
|
|
336
|
-
this.observer?.disconnect()
|
|
337
|
-
if (!window.ResizeObserver) return
|
|
338
|
-
this.observer = new ResizeObserver(() => {
|
|
339
|
-
const width = Math.round(this.clientWidth)
|
|
340
|
-
if (width && width !== this.lastWidth) { this.lastWidth = width; this.render() }
|
|
341
|
-
})
|
|
342
|
-
this.observer.observe(this)
|
|
343
|
-
})
|
|
344
|
-
}
|
|
345
|
-
disconnectedCallback() { this.observer?.disconnect() }
|
|
346
|
-
setData(data) { this.figureData = data; if (this.isConnected) this.render() }
|
|
347
|
-
error() {
|
|
348
|
-
// No guessed labels or fabricated zeroes when the input is invalid.
|
|
349
|
-
const error = node('p', 'fs-caption', this.figureData?.emptyLabel || '—')
|
|
350
|
-
error.setAttribute('role', 'alert')
|
|
351
|
-
this.replaceChildren(error)
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
class Chart extends Figure {
|
|
356
|
-
render() {
|
|
357
|
-
const data = this.figureData
|
|
358
|
-
if (!['bar', 'line'].includes(this.getAttribute('type') || 'bar') ||
|
|
359
|
-
!data || !Array.isArray(data.data) || !data.data.length || data.data.length > 100 ||
|
|
360
|
-
data.data.some((item) => typeof item?.label !== 'string' || typeof item.value !== 'number' || !Number.isFinite(item.value))) {
|
|
361
|
-
this.error(); return
|
|
362
|
-
}
|
|
363
|
-
this.replaceChildren()
|
|
364
|
-
const root = node('figure', 'fs-chart-figure')
|
|
365
|
-
heading(root, data)
|
|
366
|
-
this.append(root)
|
|
367
|
-
if (this.getAttribute('type') === 'line') this.line(root, data)
|
|
368
|
-
else this.bars(root, data)
|
|
369
|
-
caption(root, data)
|
|
370
|
-
}
|
|
371
|
-
bars(root, data) {
|
|
372
|
-
// A zero baseline also handles negative-only and mixed-sign comparisons honestly.
|
|
373
|
-
const magnitude = Math.max(1, ...data.data.map((item) => Math.abs(item.value)))
|
|
374
|
-
const low = Math.min(0, ...data.data.map((item) => item.value / magnitude))
|
|
375
|
-
const high = Math.max(0, ...data.data.map((item) => item.value / magnitude))
|
|
376
|
-
const span = high - low || 1
|
|
377
|
-
const zero = -low / span * 100
|
|
378
|
-
const list = node('div', 'fs-chart-bars')
|
|
379
|
-
list.setAttribute('role', 'list')
|
|
380
|
-
list.setAttribute('aria-label', String(data.title || data.unit || data.data.map((item) => item.label).join(', ')))
|
|
381
|
-
const narrow = this.clientWidth < 440
|
|
382
|
-
list.classList.toggle('fs-chart-bars--narrow', narrow)
|
|
383
|
-
for (const [index, item] of data.data.entries()) {
|
|
384
|
-
const row = node('div', 'fs-chart-bar-row')
|
|
385
|
-
row.setAttribute('role', 'listitem')
|
|
386
|
-
const label = node('span', 'fs-chart-bar-label', item.label)
|
|
387
|
-
const value = node('span', 'fs-chart-bar-value', number(item.value) + (data.unit ? ' ' + data.unit : ''))
|
|
388
|
-
const track = node('span', 'fs-chart-track')
|
|
389
|
-
track.setAttribute('aria-hidden', 'true')
|
|
390
|
-
const bar = node('span', 'fs-chart-bar')
|
|
391
|
-
bar.style.left = Math.min(zero, (item.value / magnitude - low) / span * 100) + '%'
|
|
392
|
-
bar.style.width = Math.abs(item.value / magnitude) / span * 100 + '%'
|
|
393
|
-
if (Number.isInteger(data.highlight) && data.highlight !== index) bar.classList.add('fs-chart-bar--context')
|
|
394
|
-
track.style.setProperty('--fs-zero', zero + '%')
|
|
395
|
-
track.append(bar)
|
|
396
|
-
row.append(label, track, value)
|
|
397
|
-
list.append(row)
|
|
398
|
-
}
|
|
399
|
-
root.append(list)
|
|
400
|
-
}
|
|
401
|
-
line(root, data) {
|
|
402
|
-
const width = Math.max(1, this.clientWidth)
|
|
403
|
-
const height = 238
|
|
404
|
-
const values = data.data.map((item) => item.value)
|
|
405
|
-
const maxAbs = Math.max(...values.map(Math.abs))
|
|
406
|
-
const magnitude = maxAbs ? Math.pow(10, Math.floor(Math.log10(maxAbs))) || Number.MIN_VALUE : 1
|
|
407
|
-
const min = Math.min(...values.map((v) => v / magnitude)), max = Math.max(...values.map((v) => v / magnitude))
|
|
408
|
-
const pad = (max - min || Math.abs(max) || 1) * .12
|
|
409
|
-
const limit = Number.MAX_VALUE / magnitude
|
|
410
|
-
const paddedLow = min >= 0 ? Math.max(0, min - pad) : min - pad
|
|
411
|
-
const paddedHigh = max <= 0 ? Math.min(0, max + pad) : max + pad
|
|
412
|
-
const roughStep = (paddedHigh - paddedLow) / 4 || .25
|
|
413
|
-
const power = Math.pow(10, Math.floor(Math.log10(roughStep)))
|
|
414
|
-
const step = ([1, 2, 5, 10].find((factor) => factor >= roughStep / power) || 10) * power
|
|
415
|
-
const low = Math.max(-limit, Math.floor(paddedLow / step) * step)
|
|
416
|
-
const high = Math.min(limit, Math.ceil(paddedHigh / step) * step) || step
|
|
417
|
-
const ticks = []
|
|
418
|
-
for (let tick = Math.ceil(low / step) * step; tick <= high + step * 1e-8; tick += step) {
|
|
419
|
-
ticks.push(Number((tick * magnitude).toPrecision(12)))
|
|
420
|
-
if (ticks.length > 8) break
|
|
421
|
-
}
|
|
422
|
-
const canvas = document.createElement('canvas')
|
|
423
|
-
const context = canvas.getContext('2d')
|
|
424
|
-
if (context) context.font = '12px ' + getComputedStyle(this).fontFamily
|
|
425
|
-
const left = Math.min(width * .35, Math.max(48, ...ticks.map((v) => (context?.measureText(number(v)).width || 36) + 14)))
|
|
426
|
-
const right = Math.max(left + 1, width - 8), top = 28, bottom = height - 32
|
|
427
|
-
const x = (i) => left + (right - left) * (values.length === 1 ? .5 : i / (values.length - 1))
|
|
428
|
-
const y = (value) => bottom - (value / magnitude - low) / (high - low) * (bottom - top)
|
|
429
|
-
const field = node('div', 'fs-chart-field')
|
|
430
|
-
const svg = mark('svg', { viewBox: \`0 0 \${width} \${height}\`, role: 'img', 'aria-label': String(data.title || data.data.map((item) => item.label).join(', ')) })
|
|
431
|
-
svg.append(mark('title', {}, data.data.map((v) => \`\${v.label}: \${number(v.value)} \${data.unit || ''}\`).join('; ')))
|
|
432
|
-
if (data.unit) svg.append(mark('text', { x: left, y: 14, class: 'fs-chart-unit' }, data.unit))
|
|
433
|
-
for (const tick of ticks) {
|
|
434
|
-
svg.append(mark('path', { d: \`M\${left} \${y(tick)}H\${right}\`, class: 'fs-chart-grid' }))
|
|
435
|
-
svg.append(mark('text', { x: left - 10, y: y(tick) + 4, 'text-anchor': 'end', class: 'fs-chart-tick' }, number(tick)))
|
|
436
|
-
}
|
|
437
|
-
const count = Math.min(values.length, width < 440 ? 3 : 5)
|
|
438
|
-
let previousRight = -Infinity
|
|
439
|
-
for (let i = 0; i < count; i++) {
|
|
440
|
-
const index = count === 1 ? 0 : Math.round(i * (values.length - 1) / (count - 1))
|
|
441
|
-
const label = data.data[index].label
|
|
442
|
-
const size = context?.measureText(label).width || label.length * 8
|
|
443
|
-
const px = x(index)
|
|
444
|
-
const labelLeft = i === 0 ? px : i === count - 1 ? px - size : px - size / 2
|
|
445
|
-
if (labelLeft < previousRight + 8 || size > right - left) continue
|
|
446
|
-
previousRight = labelLeft + size
|
|
447
|
-
svg.append(mark('text', { x: px, y: height - 9, 'text-anchor': i === 0 ? 'start' : i === count - 1 ? 'end' : 'middle', class: 'fs-chart-tick' }, label))
|
|
448
|
-
}
|
|
449
|
-
const path = values.map((value, i) => \`\${i ? 'L' : 'M'}\${x(i)} \${y(value)}\`).join(' ')
|
|
450
|
-
svg.append(mark('path', { d: path, class: 'fs-chart-line' }))
|
|
451
|
-
for (const [i, value] of values.entries()) svg.append(mark('circle', { cx: x(i), cy: y(value), r: 2.5, class: 'fs-chart-point' }))
|
|
452
|
-
const guide = mark('path', { class: 'fs-chart-guide', visibility: 'hidden' })
|
|
453
|
-
const dot = mark('circle', { r: 4, class: 'fs-chart-point', visibility: 'hidden' })
|
|
454
|
-
svg.append(guide, dot)
|
|
455
|
-
const detail = node('output', 'fs-chart-detail')
|
|
456
|
-
const hit = node('button', 'fs-chart-hit')
|
|
457
|
-
hit.type = 'button'
|
|
458
|
-
hit.setAttribute('aria-label', String(data.inspectLabel || data.title || data.data.map((item) => item.label).join(', ')))
|
|
459
|
-
hit.style.left = left + 'px'; hit.style.top = top + 'px'
|
|
460
|
-
hit.style.width = (right - left) + 'px'; hit.style.height = (bottom - top) + 'px'
|
|
461
|
-
let current = 0
|
|
462
|
-
const show = (position) => {
|
|
463
|
-
current = Math.max(0, Math.min(values.length - 1, position))
|
|
464
|
-
const a = Math.floor(current), b = Math.ceil(current), t = current - a
|
|
465
|
-
const value = (values[a] / magnitude * (1 - t) + values[b] / magnitude * t) * magnitude
|
|
466
|
-
const px = x(current)
|
|
467
|
-
guide.setAttribute('d', \`M\${px} \${top}V\${bottom}\`)
|
|
468
|
-
dot.setAttribute('cx', px); dot.setAttribute('cy', y(value))
|
|
469
|
-
guide.setAttribute('visibility', 'visible'); dot.setAttribute('visibility', 'visible')
|
|
470
|
-
const label = a === b ? data.data[a].label : \`\${data.data[a].label} – \${data.data[b].label}\`
|
|
471
|
-
detail.textContent = \`\${label} · \${number(value)} \${data.unit || ''}\`
|
|
472
|
-
}
|
|
473
|
-
hit.addEventListener('pointermove', (event) => {
|
|
474
|
-
detail.removeAttribute('aria-live')
|
|
475
|
-
const bounds = hit.getBoundingClientRect()
|
|
476
|
-
show((event.clientX - bounds.left) / bounds.width * (values.length - 1))
|
|
477
|
-
})
|
|
478
|
-
hit.addEventListener('focus', () => show(Math.round(current)))
|
|
479
|
-
hit.addEventListener('click', (event) => {
|
|
480
|
-
if (!event.detail) { show(Math.round(current)); return }
|
|
481
|
-
const bounds = hit.getBoundingClientRect()
|
|
482
|
-
show((event.clientX - bounds.left) / bounds.width * (values.length - 1))
|
|
483
|
-
})
|
|
484
|
-
hit.addEventListener('keydown', (event) => {
|
|
485
|
-
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return
|
|
486
|
-
event.preventDefault()
|
|
487
|
-
detail.setAttribute('aria-live', 'polite')
|
|
488
|
-
show(event.key === 'Home' ? 0 : event.key === 'End' ? values.length - 1 : Math.round(current) + (event.key === 'ArrowRight' ? 1 : -1))
|
|
489
|
-
})
|
|
490
|
-
field.append(svg, hit)
|
|
491
|
-
root.append(field, detail)
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
class Flow extends Figure {
|
|
496
|
-
render() {
|
|
497
|
-
const data = this.figureData
|
|
498
|
-
if (!data || !Array.isArray(data.steps) || !data.steps.length || data.steps.length > 12 ||
|
|
499
|
-
data.steps.some((step) => !step || typeof step.label !== 'string' ||
|
|
500
|
-
(step.branches !== undefined && (!Array.isArray(step.branches) || step.branches.length > 8 ||
|
|
501
|
-
step.branches.some((branch) => !branch || typeof branch.label !== 'string'))))) { this.error(); return }
|
|
502
|
-
this.replaceChildren()
|
|
503
|
-
const root = node('figure', 'fs-flow-figure')
|
|
504
|
-
heading(root, data)
|
|
505
|
-
const list = node('ol', 'fs-flow-steps')
|
|
506
|
-
list.classList.toggle('fs-flow-steps--vertical', this.clientWidth < data.steps.length * 145)
|
|
507
|
-
list.style.setProperty('--fs-step-count', data.steps.length)
|
|
508
|
-
for (const [index, step] of data.steps.entries()) {
|
|
509
|
-
const item = node('li', 'fs-flow-step')
|
|
510
|
-
const status = ['done', 'active'].includes(step.status) ? step.status : 'pending'
|
|
511
|
-
item.dataset.status = status
|
|
512
|
-
if (status === 'active') item.setAttribute('aria-current', 'step')
|
|
513
|
-
const marker = node('span', 'fs-flow-marker', String(index + 1).padStart(2, '0'))
|
|
514
|
-
marker.setAttribute('aria-hidden', 'true')
|
|
515
|
-
const content = node('div', 'fs-flow-content')
|
|
516
|
-
content.append(node('span', 'fs-flow-label', step.label))
|
|
517
|
-
if (step.detail) content.append(node('span', 'fs-flow-description', step.detail))
|
|
518
|
-
if (Array.isArray(step.branches) && step.branches.length) {
|
|
519
|
-
const branches = node('ul', 'fs-flow-branches')
|
|
520
|
-
for (const branch of step.branches) {
|
|
521
|
-
const branchNode = node('li', 'fs-flow-branch')
|
|
522
|
-
branchNode.append(node('span', 'fs-flow-label', branch.label || ''))
|
|
523
|
-
if (branch.detail) branchNode.append(node('span', 'fs-flow-description', branch.detail))
|
|
524
|
-
branches.append(branchNode)
|
|
525
|
-
}
|
|
526
|
-
content.append(branches)
|
|
527
|
-
}
|
|
528
|
-
item.append(marker, content); list.append(item)
|
|
529
|
-
}
|
|
530
|
-
root.append(list); caption(root, data); this.append(root)
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// 10-09(对标 ChatGPT Intelligent UI「只能拼固定组件库」):统一的拼装部件,模型只给 JSON,外观由宿主定。
|
|
535
|
-
const askOf = (text) => { try { window.forsionSketch.ask(String(text)) } catch (e) { console.warn(e) } }
|
|
536
|
-
const button = (label, onClick) => { const b = node('button', 'fs-button', label); b.type = 'button'; b.addEventListener('click', onClick); return b }
|
|
537
|
-
|
|
538
|
-
/** <fs-compare>:并排选项 {title?, caption?, options:[{label, summary?, points?:string[], ask?:{label?, text}}]}(2–6 项)。 */
|
|
539
|
-
class Compare extends Figure {
|
|
540
|
-
render() {
|
|
541
|
-
const d = this.figureData
|
|
542
|
-
const ok = d && Array.isArray(d.options) && d.options.length >= 2 && d.options.length <= 6 &&
|
|
543
|
-
d.options.every((o) => o && typeof o.label === 'string' &&
|
|
544
|
-
(o.points === undefined || (Array.isArray(o.points) && o.points.length <= 8 && o.points.every((p) => typeof p === 'string'))) &&
|
|
545
|
-
(o.ask === undefined || (o.ask && typeof o.ask.text === 'string')))
|
|
546
|
-
if (!ok) { this.error(); return }
|
|
547
|
-
this.replaceChildren()
|
|
548
|
-
const root = node('div', 'fs-compare')
|
|
549
|
-
heading(root, d)
|
|
550
|
-
const grid = node('div', 'fs-grid')
|
|
551
|
-
for (const o of d.options) {
|
|
552
|
-
const card = node('section', 'fs-option')
|
|
553
|
-
card.append(node('h4', '', o.label))
|
|
554
|
-
if (typeof o.summary === 'string' && o.summary) card.append(node('p', 'fs-muted', o.summary))
|
|
555
|
-
if (Array.isArray(o.points) && o.points.length) { const ul = node('ul', 'fs-points'); for (const p of o.points) ul.append(node('li', '', p)); card.append(ul) }
|
|
556
|
-
if (o.ask) { const act = node('div', 'fs-actions'); act.append(button(o.ask.label || o.label, () => askOf(o.ask.text))); card.append(act) }
|
|
557
|
-
grid.append(card)
|
|
558
|
-
}
|
|
559
|
-
root.append(grid); caption(root, d); this.append(root)
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
/** <fs-choice>:追问选择题 {question, options:[{label, ask?}], multiple?, ask?("{choices}" 占位), submitLabel?, caption?}。
|
|
564
|
-
* 单选点了就把 ask(缺省 label)当用户消息发出;多选勾完按提交,把 ask 模板里的 {choices} 换成所选。 */
|
|
565
|
-
class Choice extends Figure {
|
|
566
|
-
render() {
|
|
567
|
-
const d = this.figureData
|
|
568
|
-
const ok = d && typeof d.question === 'string' && Array.isArray(d.options) && d.options.length >= 2 && d.options.length <= 8 &&
|
|
569
|
-
d.options.every((o) => o && typeof o.label === 'string' && (o.ask === undefined || typeof o.ask === 'string'))
|
|
570
|
-
if (!ok) { this.error(); return }
|
|
571
|
-
this.picked ||= new Set() // 宽度变化重绘时保住已勾的
|
|
572
|
-
for (const i of [...this.picked]) if (i >= d.options.length) this.picked.delete(i) // setData 换了更短的选项表:越界的勾作废(Codex 10-09)
|
|
573
|
-
this.replaceChildren()
|
|
574
|
-
const root = node('div', 'fs-choice')
|
|
575
|
-
root.setAttribute('role', 'group')
|
|
576
|
-
root.append(node('p', 'fs-choice-question', d.question))
|
|
577
|
-
const opts = node('div', 'fs-choice-options')
|
|
578
|
-
if (d.multiple) {
|
|
579
|
-
for (const [i, o] of d.options.entries()) {
|
|
580
|
-
const l = node('label', 'fs-check'); const c = node('input'); c.type = 'checkbox'; c.checked = this.picked.has(i)
|
|
581
|
-
c.addEventListener('change', () => { if (c.checked) this.picked.add(i); else this.picked.delete(i) })
|
|
582
|
-
l.append(c, node('span', '', o.label)); opts.append(l)
|
|
583
|
-
}
|
|
584
|
-
const act = node('div', 'fs-actions')
|
|
585
|
-
act.append(button(d.submitLabel || 'OK', () => {
|
|
586
|
-
const chosen = [...this.picked].sort((x, y) => x - y).map((i) => d.options[i]?.label).filter(Boolean)
|
|
587
|
-
if (chosen.length) askOf(String(d.ask || '{choices}').replace('{choices}', chosen.join(', ')))
|
|
588
|
-
}))
|
|
589
|
-
root.append(opts, act)
|
|
590
|
-
} else {
|
|
591
|
-
for (const o of d.options) opts.append(button(o.label, () => askOf(typeof o.ask === 'string' ? o.ask : o.label)))
|
|
592
|
-
root.append(opts)
|
|
593
|
-
}
|
|
594
|
-
caption(root, d); this.append(root)
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/** <fs-checklist>:勾选清单 {title?, caption?, items:[{label, detail?}]}(1–40 项);勾选状态经 forsionSketch.setState 存在本机
|
|
599
|
-
* (键 checklists[<元素 id 或 'checklist'>]),重开这张卡还在。 */
|
|
600
|
-
class Checklist extends Figure {
|
|
601
|
-
render() {
|
|
602
|
-
const d = this.figureData
|
|
603
|
-
const ok = d && Array.isArray(d.items) && d.items.length >= 1 && d.items.length <= 40 &&
|
|
604
|
-
d.items.every((it) => it && typeof it.label === 'string' && (it.detail === undefined || typeof it.detail === 'string'))
|
|
605
|
-
if (!ok) { this.error(); return }
|
|
606
|
-
const key = this.id || 'checklist'
|
|
607
|
-
const st = window.forsionSketch?.state
|
|
608
|
-
const saved = st && typeof st === 'object' && st.checklists ? st.checklists[key] : undefined
|
|
609
|
-
const done = new Set(Array.isArray(saved) ? saved.filter((i) => Number.isInteger(i)) : [])
|
|
610
|
-
this.replaceChildren()
|
|
611
|
-
const root = node('div', 'fs-checklist')
|
|
612
|
-
heading(root, d)
|
|
613
|
-
const ul = node('ul', 'fs-checklist-items')
|
|
614
|
-
for (const [i, it] of d.items.entries()) {
|
|
615
|
-
const li = node('li'); const l = node('label', 'fs-check'); const c = node('input'); c.type = 'checkbox'; c.checked = done.has(i)
|
|
616
|
-
c.addEventListener('change', () => {
|
|
617
|
-
if (c.checked) done.add(i); else done.delete(i)
|
|
618
|
-
const cur = window.forsionSketch.state
|
|
619
|
-
const base = cur && typeof cur === 'object' && !Array.isArray(cur) ? cur : {}
|
|
620
|
-
try { window.forsionSketch.setState({ ...base, checklists: { ...(base.checklists || {}), [key]: [...done].sort((x, y) => x - y) } }) } catch (e) { console.warn(e) }
|
|
621
|
-
})
|
|
622
|
-
const text = node('span', '', it.label)
|
|
623
|
-
if (it.detail) text.append(node('small', 'fs-muted', ' ' + it.detail))
|
|
624
|
-
l.append(c, text); li.append(l); ul.append(li)
|
|
625
|
-
}
|
|
626
|
-
root.append(ul); caption(root, d); this.append(root)
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
function register() {
|
|
631
|
-
if (!customElements.get('fs-chart')) customElements.define('fs-chart', Chart)
|
|
632
|
-
if (!customElements.get('fs-flow')) customElements.define('fs-flow', Flow)
|
|
633
|
-
if (!customElements.get('fs-compare')) customElements.define('fs-compare', Compare)
|
|
634
|
-
if (!customElements.get('fs-choice')) customElements.define('fs-choice', Choice)
|
|
635
|
-
if (!customElements.get('fs-checklist')) customElements.define('fs-checklist', Checklist)
|
|
636
|
-
}
|
|
637
|
-
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', register)
|
|
638
|
-
else register()
|
|
639
|
-
})()
|
|
640
|
-
`,q=16384,G="forsion_sketch_state_v1",M=48;function S(t){try{const e=JSON.stringify(t);return e&&new TextEncoder().encode(e).length<=q?e:void 0}catch{return}}function ft(t,e,n){let s=2166136261;for(let i=0;i<n.length;i++)s=Math.imul(s^n.charCodeAt(i),16777619);return JSON.stringify([t,e,n.length,s>>>0])}function W(t){const e=t.getItem(G);if(!e||e.length>q*(M+4))return[];let n;try{n=JSON.parse(e)}catch{return[]}return Array.isArray(n)?n.filter(s=>!!s&&typeof s.key=="string"&&s.key.length<2048&&S(s.state)!==void 0).slice(-M):[]}function pt(t){try{return W(localStorage).find(e=>e.key===t)?.state??null}catch{return null}}function ut(t,e){const n=S(e);if(!(!n||t.length>=2048))try{const s=W(localStorage).filter(i=>i.key!==t);s.push({key:t,state:JSON.parse(n)}),localStorage.setItem(G,JSON.stringify(s.slice(-M)))}catch{}}const ht="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; media-src data: blob:; font-src data:",mt="allow-scripts",P=40,gt=2400,bt=220,vt={"--fs-bg":"transparent"},xt=[["--fs-surface","--overlay-subtle"],["--fs-text","--text"],["--fs-muted","--text-muted"],["--fs-faint","--text-faint"],["--fs-border","--border"],["--fs-rule","--overlay-medium"],["--fs-accent","--accent-ink"],["--fs-accent-soft","--accent-light"],["--fs-green","--green"],["--fs-danger","--danger"],["--fs-radius","--radius-sm"],["--fs-font","--font-ui"],["--fs-mono","--font-mono"]];function B(t){return String(t).replace(/[<>]/g,"").trim()}function K(t){const e=getComputedStyle(t),n={...vt};for(const[s,i]of xt){const c=B(e.getPropertyValue(i));c&&(n[s]=c)}return n["color-scheme"]=document.documentElement.getAttribute("data-mode")==="dark"?"dark":"light",n}const w=new Set;let g=null;function yt(t){return w.add(t),!g&&typeof MutationObserver<"u"&&(g=new MutationObserver(()=>{for(const e of w)e()}),g.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme","data-skin","data-bg","data-mode","data-flat","data-glass","class"]})),()=>{w.delete(t),!w.size&&g&&(g.disconnect(),g=null)}}const kt=":root{--fs-s1:var(--fs-accent);--fs-s2:color-mix(in srgb,var(--fs-text) 62%,transparent);--fs-s3:color-mix(in srgb,var(--fs-text) 42%,transparent);--fs-s4:color-mix(in srgb,var(--fs-text) 26%,transparent);--fs-s5:color-mix(in srgb,var(--fs-text) 14%,transparent)}";function wt(t){const e=Object.entries(t).map(([n,s])=>`${n.replace(/[^a-z-]/gi,"")}:${B(s)}`).join(";");return e?`:root{${e}}`:""}function St(t,e={},n=null,s=""){const i=(S(n)??"null").replace(/</g,"\\u003c").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029"),c=lt.replace("__SKETCH_INITIAL_STATE__",()=>i).replace("__SKETCH_NONCE__",()=>s.replace(/[^A-Za-z0-9-]/g,""));return`<!doctype html><html><head><meta http-equiv="Content-Security-Policy" content="${ht}"><meta name="viewport" content="width=device-width, initial-scale=1"><style>${wt(e)}${kt}${ct}</style><script>${c};
|
|
641
|
-
${dt}<\/script></head><body>${t}</body></html>`}const X=`(?![^>]*\\stype\\s*=\\s*["']?application/json\\b)`,At=new RegExp(`<script\\b${X}[^>]*>[\\s\\S]*?<\/script\\s*>`,"gi"),Et=new RegExp(`<script\\b${X}[^>]*>[\\s\\S]*$`,"i"),Mt=/<script\b[^>]*$/i,Tt=/<[a-zA-Z][^>]*(?:>|$)/g,Nt=/\son[a-z]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi,Ct=/<(fs-[a-z]+)\b(?![\s\S]*<\/\1\s*>)[\s\S]*$/i;function J(t){return t.replace(At,"").replace(Et,"").replace(Mt,"").replace(Ct,"").replace(Tt,e=>e.replace(Nt,""))}const _t=400,Lt=1500,Rt=2e4,zt=150;function Ot(){try{const t=window.Capacitor;return!!(t&&typeof t.isNativePlatform=="function"&&t.isNativePlatform())}catch{return!1}}function Ht(t){return!t||document.activeElement!==t?!1:navigator.userActivation?.isActive===!0}const It=()=>crypto.randomUUID?crypto.randomUUID():Array.from(crypto.getRandomValues(new Uint8Array(16)),t=>t.toString(16).padStart(2,"0")).join(""),$t=({item:t,actions:e,stateScope:n,onAsk:s})=>{const{t:i}=U(),c=a.useMemo(()=>n&&!t.draft?ft(n,t.callId,t.html):null,[n,t.callId,t.html,t.draft]),u=a.useRef(s);u.current=s;const x=a.useRef(!!t.draft);x.current=!!t.draft;const T=a.useRef(0),Y=a.useRef(t.draft?J(t.html):t.html),Z=a.useRef(c?pt(c):null),N=a.useRef(It()),y=a.useRef(null),C=a.useRef(null),h=a.useRef(null),[_,Q]=a.useState(t.draft?P:bt),[b,tt]=a.useState(!1),[L,et]=a.useState(!1),[k,nt]=a.useState(null);a.useLayoutEffect(()=>{y.current&&nt(K(y.current))},[]),a.useEffect(()=>yt(()=>{const r=y.current,d=h.current?.contentWindow;r&&d&&d.postMessage({type:"sketch-theme",vars:K(r)},"*")}),[]);const A=a.useRef(!1),E=a.useRef(!1),v=a.useRef(null),m=a.useRef(void 0),R=r=>{h.current?.contentWindow?.postMessage(r,"*")},z=()=>{if(m.current=void 0,!A.current)return;const r=v.current;v.current=null,r!==null&&x.current&&!E.current&&R({type:"sketch-draft",html:r})},O=()=>{!A.current||E.current||(E.current=!0,clearTimeout(m.current),m.current=void 0,v.current=null,R({type:"sketch-final",html:t.html}))},H=a.useRef(!!t.draft);a.useEffect(()=>{t.draft?(v.current=J(t.html),m.current||(m.current=setTimeout(z,zt))):H.current&&O()},[t.html,t.draft]),a.useEffect(()=>()=>clearTimeout(m.current),[]);const at=()=>{A.current=!0,!x.current&&H.current?O():v.current!==null&&z()};a.useEffect(()=>{let r,d;const p=()=>{c&&r!==void 0&&ut(c,JSON.parse(r)),r=void 0,d=void 0},$=j=>{if(!h.current||j.source!==h.current.contentWindow)return;const o=j.data;if(o?.type==="sketch-state"&&c){const f=S(o.state);f!==void 0&&(r=f,d||(d=setTimeout(p,200)))}o&&o.type==="sketch-height"&&Number.isFinite(Number(o.height))&&Q(Math.min(gt,Math.max(P,Math.ceil(Number(o.height)))));const D=o&&o.nonce===N.current&&!x.current&&Ht(h.current);if(D&&o.type==="sketch-ask"&&typeof o.text=="string"&&u.current){const f=Date.now(),F=o.text.trim().slice(0,_t);F&&f-T.current>=Lt&&(T.current=f,u.current(F))}if(D&&o.type==="sketch-copy"&&typeof o.text=="string"){const f=o.text.slice(0,Rt);f&&navigator.clipboard?.writeText(f).catch(()=>{})}};return window.addEventListener("message",$),window.addEventListener("pagehide",p),()=>{window.removeEventListener("message",$),window.removeEventListener("pagehide",p),clearTimeout(d),p()}},[c]),a.useEffect(()=>{const r=C.current;if(!r||b)return;const d=()=>et(r.scrollHeight>r.clientHeight+1);d();const p=new ResizeObserver(d);return p.observe(r),()=>p.disconnect()},[_,b]);const st=a.useMemo(()=>k?St(Y.current,k,Z.current,N.current):"",[k]),I=t.draft?void 0:e;return l.jsxs("div",{className:"sketch-card",ref:y,"data-sketch-call-id":t.callId,"data-sketch-draft":t.draft?"":void 0,children:[(t.title||I)&&l.jsxs("div",{className:"sketch-card-head",children:[t.title&&l.jsx("div",{className:"sketch-card-title",children:t.title}),I?.(t)]}),l.jsx("div",{ref:C,className:`sketch-clip${b?" open":""}${L&&!b?" faded":""}`,children:k&&l.jsx("iframe",{ref:h,className:"sketch-frame",sandbox:mt,srcDoc:st,style:{height:_},title:t.title||"sketch",onLoad:at})}),L&&l.jsx("button",{type:"button",className:"sketch-card-toggle",onClick:()=>tt(r=>!r),children:i(b?"sketch.collapse":"sketch.expand")})]})},jt=({item:t})=>{const{t:e}=U();return l.jsxs("div",{className:"sketch-card","data-sketch-call-id":t.callId,children:[t.title&&l.jsx("div",{className:"sketch-card-title",children:t.title}),l.jsx("div",{className:"sketch-card-note",children:e("sketch.mobileUnavailable")})]})},Vt=({items:t,actions:e,stateScope:n,onAsk:s})=>{const i=Ot(),c=i?jt:$t;return l.jsx(l.Fragment,{children:t.map(u=>l.jsx(c,{item:u,actions:i?void 0:e,stateScope:n,onAsk:i?void 0:s},`${n||""}:${u.callId}`))})};export{Pt as F,Kt as S,Vt as a,ot as b,Jt as s};
|