decaframe 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +32 -0
- package/README.md +55 -0
- package/dist/archetype.js +1 -0
- package/dist/blocks.css +2822 -0
- package/dist/blocks.js +6 -0
- package/dist/brand.js +1 -0
- package/dist/chart.js +3 -0
- package/dist/chartView.js +1 -0
- package/dist/cli.js +22 -0
- package/dist/colour.js +1 -0
- package/dist/commands.js +1 -0
- package/dist/design.js +1 -0
- package/dist/diagram.js +99 -0
- package/dist/diagramRender.js +1 -0
- package/dist/diagramTheme.js +1 -0
- package/dist/diagramView.js +1 -0
- package/dist/diagramZoom.js +2 -0
- package/dist/export/assets.js +1 -0
- package/dist/export/html.js +6 -0
- package/dist/export/spec.js +1 -0
- package/dist/export/viewer.js +1112 -0
- package/dist/extensions.js +1 -0
- package/dist/figure.js +1 -0
- package/dist/filename.js +1 -0
- package/dist/fit.js +6 -0
- package/dist/fitLoop.js +1 -0
- package/dist/furniture.js +1 -0
- package/dist/icons.js +1 -0
- package/dist/id.js +1 -0
- package/dist/index.js +1 -0
- package/dist/item.js +1 -0
- package/dist/layout.js +1 -0
- package/dist/lint.js +1 -0
- package/dist/markdown.js +32 -0
- package/dist/mcp/browser.js +81 -0
- package/dist/mcp/channel.js +7 -0
- package/dist/mcp/export.js +1 -0
- package/dist/mcp/exportCli.js +11 -0
- package/dist/mcp/fit.js +1 -0
- package/dist/mcp/open.js +1 -0
- package/dist/mcp/openCli.js +1 -0
- package/dist/mcp/read.js +1 -0
- package/dist/mcp/server.js +31 -0
- package/dist/mcp/session.js +2 -0
- package/dist/mcp/stdio.js +2 -0
- package/dist/media.js +1 -0
- package/dist/notes.js +1 -0
- package/dist/page.js +1 -0
- package/dist/placeholder.js +1 -0
- package/dist/present/constants.js +1 -0
- package/dist/prose.js +1 -0
- package/dist/registry.js +1 -0
- package/dist/sizePreview.js +1 -0
- package/dist/slotMarkdown.js +3 -0
- package/dist/table.js +2 -0
- package/dist/template/catalogue.json +1 -0
- package/dist/template/index.js +1 -0
- package/dist/template/look.js +1 -0
- package/dist/text.js +8 -0
- package/dist/theme/atelier.json +1 -0
- package/dist/theme/aurora.json +1 -0
- package/dist/theme/boardroom.json +1 -0
- package/dist/theme/decaframe.json +1 -0
- package/dist/theme/editorial.json +1 -0
- package/dist/theme/emit.js +19 -0
- package/dist/theme/fonts.js +1 -0
- package/dist/theme/foundry.json +1 -0
- package/dist/theme/index.js +1 -0
- package/dist/theme/meadow.json +1 -0
- package/dist/theme/signal.json +1 -0
- package/dist/theme/tokens.json +1 -0
- package/dist/videoPlay.js +1 -0
- package/editor/assets/App-BvOGZ4cc.js +56 -0
- package/editor/assets/PresenterView-Barr5wsl.js +1 -0
- package/editor/assets/Shell-Dvc8Yvly.js +413 -0
- package/editor/assets/_commonjsHelpers-CqkleIqs.js +1 -0
- package/editor/assets/abnfDiagram-N423BO3Z-DMNuYXhv.js +1 -0
- package/editor/assets/arc-DOR9Dhm9.js +1 -0
- package/editor/assets/architectureDiagram-T3A2C74G-CpcCHDGw.js +36 -0
- package/editor/assets/blockDiagram-VBNYF7ZC-CbCqcHim.js +132 -0
- package/editor/assets/c4Diagram-5PPSVZJV-DouVYESX.js +10 -0
- package/editor/assets/channel-Dt7MgGxB.js +1 -0
- package/editor/assets/chunk-2GRJ4B5K-DCJz-Q3N.js +1 -0
- package/editor/assets/chunk-2Q5K7J3B-D5q9RvV7.js +1 -0
- package/editor/assets/chunk-5RXB4S5H-DnaXL-Vg.js +231 -0
- package/editor/assets/chunk-5VM5RSS4-Dsc890tp.js +15 -0
- package/editor/assets/chunk-6Q2QTUOP-B5F620Er.js +88 -0
- package/editor/assets/chunk-GF5L2VYU-D6F2cocl.js +206 -0
- package/editor/assets/chunk-JWPE2WC7-bRknM2iE.js +1 -0
- package/editor/assets/chunk-KBJHAD2P-DOPClpqF.js +1 -0
- package/editor/assets/chunk-RYQCIY6F-CvMFFqLU.js +1 -0
- package/editor/assets/chunk-XXDRQBXY-1qxA3khf.js +1 -0
- package/editor/assets/classDiagram-JCYQIIEL-DCQKoo1F.js +1 -0
- package/editor/assets/classDiagram-v2-OCEON4UE-DCQKoo1F.js +1 -0
- package/editor/assets/cose-bilkent-JH36ORCC-ClwDXN93.js +1 -0
- package/editor/assets/cynefin-VYW2F7L2-D_3oIqit.js +178 -0
- package/editor/assets/cynefinDiagram-MW4NZA55-5VKykgH3.js +62 -0
- package/editor/assets/cytoscape.esm-BjAbmLdb.js +331 -0
- package/editor/assets/dagre-VZM6K2ZE-DcheWwdS.js +4 -0
- package/editor/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/editor/assets/diagram-7IWD3JNH-BKU8hufg.js +30 -0
- package/editor/assets/diagram-B4RE2ZJO-C6NwNXMM.js +3 -0
- package/editor/assets/diagram-LBJQPF4R-D5l8kaaB.js +24 -0
- package/editor/assets/diagram-Q27KOJAE-B839mp_E.js +24 -0
- package/editor/assets/diagram-UB23O5K3-BEJ8nrHN.js +41 -0
- package/editor/assets/ebnfDiagram-BXEA7PRR-Clexv7zP.js +1 -0
- package/editor/assets/erDiagram-JOGREHBK-CQiS9gcI.js +85 -0
- package/editor/assets/flowDiagram-UKHOOZJN-CKnVd1JX.js +156 -0
- package/editor/assets/ganttDiagram-PKOTCBZU-B2_YsT7P.js +292 -0
- package/editor/assets/gitGraphDiagram-DS77QQ5N-woKMsCpe.js +106 -0
- package/editor/assets/graph-C9eacEi8.js +1 -0
- package/editor/assets/index-CBRsrwap.css +1 -0
- package/editor/assets/index-CnMC8Z9d.js +9167 -0
- package/editor/assets/index-CzJ1nSGZ.js +61 -0
- package/editor/assets/infoDiagram-6WML65LV-DQBHUzYs.js +2 -0
- package/editor/assets/init-Gi6I4Gst.js +1 -0
- package/editor/assets/ishikawaDiagram-WSZJBQD7-B0ECJjey.js +70 -0
- package/editor/assets/journeyDiagram-NVQOT4AX-C6IoE7ds.js +139 -0
- package/editor/assets/kanban-definition-27J2QSJJ-BsRx6-QA.js +89 -0
- package/editor/assets/katex-C5jXJg4s.js +257 -0
- package/editor/assets/layout-DEXfKzaS.js +1 -0
- package/editor/assets/linear-CZdkbpsZ.js +1 -0
- package/editor/assets/map-Czzmt4hB.js +1 -0
- package/editor/assets/mermaid.core-BD7SxJD7.js +314 -0
- package/editor/assets/mindmap-definition-FAOFIHXS-zQTmawZC.js +96 -0
- package/editor/assets/ordinal-Cboi1Yqb.js +1 -0
- package/editor/assets/pegDiagram-VL7TDLO6-DwfoSDjb.js +1 -0
- package/editor/assets/pieDiagram-7S7Q4E2Y-CFju-NFP.js +39 -0
- package/editor/assets/quadrantDiagram-CIZ2JOQS-BD6kYPyE.js +7 -0
- package/editor/assets/railroadDiagram-AXF67PYL-RqQwm-lL.js +1 -0
- package/editor/assets/requirementDiagram-LRYGKXZP-piqHrBVs.js +84 -0
- package/editor/assets/sankeyDiagram-W5VNT64P-CiALLT6P.js +40 -0
- package/editor/assets/sequenceDiagram-SI44F4Z6-CjpaYPxa.js +162 -0
- package/editor/assets/sizeCapture-X5ZJPWSS-DG9i3uEZ.js +1 -0
- package/editor/assets/stateDiagram-OKZ733FA-BgfrUiLR.js +1 -0
- package/editor/assets/stateDiagram-v2-UEYNNEHI-OBUiw3fk.js +1 -0
- package/editor/assets/swimlanes-SLNWSIFB-edAhP15G.js +2 -0
- package/editor/assets/swimlanesDiagram-ULZ7WXOC-BvcKTcG0.js +8 -0
- package/editor/assets/timeline-definition-Z64GVDOM-CLCag48V.js +120 -0
- package/editor/assets/vennDiagram-T6HMQDX7-CNuGpJHG.js +34 -0
- package/editor/assets/wardleyDiagram-T6FBY63Y-D-NVpbtn.js +78 -0
- package/editor/assets/xychartDiagram-ELKLHX3M-CmO8305Y.js +7 -0
- package/editor/favicon.ico +0 -0
- package/editor/index.html +15 -0
- package/package.json +61 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getSchema as l}from"@tiptap/core";import m from"@tiptap/starter-kit";import n from"@tiptap/extension-text-align";import{generateUniqueIds as p}from"@tiptap/extension-unique-id";import{Box as f,CountAttr as c,Stat as d,StatRow as u,Steps as g,Timeline as S}from"./blocks.js";import{FitLoop as T}from"./fitLoop.js";import{Chart as h}from"./chart.js";import{Diagram as I}from"./diagram.js";import{ColourMark as P,ColourStyle as L,HighlightMark as k}from"./colour.js";import{BlockCommands as b}from"./commands.js";import{Ids as x}from"./id.js";import{Item as y}from"./item.js";import{Layout as C,LayoutRegion as B}from"./layout.js";import{ImageBlock as D,ImageGrid as A,ImageText as E,InlineImage as M,MediaItem as O,People as N,VideoBlock as v}from"./media.js";import{Attribution as w,ProseBulletList as R,ProseCode as H,ProseHeading as V,ProseListItem as Q,ProseOrderedList as _,ProseParagraph as q,ProseSeparator as G,ProseTaskItem as K,ProseTaskList as U,QUOTE_SLOTS as j,Quote as z,QuoteText as F}from"./prose.js";import{Doc as X,Page as Y,PageAccent as J}from"./page.js";import{DecaTable as W,DecaTableCell as Z,DecaTableHeader as $,DecaTableRow as ee}from"./table.js";import{ItemBody as te,ItemMeta as oe,ItemTitle as re,SLOT_NODES as ae,MediaBody as ie,MediaCaption as se,MediaTitle as le,PageEyebrow as me,PageSubtitle as ne,PageTitle as pe,SlotEditing as fe,ChartAxisX as ce,ChartAxisY as de,ChartTitle as ue,StatLabel as ge,StatNote as Se,StatValue as Te}from"./text.js";const he=[...ae,...j,"paragraph","heading","tableCell","tableHeader"];function s(){return[m.configure({document:!1,paragraph:!1,heading:!1,bulletList:!1,orderedList:!1,listItem:!1,blockquote:!1,codeBlock:!1,horizontalRule:!1,hardBreak:{keepMarks:!0},trailingNode:!1,listKeymap:!1}),n.configure({types:he,alignments:["left","center","right"]}),L,P,k,X,Y,c,T,x,b,fe,J,me,pe,ne,f,g,S,y,oe,re,te,u,d,ge,Te,Se,D,A,E,N,v,O,se,le,ie,W,ee,Z,$,h,ue,de,ce,I,q,V,R,_,Q,U,K,z,F,w,G,H,C,B,M]}function _e(t){return p(Le(t),s())}let a=null;const Ie=()=>{if(!a){const t=l(s());a=new Set(Object.keys(t.nodes).filter(o=>"v"in(t.nodes[o].spec.attrs??{})))}return a},Pe=1;function Le(t){const o=Ie(),i=e=>{const r={...e};return e.type&&o.has(e.type)&&(e.attrs?.v??null)===null&&(r.attrs={...e.attrs,v:Pe}),e.content&&(r.content=e.content.map(i)),r};return i(t)}export{he as ALIGNABLE,Pe as OLDEST_VERSION,s as decaframeExtensions,_e as loadDocument,Le as stampVersions};
|
package/dist/figure.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{FIGURE_ATTR as x}from"./design.js";const D=t=>({[x]:{default:null,parseHTML:r=>{const n=r.getAttribute("data-figure");return n&&t.includes(n)?n:null},renderHTML:r=>r[x]?{"data-figure":String(r[x])}:{}}}),R=t=>!!t?.attrs&&x in t.attrs,F=["road","climb","core"],_=20,A=16,i=100,u=t=>_-A*Math.sin(Math.PI*t/i);function S(t){return Array.from({length:Math.max(t,2)},(r,n)=>{const o=(n+.5)*i;return{x:o,y:u(o)}})}const m={min:3,max:4};function I(){const r=-i*.3,n=i*(m.max+.3),o=[{x:r,y:u(r)}];for(let a=-1;a<=m.max;a++)for(let c=0;c<12;c++){const s=(a+c/12)*i;s<=r||s>=n||o.push({x:s,y:u(s)})}return o.push({x:n,y:u(n)}),o}function G(){return L(I())}function L(t){const r=o=>t[Math.min(Math.max(o,0),t.length-1)];let n=`M ${e(t[0].x)} ${e(t[0].y)}`;for(let o=0;o<t.length-1;o++){const[a,c,s,f]=[r(o-1),r(o),r(o+1),r(o+2)],d={x:c.x+(s.x-a.x)/6,y:c.y+(s.y-a.y)/6},$={x:s.x-(f.x-c.x)/6,y:s.y-(f.y-c.y)/6};n+=` C ${e(d.x)} ${e(d.y)} ${e($.x)} ${e($.y)} ${e(s.x)} ${e(s.y)}`}return n}const N={w:i,h:40},U="http://www.w3.org/2000/svg",V=t=>S(t).map(r=>r.y<_),p={w:100,h:40},g=4,l=t=>p.h-g-(p.h-2*g)*t;function M(){return Array.from({length:13},(r,n)=>{const o=n/12;return{x:e(o*p.w),y:e(l(o))}})}function C(){const t=M();return`M ${t[0].x} ${t[0].y}`+t.slice(1).map(r=>` L ${r.x} ${r.y}`).join("")}function W(){const t=M();return`${C()} L ${t[t.length-1].x} ${p.h} L ${t[0].x} ${p.h} Z`}const X=()=>({y0:e(l(0)/p.h),y1:e(l(1)/p.h)}),P={min:3,max:5},E={w:100,h:50},h=50,b=(t,r)=>{const n=e(h*(r-t+1)/r);return{rx:n,ry:n}},O=82,w=.82,j=t=>(E.h-h*t)/E.h,H=()=>({r:h,span:O,seat:e(O*w)});function Y(){return Array.from({length:y.max},(t,r)=>b(r+1,y.max))}const y={min:3,max:3},T={road:m,climb:P,core:y},k=t=>R(t.type.spec)&&!t.attrs[x];function v(t,r){const n=t.attrs[x],o=n?T[n]:void 0;return o?r<o.min?`${n} needs at least ${o.min} stops`:r>o.max?`${n} holds at most ${o.max} stops`:null:null}const e=t=>Math.round(t*100)/100;export{P as CLIMB_STOPS,p as CLIMB_VIEWBOX,i as COLUMN,h as CORE_R,w as CORE_SEAT,O as CORE_SPAN,y as CORE_STOPS,E as CORE_VIEWBOX,x as FIGURE_ATTR,F as FIGURE_NAMES,T as FIGURE_STOPS,N as FIGURE_VIEWBOX,A as ROAD_AMP,_ as ROAD_MID,m as ROAD_STOPS,U as SVG_NS,X as climbEnds,W as climbFaceD,M as climbLine,C as climbPathD,l as climbY,j as coreEdge,H as coreEnds,b as coreLayer,Y as coreLayers,R as declaresFigure,D as figureAttribute,V as figureCrests,v as figureProblem,I as roadLine,G as roadPathD,S as roadPoints,u as roadY,k as widthlessFigureBlock};
|
package/dist/filename.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const i="Untitled document",m=255,c=".html",f=new Set(["CON","PRN","AUX","NUL",...Array.from({length:9},(t,n)=>`COM${n+1}`),...Array.from({length:9},(t,n)=>`LPT${n+1}`)]),u=/[\u0000-\u001f\u007f<>:"/\\|?*]+/g,a=new TextEncoder,l=t=>a.encode(t).length;function p(t,n){return t.trim()||n?.trim()||i}const E=t=>t.replace(/^[.\s]+/,"").replace(/[.\s]+$/,"");function h(t,n){if(l(t)<=n)return t;let e="",o=0;for(const r of t){const s=l(r);if(o+s>n)break;e+=r,o+=s}return E(e)}function N(t,n){let e=E(p(t,n).replace(u,"-"));(!e||/^-+$/.test(e))&&(e=i),f.has(e.split(".")[0].toUpperCase())&&(e=`_${e}`);const o=e.toLowerCase().endsWith(c),r=o?e.slice(-c.length):c,s=o?E(e.slice(0,-c.length)):e;return(h(s,255-l(r))||i)+r}export{m as MAX_FILENAME_BYTES,i as UNTITLED,p as documentName,N as exportFilename};
|
package/dist/fit.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
function w(u,e){const[n,i]=u.split("/").map(c=>parseFloat(c));return!n||!i||!Number.isFinite(n)||!Number.isFinite(i)?e.height:e.width*i/n}function F(u,e){const n=getComputedStyle(u),i=parseFloat(n.paddingBottom)||0,c=e.top+w(n.aspectRatio,e)-i;let r=-1/0;for(const l of u.children)l.classList.contains("page-accent")||(r=Math.max(r,l.getBoundingClientRect().bottom));return r===-1/0?0:Math.max(0,Math.round(r-c))}function L(u){const e=[];for(const n of u.querySelectorAll(".page")){if(!(n instanceof HTMLElement))continue;const i=n.getBoundingClientRect(),c=getComputedStyle(n),r=parseFloat(c.paddingBottom)||0,l=w(c.aspectRatio,i),[a,x]=c.aspectRatio.split("/").map(o=>parseFloat(o)),h=!!(a&&x&&Number.isFinite(a)&&Number.isFinite(x)),m=i.top+l-r,g=o=>o.closest("[data-slot]")===null,M=[];for(const o of n.querySelectorAll(".blk")){const d=o.getAttribute("data-id");if(!d||!g(o))continue;const f=o.getBoundingClientRect(),p=Math.round(f.bottom-m);p>0&&M.push({id:d,over:p,height:Math.round(f.height)})}let y=null;for(const o of n.children){if(!o.classList.contains("blk")&&o.getAttribute("data-node")!=="layout")continue;const d=o.getAttribute("data-id");d&&o.getBoundingClientRect().bottom<=m&&(y=d)}let s=null;for(const o of n.querySelectorAll("[data-slot]")){const d=o.getAttribute("data-slot");if(!d||!o.textContent?.trim())continue;const f=Math.round(o.getBoundingClientRect().height);if(s&&f<=s.height)continue;const p=o.closest(".blk")?.getAttribute("data-id");p&&(s={blockId:p,node:d,child:o.closest("[data-node]")?.getAttribute("data-node")??"",height:f})}if(!s)for(const o of n.querySelectorAll(".blk")){const d=o.getAttribute("data-id");if(!d||!g(o)||!o.textContent?.trim())continue;const f=Math.round(o.getBoundingClientRect().height);(!s||f>s.height)&&(s={blockId:d,node:"",child:"",height:f})}e.push({id:n.getAttribute("data-id")??"",bounded:h,over:F(n,i),frame:Math.round(l),past:M,lastFitting:y,tallest:s})}return e}function T(u,e){const n=[];for(const i of u.querySelectorAll(".page")){if(!(i instanceof HTMLElement))continue;const c=i.getAttribute("data-id")??"",r=getComputedStyle(i),l=i.getBoundingClientRect(),a={left:l.left+parseFloat(r.paddingLeft),top:l.top+parseFloat(r.paddingTop),right:l.right-parseFloat(r.paddingRight),bottom:l.bottom-parseFloat(r.paddingBottom)},x=i.querySelector(':scope > [data-node="accent"], :scope > .page-accent'),h=i.getAttribute("data-accent");if(x&&h&&h!=="full"){const t=x.getBoundingClientRect();h==="lead"?a.left=Math.max(a.left,t.right):h==="trail"?a.right=Math.min(a.right,t.left):h==="top"&&(a.top=Math.max(a.top,t.bottom))}const m=a.right-a.left,g=a.bottom-a.top;if(m<=0||g<=0){n.push(null);continue}const M=[];for(const t of i.children)if(!t.matches('[data-node="accent"], .page-accent')){if(t.matches('[data-node="layout"], .blk-layout')){for(const b of t.querySelectorAll('[data-node="region"], .layout-region'))for(const S of b.children)M.push(S.getBoundingClientRect());continue}M.push(t.getBoundingClientRect())}const y=!!h||i.hasAttribute("data-surface"),s=M.filter(t=>t.width>1&&t.height>1);if(!s.length){n.push({id:c,ink:0,vSpan:0,wide:0,gap:100,parts:0,painted:y});continue}const o=new Uint8Array(e*e);for(const t of s){const b=Math.max(0,Math.floor((t.left-a.left)/m*e)),S=Math.min(e,Math.ceil((t.right-a.left)/m*e)),v=Math.max(0,Math.floor((t.top-a.top)/g*e)),H=Math.min(e,Math.ceil((t.bottom-a.top)/g*e));for(let C=v;C<H;C++)for(let R=b;R<S;R++)o[C*e+R]=1}let d=0;for(let t=0;t<o.length;t++)d+=o[t];const f=Math.min(...s.map(t=>t.top)),p=Math.max(...s.map(t=>t.bottom)),q=Math.max(...s.map(t=>t.width)),k=s.map(t=>[t.top,t.bottom]).sort((t,b)=>t[0]-b[0]);let A=0,B=k[0][1];for(const[t,b]of k.slice(1))t>B&&(A=Math.max(A,t-B)),B=Math.max(B,b);A=Math.max(A,f-a.top,a.bottom-p),n.push({id:c,ink:Math.round(d/(e*e)*100),vSpan:Math.round((p-f)/g*100),wide:Math.round(q/m*100),gap:Math.round(A/g*100),parts:s.length,painted:y})}return n}const O=.8,$=.02;function E(u,e,n,i){return!(u>0)||!(e>0)||!(n>0)?1:Math.max(i,Math.min(1,u*n/e))}function I(u,e,n){const i=[];for(const c of u.querySelectorAll(".page")){if(!(c instanceof HTMLElement))continue;const r=c.getAttribute("style");let l=1,a=F(c,c.getBoundingClientRect());for(;a>0&&l>e;)l=Math.max(e,Math.round((l-n)*1e3)/1e3),c.style.setProperty("--fit",String(l)),a=F(c,c.getBoundingClientRect());r===null?c.removeAttribute("style"):c.setAttribute("style",r),i.push({id:c.getAttribute("data-id")??"",scale:l,over:a})}return i}const N=()=>`const frameHeight = ${w};
|
|
2
|
+
const overflowOf = ${F};
|
|
3
|
+
const readFit = ${L};
|
|
4
|
+
const readFill = ${T};
|
|
5
|
+
const readScale = ${I};
|
|
6
|
+
`;export{O as FIT_FLOOR,$ as FIT_STEP,E as fitFactorOf,N as fitSource,w as frameHeight,F as overflowOf,T as readFill,L as readFit,I as readScale};
|
package/dist/fitLoop.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Extension as x}from"@tiptap/core";import{Plugin as D,PluginKey as T}from"@tiptap/pm/state";import{Decoration as P,DecorationSet as z}from"@tiptap/pm/view";import{FIT_FLOOR as F,FIT_STEP as A,fitFactorOf as E}from"./fit.js";const u=new T("fitLoop"),b=.005,S=A,L=t=>Math.round(t*1e3)/1e3;function R(t){const e=t.borderBoxSize?.[0];if(!e)return{available:0,content:0};const[o,n]=getComputedStyle(t.target).aspectRatio.split("/").map(i=>parseFloat(i));return!o||!n||!Number.isFinite(o)||!Number.isFinite(n)?{available:0,content:0}:{available:e.inlineSize*n/o-(e.blockSize-t.contentRect.height),content:t.contentRect.height}}function q(t,e){const o=new Set([...Object.keys(t),...Object.keys(e)]);for(const n of o)if(Math.abs((t[n]??1)-(e[n]??1))>b)return!0;return!1}function I(t,e){if(!t.attrs.autoFit)return[];const o=[];return t.descendants((n,a)=>{if(n.type.name!=="page")return!1;const i=e[n.attrs.id];return i!==void 0&&o.push(P.node(a,a+n.nodeSize,{style:`--fit:${i}`})),!1}),o}const v=x.create({name:"fitLoop",addProseMirrorPlugins(){return[new D({key:u,state:{init:()=>({}),apply:(t,e)=>t.getMeta(u)??e},props:{decorations(t){const e=I(t.doc,u.getState(t)??{});return e.length?z.create(t.doc,e):null}},view(t){let e=!1;const o=new Map,n=new Map,a=new ResizeObserver(d=>{if(e||!t.state.doc.attrs.autoFit)return;const s=u.getState(t.state)??{},r={...s};let c=!1;for(const y of d){const M=y.target,f=M.getAttribute("data-id")??o.get(M);if(!f)continue;const{available:m,content:g}=R(y);if(!(m>0)||!(g>0))continue;const l=s[f]??1,O=g>m;let p=l;if(O)n.set(f,Math.min(n.get(f)??1/0,l)),p=Math.max(F,Math.min(E(m,g,l,F),l-S));else if(l<1){const k=n.get(f)??1/0,h=Math.min(1,l+S);(h>=1-b?1:h)<k-1e-9&&(p=h)}Math.abs(p-l)<=1e-9||(c=!0,p<1-b?r[f]=L(p):delete r[f])}c&&t.dispatch(t.state.tr.setMeta(u,r).setMeta("addToHistory",!1))}),i=()=>{if(e)return;const d=new Set;t.state.doc.descendants((s,r)=>{if(s.type.name!=="page")return!1;const c=t.nodeDOM(r);return c instanceof HTMLElement&&(d.add(c),o.set(c,s.attrs.id),a.observe(c)),!1});for(const s of[...o.keys()])d.has(s)||(a.unobserve(s),o.delete(s))};return typeof requestAnimationFrame=="function"&&requestAnimationFrame(i),{update:(d,s)=>{if(s.doc!==t.state.doc&&(i(),n.clear(),!t.state.doc.attrs.autoFit)){const r=u.getState(t.state)??{};Object.keys(r).length&&t.dispatch(t.state.tr.setMeta(u,{}).setMeta("addToHistory",!1))}},destroy:()=>{e=!0,a.disconnect()}}}})]}});export{v as FitLoop,I as fitDecorations,u as fitKey};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const f=/[\u0000-\u0020\u007f]+/g;function s(o){return`'${o.replace(f," ").replace(/\\/g,"\\00005c ").replace(/'/g,"\\000027 ").replace(/"/g,"\\000022 ")}'`}const i=64e3,a=180,c=20,n=3;function d(o,e){const t=Math.min(a/o,c/e,1/n);return{w:Math.max(1,Math.round(o*t*n)),h:Math.max(1,Math.round(e*t*n))}}const g=(o,e)=>o>0&&e>0&&o/e>a/c,h=o=>o>i;function w(o,e){return!(o>0)||!(e>0)?"This picture doesn't say what size it is, so there's no way to know how big to draw it. A PNG or JPG works, or an SVG that has a width and height set.":null}const l=/^data:image\/(png|jpeg|gif|webp|avif|svg\+xml);base64,[A-Za-z0-9+/]+={0,2}$/;function u(o){return o===""?null:o.startsWith("data:image/")?o.length>i?"That picture is too heavy to save inside the document, even after being resized. A logo saved as a PNG or an SVG will usually be a fraction of it.":l.test(o)?null:"That file could not be read as a picture. A PNG, JPG, GIF, WebP or SVG will work.":"Choose a picture file from your computer. A web address will not work here \u2014 the picture is saved inside the document so that a shared copy still shows it with no internet."}function p(o){const e=typeof o.footer=="string"?o.footer:"",t=typeof o.footerLogo=="string"?o.footerLogo:"",r=u(t)===null?t:"";return{"--footer-words":s(e),"--footer-logo":r===""?"none":`url(${s(r)})`,"--footer-logo-on":r===""?"0":"1"}}const m=o=>Object.entries(p(o)).map(([e,t])=>`${e}:${t}`).join(";");function x(o){const e=typeof o.footer=="string"?o.footer:"",t=typeof o.footerLogo=="string"?o.footerLogo:"",r=e!==""||u(t)===null&&t!=="";return{...r?{"data-footer-on":""}:{},...r&&o.footerLogoWide?{"data-footer-wide":""}:{},...o.footerNumbers?{"data-page-numbers":""}:{}}}export{i as LOGO_CHARS,c as MARK_BAND_H,a as MARK_MAX_W,n as MARK_SCALE,s as cssString,x as docFurnitureAttrs,p as docFurnitureProps,m as docFurnitureStyle,u as logoProblem,d as markBox,g as markIsWide,w as markShapeProblem,h as needsFit};
|
package/dist/icons.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{icons as f}from"lucide";const g={Technology:{cpu:"Cpu",database:"Database",server:"Server",cloud:"Cloud",terminal:"Terminal",network:"Network",layers:"Layers",boxes:"Boxes",signal:"Signal",zap:"Zap"},"Process & work":{workflow:"Workflow",branch:"GitBranch",route:"Route",activity:"Activity",clock:"Clock",calendar:"Calendar",clipboard:"ClipboardList",filter:"Filter",package:"Package",wrench:"Wrench"},"Trust & risk":{lock:"Lock",shield:"Shield",alert:"TriangleAlert",scale:"Scale",key:"Key",bug:"Bug",eye:"Eye",gavel:"Gavel",bell:"Bell",gauge:"Gauge"},"Business & money":{globe:"Globe",search:"Search",trend:"TrendingUp",coin:"Coins",wallet:"Wallet",briefcase:"Briefcase",receipt:"Receipt",tag:"Tag",cart:"ShoppingCart",building:"Building2"},"People & communication":{radio:"Radio",user:"User",users:"Users",handshake:"Handshake",message:"MessageCircle",mail:"Mail",phone:"Phone",megaphone:"Megaphone",heart:"Heart",award:"Award"},"Ideas & direction":{target:"Target",rocket:"Rocket",flag:"Flag",compass:"Compass",map:"Map",lightbulb:"Lightbulb",sparkles:"Sparkles",puzzle:"Puzzle",trophy:"Trophy",star:"Star"},"Fields & sectors":{book:"Book",flask:"FlaskConical",leaf:"Leaf",pill:"Pill",truck:"Truck",factory:"Factory",house:"House",plane:"Plane",store:"Store",landmark:"Landmark"}},y=Object.keys(g),c=Object.assign({},...Object.values(g)),u=Object.keys(c),m={helpcircle:"message",help:"message",headphones:"phone",headset:"phone",lifebuoy:"shield",lifebelt:"shield",alerttriangle:"alert",alertcircle:"alert",alertoctagon:"alert",trendingup:"trend",trendingdown:"trend",trending:"trend",linechart:"trend",barchart:"gauge",piechart:"gauge",activitysquare:"activity",smartphone:"phone",messagesquare:"message",messagecircle:"message",mailopen:"mail",usercircle:"user",usergroup:"users",creditcard:"wallet",shoppingcart:"cart",filetext:"clipboard",folder:"package",settings:"wrench",tool:"wrench",shieldcheck:"shield",lockclosed:"lock",magnifyingglass:"search",searchcircle:"search"};function C(t){const e=h(t);if(!e)return null;if(c[e])return e;const r=m[e];return r&&c[r]?r:null}const v=Object.fromEntries(Object.entries(g).map(([t,e])=>[t,Object.keys(e)])),h=t=>t.toLowerCase().replace(/[^a-z]/g,"");function b(t,e){if(t===e)return 0;let r=Array.from({length:e.length+1},(l,n)=>n);for(let l=1;l<=t.length;l++){const n=[l];for(let a=1;a<=e.length;a++)n[a]=Math.min(r[a]+1,n[a-1]+1,r[a-1]+(t[l-1]===e[a-1]?0:1));r=n}return r[e.length]}function S(t){const e=h(t);if(!e)return null;if(c[e])return e;const r=u.filter(o=>e.includes(o)||o.includes(e)).sort((o,s)=>s.length-o.length);if(r.length>0)return r[0];const l=Math.min(2,Math.max(1,Math.floor(e.length/4)));let n=null,a=l+1;for(const o of u){const s=b(e,o);s<a&&(a=s,n=o)}return a<=l?n:null}const i=new Map;function x(t){if(i.has(t))return i.get(t);const e=c[t],r=e?f[e]:void 0;if(!r)return i.set(t,null),null;const n=`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">${r.map(([o,s])=>{const p=Object.entries(s).map(([d,k])=>`${d}="${k}"`).join(" ");return`<${o} ${p}/>`}).join("")}</svg>`,a=`url("data:image/svg+xml,${encodeURIComponent(n)}")`;return i.set(t,a),a}export{c as ICONS,m as ICON_ALIASES,g as ICON_CATEGORIES,y as ICON_CATEGORY_NAMES,u as ICON_NAMES,v as ICON_SHELVES,x as iconMask,S as nearestIcon,C as resolveIcon};
|
package/dist/id.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{UniqueID as a}from"@tiptap/extension-unique-id";const l=["page","box","steps","timeline","statRow","imageBlock","imageGrid","imageText","table","item","stat","mediaItem","paragraph","heading","bulletList","orderedList","quote","people","taskList","videoBlock","horizontalRule","codeBlock","layout","layoutRegion","pageAccent","chart","diagram"],o={page:"pg",box:"blk",steps:"blk",timeline:"blk",statRow:"blk",imageBlock:"blk",imageGrid:"blk",imageText:"blk",table:"blk",item:"itm",stat:"st",mediaItem:"med",paragraph:"blk",heading:"blk",bulletList:"blk",orderedList:"blk",quote:"blk",people:"blk",taskList:"blk",videoBlock:"blk",horizontalRule:"blk",codeBlock:"blk",layout:"blk",layoutRegion:"reg",pageAccent:"acc",chart:"blk",diagram:"blk"},i=e=>`${o[e]??"nod"}_${Math.random().toString(36).slice(2,8)}`,b=a.configure({types:[...l],generateID:({node:e})=>i(e.type.name)}),k=e=>({v:{default:e,parseHTML:t=>Number(t.getAttribute("data-v"))||e,renderHTML:t=>({"data-v":String(t.v??e)})}});export{l as ID_TYPES,b as Ids,i as mintId,k as versionAttribute};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ARCHETYPES as o,CATALOGUE as t,blockLabel as a,byNode as i,isBlockNode as T,byType as A,countOf as s,figuresOf as E,shapeCountProblem as S,Box as l,CountAttr as m,Stat as _,StatRow as I,Steps as O,Timeline as n}from"./blocks.js";import{CHART_ANIMATION_MS as d,CHART_DRAWN_EVENT as N,CHART_KINDS as p,CHART_NODE as f,CHART_PLOT_CLASS as L,CHART_STACKS as D,CONNECT_NULLS as M,Chart as P,DONUT_HOLE as c,MAX_CATEGORIES as C,MAX_SERIES as F,STACKABLE_KINDS as x,WEDGE_KINDS as u,WEDGE_RADIUS as g,SERIES_FIELDS as b,SERIES_TOKENS as h,STACK_ATTR as K,applyChartText as y,applySeries as G,blockForSeries as B,chartChildren as H,chartOption as k,parseNumber as U,parsePasted as v,seriesBuildable as V,seriesColour as w,seriesCountOf as W,seriesOf as X,seriesProblem as Y}from"./chart.js";import{mountChart as Z}from"./chartView.js";import{CRAMPED_FAMILIES as q,DIAGRAM_DRAWING_CLASS as j,DIAGRAM_DRAWN_EVENT as J,DIAGRAM_FIELDS as $,DIAGRAM_KINDS as ee,DIAGRAM_NODE as re,DIAGRAM_STARTERS as oe,DIAGRAM_TYPES as te,Diagram as ae,crampedHint as ie,diagramFamily as Te,diagramOf as Ae,restateLine as se,sourceProblem as Ee,starterSource as Se}from"./diagram.js";import{FETCHING_ELEMENTS as me,URL_BEARING_ATTRS as _e,cleanSvg as Ie,diagramConfig as Oe,drawDiagram as ne,resolveRoles as Re}from"./diagramRender.js";import{DIAGRAM_TOKENS as Ne,categorical as pe,diagramTheme as fe,diagramVars as Le,mix as De,onFill as Me}from"./diagramTheme.js";import{DIAGRAM_ZOOM_MAX as ce,DIAGRAM_ZOOM_MIN as Ce,DIAGRAM_ZOOM_STEP as Fe,attachZoom as xe,clampZoom as ue,panLimit as ge,zoomButton as be,zoomIcon as he}from"./diagramZoom.js";import{errorLine as ye,messageOf as Ge}from"./diagramView.js";import{fitDiagnostic as He,lintDocument as ke,lintPage as Ue}from"./lint.js";import{FIT_FLOOR as Ve,FIT_STEP as we,fitFactorOf as We,fitSource as Xe,readScale as Ye,frameHeight as ze,overflowOf as Ze,readFill as Qe,readFit as qe}from"./fit.js";import{Item as Je}from"./item.js";import{DEFAULT_LAYOUT as er,LAYOUTS as rr,LAYOUT_NAMES as or,LAYOUT_NODES as tr,Layout as ar,LayoutRegion as ir,layoutNamed as Tr}from"./layout.js";import{VideoPlay as sr,videoPlayKey as Er}from"./videoPlay.js";import{SizePreviewExtension as lr,setSizePreview as mr,sizePreviewKey as _r}from"./sizePreview.js";import{ACCENT_POSITIONS as Or,PAGE_SURFACES as nr,ALIGNMENTS as Rr,DOC_ATTRS as dr,Doc as Nr,FIXED_FORMATS as pr,FORMATS as fr,PAPER_FORMATS as Lr,Page as Dr,PageAccent as Mr}from"./page.js";import{LOGO_CHARS as cr,MARK_BAND_H as Cr,MARK_MAX_W as Fr,MARK_SCALE as xr,cssString as ur,docFurnitureAttrs as gr,docFurnitureProps as br,docFurnitureStyle as hr,logoProblem as Kr,markBox as yr,markIsWide as Gr,markShapeProblem as Br,needsFit as Hr}from"./furniture.js";import{framingOf as Ur,ImageBlock as vr,ImageGrid as Vr,ImageText as wr,MediaItem as Wr,mediaFramingProblem as Xr,mediaSrcProblem as Yr,People as zr,mediaKindOf as Zr,posterCandidates as Qr,posterFor as qr,VideoBlock as jr,youtubeId as Jr,ZOOM_MAX as $r}from"./media.js";import{ItemBody as ro,ItemMeta as oo,ItemTitle as to,MediaBody as ao,MediaCaption as io,MediaTitle as To,PageEyebrow as Ao,PageSubtitle as so,PageTitle as Eo,SlotEditing as So,ChartAxisX as lo,ChartAxisY as mo,ChartTitle as _o,StatLabel as Io,StatNote as Oo,StatValue as no,slotContent as Ro,slotText as No}from"./text.js";import{ICON_ALIASES as fo,ICON_CATEGORIES as Lo,ICON_CATEGORY_NAMES as Do,ICON_NAMES as Mo,ICON_SHELVES as Po,ICONS as co,iconMask as Co,nearestIcon as Fo,resolveIcon as xo}from"./icons.js";import{Ids as go,ID_TYPES as bo,mintId as ho,versionAttribute as Ko}from"./id.js";import{BlockCommands as Go,chromeOf as Bo,findById as Ho,moveTo as ko,propsOf as Uo}from"./commands.js";import{applyEntries as Vo,childFor as wo,childrenFor as Wo,childTypeFor as Xo,entriesOf as Yo,entryDrawsNothing as zo,fieldForSlotNode as Zo,fieldsFor as Qo,isEntryArchetype as qo,payloadKeyFor as jo,requiredFor as Jo,textSlotsFor as $o}from"./archetype.js";import{Attribution as rt,HEADING_LEVELS as ot,KIND_OF as tt,LINE_KINDS as at,LIST_NODES as it,PROSE_FIELDS as Tt,Quote as At,QuoteText as st,TEXTLESS_NODES as Et,ProseBulletList as St,ProseCode as lt,ProseHeading as mt,ProseListItem as _t,ProseOrderedList as It,ProseParagraph as Ot,ProseSeparator as nt,ProseTaskItem as Rt,ProseTaskList as dt,itemNodeFor as Nt,applyLines as pt,blockForLines as ft,groupLines as Lt,linesBuildable as Dt,linesOf as Mt}from"./prose.js";import{MARKDOWN_SPECS as ct,blocksFromMarkdown as Ct,blocksOnly as Ft,directiveProblem as xt,markdownExtensions as ut,markdownManager as gt,pagesFromMarkdown as bt}from"./markdown.js";import{DecaTable as Kt,DecaTableCell as yt,DecaTableHeader as Gt,DecaTableRow as Bt,TABLE_FIELDS as Ht,TABLE_NODES as kt,COLUMNS_ATTR as Ut,MIN_COLUMN as vt,applyTable as Vt,blockForTable as wt,colsOf as Wt,columnFloor as Xt,declaresColumns as Yt,normaliseWidths as zt,tableBuildable as Zt,tableOf as Qt,tableProblem as qt,widthsNow as jt,widthsOf as Jt}from"./table.js";import{MARK_BOX as ea,MARK_PATH as ra,WORDMARK_BOX as oa,WORDMARK_PATH as ta}from"./brand.js";import{decaframeExtensions as ia,loadDocument as Ta,stampVersions as Aa,OLDEST_VERSION as sa}from"./extensions.js";import{BUILT_IN as Sa,PUBLISHER as la,activeRegistry as ma,missMessage as _a,resolveBlock as Ia,resolveTemplate as Oa,resolveTheme as na,themeOrDefault as Ra}from"./registry.js";import{Placeholders as Na,placeholderFor as pa}from"./placeholder.js";import{PALETTE as La,colourToken as Da}from"./colour.js";import{VARIANT_ATTR as Pa,ARRANGEMENT_ATTR as ca,FIGURE_ATTR as Ca,DESIGN_ATTR as Fa,SLOT_OFF as xa,ICON_POSITIONS as ua,ICON_POSITION_ATTR as ga,WIDTH_ATTR as ba,PLACE_ATTR as ha,BLOCK_PLACES as Ka,WIDTH_FULL as ya,MIN_WIDTH as Ga,normaliseWidth as Ba,declaresSize as Ha,sizeAttributes as ka,MARK_ATTR as Ua,BLOCK_MARKS as va,MARK_ICON as Va,MARK_STARTER as wa,declaresMark as Wa,markAttribute as Xa,blockAttributes as Ya,designProblem as za,designStyle as Za,sameDesign as Qa,serializeDesign as qa,parseDesign as ja,slotAccepts as Ja}from"./design.js";import{FIGURE_NAMES as ei,FIGURE_VIEWBOX as ri,SVG_NS as oi,COLUMN as ti,ROAD_MID as ai,ROAD_STOPS as ii,declaresFigure as Ti,figureAttribute as Ai,figureCrests as si,figureProblem as Ei,FIGURE_STOPS as Si,widthlessFigureBlock as li,roadPathD as mi,roadPoints as _i}from"./figure.js";import{DEFAULT_THEME as Oi,EDITORIAL_THEME as ni,THEMES as Ri,TOKEN_DICTIONARY as di,TOKEN_NAMES as Ni,THEME_TOKENS as pi,PALETTE_TOKENS as fi,defaultThemeId as Li,emitTheme as Di,themeById as Mi,validateTheme as Pi,VARIANT_SLOTS as ci,VARIANT_SLOT_SPECS as Ci,CORE_VARIANTS as Fi,DEFAULT_VARIANT as xi,variantGloss as ui,variantLabel as gi,variantsFor as bi,FONTS as hi,fontHref as Ki,fontLinks as yi,fontsFor as Gi}from"./theme/index.js";import{TEMPLATES as Hi,TEMPLATE_SHELVES as ki,instantiate as Ui,stampIds as vi,stripIds as Vi,templateById as wi}from"./template/index.js";import{describeLook as Xi}from"./template/look.js";import{notesExtensions as zi}from"./notes.js";import{PRESENT_IDLE_MS as Qi,PRESENT_NEXT_KEYS as qi,PRESENT_PREV_KEYS as ji}from"./present/constants.js";import{MAX_FILENAME_BYTES as $i,UNTITLED as eT,documentName as rT,exportFilename as oT}from"./filename.js";export{Or as ACCENT_POSITIONS,Rr as ALIGNMENTS,o as ARCHETYPES,ca as ARRANGEMENT_ATTR,rt as Attribution,va as BLOCK_MARKS,Ka as BLOCK_PLACES,Sa as BUILT_IN,Go as BlockCommands,l as Box,t as CATALOGUE,d as CHART_ANIMATION_MS,N as CHART_DRAWN_EVENT,p as CHART_KINDS,f as CHART_NODE,L as CHART_PLOT_CLASS,D as CHART_STACKS,ti as COLUMN,Ut as COLUMNS_ATTR,M as CONNECT_NULLS,Fi as CORE_VARIANTS,q as CRAMPED_FAMILIES,P as Chart,lo as ChartAxisX,mo as ChartAxisY,_o as ChartTitle,m as CountAttr,er as DEFAULT_LAYOUT,Oi as DEFAULT_THEME,xi as DEFAULT_VARIANT,Fa as DESIGN_ATTR,j as DIAGRAM_DRAWING_CLASS,J as DIAGRAM_DRAWN_EVENT,$ as DIAGRAM_FIELDS,ee as DIAGRAM_KINDS,re as DIAGRAM_NODE,oe as DIAGRAM_STARTERS,Ne as DIAGRAM_TOKENS,te as DIAGRAM_TYPES,ce as DIAGRAM_ZOOM_MAX,Ce as DIAGRAM_ZOOM_MIN,Fe as DIAGRAM_ZOOM_STEP,dr as DOC_ATTRS,c as DONUT_HOLE,Kt as DecaTable,yt as DecaTableCell,Gt as DecaTableHeader,Bt as DecaTableRow,ae as Diagram,Nr as Doc,ni as EDITORIAL_THEME,me as FETCHING_ELEMENTS,Ca as FIGURE_ATTR,ei as FIGURE_NAMES,Si as FIGURE_STOPS,ri as FIGURE_VIEWBOX,Ve as FIT_FLOOR,we as FIT_STEP,pr as FIXED_FORMATS,hi as FONTS,fr as FORMATS,ot as HEADING_LEVELS,co as ICONS,fo as ICON_ALIASES,Lo as ICON_CATEGORIES,Do as ICON_CATEGORY_NAMES,Mo as ICON_NAMES,ua as ICON_POSITIONS,ga as ICON_POSITION_ATTR,Po as ICON_SHELVES,bo as ID_TYPES,go as Ids,vr as ImageBlock,Vr as ImageGrid,wr as ImageText,Je as Item,ro as ItemBody,oo as ItemMeta,to as ItemTitle,tt as KIND_OF,rr as LAYOUTS,or as LAYOUT_NAMES,tr as LAYOUT_NODES,at as LINE_KINDS,it as LIST_NODES,cr as LOGO_CHARS,ar as Layout,ir as LayoutRegion,ct as MARKDOWN_SPECS,Ua as MARK_ATTR,Cr as MARK_BAND_H,ea as MARK_BOX,Va as MARK_ICON,Fr as MARK_MAX_W,ra as MARK_PATH,xr as MARK_SCALE,wa as MARK_STARTER,C as MAX_CATEGORIES,$i as MAX_FILENAME_BYTES,F as MAX_SERIES,vt as MIN_COLUMN,Ga as MIN_WIDTH,ao as MediaBody,io as MediaCaption,Wr as MediaItem,To as MediaTitle,sa as OLDEST_VERSION,nr as PAGE_SURFACES,La as PALETTE,fi as PALETTE_TOKENS,Lr as PAPER_FORMATS,ha as PLACE_ATTR,Qi as PRESENT_IDLE_MS,qi as PRESENT_NEXT_KEYS,ji as PRESENT_PREV_KEYS,Tt as PROSE_FIELDS,la as PUBLISHER,Dr as Page,Mr as PageAccent,Ao as PageEyebrow,so as PageSubtitle,Eo as PageTitle,zr as People,Na as Placeholders,St as ProseBulletList,lt as ProseCode,mt as ProseHeading,_t as ProseListItem,It as ProseOrderedList,Ot as ProseParagraph,nt as ProseSeparator,Rt as ProseTaskItem,dt as ProseTaskList,At as Quote,st as QuoteText,ai as ROAD_MID,ii as ROAD_STOPS,b as SERIES_FIELDS,h as SERIES_TOKENS,xa as SLOT_OFF,x as STACKABLE_KINDS,K as STACK_ATTR,oi as SVG_NS,lr as SizePreviewExtension,So as SlotEditing,_ as Stat,Io as StatLabel,Oo as StatNote,I as StatRow,no as StatValue,O as Steps,Ht as TABLE_FIELDS,kt as TABLE_NODES,Hi as TEMPLATES,ki as TEMPLATE_SHELVES,Et as TEXTLESS_NODES,Ri as THEMES,pi as THEME_TOKENS,di as TOKEN_DICTIONARY,Ni as TOKEN_NAMES,n as Timeline,eT as UNTITLED,_e as URL_BEARING_ATTRS,Pa as VARIANT_ATTR,ci as VARIANT_SLOTS,Ci as VARIANT_SLOT_SPECS,jr as VideoBlock,sr as VideoPlay,u as WEDGE_KINDS,g as WEDGE_RADIUS,ba as WIDTH_ATTR,ya as WIDTH_FULL,oa as WORDMARK_BOX,ta as WORDMARK_PATH,$r as ZOOM_MAX,ma as activeRegistry,y as applyChartText,Vo as applyEntries,pt as applyLines,G as applySeries,Vt as applyTable,xe as attachZoom,Ya as blockAttributes,ft as blockForLines,B as blockForSeries,wt as blockForTable,a as blockLabel,Ct as blocksFromMarkdown,Ft as blocksOnly,i as byNode,A as byType,pe as categorical,H as chartChildren,k as chartOption,wo as childFor,Xo as childTypeFor,Wo as childrenFor,Bo as chromeOf,ue as clampZoom,Ie as cleanSvg,Da as colourToken,Wt as colsOf,Xt as columnFloor,s as countOf,ie as crampedHint,ur as cssString,ia as decaframeExtensions,Yt as declaresColumns,Ti as declaresFigure,Wa as declaresMark,Ha as declaresSize,Li as defaultThemeId,Xi as describeLook,za as designProblem,Za as designStyle,Oe as diagramConfig,Te as diagramFamily,Ae as diagramOf,fe as diagramTheme,Le as diagramVars,xt as directiveProblem,gr as docFurnitureAttrs,br as docFurnitureProps,hr as docFurnitureStyle,rT as documentName,ne as drawDiagram,Di as emitTheme,Yo as entriesOf,zo as entryDrawsNothing,ye as errorLine,oT as exportFilename,Zo as fieldForSlotNode,Qo as fieldsFor,Ai as figureAttribute,si as figureCrests,Ei as figureProblem,E as figuresOf,Ho as findById,He as fitDiagnostic,We as fitFactorOf,Xe as fitSource,Ki as fontHref,yi as fontLinks,Gi as fontsFor,ze as frameHeight,Ur as framingOf,Lt as groupLines,Co as iconMask,Ui as instantiate,T as isBlockNode,qo as isEntryArchetype,Nt as itemNodeFor,Tr as layoutNamed,Dt as linesBuildable,Mt as linesOf,ke as lintDocument,Ue as lintPage,Ta as loadDocument,Kr as logoProblem,Xa as markAttribute,yr as markBox,Gr as markIsWide,Br as markShapeProblem,ut as markdownExtensions,gt as markdownManager,Xr as mediaFramingProblem,Zr as mediaKindOf,Yr as mediaSrcProblem,Ge as messageOf,ho as mintId,_a as missMessage,De as mix,Z as mountChart,ko as moveTo,Fo as nearestIcon,Hr as needsFit,Ba as normaliseWidth,zt as normaliseWidths,zi as notesExtensions,Me as onFill,Ze as overflowOf,bt as pagesFromMarkdown,ge as panLimit,ja as parseDesign,U as parseNumber,v as parsePasted,jo as payloadKeyFor,pa as placeholderFor,Qr as posterCandidates,qr as posterFor,Uo as propsOf,Qe as readFill,qe as readFit,Ye as readScale,Jo as requiredFor,Ia as resolveBlock,xo as resolveIcon,Re as resolveRoles,Oa as resolveTemplate,na as resolveTheme,se as restateLine,mi as roadPathD,_i as roadPoints,Qa as sameDesign,qa as serializeDesign,V as seriesBuildable,w as seriesColour,W as seriesCountOf,X as seriesOf,Y as seriesProblem,mr as setSizePreview,S as shapeCountProblem,ka as sizeAttributes,_r as sizePreviewKey,Ja as slotAccepts,Ro as slotContent,No as slotText,Ee as sourceProblem,vi as stampIds,Aa as stampVersions,Se as starterSource,Vi as stripIds,Zt as tableBuildable,Qt as tableOf,qt as tableProblem,wi as templateById,$o as textSlotsFor,Mi as themeById,Ra as themeOrDefault,Pi as validateTheme,ui as variantGloss,gi as variantLabel,bi as variantsFor,Ko as versionAttribute,Er as videoPlayKey,li as widthlessFigureBlock,jt as widthsNow,Jt as widthsOf,Jr as youtubeId,be as zoomButton,he as zoomIcon};
|
package/dist/item.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Node as a,mergeAttributes as s}from"@tiptap/core";import{emphasisAttribute as r}from"./design.js";import{iconMask as c}from"./icons.js";const u=a.create({name:"item",content:"itemMeta? itemTitle? itemBody?",defining:!0,addAttributes(){return{icon:{default:null,parseHTML:t=>t.getAttribute("data-icon"),renderHTML:t=>t.icon?{"data-icon":t.icon}:{}},...r()}},parseHTML:()=>[{tag:'li[data-node="item"]'}],renderHTML({node:t,HTMLAttributes:o}){const e=s(o,{"data-node":"item",class:"item"}),i=t.attrs.icon?c(t.attrs.icon):null,n=["div",{class:"item-slots"},0];return i?["li",e,["span",{class:"item-icon",contenteditable:"false",style:`--icon:${i}`}],n]:["li",e,n]}});export{u as Item};
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Node as t,mergeAttributes as n}from"@tiptap/core";import{versionAttribute as r}from"./id.js";const o=[{name:"two-cols",label:"Two columns",whenToUse:"Two things of equal weight side by side \u2014 a claim and its evidence, before and after, or a picture beside the words about it. Prefer this over two pages when the pair is the point.",regions:[{name:"col-1",label:"Column 1"},{name:"col-2",label:"Column 2"}]},{name:"three-cols",label:"Three columns",whenToUse:"Three parallel things that each need a block of their own rather than one entry. If each is only a title and a sentence, a box grid at three holds more and reads better.",regions:[{name:"col-1",label:"Column 1"},{name:"col-2",label:"Column 2"},{name:"col-3",label:"Column 3"}]},{name:"four-cols",label:"Four columns",whenToUse:"Four parallel things, each about a QUARTER of the frame \u2014 a logo row, four figures, a quarter-by-quarter split. A column this narrow holds a title and a line; a `box` grid at four holds more and stays readable, so reach for this when each column needs its own BLOCK.",regions:[{name:"col-1",label:"Column 1"},{name:"col-2",label:"Column 2"},{name:"col-3",label:"Column 3"},{name:"col-4",label:"Column 4"}]},{name:"five-cols",label:"Five columns",whenToUse:"Five parallel things. Each column is about a FIFTH of the frame, which is a mark, a figure or two or three words \u2014 not a sentence. A block inside one reflows to it and will stack.",regions:[{name:"col-1",label:"Column 1"},{name:"col-2",label:"Column 2"},{name:"col-3",label:"Column 3"},{name:"col-4",label:"Column 4"},{name:"col-5",label:"Column 5"}]}],u=o.map(e=>e.name),m=e=>o.find(l=>l.name===e),a=o[0].name,c=new Set(["layout","layoutRegion"]),d=t.create({name:"layout",content:"layoutRegion+",defining:!0,isolating:!0,addAttributes(){return{layout:{default:a,parseHTML:e=>e.getAttribute("data-layout")??a,renderHTML:e=>({"data-layout":e.layout??a})},...r(1)}},parseHTML:()=>[{tag:'div[data-node="layout"]'}],renderHTML:({HTMLAttributes:e})=>["div",n(e,{"data-node":"layout",class:"blk blk-layout"}),0]}),b=t.create({name:"layoutRegion",content:"block+",defining:!0,isolating:!0,addAttributes(){return{region:{default:null,parseHTML:e=>e.getAttribute("data-region"),renderHTML:e=>e.region?{"data-region":e.region}:{}}}},parseHTML:()=>[{tag:'div[data-node="region"]'}],renderHTML:({HTMLAttributes:e})=>["div",n(e,{"data-node":"region",class:"layout-region"}),0]});export{a as DEFAULT_LAYOUT,o as LAYOUTS,u as LAYOUT_NAMES,c as LAYOUT_NODES,d as Layout,b as LayoutRegion,m as layoutNamed};
|
package/dist/lint.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{byNode as _,countOf as N}from"./blocks.js";import{colsOf as R,tableOf as E}from"./table.js";import{entriesOf as F,entryDrawsNothing as S,fieldForSlotNode as D,isEntryArchetype as w,requiredFor as b,textSlotsFor as I}from"./archetype.js";import{NOT_CONTENT as T,chromeOf as A}from"./commands.js";const y=e=>typeof e!="string"||e.trim()==="",C={prose:["line","lines"],table:["row","rows"],items:["entry","entries"],stats:["entry","entries"],media:["entry","entries"]},k=(e,a)=>{const[r,d]=C[a]??["child","children"];return`${e} ${e===1?r:d}`};function P(e){const a=[],r=e.attrs.id;for(const[o,t]of Object.entries(A(e)))y(t)&&a.push({code:"emptyChrome",pageId:r,message:`page '${r}' has an empty ${o}: the slot is still there and still takes up space, but it paints nothing. Give it text with set_page, or pass '' for ${o} to remove the heading slot entirely.`});let d=0;return e.forEach(o=>{T.has(o.type.name)||(d+=1)}),d===0&&a.push({code:"emptyPage",pageId:r,message:`page '${r}' holds no blocks \u2014 it exports as a blank page. Put content on it with add_prose, add_items, add_stats or another add_ tool. add_template_page cannot fill it: that adds a composed page of its own.`}),e.forEach(o=>{const t=_(o.type.name);if(!t)return;const n=o.attrs.id,l=(s,i)=>a.push({code:s,pageId:r,blockId:n,message:i}),u=w(t.archetype)?F(o):[],v=[...new Set([...b(t.archetype),...I(t.archetype)])],g=s=>S(t.archetype,s),m=t.archetype==="table"?E(o):null,$=m!==null&&[...m.headers??[],...(m.rows??[]).flat()].every(s=>y(s)),x=$||u.length>0&&u.every(g),O=t.arrangements.find(s=>s.name===o.attrs.arrangement),[f,p]=O?.countRange??t.countRange;if((f!==p||w(t.archetype))&&!x){const s=N(o),i=k(s,t.archetype),h=`update_${t.archetype}`,c=f===p?`holds exactly ${k(f,t.archetype)}`:`is designed for ${f} to ${p}`;s>p?l("countOutOfRange",`${t.type} '${n}' holds ${i}; its layout ${c} and has no rule past ${p}, so the extra ones are drawn cramped. Split it across two blocks, or move some onto another page.`):s<f&&l("countOutOfRange",`${t.type} '${n}' holds ${i}; its layout ${c}, so it draws as though the rest were still coming. Add more with ${h}, or swap the block \u2014 list_blocks gives every block's designed range.`)}if(t.colRange&&m)if($)l("emptyEntry",`${t.type} '${n}' draws nothing \u2014 every cell in it is empty, so it renders as a grid of blank boxes. Fill the cells with update_table, echoing the shape back with text in it, or remove_block '${n}' if the whole table is a placeholder.`);else{const[s,i]=t.colRange,h=R(o),c=`is designed for ${s} to ${i} columns`;h>i?l("colsOutOfRange",`${t.type} '${n}' has ${h} columns; its layout ${c}, so past ${i} the cells are too narrow to read. Drop a column, or turn the table on its side so the long list runs down the rows instead of across the columns.`):h<s&&l("colsOutOfRange",`${t.type} '${n}' has ${h} column${h===1?"":"s"}; its layout ${c}. A single column of values is a list, not a table \u2014 use bullets, or add the column that says what the values are.`)}for(const s of u){const i=s,h=i.id;if(g(s)){l("emptyEntry",`${t.type} '${n}': entry '${h}' draws nothing \u2014 none of ${v.join(", ")} is filled in, and a decoration on its own (an icon, a state) is not content. Fill one with update_${t.archetype}, leave the entry out of that array to drop it, or remove_block '${n}' if the whole block is a placeholder.`);continue}for(const c of b(t.archetype))y(i[c])&&l("emptySlot",`${t.type} '${n}': entry '${h}' has an empty '${c}', which every entry in the '${t.archetype}' archetype must carry. Set it with update_${t.archetype}, echoing the other entries back unchanged \u2014 an entry left out of that array is removed.`)}}),a}function z(e){const a=[];return e.forEach(r=>a.push(...P(r))),a}function H(e,a){if(e.over<=0)return null;const r=()=>{const o=e.tallest;if(o&&o.height>=e.over){const n=o.node?D(o.child,o.node):void 0;return`${n?`the '${n}' text`:"the longest text"} in block '${o.blockId}' is the tallest on the page at ${o.height}px, so shortening that one alone recovers the ${e.over}px \u2014 nothing else needs to change.`}const t=e.past.filter(n=>n.height>=e.over).sort((n,l)=>n.height-l.height)[0];return t?`block '${t.id}' is ${t.height}px tall and ${e.over}px is past the frame, so giving that one block a page of its own recovers it. Add a page and move it there.`:e.lastFitting?`more than any single block's worth is past the frame, so split the page: everything up to and including block '${e.lastFitting}' fits, and what follows it belongs on a new page.`:"not even the first block fits inside the frame, so there is nothing to split off \u2014 this page needs fewer entries or much shorter text before it can hold anything."},d=()=>a===void 0||a<=0?"":e.over===a?" This is unchanged from before your last write, so that change was not the fix. Do not try the same kind of edit again: either apply the remedy above, or stop and tell the person you are working for that this page needs a decision about what to cut.":e.over<a?` Down from ${a}px, so this is working \u2014 keep going.`:` Up from ${a}px: the last change made it worse.`;return{code:"pageOverflow",pageId:e.id,message:`page '${e.id}' is ${e.over}px past the bottom of its frame, which is ${e.frame}px tall \u2014 a reader would not see that much. To fix it, ${r()}${d()}`}}export{H as fitDiagnostic,z as lintDocument,P as lintPage};
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import{Markdown as xt,MarkdownManager as St}from"@tiptap/markdown";import{CATALOGUE as At,byNode as z,byType as Tt}from"./blocks.js";import{LAYOUT_NAMES as F,layoutNamed as W}from"./layout.js";import{ID_TYPES as It,mintId as w}from"./id.js";import{ARRANGEMENT_ATTR as A,BLOCK_PLACES as Z,FIGURE_ATTR as P,ICON_POSITIONS as J,ICON_POSITION_ATTR as E,MARK_ATTR as N,MIN_WIDTH as Q,PLACE_ATTR as M,VARIANT_ATTR as S,WIDTH_ATTR as O}from"./design.js";import{nearestIcon as vt,resolveIcon as Et}from"./icons.js";import{mediaSrcProblem as tt,youtubeId as et}from"./media.js";import{CHART_STACKS as nt,seriesProblem as Nt}from"./chart.js";import{ACCENT_POSITIONS as rt,PAGE_SURFACES as st}from"./page.js";import{CORE_VARIANTS as Mt,DEFAULT_VARIANT as Ot,variantsFor as Bt}from"./theme/emit.js";import{decaframeExtensions as Ct}from"./extensions.js";const _t="renderChildren",Rt=e=>{const t=String(e);return/^[\w./:#%?&=+~@-]+$/.test(t)&&t!==""?t:t.includes('"')?`'${t.replace(/'/g,"")}'`:`"${t}"`},B=e=>Object.entries(e).filter(([,t])=>t!=null&&t!==!1&&t!=="").map(([t,n])=>n===!0?t:`${t}=${Rt(n)}`).join(" "),I=(e,t={})=>{const n=B(t);return n?`::${e}: ${n}::`:`::${e}::`},C=e=>{const t=B(e);return t?` {${t}}`:""};function v(e){const t=z(e.type??""),n=e.attrs??{},r=n[P]??n[A];return{[S]:n[S]&&n[S]!==Ot?n[S]:null,[A]:r??null,[E]:n[E],[N]:n[N],[O]:n[O],[M]:n[M]}}const x=(e,t,n)=>{const r=(e.content??[]).findIndex(o=>o.type===t);return r<0?"":n.renderChild(e.content[r],r).trim()},D="::end::",_="about:blank",ot=e=>({renderMarkdown:(t,n)=>(t.content??[]).map((r,o)=>n.renderChild(r,o)).filter(r=>r.trim()).join(e)}),k=()=>({renderMarkdown:(e,t)=>e.content?.length?t[_t](e,""):""}),jt=()=>({renderMarkdown:(e,t)=>{const n=z(e.type??"");if(!n||!e.content?.length)return"";const r=e.content.map((o,a)=>t.renderChild(o,a).trim()).filter(Boolean);return[I(n.type,v(e)),...r,D].join(`
|
|
2
|
+
|
|
3
|
+
`)}}),Lt=()=>({renderMarkdown:(e,t)=>{const n=e.attrs??{},r=x(e,"itemTitle",t),o=x(e,"itemBody",t),a=`### ${r}${C({icon:n.icon,meta:x(e,"itemMeta",t)||null,emphasis:n.emphasis===!0})}`;return o?`${a}
|
|
4
|
+
${o}`:a}}),Yt=()=>({renderMarkdown:(e,t)=>{const n=x(e,"statValue",t),r=x(e,"statNote",t),o=`### ${n}${C({label:x(e,"statLabel",t)||null})}`;return r?`${o}
|
|
5
|
+
${r}`:o}}),at={doc:ot(`
|
|
6
|
+
|
|
7
|
+
`),page:{renderMarkdown:(e,t)=>{const n=e.attrs??{},r=u=>{const p=(e.content??[]).find(m=>m.type===u);return p?T(p):""},o=(e.content??[]).find(u=>u.type==="pageAccent"),a=I("page",{eyebrow:r("pageEyebrow")||null,subtitle:r("pageSubtitle")||null,accent:n.accent,src:o?.attrs?.src??null,alt:o?.attrs?.alt??null,surface:n.surface,mode:n.mode}),s=r("pageTitle"),c=(e.content??[]).map((u,p)=>t.renderChild(u,p)).filter(u=>u.trim()).join(`
|
|
8
|
+
|
|
9
|
+
`),l=e.attrs?.notes,i=l?`<!-- notes
|
|
10
|
+
${l}
|
|
11
|
+
-->`:"";return[a,s?`# ${s}`:"",c,i].filter(Boolean).join(`
|
|
12
|
+
|
|
13
|
+
`)}},layout:{renderMarkdown:(e,t)=>{const n=(e.content??[]).map((r,o)=>t.renderChild(r,o)).filter(r=>r.trim());return[I("layout",{layout:e.attrs?.layout}),...n,D].join(`
|
|
14
|
+
|
|
15
|
+
`)}},layoutRegion:{renderMarkdown:(e,t)=>{const n=(e.content??[]).map((r,o)=>t.renderChild(r,o)).filter(r=>r.trim());return[`::${e.attrs?.region}::`,...n].join(`
|
|
16
|
+
|
|
17
|
+
`)}},pageAccent:{renderMarkdown:()=>""},pageEyebrow:{renderMarkdown:()=>""},pageTitle:{renderMarkdown:()=>""},pageSubtitle:{renderMarkdown:()=>""},quote:{markdownTokenName:"blockquote",parseMarkdown:(e,t)=>{const n=t.parseChildren(e.tokens??[]),r=n[n.length-1],o=r?.type==="paragraph"?T(r):"",a=n.length>1&&/^— /.test(o)?o.slice(2).trim():null,s=a?n.slice(0,-1):n,c=[];for(const l of s)c.length&&c.push({type:"hardBreak"}),c.push(...l.content??[]);return t.createNode("quote",null,[t.createNode("quoteText",null,c),...a?[t.createNode("attribution",null,[{type:"text",text:a}])]:[]])},renderMarkdown:(e,t)=>{const r=(e.content??[]).map((c,l)=>t.renderChild(c,l).trim()).filter(Boolean);if(!r.length)return"";const[o,a]=r;return[o,...a?["",`\u2014 ${a}`]:[]].map(c=>c?`> ${c}`:">").join(`
|
|
18
|
+
`)}},quoteText:k(),attribution:k(),table:{renderMarkdown:(e,t)=>{const n=(e.content??[]).map((u,p)=>(u.content??[]).map((m,d)=>t.renderChild(m,d).trim().replace(/\|/g,"\\|")));if(!n.length)return"";const r=Math.max(...n.map(u=>u.length)),o=u=>[...u,...Array(r-u.length).fill("")],a=u=>`| ${o(u).join(" | ")} |`,[s,...c]=n,l=[a(s),`|${" --- |".repeat(r)}`,...c.map(a)].join(`
|
|
19
|
+
`);return B(v(e))?`${I("table",v(e))}
|
|
20
|
+
${l}`:l}},tableRow:{renderMarkdown:()=>""},paragraph:{renderMarkdown:(e,t)=>{const n=e.content??[];if(!n.length)return"";const r=t.renderChildren(n,"");return B(v(e))?`${I("text",v(e))}
|
|
21
|
+
${r}`:r}},tableCell:k(),tableHeader:k(),chart:{renderMarkdown:(e,t)=>{const n=g=>e.content?.[g]?t.renderChild(e.content[g],g).trim():"",[r,o,a]=[n(0),n(1),n(2)],s=e.attrs??{},c=s.categories??[],l=s.series??[],i=g=>g.replace(/\|/g,"\\|"),u=g=>`| ${g.map(i).join(" | ")} |`,p=["",...l.map(g=>`${g.name??""}${C({as:g.as??null})}`)],m=c.map((g,f)=>u([g,...l.map(y=>{const $=y.values?.[f];return $==null?"":String($)})])),d=[u(p),`|${" --- |".repeat(p.length)}`,...m].join(`
|
|
22
|
+
`);return[I("chart",{...v(e),stack:s.stack&&s.stack!=="none"?s.stack:null,title:r||null,axisX:a||null,axisY:o||null}),d,D].join(`
|
|
23
|
+
|
|
24
|
+
`)}},diagram:{renderMarkdown:e=>{const t=String(e.attrs?.source??"").trim();return t?["```mermaid",t,"```"].join(`
|
|
25
|
+
`):""}},chartTitle:k(),chartAxisX:k(),chartAxisY:k(),mediaItem:{renderMarkdown:(e,t,n)=>{const r=e.attrs??{},o=r.src??"",a=(r.alt??"").replace(/[[\]]/g,""),s=n?.parentType==="videoBlock"||!!o&&!!et(o),c=(s?o||_:``)+C({alt:s&&a?a:null,caption:x(e,"mediaCaption",t)||null,focusX:r.focusX,focusY:r.focusY,zoom:r.zoom}),l=x(e,"mediaTitle",t),i=x(e,"mediaBody",t);return[c,l?`### ${l}`:"",i].filter(Boolean).join(`
|
|
26
|
+
`)}},mediaTitle:k(),mediaBody:k(),mediaCaption:k(),itemMeta:k(),itemTitle:k(),itemBody:ot(`
|
|
27
|
+
|
|
28
|
+
`),statLabel:k(),statValue:k(),statNote:k(),item:Lt(),stat:Yt()};for(const e of At)e.archetype==="prose"||e.archetype==="table"||(at[e.node]??=jt());function Pt(){return[...Ct().map(t=>{const n=at[t.name];return n?t.extend(n):t}),xt]}const it=()=>new St({extensions:Pt()}),Dt=new Set(It);function R(e){const t=e.type??"";if(t==="tableCell"||t==="tableHeader"){const o=(e.content??[]).flatMap(a=>a.content??[]);return{...e,attrs:{colspan:1,rowspan:1},content:o.length?o:void 0}}if(t==="listItem"||t==="taskItem"){const o=(e.content??[]).map(R).filter(Boolean),a=o.filter(c=>c.type==="paragraph"),s=o.filter(c=>c.type!=="paragraph");if(a.length>1){const c=[];for(const l of a)c.length&&c.push({type:"hardBreak"}),c.push(...l.content??[]);a.splice(1,a.length-1),a[0]={...a[0],content:c}}return{...e,attrs:{...e.attrs},content:[...a,...s]}}const n=(e.content??[]).map(R).filter(Boolean),r=Dt.has(t)?{...e,attrs:{...e.attrs,id:w(t)}}:e;return n.length?{...r,content:n}:{...r,content:void 0}}const ct=/^::\s*([a-zA-Z0-9-]+)\s*(?::\s*(.*?))?\s*::$/,K=/\s*\{([^{}]*)\}\s*$/,Kt=/([A-Za-z][\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+))|([A-Za-z][\w-]*)/g;function j(e,t={}){const n={},r=e.trim();if(t.positional!==!1&&r&&!r.includes("=")&&!/\s/.test(r))return{positional:r,attrs:n};for(const o of r.matchAll(Kt))o[5]?n[o[5]]=!0:n[o[1]]=o[2]??o[3]??o[4]??"";return{positional:null,attrs:n}}const Xt=(e,t)=>{const{positional:n,attrs:r}=j(t),o=Tt(e),a=e==="layout"||F.includes(e),s=a?"layout":e==="page"?"page":e==="end"?"end":o?"block":"region";return{kind:s,name:e,attrs:r,positional:a&&e!=="layout"?e:n,meta:s==="block"?o:void 0}},lt=["eyebrow","subtitle","accent","src","alt","surface","mode"],L=[S,A,E,N,O,M],ut=["stack","title","axisX","axisY"],X=["icon","meta","emphasis"],pt=["label","note","value"],ft=["caption","alt","focusX","focusY","zoom"],Ht=/^https?:\/\/\S+$/,Gt=new Set(["paragraph","bulletList","orderedList"]);function dt(e){const t=[[]];for(const n of e.content??[]){if(n.type==="hardBreak"){t.push([]);continue}if(n.type!=="text"||n.text===void 0){t[t.length-1].push(n);continue}n.text.split(`
|
|
29
|
+
`).forEach((o,a)=>{a>0&&t.push([]),o&&t[t.length-1].push({...n,text:o})})}return t}function Y(e,t){const n=[];t.forEach((o,a)=>{if(a>0){const s=n[n.length-1],c=o[0];if(s?.type==="text"&&c?.type==="text"&&JSON.stringify(s.marks??[])===JSON.stringify(c.marks??[])){n[n.length-1]={...s,text:`${s.text}
|
|
30
|
+
${c.text}`},n.push(...o.slice(1));return}n.push({type:"hardBreak"})}n.push(...o)});const r=n.filter(o=>o.type!=="text"||o.text);return r.length?{...e,content:r}:null}function Ut(e){if(e.type!=="paragraph")return null;const t=dt(e),n=a=>a.length&&a.every(s=>s.type==="text")?a.map(s=>s.text??"").join("").trim():null,r=t.findIndex(a=>{const s=n(a);return s!==null&&ct.test(s)});if(r<0)return null;const o=ct.exec(n(t[r]));return{before:Y(e,t.slice(0,r)),marker:Xt(o[1],o[2]??""),rest:Y(e,t.slice(r+1))}}function H(e){const t=[];let n=e;for(;n;){const r=Ut(n);if(!r){t.push({block:n});break}r.before&&t.push({block:r.before}),t.push({marker:r.marker}),n=r.rest}return t}const b=e=>e.join(", ");function G(e){const t=[...e.content??[]],n=t[t.length-1];if(n?.type==="text"&&n.text){const r=K.exec(n.text);if(r){const o=n.text.slice(0,r.index).replace(/\s+$/,"");return o?t[t.length-1]={...n,text:o}:t.pop(),{title:t,attrs:j(r[1],{positional:!1}).attrs}}}return{title:t,attrs:{}}}function mt(e,t){const n=[];let r=0;for(;r<e.length;){const o=e[r];if(o.marker?.kind!=="block"){n.push(o),r+=1;continue}const{marker:a}=o,s=a.meta,c=`"::${a.name}::"`,l={id:w(s.node),v:s.version};for(const[p,m]of Object.entries(a.attrs)){if(s.archetype==="chart"&&ut.includes(p))continue;if(!L.includes(p)){const h=s.archetype==="chart"?[...L,...ut]:L,g=X.includes(p)?` "${p}" belongs to an item, in the braces after its heading: \`### Title {${p}=\u2026}\`.`:"";return{pieces:n,problem:`${c} has no attribute "${p}". A block marker takes: ${b(h)}.${g}`}}const d=String(m);if(p===S){const h=s.variantSlots.length?Bt(s.variantSlots,t,{core:!0}):[Mt[0]];if(!h.includes(d))return{pieces:n,problem:`${c} has no style "${d}". Its styles: ${b(h)}.`};l[S]=d}else if(p===A){const h=s.arrangements.map(f=>f.name),g=(s.figures??[]).map(f=>f.name);if(h.includes(d))l[A]=d;else if(g.includes(d))l[P]=d;else return{pieces:n,problem:`${c} has no shape "${d}". Its shapes: ${b(h)||"none"}${g.length?`; its figures: ${b(g)}`:""}.`}}else if(p===E){if(!s.slots.includes("icon"))return{pieces:n,problem:`${c} draws no icon, so "${p}" means nothing on it.`};if(!J.includes(d))return{pieces:n,problem:`${c} has no icon position "${d}". Positions: ${b(J)}.`};l[E]=d}else if(p===N){if(!s.marks.length)return{pieces:n,problem:`${c} marks nothing, so "${p}" means nothing on it.`};if(!s.marks.includes(d))return{pieces:n,problem:`${c} has no mark "${d}". Its marks: ${b(s.marks)}.`};l[N]=d}else if(p===O){const h=Number(d);if(Number.isNaN(h)||h<Q||h>100)return{pieces:n,problem:`${c} has width "${d}"; a width is a share of the frame, ${Q} to 100.`};l[O]=h}else if(p===M){if(!Z.includes(d))return{pieces:n,problem:`${c} has no place "${d}". Places: ${b(Z)}.`};l[M]=d}}const i=[];for(r+=1;r<e.length;){const p=e[r];if(p.marker){p.marker.kind==="end"&&(r+=1);break}i.push(p.block),r+=1}const u=s.archetype==="items"?Vt(s,c,l,i):s.archetype==="stats"?qt(s,c,l,i):s.archetype==="media"?zt(s,c,l,i):s.archetype==="chart"?Ft(s,c,a.attrs,l,i):Wt(s,c,l,i);if(typeof u=="string")return{pieces:n,problem:u};n.push(...u.map(p=>({block:p})))}return{pieces:n,problem:null}}const ht=e=>e.flatMap((t,n)=>[...n?[{type:"hardBreak"}]:[],...t.content??[]]);function U(e){const t=[...e],n=t[t.length-1];if(n?.type==="text"&&n.text){const r=K.exec(n.text);if(r){const o=n.text.slice(0,r.index).replace(/\s+$/,"");return o?t[t.length-1]={...n,text:o}:t.pop(),{rest:t,attrs:j(r[1],{positional:!1}).attrs}}}return{rest:t,attrs:{}}}const V=(e,t,n,r)=>`${t} under ${e} has no attribute "${n}". It takes: ${b(r)}.`;function Vt(e,t,n,r){const o=[];let a=null;const s=()=>{if(!a)return null;const i={id:w("item")};let u=null;for(const[m,d]of Object.entries(a.attrs)){if(!X.includes(m))return V(t,"an item",m,X);if(m==="icon"){const h=Et(String(d));if(!h){const g=vt(String(d));return`an item under ${t} asks for icon "${String(d)}", which does not exist${g?` \u2014 did you mean "${g}"?`:"."}`}i.icon=h}else if(m==="emphasis"){if(d!==!0&&d!=="true")return`an item under ${t} gives emphasis "${String(d)}"; it is a flag \u2014 write {emphasis} or leave it out.`;i.emphasis=!0}else u=String(d)}const p=[];return u&&p.push({type:"itemMeta",content:[{type:"text",text:u}]}),a.title.length?(p.push({type:"itemTitle",content:a.title}),a.body.length&&p.push({type:"itemBody",content:a.body}),o.push({type:"item",attrs:i,content:p}),a=null,null):`an item under ${t} has no title \u2014 a heading that is only its braces.`};for(const i of r)if(i.type==="heading"){const u=s();if(u)return u;a={...G(i),body:[]}}else if(a)if(Gt.has(i.type??""))a.body.push(i);else return`an item under ${t} holds a ${i.type} block, which an item's body cannot: paragraphs and lists only.`;else return`${t} opens with something that is not an item: an item is a HEADING, and the lines under it are its body.`;const c=s();if(c)return c;if(!o.length)return`${t} holds no item. An item is a heading under the marker, with its text beneath it.`;const l=n[P];if(l){const[i,u]=(e.figures??[]).find(p=>p.name===l).countRange;if(o.length<i||o.length>u)return`${t} wears the figure "${l}", which holds ${i===u?i:`${i}\u2013${u}`} items and was given ${o.length}. A figure's range is a limit.`}return[{type:e.node,attrs:n,content:o}]}function qt(e,t,n,r){const o=[];let a=null;const s=()=>{if(!a)return null;const l=[];let i=ht(a.note),u=a.value,p=null;for(const[m,d]of Object.entries(a.attrs)){if(!pt.includes(m))return V(t,"a figure",m,pt);m==="label"?p=String(d):m==="note"?i=[{type:"text",text:String(d)}]:(p===null&&a.value.length&&(p=a.value.map(h=>h.text??"").join("")),u=[{type:"text",text:String(d)}])}return p&&l.push({type:"statLabel",content:[{type:"text",text:p}]}),u.length?(l.push({type:"statValue",content:u}),i.length&&l.push({type:"statNote",content:i}),o.push({type:"stat",attrs:{id:w("stat")},content:l}),a=null,null):`a figure under ${t} has no value \u2014 a heading that is only its braces.`};for(const l of r)if(l.type==="heading"){const i=s();if(i)return i;const{title:u,attrs:p}=G(l);a={value:u,attrs:p,note:[]}}else if(a){if(l.type!=="paragraph")return`a figure under ${t} holds a ${l.type} block; a note is a line of text.`;a.note.push(l)}else return`${t} opens with something that is not a figure: a figure is a HEADING holding its value, with its note beneath.`;const c=s();return c||(o.length?[{type:e.node,attrs:n,content:o}]:`${t} holds no figure. A figure is a heading under the marker whose text is the value.`)}function zt(e,t,n,r){const o=e.type==="youtube",a=[];let s=null;const c=()=>{if(!s)return null;if(s.src===_&&(s.src=""),s.src){const h=tt(s.src,o?"video":"image");if(h)return`under ${t}: ${h}`;if(o&&!et(s.src))return`${t} takes a YouTube address line \u2014 'https://www.youtube.com/watch?v=\u2026' or 'https://youtu.be/\u2026' \u2014 and "${s.src}" is not one.`}const m={id:w("mediaItem"),src:s.src||null,alt:s.alt},d=[];for(const[h,g]of Object.entries(s.attrs)){if(!ft.includes(h))return V(t,"a picture",h,ft);if(h==="alt")m.alt=String(g);else if(h==="caption")d.push({type:"mediaCaption",content:[{type:"text",text:String(g)}]});else{const f=Number(g);if(Number.isNaN(f))return`a picture under ${t} gives ${h} "${String(g)}", which is not a number.`;m[h]=f}}return s.title?.length&&d.push({type:"mediaTitle",content:s.title}),s.body.length&&d.push({type:"mediaBody",content:ht(s.body)}),a.push({type:"mediaItem",attrs:m,content:d}),s=null,null},l=m=>Ht.test(m)||m===_,i=m=>m[0]?.type==="inlineImage"||m.length>0&&m.every(d=>d.type==="text")&&l(U(m).rest.map(d=>d.text??"").join("").trim()),u=r.flatMap(m=>{if(m.type!=="paragraph")return[m];const d=dt(m),h=[];let g=[];for(const y of d){if(i(y)&&g.length){const $=Y(m,g);$&&h.push($),g=[]}g.push(y)}const f=Y(m,g);return f&&h.push(f),h});for(const m of u){const d=m.type==="paragraph"?m.content?.[0]:m.type==="inlineImage"?m:void 0,h=d?.type==="inlineImage"?d:null,g=d?.type==="text"&&m.content?.every(f=>f.type==="text")?U(m.content??[]).rest.map(f=>f.text??"").join("").trim():"";if(h||l(g)){const f=c();if(f)return f;if(h&&o)return`${t} takes a video ADDRESS on a line of its own, not a picture.`;if(!h&&!o)return`${t} takes a picture line \u2014  \u2014 not a bare address; a bare address is ::youtube::'s.`;const{attrs:y}=U(m.type==="inlineImage"?[]:(m.content??[]).slice(h?1:0));s={src:h?String(h.attrs?.src??""):g,alt:h?h.attrs?.alt??null:null,attrs:y,title:null,body:[]}}else if(s)if(m.type==="heading"){if(s.title)return`a picture under ${t} has two headings; one is its title, the paragraphs under it are its words.`;s.title=G(m).title}else{if(m.type!=="paragraph")return`a picture under ${t} holds a ${m.type} block; its words are paragraphs.`;s.body.push(m)}else return`${t} opens with something that is not a ${o?"video address":"picture"}: ${o?"a bare YouTube address":""} on a line of its own starts each entry.`}const p=c();return p||(a.length?[{type:e.node,attrs:n,content:a}]:`${t} holds no ${o?"video":"picture"}.`)}function Ft(e,t,n,r,o){const a=o[0];if(!a||a.type!=="table"||o.length!==1)return`${t} must be followed by exactly ONE markdown table: the header row names the series, each row after it is a category and its values.`;const s=(a.content??[]).map(f=>(f.content??[]).map(y=>T(y).trim()));if(s.length<2)return`${t}'s table needs a header row and at least one row of values.`;const[c,...l]=s,i=l.map(f=>f[0]??""),u=c.slice(1).map((f,y)=>{const $=K.exec(f),q=$?j($[1],{positional:!1}).attrs.as:void 0;return{name:$?f.slice(0,$.index).trim():f,...q?{as:String(q)}:{},values:l.map(wt=>(wt[y+1]??"").trim())}});for(const f of u)for(const[y,$]of f.values.entries())if($!==""&&Number.isNaN(Number($)))return`${t}: the cell "${$}" (row "${i[y]}", column "${f.name}") is not a number. A gap is an empty cell.`;const p={categories:i,series:u.map(f=>({name:f.name,..."as"in f?{as:f.as}:{},values:f.values.map(y=>y===""?null:Number(y))}))},m=r[A],d=Nt(p,m);if(d)return`${t}: ${d}`;const h={};for(const[f,y]of Object.entries(n))if(!L.includes(f))if(f==="stack"){if(!nt.includes(String(y)))return`${t} has no stack "${String(y)}". Stacks: ${b(nt)}.`;r.stack=String(y)}else h[f]=String(y);const g=(f,y)=>y?{type:f,content:[{type:"text",text:y}]}:{type:f};return[{type:e.node,attrs:{...r,categories:p.categories,series:p.series},content:[g("chartTitle",h.title),g("chartAxisY",h.axisY),g("chartAxisX",h.axisX)]}]}function Wt(e,t,n,r){const{id:o,v:a,...s}=n;if(!e.arrangements.length&&!e.variantSlots.length&&e.type!=="diagram")return`${t} sets nothing: a ${e.type} has no style and no shape to name. Write it as ordinary markdown.`;if(!Object.keys(s).length)return`${t} names no attribute. It takes: ${b([...e.variantSlots.length?[S]:[],...e.arrangements.length?[A]:[]])}.`;const[c,...l]=r,i=e.type==="diagram"&&c?.type==="codeBlock"&&String(c.attrs?.language??"").toLowerCase()==="mermaid";return!c||c.type!==e.node&&!i?`${t} must sit directly over a ${e.type} block${c?`, and what follows it is a ${c.type}`:""}.`:[{...c,attrs:{...c.attrs??{},...s}},...l]}function Zt(e){for(const[t,n]of Object.entries(e.attrs)){if(!lt.includes(t))return`"::page::" has no attribute "${t}". A page line takes: ${b(lt)}.`;const r=String(n);if(t==="accent"&&!rt.includes(r))return`"::page::" has no accent "${r}". Accents: ${b(rt)}.`;if(t==="surface"&&!st.includes(r))return`"::page::" has no surface "${r}". Surfaces: ${b(st)}.${["dark","light"].includes(r)?` A ${r} page is mode=${r}.`:""}`;if(t==="mode"&&!["light","dark"].includes(r))return`"::page::" has no mode "${r}". Modes: light, dark.`;if(t==="src"){const o=tt(r,"image");if(o)return`"::page::" src: ${o}`;if(!e.attrs.accent)return'"::page::" names a src but no accent to put it in. Add accent=lead, trail, top or full.'}}return e.positional?`"::page::" takes key=value pairs, not "${e.positional}".`:null}function de(e,t={}){const n=mt(e.flatMap(H),t.theme);if(n.problem)return n.problem;let r=null,o=new Set;for(const a of n.pieces){const s=a.marker;if(!s)continue;if(s.kind==="page"){if(t.pageless)return'"::page::" describes a page of its own, and this import lands on a page that exists: use set_page for its look.';const l=Zt(s);if(l)return l;continue}if(s.kind==="layout"){const l=s.positional??String(s.attrs.layout??""),i=W(l);if(!i)return`unknown layout "${l}". Layouts: ${F.join(", ")}`;r=i,o=new Set;continue}if(s.kind==="end"){if(!r)return'"::end::" closes nothing: no block or layout marker is open above it.';r=null;continue}if(!r)return`"::${s.name}::" has no "::layout: \u2026::" above it, so there is no region by that name. A region marker only means something inside a layout.`;const c=r.regions.map(l=>l.name);if(!c.includes(s.name))return`"${r.name}" has no region "${s.name}". Its regions: ${c.join(", ")}`;if(o.has(s.name))return`"::${s.name}::" appears twice under one "${r.name}". Each region is written once, and everything until the next marker belongs to it.`;o.add(s.name)}return null}function gt(e,t){if(!e.some(i=>i.marker))return{blocks:e.map(i=>i.block),page:null};const n=[];let r=null,o=null,a=null,s=null;const c=()=>{!o||!a||(n.push({type:"layout",attrs:{id:w("layout"),layout:o.name},content:o.regions.map(i=>({type:"layoutRegion",attrs:{id:w("layoutRegion"),region:i.name},content:a.get(i.name)?.length?a.get(i.name):[{type:"paragraph"}]}))}),o=null,a=null,s=null)},l=i=>{o&&a&&s?a.set(s,[...a.get(s)??[],i]):(o&&a&&c(),n.push(i))};for(const i of mt(e,t).pieces){if(i.block){l(i.block);continue}const u=i.marker;if(u.kind==="page"){r??=u.attrs;continue}if(u.kind==="end"){c();continue}u.kind==="layout"?(c(),o=W(u.positional??String(u.attrs.layout??"")),a=new Map):s=u.name}return c(),{blocks:n,page:r}}const yt=`<!-- notes
|
|
31
|
+
`,$t=`
|
|
32
|
+
-->`;function Jt(e){let t;const n=e.filter(r=>{if(r.type!=="paragraph")return!0;const o=r.content;if(o?.length!==1||o[0].type!=="text")return!0;const a=o[0].text??"";return!a.startsWith(yt)||!a.endsWith($t)?!0:(t===void 0&&(t=a.slice(yt.length,-$t.length)),!1)});return t===void 0?{blocks:e}:{blocks:n,notes:t}}function me(e,t=1,n){const o=(it().parse(e).content??[]).map(R).filter(Boolean),a=[];let s=null;const c=(i,u)=>{s={page:{blocks:[]},pieces:[],opened:i,attrs:u},a.push(s)},l=()=>s&&!s.pieces.length&&!s.attrs;for(const i of o.flatMap(H)){if(i.marker?.kind==="page"){if(l()){s.attrs=i.marker.attrs,s.opened==="start"&&(s.opened="marker");continue}c("marker",i.marker.attrs);continue}const u=i.block,p=u?.type==="heading"&&u.attrs?.level===t;if(p&&s&&!s.page.title&&!s.pieces.length&&s.opened==="marker"){s.page.title=T(u);continue}if((p||!s)&&(c(p?"heading":"start",null),p)){s.page.title=T(u);continue}s.pieces.push(i)}for(const i of a){const u=i.pieces.filter(y=>y.block).map(y=>y.block),{blocks:p,notes:m}=Jt(u),d=new Set(p),h=i.pieces.filter(y=>y.marker||d.has(y.block)),g=gt(h,n);i.page.blocks=bt(g.blocks),m!==void 0&&(i.page.notes=m);const f=i.attrs??g.page;f&&(f.eyebrow&&(i.page.eyebrow=String(f.eyebrow)),f.subtitle&&(i.page.subtitle=String(f.subtitle)),f.accent&&(i.page.accent=String(f.accent)),f.src&&(i.page.accentSrc=String(f.src)),f.alt&&(i.page.accentAlt=String(f.alt)),f.surface&&(i.page.surface=String(f.surface)),f.mode&&(i.page.mode=String(f.mode)))}return a.map(i=>i.page)}const he=(e,t)=>bt(gt(Qt(e).flatMap(H),t).blocks),Qt=e=>(it().parse(e).content??[]).map(R).filter(Boolean),kt=e=>te(ee(e)),bt=e=>e.map(t=>t.type!=="layout"?kt(t):{...t,content:(t.content??[]).map(n=>({...n,content:(n.content??[]).map(kt)}))});function te(e){if(e.type!=="codeBlock"||String(e.attrs?.language??"").trim().toLowerCase()!=="mermaid")return e;const t=T(e).trim();if(!t)return e;const{width:n,place:r}=e.attrs??{};return{type:"diagram",attrs:{id:w("diagram"),source:t,...n?{width:n}:{},...r?{place:r}:{}}}}function ee(e){const t=e.type==="inlineImage"?e:(e.content??[]).length===1?e.content[0]:null;return e.type!=="paragraph"&&e.type!=="inlineImage"?e:t?.type!=="inlineImage"||!t.attrs?.src?e.type==="inlineImage"?{type:"paragraph",attrs:{id:w("paragraph")},content:[e]}:e:{type:"imageBlock",attrs:{id:w("imageBlock")},content:[{type:"mediaItem",attrs:{id:w("mediaItem"),src:t.attrs.src,alt:t.attrs.alt??null}}]}}function T(e){return e.type==="text"?e.text??"":(e.content??[]).map(T).join("")}export{at as MARKDOWN_SPECS,he as blocksFromMarkdown,Qt as blocksOnly,de as directiveProblem,Pt as markdownExtensions,it as markdownManager,me as pagesFromMarkdown};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import{createServer as v}from"node:http";import{spawn as b}from"node:child_process";import{createReadStream as $,existsSync as k}from"node:fs";import{dirname as _,extname as x,join as l,normalize as O,sep as N}from"node:path";import{createRequire as c}from"node:module";import{FETCHING_ELEMENTS as F,FIT_FLOOR as J,FIT_STEP as R,URL_BEARING_ATTRS as E,cleanSvg as T,diagramConfig as j,fitSource as A,resolveRoles as P}from"decaframe";import{DIAGRAM_TOKENS as C,diagramTheme as I}from"decaframe";let u=null,d=null,s=null,m=null;async function h(){if(s)return s;try{const{chromium:e}=await import("playwright-core");return s=await e.launch(),s}catch{return null}}const H="npx playwright-core install chromium-headless-shell";function X(){try{const e=l(_(c(import.meta.url).resolve("playwright-core/package.json")),"cli.js"),t=b(process.execPath,[e,"install","chromium-headless-shell"],{stdio:"ignore",detached:!0});t.on("error",()=>{}),t.unref()}catch{}}const L=()=>{try{const e=c(import.meta.url),t=c(e.resolve("decaframe"));return l(t.resolve("mermaid"),"..")}catch{return null}};async function B(e){const t=v((a,r)=>{const o=O(decodeURIComponent((a.url??"/").split("?")[0])).replace(/^([/\\])+/,""),i=l(e,o);if(!i.startsWith(e+N)||!k(i)){r.writeHead(404),r.end();return}r.writeHead(200,{"content-type":x(i)===".mjs"?"text/javascript":"text/plain"}),$(i).pipe(r)});await new Promise(a=>t.listen(0,"127.0.0.1",a)),m=t;const n=t.address();return`http://127.0.0.1:${typeof n=="object"&&n?n.port:0}`}async function p(){return u??=(async()=>{const e=L();if(!e)return null;try{const t=await h();if(!t)return null;const n=await t.newPage(),a=await B(e);return await n.goto(`${a}/blank`),await n.evaluate(`import('${a}/mermaid.esm.min.mjs').then((m) => {
|
|
2
|
+
window.__mermaid = m.default
|
|
3
|
+
window.__mermaid.initialize({ startOnLoad: false, securityLevel: 'strict' })
|
|
4
|
+
window.__ready = true
|
|
5
|
+
}).catch((e) => { window.__failed = String(e) })`),await n.waitForFunction("window.__ready === true || window.__failed",void 0,{timeout:3e4}),await n.evaluate("window.__failed")?null:n}catch{return null}})(),u}async function Y(e){const t=await p();if(!t)return null;try{return await t.evaluate(`(async () => {
|
|
6
|
+
try {
|
|
7
|
+
const r = await window.__mermaid.parse(${JSON.stringify(e)})
|
|
8
|
+
return { ok: true, kind: r && r.diagramType }
|
|
9
|
+
} catch (e) {
|
|
10
|
+
const hash = e && e.hash
|
|
11
|
+
const line = hash && hash.loc && hash.loc.first_line
|
|
12
|
+
return {
|
|
13
|
+
ok: false,
|
|
14
|
+
message: String((e && e.message) || e),
|
|
15
|
+
line: typeof line === 'number' ? line : undefined,
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
})()`)}catch{return null}}const f=new Map;async function M(e,t,n){const a=f.get(t);if(a!==void 0)return a;const r=await e.evaluate(`(async () => {
|
|
19
|
+
const link = document.createElement('link')
|
|
20
|
+
link.rel = 'stylesheet'
|
|
21
|
+
link.href = ${JSON.stringify(t)}
|
|
22
|
+
const settled = new Promise((r) => { link.onload = () => r(true); link.onerror = () => r(false) })
|
|
23
|
+
document.head.append(link)
|
|
24
|
+
if (!(await settled)) return false
|
|
25
|
+
await document.fonts.ready
|
|
26
|
+
const matched = await document.fonts.load('400 16px "' + ${JSON.stringify(n)} + '"')
|
|
27
|
+
return matched.length > 0
|
|
28
|
+
})()`).catch(()=>!1);return f.set(t,r),r}const w=new Map;async function D(e,t){if(!t.vars)return null;const n=`${t.mode}|${JSON.stringify(t.vars)}`,a=w.get(n);if(a!==void 0)return a;const r=Object.entries(t.vars).map(([y,S])=>`--${y}: ${S};`).join(" "),o=await e.evaluate(`(() => {
|
|
29
|
+
const resolveRoles = ${P.toString()}
|
|
30
|
+
const host = document.createElement('div')
|
|
31
|
+
host.setAttribute('style', ${JSON.stringify(r)})
|
|
32
|
+
document.body.append(host)
|
|
33
|
+
try { return resolveRoles(host, ${JSON.stringify(C)}) } finally { host.remove() }
|
|
34
|
+
})()`),i=o?I(o,t.mode==="dark"):null;return w.set(n,i),i}async function Z(e){const t=await p();if(!t)return{problem:`this diagram could not be drawn, because no browser could be started to draw it. Diagrams are rendered by a headless browser that decaframe downloads once, in the background, when it starts; if it is still missing, run \`${H}\` and export again, and the picture will be in the file.`};const n=e.fontHref&&!await M(t,e.fontHref,e.fontFace)?e.fontFace:void 0,a=await D(t,e);return{...await t.evaluate(`(async () => {
|
|
35
|
+
const diagramConfig = ${j.toString()}
|
|
36
|
+
const cleanSvg = ${T.toString()}
|
|
37
|
+
try {
|
|
38
|
+
window.__mermaid.initialize(diagramConfig(${JSON.stringify(e.mode)}, ${JSON.stringify(e.fontFamily)}, ${JSON.stringify(a??void 0)}))
|
|
39
|
+
const out = await window.__mermaid.render(${JSON.stringify(e.id)}, ${JSON.stringify(e.source)})
|
|
40
|
+
return { svg: cleanSvg(out.svg, ${JSON.stringify(F)}, ${JSON.stringify(E)}) }
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return { problem: String((e && e.message) || e).trim() || 'this diagram could not be drawn.' }
|
|
43
|
+
} finally {
|
|
44
|
+
|
|
45
|
+
const scratch = document.getElementById(${JSON.stringify(e.id)})
|
|
46
|
+
if (scratch) scratch.remove()
|
|
47
|
+
}
|
|
48
|
+
})()`),...n?{fontMissing:n}:{}}}async function g(){return d??=(async()=>{const e=await h();if(!e)return null;try{return await e.newPage({viewport:{width:1400,height:900}})}catch{return null}})(),d}async function q(e,t){const n=await g();if(!n)return{};try{return await n.setContent(e,{waitUntil:"load"}),await n.evaluate("document.fonts.ready"),await n.evaluate("new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r(null))))"),await n.evaluate(`(() => {
|
|
49
|
+
const out = {}
|
|
50
|
+
for (const id of ${JSON.stringify(t)}) {
|
|
51
|
+
const plot = document.querySelector('.dfx-deck .blk-chart[data-id="' + id + '"] .chart-plot')
|
|
52
|
+
if (plot && plot.offsetWidth > 0 && plot.offsetHeight > 0) {
|
|
53
|
+
out[id] = { width: plot.offsetWidth, height: plot.offsetHeight }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out
|
|
57
|
+
})()`)}catch{return{}}}async function ee(e,t,n=!0){const a=await g();if(!a)return{problem:"no browser is available in this process, so fit was not measured."};try{await a.setContent(e,{waitUntil:"load"}),await a.evaluate(`(() => {
|
|
58
|
+
const s = document.createElement('style')
|
|
59
|
+
s.textContent = '.dfx-fit { transform: none !important; position: static !important; }'
|
|
60
|
+
document.head.append(s)
|
|
61
|
+
})()`),await a.evaluate("new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r(null))))");const r=await a.evaluate(`(async () => {
|
|
62
|
+
await document.fonts.ready
|
|
63
|
+
const out = []
|
|
64
|
+
for (const f of ${JSON.stringify(t)}) {
|
|
65
|
+
try {
|
|
66
|
+
const matched = await document.fonts.load('400 16px "' + f + '"')
|
|
67
|
+
if (!matched.length) out.push(f)
|
|
68
|
+
} catch { out.push(f) }
|
|
69
|
+
}
|
|
70
|
+
return out
|
|
71
|
+
})()`);if(r.length)return{problem:`the fonts did not load (${r.join(", ")}), and type at fallback metrics measures a different page \u2014 fit was not measured rather than measured wrongly.`};const o=await a.evaluate("(() => { const el = document.querySelector('.dfx-deck .page'); return el ? Math.round(el.getBoundingClientRect().width) : 0 })()");if(o&&o!==1152)return{problem:`the frame measured ${o}px where the editor draws 1152px, so every reading would be wrong by the same ratio \u2014 fit was not measured.`};const i=await a.evaluate(`(() => {
|
|
72
|
+
${A()}
|
|
73
|
+
const deck = document.querySelector('.dfx-deck')
|
|
74
|
+
if (!deck) return { pages: [], fill: [], scale: [] }
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
pages: readFit(deck),
|
|
78
|
+
fill: readFill(deck, 120),
|
|
79
|
+
scale: ${n} ? readScale(deck, ${J}, ${R}) : [],
|
|
80
|
+
}
|
|
81
|
+
})()`);return i.pages.length?{pages:i.pages,fill:i.fill,scale:i.scale}:{problem:"the rendered document held no pages to measure."}}catch(r){return{problem:`fit could not be measured: ${String(r?.message??r)}`}}}async function te(){const e=s;s=null,u=null,d=null,f.clear(),w.clear(),m?.close(),m=null,await e?.close().catch(()=>{})}export{H as BROWSER_INSTALL,te as closeBrowser,Z as drawForExport,X as ensureBrowser,ee as measureFit,q as measurePlots,Y as parseDiagram};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{createReadStream as b}from"node:fs";import{realpath as y,stat as S}from"node:fs/promises";import{createServer as O}from"node:http";import{extname as j,join as N,normalize as $,resolve as J,sep as E}from"node:path";import{exportReport as H}from"decaframe/export";import{exportDrawn as L}from"./export.js";import{docFileName as v}from"./session.js";const w=8*1024*1024,B=/^(?:localhost|127(?:\.\d{1,3}){3}|\[::1\])(?::\d{1,5})?$/i,x=e=>B.test((e??"").trim()),C=e=>{if(e===void 0)return!0;try{return x(new URL(e).host)}catch{return!1}},c=(e,h,s)=>{const n=JSON.stringify(s);e.writeHead(h,{"content-type":"application/json","content-length":Buffer.byteLength(n)}),e.end(n)};function P(e){return new Promise((h,s)=>{let n=0;const i=[];e.on("data",d=>{if(n+=d.length,n>w){s(new Error(`body exceeds ${w} bytes`)),e.destroy();return}i.push(d)}),e.on("end",()=>h(Buffer.concat(i).toString("utf8"))),e.on("error",s)})}const R={".html":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".webp":"image/webp",".gif":"image/gif",".ico":"image/x-icon",".woff2":"font/woff2"};async function T(e,h,s){let n;try{n=decodeURIComponent(h)}catch{return!1}const i=$(n==="/"?"/index.html":n);let d,m;try{d=await y(N(e,i));const l=await y(e);if(d!==l&&!d.startsWith(l+E))return!1;const p=await S(d);if(!p.isFile())return!1;m=p.size}catch{return!1}return s.writeHead(200,{"content-type":R[j(d).toLowerCase()]??"application/octet-stream","content-length":m,"cache-control":"no-store"}),b(d).pipe(s),!0}function A(e,h,s={}){const n=s.serve?J(s.serve):null,i=new Set,d=r=>{for(const t of i)try{t.write(`event: change
|
|
2
|
+
data: ${JSON.stringify({revision:r})}
|
|
3
|
+
|
|
4
|
+
`)}catch{i.delete(t)}},m=e.onChange(d),l=O((r,t)=>{p(r,t).catch(a=>{const o=a.message;console.error(`decaframe channel: ${o}`),t.headersSent?t.end():c(t,500,{error:o})})});async function p(r,t){const a=(r.url??"/").split("?")[0];if(!x(r.headers.host)){c(t,403,{error:`refused: Host '${r.headers.host??""}' is not this machine`});return}if(!C(r.headers.origin)){c(t,403,{error:`refused: Origin '${r.headers.origin}' is not a page served on this machine`});return}if(r.method==="GET"&&a==="/boot"){e.sync(),c(t,200,{doc:e.editor.getJSON(),channel:!0,name:v(e.path)});return}if(r.method==="GET"&&a==="/document"){e.sync(),c(t,200,{revision:e.revision,doc:e.editor.getJSON()});return}if(r.method==="GET"&&a==="/events"){e.sync(),t.writeHead(200,{"content-type":"text/event-stream","cache-control":"no-cache",connection:"keep-alive"}),t.write(`event: hello
|
|
5
|
+
data: ${JSON.stringify({revision:e.revision,doc:e.editor.getJSON()})}
|
|
6
|
+
|
|
7
|
+
`),i.add(t),t.on("close",()=>i.delete(t));return}if(r.method==="GET"&&a==="/export"){e.sync();let o;try{o=await L(e.editor.getJSON(),v(e.path))}catch(u){c(t,500,{error:`export failed: ${u.message}`});return}const f=H(o);t.writeHead(200,{"content-type":"text/html; charset=utf-8","content-length":Buffer.byteLength(o),"x-export-report":encodeURIComponent(JSON.stringify(f))}),t.end(o);return}if(r.method==="POST"&&a==="/document"){const o=await P(r);let f;try{f=JSON.parse(o)}catch(g){c(t,400,{error:`body is not valid JSON: ${g.message}`});return}if(typeof f.revision!="number"||typeof f.doc!="object"||f.doc===null){c(t,400,{error:"expected { revision: number, doc: object }"});return}let u;try{u=e.replace(f.doc,f.revision)}catch(g){c(t,400,{error:`document rejected, nothing changed: ${g.message}`});return}if(!u){c(t,409,{revision:e.revision,doc:e.editor.getJSON()});return}c(t,200,{revision:e.revision});return}r.method==="GET"&&n&&await T(n,a,t)||c(t,404,{error:`no route ${r.method} ${a}`})}return new Promise((r,t)=>{const a=o=>{m(),t(o)};l.once("error",a),l.listen(h,"127.0.0.1",()=>{l.off("error",a),r({port:l.address().port,close:()=>new Promise(o=>{m();for(const f of i)f.end();i.clear(),l.close(()=>o())})})})})}export{A as startChannel};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{exportDocument as t}from"decaframe/export";import{drawForExport as m,measurePlots as a}from"./browser.js";const x=(r,o)=>t(r,{diagram:m,chartBox:a,fileName:o});export{x as exportDrawn};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{readFileSync as m,writeFileSync as d}from"node:fs";import{resolve as l}from"node:path";import{exportReport as g}from"decaframe/export";import{missMessage as h}from"decaframe";import{closeBrowser as i}from"./browser.js";import{exportDrawn as f}from"./export.js";import{resolveExportPath as w}from"./open.js";import{docFileName as u}from"./session.js";const t=l(process.argv[2]??process.env.DECAFRAME_DOC??"document.json"),o=w(process.argv[3]??t.replace(/\.json$/,"")+".html");o.ok||(process.stderr.write(`${o.message}
|
|
2
|
+
`),process.exit(1));const n=o.path;let s;try{s=await f(JSON.parse(m(t,"utf8")),u(t))}catch(e){process.stderr.write(`export failed: ${e.message}
|
|
3
|
+
`),await i(),process.exit(1)}d(n,s),await i();const $=(s.match(/class="dfx-slide"/g)??[]).length,{missingImages:r,themeFallback:p,themeFallbackReason:x,diagramProblems:a,diagramFontMissing:c}=g(s);if(process.stdout.write(`${n}
|
|
4
|
+
${$} pages, ${(s.length/1024).toFixed(0)} kB
|
|
5
|
+
`),p&&process.stdout.write(`${h({reason:x??"unknown",kind:"theme",name:p})}
|
|
6
|
+
`),r.length){process.stdout.write(`${r.length} picture(s) MISSING, shown as a placeholder:
|
|
7
|
+
`);for(const e of r)process.stdout.write(` ${e}
|
|
8
|
+
`)}if(a.length){process.stdout.write(`${a.length} diagram(s) NOT DRAWN, shown as a note:
|
|
9
|
+
`);for(const e of a)process.stdout.write(` ${e}
|
|
10
|
+
`)}c&&process.stdout.write(`'${c}' did not load where the diagrams were drawn, so their boxes are sized for a substitute face. Check the network and export again.
|
|
11
|
+
`);
|
package/dist/mcp/fit.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{exportHTML as i,loadBlocksCSS as s,themesIn as a}from"decaframe/export";import{fontsFor as d}from"decaframe";import{drawForExport as f,measureFit as u}from"./browser.js";async function r(t){let e;try{e=await i({doc:t,css:s(),diagram:f,emit:{}})}catch(o){return{problem:`the document could not be rendered for measurement: ${String(o?.message??o)}`}}const n=[...new Set(d(a(t)).map(o=>o.family))];return u(e,n,t.attrs?.autoFit!==!1)}const x=(t,e)=>t.pages?.find(n=>n.id===e),w=(t,e)=>t.fill?.find(n=>n!==null&&n.id===e),y=(t,e)=>t.scale?.find(n=>n.id===e),b=(t,e)=>t===void 0||t>=1||e?void 0:`advisory: this page does not fit at full size, so it is drawn at about ${Math.round(t*100/5)*5}% to fit its frame. Auto-fit is on for this document \u2014 a person turns that off, no tool does.`,p=25,m=45,F=t=>{if(!t||t.painted)return;const e=t.ink<p,n=t.gap>m;if(!(!e&&!n))return`advisory, no action required: this page uses ${t.ink}% of its frame and its largest empty band is ${t.gap}% of the height. Sparse can be deliberate \u2014 a divider or a single quotation reads this way on purpose. Nothing is wrong with the document and nothing here needs fixing.`};async function c(t,e){const n=(t.content??[]).find(o=>o.attrs?.id===e);return n?r({...t,content:[n]}):{problem:`page '${e}' is not in the document, so fit was not measured.`}}const I=(t,e)=>e===void 0?r(t):c(t,e);export{m as GAP_CEILING,p as INK_FLOOR,F as fillAdvisory,r as fitOf,c as fitOfPage,I as measureInBrowser,w as pageFillIn,x as pageFitIn,y as pageScaleIn,b as scaleAdvisory};
|
package/dist/mcp/open.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{existsSync as s,statSync as r}from"node:fs";import{dirname as c,extname as a,resolve as i}from"node:path";function u(t){const e=i(t);if(a(e).toLowerCase()!==".json")return{ok:!1,code:"notADocument",message:`'${t}' is not a .json file. A document is stored as JSON, and refusing anything else is what stops a typo overwriting a file that was never a document. An exported .html is an artifact rather than the document. Nothing was opened and nothing was written.`};const o=d(e,t,"opened or saved");if(o)return o;const n=s(e);return n&&!r(e).isFile()?{ok:!1,code:"notADocument",message:`'${t}' is not a file. Nothing was opened.`}:{ok:!0,path:e,existed:n}}function d(t,e,o){const n=c(t);return s(n)&&r(n).isDirectory()?null:{ok:!1,code:"noSuchFolder",message:`'${n}' does not exist, so '${e}' cannot be ${o} there. This creates no folders \u2014 a mistyped path would otherwise scatter empty directories. Nothing was opened and nothing was written. Pass a folder that exists, or a bare file name.`}}function l(t){const e=i(t),o=a(e).toLowerCase();if(o!==".html"&&o!==".htm")return{ok:!1,code:"notAnExport",message:`'${t}' is not an .html file. An export is written as HTML, and refusing any other name is what stops a mistyped path overwriting a file that was never an export. Nothing was written.`};const n=d(e,t,"written");return n||(s(e)&&!r(e).isFile()?{ok:!1,code:"notAnExport",message:`'${t}' is not a file. Nothing was written.`}:{ok:!0,path:e})}const p=(t,e)=>`'${t}' could not be read as a document: ${e.message}.`;export{u as resolveDocumentPath,l as resolveExportPath,p as unreadableDocument};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as p}from"node:child_process";import{existsSync as a}from"node:fs";import{dirname as m,resolve as l}from"node:path";import{fileURLToPath as d}from"node:url";import{startChannel as f}from"./channel.js";import{resolveDocumentPath as h,unreadableDocument as u}from"./open.js";import{Session as g}from"./session.js";const i=l(m(d(import.meta.url)),"../../editor");function w(){return(process.platform==="darwin"?["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge","/Applications/Brave Browser.app/Contents/MacOS/Brave Browser","/Applications/Chromium.app/Contents/MacOS/Chromium"]:process.platform==="win32"?[`${process.env.ProgramFiles??""}\\Google\\Chrome\\Application\\chrome.exe`,`${process.env["ProgramFiles(x86)"]??""}\\Google\\Chrome\\Application\\chrome.exe`,`${process.env.ProgramFiles??""}\\Microsoft\\Edge\\Application\\msedge.exe`]:["/usr/bin/google-chrome","/usr/bin/chromium","/usr/bin/chromium-browser","/usr/bin/microsoft-edge","/usr/bin/brave-browser"]).find(o=>o&&a(o))??null}const C=()=>process.platform==="darwin"?["open",[]]:process.platform==="win32"?["cmd",["/c","start",""]]:["xdg-open",[]];function $(e){const o=w(),[n,t]=o?[o,[`--app=${e}`]]:(()=>{const[r,s]=C();return[r,[...s,e]]})();try{const r=p(n,t,{detached:!0,stdio:"ignore"});r.on("error",s=>{console.error(`decaframe: could not open a browser (${s.message}). Open this yourself:`),console.error(` ${e}`)}),r.unref()}catch(r){console.error(`decaframe: could not open a browser (${r.message}). Open this yourself:`),console.error(` ${e}`)}}async function S(e){e.length>1&&(console.error(`deca open takes one file (got ${e.join(" ")})`),process.exit(2));const o=h(e[0]??process.env.DECAFRAME_DOC??"document.json");o.ok||(console.error(o.message),process.exit(2));let n;try{n=new g(o.path)}catch(c){console.error(`${u(e[0]??o.path,c)} Nothing was opened.`),process.exit(1)}a(i)||(console.error(`decaframe: no editor bundle at ${i}. In this repo, run \`pnpm build\` first.`),process.exit(1));const t=await f(n,0,{serve:i}),r=n.watchFile(),s=`http://127.0.0.1:${t.port}/`;console.error(`decaframe \xB7 ${o.path}`),console.error(`decaframe \xB7 ${s}`),console.error("decaframe \xB7 Ctrl-C to close"),process.env.DECAFRAME_NO_BROWSER||$(s);for(const c of["SIGINT","SIGTERM"])process.once(c,()=>{r(),t.close().then(()=>process.exit(0))})}export{S as runOpen};
|
package/dist/mcp/read.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{byNode as o,chromeOf as f,propsOf as i,slotText as m}from"decaframe";const c=t=>t.items??t.stats??t.media??[],b=t=>c(i(t)),y=t=>{const a=i(t);return a.content?{content:a.content}:a.table?{table:a.table}:a.chart?{chart:a.chart,...x(t)}:a.diagram?{diagram:a.diagram}:{entries:c(a)}},p=[["title","chartTitle"],["axisX","chartAxisX"],["axisY","chartAxisY"]];function x(t){const a={};return t.forEach(r=>{const s=p.find(([,n])=>n===r.type.name);if(!s)return;const e=m(r);e&&(a[s[0]]=e)}),a}function u(t){const a=[];return t.forEach(r=>{if(r.type.name==="layout"){a.push({id:r.attrs.id,type:"layout",archetype:"layout",layout:r.attrs.layout,regions:(()=>{const e=[];return r.forEach(n=>e.push({id:n.attrs.id,region:n.attrs.region,blocks:u(n)})),e})()});return}const s=o(r.type.name);s&&a.push({id:r.attrs.id,type:s.type,archetype:s.archetype,...r.attrs.variant?{variant:r.attrs.variant}:{},...r.attrs.arrangement?{arrangement:r.attrs.arrangement}:{},...r.attrs.figure?{arrangement:r.attrs.figure}:{},...r.attrs.iconPosition?{iconPosition:r.attrs.iconPosition}:{},...r.attrs.mark?{mark:r.attrs.mark}:{},...r.attrs.stack&&r.attrs.stack!=="none"?{stack:r.attrs.stack}:{},...y(r)})}),a}function k(t){const a=u(t);return{id:t.attrs.id,...t.attrs.mode?{mode:t.attrs.mode}:{},...t.attrs.accent?{accent:t.attrs.accent}:{},...t.attrs.surface?{surface:t.attrs.surface}:{},...t.attrs.notes?{notes:t.attrs.notes}:{},...t.firstChild?.type.name==="pageAccent"&&t.firstChild.attrs.src?{accentSrc:t.firstChild.attrs.src}:{},...f(t),blocks:a}}const E=t=>{const a=[];return t.forEach(r=>a.push(k(r))),{title:t.attrs.title,format:t.attrs.format,align:t.attrs.align,autoFit:t.attrs.autoFit!==!1,theme:t.attrs.theme,mode:t.attrs.mode,pages:a}},O=t=>{const a=[];return t.forEach(r=>a.push(r.attrs.id)),a},T=t=>{const a=[];return t.forEach(r=>{o(r.type.name)&&a.push(r.attrs.id)}),a},d=(t,a)=>{const r=new Set(t);return a.find(s=>!r.has(s))};export{d as added,T as blockIdsOn,E as documentView,b as entriesOfBlock,O as pageIds,k as pageView};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import{writeFileSync as Tt}from"node:fs";import{McpServer as St}from"@modelcontextprotocol/sdk/server/mcp.js";import{z as o}from"zod";import{ALIGNMENTS as Et,ARCHETYPES as we,CORE_VARIANTS as Ge,DEFAULT_VARIANT as Ot,FORMATS as xt,ICON_NAMES as Rt,ICON_SHELVES as Nt,PAGE_SURFACES as Pt,PAPER_FORMATS as jt,ICON_POSITIONS as ie,LINE_KINDS as se,LIST_NODES as Ke,nearestIcon as It,resolveIcon as Ye,activeRegistry as qe,byNode as W,defaultThemeId as Lt,describeLook as Dt,isBlockNode as Mt,resolveBlock as Ct,resolveTemplate as ze,themeOrDefault as Xe,variantGloss as Bt,variantsFor as ce,blocksFromMarkdown as Ht,blocksOnly as Ft,directiveProblem as Ut,pagesFromMarkdown as Vt,entryDrawsNothing as Wt,fieldsFor as ne,findById as ae,fitDiagnostic as Je,groupLines as Gt,lintDocument as Kt,lintPage as Yt,mediaFramingProblem as qt,mediaKindOf as zt,mediaSrcProblem as Qe,payloadKeyFor as Xt,requiredFor as Ze,seriesProblem as Jt,tableProblem as Qt,textSlotsFor as Zt,MAX_SERIES as en,CHART_STACKS as tn,LAYOUTS as ke,layoutNamed as et,crampedHint as tt,DIAGRAM_KINDS as nn,restateLine as an,sourceProblem as on}from"decaframe";import{closeBrowser as rn,parseDiagram as sn}from"./browser.js";import{fillAdvisory as cn,measureInBrowser as ln,pageFillIn as dn,pageFitIn as hn,pageScaleIn as un,scaleAdvisory as pn}from"./fit.js";import{exportReport as mn}from"decaframe/export";import{exportDrawn as gn}from"./export.js";import{resolveDocumentPath as fn,resolveExportPath as bn,unreadableDocument as yn}from"./open.js";import{docFileName as wn}from"./session.js";import{added as J,blockIdsOn as j,documentView as kn,pageIds as V,pageView as vn}from"./read.js";const Q=(n,s=!1)=>({content:[{type:"text",text:JSON.stringify(n,null,2)}],...s?{isError:!0}:{}}),K=(n={})=>Q({ok:!0,...n}),d=(n,s)=>Q({ok:!1,code:n,message:s},!0),$n=([n,s])=>n===s?`exactly ${n} item${n===1?"":"s"}`:`${n}\u2013${s} items`,le=o.string().optional().describe("Echo an id from get_document to update that entry in place. Omit to create one."),_n=o.looseObject({id:le,meta:o.string().optional().describe("Secondary label \u2014 a date, phase or category."),title:o.string().optional().describe("The entry heading."),body:o.string().optional().describe('A sentence or two under the title. Markdown: a newline is a line break, a blank line starts a new paragraph, and lines led by "- " or "1. " become a list.'),icon:o.string().nullable().optional().describe("Icon name. Only blocks whose `renders` lists icon will draw it."),emphasis:o.boolean().nullable().optional().describe("Set true on the ONE entry this page is about; its siblings then recede. Not ordinal \u2014 the entry that matters is often not the first. Leave it off entirely when none stands out.")}),An=o.looseObject({id:le,label:o.string().optional().describe('What the figure measures, e.g. "Median query".'),value:o.string().optional().describe('REQUIRED. The large figure itself, e.g. "340ms".'),note:o.string().optional().describe("A qualifying line under the figure.")}),Tn=o.looseObject({id:le,src:o.string().optional().describe("REQUIRED. The full https:// address of an image that is already online. There is no upload here, and a file path, a relative path or a data: URL is refused."),alt:o.string().optional().describe("What the picture shows, for someone who cannot see it. Not printed on the page."),caption:o.string().optional().describe("A line printed under the picture, read by everyone."),title:o.string().optional().describe("Only blocks whose `renders` lists title will draw it."),body:o.string().optional().describe("Only blocks whose `renders` lists body will draw it."),focusX:o.number().nullable().optional().describe("Framing, a HUMAN's: how far across the picture the part worth showing is, 0 to 100. Pass back whatever get_document gave you. Do not invent one \u2014 you cannot see the photograph."),focusY:o.number().nullable().optional().describe("Framing, a HUMAN's: how far down the picture, 0 to 100. Pass back what you were given."),zoom:o.number().nullable().optional().describe("Framing, a HUMAN's: how close in, 1 or more (1 fills the frame). Pass back what you were given. Omit all three and the middle of the picture shows, which is the normal case.")}),ve={items:_n,stats:An,media:Tn},$e=(n={})=>o.looseObject({id:le,kind:o.string().optional().describe(n.existing?"The block's own kind, if you echo it \u2014 a different one is refused, because changing what a block IS is set_block_type. Omit it and nothing changes.":`One of: ${se.join(", ")}. Omit for an ordinary paragraph ('text').`),text:o.string().optional().describe("The line itself, as plain text. No markdown, no HTML \u2014 the theme owns the look. A newline is a real line break. A 'separator' takes none and is refused if you send one."),attribution:o.string().optional().describe("A quote's source, on the SAME line as its text \u2014 only the 'quote' kind draws it. Echo it back on an update or it is removed."),checked:o.boolean().optional().describe("Whether a 'checklist' line is ticked. Defaults to unticked; only that kind stores it. Echo it back on an update or the tick is cleared."),level:o.number().int().optional().describe("A heading's level, 1 to 6, exactly as markdown's # to ###### are. Defaults to 3. Only 'heading' draws it. Echo it back on an update or the heading returns to 3."),language:o.string().nullable().optional().describe("A code fence's language, such as js, python or sql. Only the 'code' kind stores it. Nothing colours it (there is no syntax highlighter); it travels with the document and out to markdown again."),depth:o.number().int().optional().describe("How deep a list item is nested. 0 is the outermost and is the default. A depth more than one deeper than the line before it is clamped, never refused. Only list kinds use it."),arrangement:o.string().nullable().optional().describe("This block's shape, where its kind has more than one \u2014 "+On()+". Omit to leave it alone; null clears it back to the default.")}),Sn=`A whole passage, in order \u2014 ONE CALL, however many paragraphs. Each line is {kind, text} and each becomes its own block: ${se.join(", ")}. Consecutive \`bullets\` (or \`numbered\`) lines join into a single list; everything else stands alone, so two \`text\` lines are two paragraphs that can be moved and restyled separately. A quote line carries its source in its own \`attribution\` field. THIS IS WHERE MARKDOWN LANDS: a heading takes a \`level\` 1\u20136, a \`separator\` is \`---\` and takes no text, a \`code\` line takes a \`language\`, and a nested list item takes a \`depth\`. To write a whole markdown document at once, use import_markdown instead of this.`,En="This block's text. A paragraph, heading or quote holds exactly ONE line, so send one \u2014 to add another, call add_prose. A list holds its items: echo an id from get_document to change that item, omit the id to add one, leave an id out to remove it. To change what a block IS \u2014 a paragraph into a bullet, say \u2014 use set_block_type, not this.",On=()=>T().filter(n=>n.archetype==="prose"&&n.arrangements.length>0).map(n=>`${n.type} takes ${n.arrangements.map(s=>s.name).join(", ")}`).join("; "),nt=n=>{const s=n.match(/^.*?[.!?](?=\s|$)/s);return(s?s[0]:n).trim()},at=n=>T().filter(s=>s.archetype===n).map(s=>`${s.type} \u2014 ${nt(s.whenToUse)}`).join(" | "),ot=n=>{const s=ne(n).filter(v=>v!=="id"),w=Ze(n);return`Entries for the '${n}' archetype: ${s.join(", ")}${w.length?` \u2014 ${w.join(", ")} required`:" \u2014 none required"}. A block only DRAWS the slots in its \`renders\` list (see list_blocks); the rest are stored and reappear if you swap the block type.`},xn=()=>{const n=Xe(Lt()).theme,s=T().filter(v=>v.variantSlots.length>0),w=v=>s.filter($=>ce($.variantSlots,n,{core:!0}).includes(v));return Ge.map(v=>{const $=w(v),O=$.length<s.length?` (${$.map(f=>f.type).join(", ")})`:"";return`${v} \u2014 ${Bt(v)}${O}`}).join(" | ")},C=()=>({variant:o.string().nullable().optional().describe(`The block's style, a NAME the theme paints alike under every theme: ${xn()}. Omit to leave styling alone; pass '' for '${Ot}'.`)}),Rn=([n,s])=>n===s?`${n}`:`${n}\u2013${s}`,Nn=n=>T().filter(s=>s.archetype===n&&(s.arrangements.length||s.figures?.length)).map(s=>{const w=s.arrangements.map($=>$.name).join(", "),v=(s.figures??[]).map($=>`${$.name} (${Rn($.countRange)})`).join(", ");return`${s.type}: ${w}${v?`; or a FIGURE \u2014 ${v}`:""}`}).join(" | "),rt=n=>({arrangement:o.string().nullable().optional().describe(`The SHAPE the block lays its entries out in \u2014 same fields, same count, a different layout, and a shape changes how much text fits. Per block: ${Nn(n)}. A figure is an illustrated shape whose count range is a LIMIT rather than guidance \u2014 a write outside it is refused \u2014 and a block wears one shape OR one figure, never both. Omit to leave it alone; pass '' for the default.`)}),Pn=n=>T().filter(s=>s.archetype===n).flatMap(s=>s.arrangements.map(w=>`${w.name} \u2014 ${nt(w.whenToUse)}`)).join(" | "),it=()=>({arrangement:o.string().nullable().optional().describe(`WHICH KIND of chart to draw. ${Pn("chart")} Omit to leave it alone; pass '' for the default.`)}),de=n=>T().some(s=>s.archetype===n&&(s.arrangements.length>0||s.figures?.length)),st={iconPosition:o.string().nullable().optional().describe(`Where a block's icons sit: ${ie.join(", ")}. 'lead' puts it in a gutter beside the title, 'top' above it \u2014 a gutter spends width, a stacked icon spends height, so this changes how much text fits. Omit to leave it alone; pass '' for the shape's own default. REFUSED on a block that draws no icon \u2014 check \`renders\` in list_blocks first, since a block may store an icon it never shows.`)},ct=n=>T().some(s=>s.archetype===n&&s.slots.includes("icon")),lt=n=>T().filter(s=>s.archetype===n&&s.marks.length>0).map(s=>`${s.type}: ${s.marks.join(", ")}`).join(" | "),dt=n=>({mark:o.string().nullable().optional().describe(`What this block's items are marked with. Per block, the first named being its default \u2014 ${lt(n)}. \`icon\` puts each item's own icon in the mark's place; SETTING IT FILLS IN A STARTER ICON on any entry that has none, so choose each entry's \`icon\` yourself unless you want them identical. Omit to leave it alone; pass '' for the block's default. REFUSED where nothing is marked.`)}),ht=n=>T().some(s=>s.archetype===n&&s.marks.length>0),oe="A page id from get_document \u2014 or a layout REGION id, to put this block inside one column of a layout. add_layout returns its region ids.",B=n=>T().some(s=>s.archetype===n&&s.variantSlots.length>0),ut={title:o.string().optional().describe("The page's heading. Pass '' to remove it."),eyebrow:o.string().optional().describe("Small label above the title \u2014 section or number. Pass '' to remove it."),subtitle:o.string().optional().describe("A line under the title. Pass '' to remove it.")},pt={mode:o.enum(["light","dark"]).nullable().optional().describe("Override the document's light/dark for THIS page only. null to go back to inheriting."),notes:o.string().nullable().optional().describe("The presenter's own text for this page, as markdown \u2014 never shown on the slide itself, only in the presenter view. Plain **bold** and lists work; pictures do not. null removes it.")},jn={accent:o.enum(["top","lead","trail","full"]).nullable().optional().describe("Where a full-bleed picture sits on this page. `lead` and `trail` DIVIDE the page \u2014 the picture owns that side and text cannot enter it. `top` is a banner across the frame; use it for a WIDE photograph, because a full-height column crops a landscape badly. `full` puts the picture behind everything with the text over it. null removes the accent AND its picture. Set accentSrc in the same call."),accentSrc:o.string().nullable().optional().describe("The full web address of the accent picture \u2014 one that is ALREADY online. Ignored unless the page has an accent."),accentAlt:o.string().nullable().optional().describe("What the accent picture shows, for anyone who cannot see it."),surface:o.enum(Pt).nullable().optional().describe("The page's own background, as a NAME the theme resolves \u2014 so swapping the theme repaints it. null goes back to the theme's ordinary paper.")},mt=()=>qe().entries.filter(n=>n.kind==="block").map(n=>n.name),G=n=>{const s=Ct(n);return s.ok?s.value:void 0},gt=()=>qe().entries.filter(n=>n.kind==="template").flatMap(n=>{const s=ze(n.name);return s.ok?[s.value]:[]}),T=()=>mt().flatMap(n=>{const s=G(n);return s?[s]:[]}),In=()=>mt().join(", "),Ln={items:"entries",stats:"entries",media:"entries",prose:"content",table:"table",chart:"chart",diagram:"diagram"};function _e(n,s){const w=n.archetype,v=ne(w),$=new Set(v);for(const[O,f]of s.entries()){for(const k of Object.keys(f))if(!$.has(k))return`entries[${O}] has no slot '${k}'. The '${w}' archetype takes exactly: ${v.join(", ")}. Put the text in one of those, or pick a block whose archetype has the slot you want (list_blocks shows each block's archetype).`;const x=f.icon;if(typeof x=="string"&&x!==""&&!Ye(x)){const k=It(x);return`entries[${O}] asks for icon '${x}', which does not exist. `+(k?`Did you mean '${k}'? `:"")+`The icons are: ${Rt.join(", ")}. Pick the closest one, or omit 'icon' entirely \u2014 a block that renders no icon is a normal thing to have.`}if(w==="media"){const k=Qe(f.src,zt(n.type));if(k)return`entries[${O}]: ${k}`;const _=qt(f);if(_)return`entries[${O}]: ${_}`}for(const k of Ze(w)){const _=f[k];if(typeof _!="string"||_==="")return`entries[${O}] is missing required '${k}'. Every entry in the '${w}' archetype needs it \u2014 e.g. {"label":"Median query","value":"340ms"}.`}const y=Zt(w);if(Wt(w,f))return`entries[${O}] has no content. An entry in the '${w}' archetype needs text in at least one of: ${y.join(", ")}. A decoration on its own \u2014 an icon, a state \u2014 draws a box with no words in it, so put the text in one of those slots, or send fewer entries.`}return null}const Ae=n=>n.map(s=>{const w=s.icon;if(typeof w!="string"||w==="")return s;const v=Ye(w);return v&&v!==w?{...s,icon:v}:s}),Te=(n,s)=>({[Xt(n)]:s});function Se(n,s={}){const w=s.kind!==!1,v=new Set(ne("prose"));for(const[$,O]of n.entries()){for(const y of Object.keys(O)){if(y==="kind"&&!w){const k=O.kind;if(k!=null&&k!==s.type)return`content[${$}] says kind '${String(k)}', but this block is a '${s.type}' and a block's kind is its TYPE once it exists. Nothing was written. Echo its own kind or omit it; use set_block_type to turn this block into another kind, or add_prose to put a new block of that kind on the page.`;continue}if(!v.has(y))return`content[${$}] has no field '${y}'. A prose line takes exactly: ${[...v].join(", ")}. Put the words in 'text' and the shape in 'kind'.`}const f=O.kind;if(f!==void 0&&!se.includes(f))return`content[${$}] has kind '${String(f)}', which does not exist. The kinds are: ${se.join(", ")}. Nothing was written \u2014 resend the line with one of those.`;const x=O.arrangement;if(x!=null){const y=f??s.type??"text",_=G(y)?.arrangements.map(R=>R.name)??[];if(!_.includes(x))return`content[${$}] has arrangement '${String(x)}', which '${y}' does not have. ${_.length?`Its shapes are: ${_.join(", ")}.`:"That block has no shapes at all \u2014 omit the field."} Nothing was written. Call list_blocks to see which blocks have shapes and what each is for.`}}return null}const Dn=`Decaframe authors visual documents \u2014 a sequence of fixed-size pages,
|
|
2
|
+
16:9 unless set_document says otherwise, each holding blocks that flow down it. You choose the
|
|
3
|
+
blocks and write their content.
|
|
4
|
+
|
|
5
|
+
START FROM A TEMPLATE: list_templates, then add_template_page for EVERY page. It ADDS a composed
|
|
6
|
+
page \u2014 heading, shape and design chosen \u2014 which you fill with set_page in one call. add_blank_page
|
|
7
|
+
is only for content no template suits. A page that merely fits is not finished: set each
|
|
8
|
+
block's variant on every set_page and vary it across the deck.
|
|
9
|
+
|
|
10
|
+
The theme owns every VALUE: no colours, fonts, sizes or coordinates appear here. The
|
|
11
|
+
NAMES are yours and you are expected to choose them: a style on a block, its shape, a page's accent
|
|
12
|
+
picture and surface, its eyebrow and subtitle.
|
|
13
|
+
|
|
14
|
+
Everything is addressed by stable id: call get_document for them. Never guess an id, and never pass
|
|
15
|
+
a position or an index as one.
|
|
16
|
+
|
|
17
|
+
A new document already has one page holding a PLACEHOLDER BLOCK, so your first get_document reports
|
|
18
|
+
a diagnostic you did not cause. Fill that page, or \u2014 if you added your own pages instead \u2014 delete it
|
|
19
|
+
with remove_page, which is also how any page you no longer want goes.
|
|
20
|
+
|
|
21
|
+
A page holds BLOCKS, never loose text \u2014 ordinary prose goes in a text block via add_prose.
|
|
22
|
+
|
|
23
|
+
Blocks are grouped into ARCHETYPES, the only schema to learn. Every block sharing one takes
|
|
24
|
+
identical content, so set_block_type swaps a look for free; each archetype has its own add_/update_
|
|
25
|
+
pair. get_document names a block's archetype; list_blocks the types; describe_block a worked example.
|
|
26
|
+
|
|
27
|
+
The document LINTS ITSELF and there is no tool to call for it. Diagnostics ride on the replies: each
|
|
28
|
+
names what is wrong, where, and the change that fixes it. Act before moving on \u2014 nothing else raises
|
|
29
|
+
it again. Pages are a fixed size, so text CAN overflow one; there is no character budget to write
|
|
30
|
+
against, because a page is measured after you change it. A reply carries a fit field only when that
|
|
31
|
+
check could NOT be made. No fit field means the page was measured and fits.`;function zn(n,s=ln){const w=new St({name:"decaframe",version:"0.1.0"},{instructions:Dn}),v=w.close.bind(w);w.close=async()=>{await rn(),await v()};const $=new Map,O=e=>[...$].filter(([,t])=>t.includes(e)).map(([t])=>t),f=(e,t,a)=>{const r=t.inputSchema??{},i=Object.keys(r);$.set(e,i),w.registerTool(e,{...t,inputSchema:o.looseObject(r)},(async c=>{const l=Object.keys(c??{}).filter(h=>!i.includes(h));if(l.length){const h=l.map(u=>[u,O(u)]).filter(([,u])=>u.length).map(([u,m])=>`'${u}' is an argument of ${m.join(" and ")}`);return d("unknownArgument",`${e} has no argument ${l.map(u=>`'${u}'`).join(", ")}. Nothing was written. It takes exactly: ${i.length?i.join(", "):"no arguments"}.`+(h.length?` ${h.join("; ")}.`:""))}return a(c)}))},x=(e,t,a)=>d("missingArgument",`${e} needs '${t}' and you sent none. Nothing was written. It takes: ${a}.`),y=()=>(n.sync(),n.editor.state.doc),k=e=>{const t=ae(y(),e);return t&&t.node.type.name==="page"?t.node:null},_=e=>{const t=ae(y(),e);return t&&W(t.node.type.name)?t.node:null},R=e=>{const t=ae(y(),e);if(!t)return null;const a=t.node.type.name;return a==="page"||a==="layoutRegion"?t.node:null},Z=e=>{const t=R(e);return t?t.type.name==="page"?t:H(y(),e):null},he=e=>{const t=[];return e.forEach(a=>{a.type.name==="layout"&&t.push(a.attrs.id)}),t},Ee=(e,t)=>{const a={};return t&&e.forEach(r=>{r.type.name!=="layout"||r.attrs.id!==t||r.forEach(i=>{a[i.attrs.region]=i.attrs.id})}),a},Oe=e=>d("unknownLayout",`No layout '${e}'. This document's layouts: ${ke.map(t=>t.name).join(", ")}.`),ee=e=>d("unknownPage",`No page or layout region '${e}'. Pages in this document: ${V(y()).join(", ")}. A layout's region ids come back from add_layout and from get_document. Call get_document for the current ids.`),Y=e=>d("unknownPage",`No page '${e}'. Pages in this document: ${V(y()).join(", ")}. Call get_document for the current ids.`),N=e=>{const t=[];return y().forEach(a=>t.push(...j(a),...he(a))),d("unknownBlock",`No block '${e}'. Blocks in this document: ${t.join(", ")}. Call get_document for the current ids.`)},q=()=>Xe(y().attrs.theme).theme,z=e=>d("unknownType",`No block type '${e}'. Available: ${In()}.`),F=(e,t)=>{if(e==null||e==="")return null;const a=ce(t.variantSlots,q(),{core:!0});return a.includes(e)?null:`'${t.type}' has no variant '${e}'. It offers: ${a.join(", ")}. Styles differ per block \u2014 a block only offers one it has somewhere to put, so a paragraph offers none and a callout page is a box of one entry wearing an accented edge. list_blocks gives each block its own list. A theme may also add names for people editing by hand; those are not on this surface, because they change with the theme and these do not.`},xe=e=>({arrangements:e.arrangements.map(t=>t.name),figures:(e.figures??[]).map(t=>t.name)}),te=(e,t)=>{if(e==null||e==="")return null;const{arrangements:a,figures:r}=xe(t);if(a.includes(e)||r.includes(e))return null;const i=[...a,...r];return`'${t.type}' has no shape '${e}'.`+(i.length?` It offers: ${i.join(", ")}. Each has its own count range \u2014 list_blocks names them, because a shape changes how much text fits.`+(r.length?` ${r.join(", ")} ${r.length>1?"are figures":"is a figure"} \u2014 an illustrated shape whose count range is a LIMIT, not guidance: a write outside it is refused. describe_block gives the range.`:""):" It has one shape, so omit the field.")},ue=(e,t,a)=>{if(e==null||e==="")return null;const r=(t.figures??[]).find(l=>l.name===e);if(!r)return null;const[i,c]=r.countRange;return a>=i&&a<=c?null:`'${e}' is a figure and holds ${i}\u2013${c} items, not ${a}. A figure draws a fixed number of places to stand, so unlike an arrangement \u2014 whose count range is guidance \u2014 this is refused rather than reported. Send a count in range, or pick a plain arrangement from describe_block.`},pe=(e,t,a)=>xe(a).figures.includes(t)?n.editor.commands.setFigure(e,t):n.editor.commands.setArrangement(e,t),me=(e,t)=>e==null||e===""?null:ie.includes(e)?t.slots.includes("icon")?t.marks.includes("icon")?`'${t.type}' puts each item's icon in its MARK \u2014 the numeral's own place \u2014 so there is no gutter to move it to. Use \`mark: 'icon'\` to turn the glyphs on, and set each entry's \`icon\`. Omit this field.`:null:`'${t.type}' draws no icon, so there is nowhere to put one \u2014 it stores an icon on an entry and never renders it. Blocks that do: ${T().filter(a=>a.slots.includes("icon")).map(a=>a.type).join(", ")}. Omit the field, or use set_block_type to swap to one of those.`:`There is no icon position '${e}'. The positions are: ${ie.join(", ")}. Pass '' to go back to whatever this block's shape does by default.`,ge=(e,t)=>{if(e==null||e===""||t.marks.includes(e)&&e!==t.marks[0])return null;if(!t.marks.length){const a=T().filter(r=>r.marks.length>0).map(r=>r.type);return`'${t.type}' marks its items with nothing, so there is nothing to change. Blocks that do: ${a.join(", ")}. Omit the field.`}return e===t.marks[0]?`'${e}' is what '${t.type}' already does when nothing is stored, so storing it would give one state two spellings. Pass '' to go back to it, or omit the field.`:`'${t.type}' has no mark '${e}'. It offers: ${t.marks.join(", ")}. '${t.marks[0]}' is the default and needs no field.`},A=(e,t)=>d("refused",`${e} was refused and the document is unchanged. Arguments: ${JSON.stringify(t)}. Commands refuse rather than corrupt: check that ids exist and belong where you used them, that entry ids came from this block, that a type swap stays inside one archetype, and that the change would not leave a page or the document empty.`),ft="not measured: fix the problems above first, then this page is checked against its frame.",Re="not measured: this document is fluid, so its pages grow with their content and none of them can overflow. Nothing here will tell you a page is too long \u2014 set a fixed format (16:9, 4:3 or A4) if you want that check back.",bt=async e=>{const t=Yt(e),a=e.attrs.id;if(t.length)return{diagnostics:t,fit:ft};if(!a)return{fit:"this page carries no id, so it could not be measured."};const r=await s(n.editor.getJSON(),a);if(r.problem)return{fit:r.problem};const i=hn(r,a);if(!i)return{fit:`page '${a}' did not render, so fit was not measured.`};if(!i.bounded)return{fit:Re};const l=jt.includes(String(y().attrs.format??""))?void 0:cn(dn(r,a)),h=un(r,a),u=h?{...i,over:h.over}:i,m=Je(u,n.fitSeen(a,u.over)),g=pn(h?.scale,!!m);return{...m?{diagnostics:[m]}:{},...l?{fill:l}:{},...g?{scale:g}:{}}},S=async(e,t={})=>K({...t,page:vn(e),...await bt(e)}),re=async(e={})=>{const t=y(),a=Kt(t),r=new Set(a.map(u=>u.pageId)),c=t.content.childCount>r.size?await s(n.editor.getJSON()):{},l=[];let h=!1;for(const u of c.pages??[]){if(r.has(u.id))continue;if(!u.bounded){h=!0;continue}const m=Je(u,n.fitSeen(u.id,u.over));m&&l.push(m)}return Q({...e,...kn(t),diagnostics:[...a,...l],...c.problem?{fit:c.problem}:h?{fit:Re}:{}})};f("list_blocks",{title:"List block types",description:"The block menu. One short row per block: what it is, when to pick it, how many entries its layout is designed for, and which entry slots it draws. Start here \u2014 block types are discovered, never memorised. For a worked example of one block, call describe_block. There is no per-block color, font or size on this surface: those values are the theme's and the theme is the account's. Change how a block LOOKS with its `variant`."},async()=>Q({variantsNote:"Each row carries the styles THAT block offers, because a block only offers one it has somewhere to put \u2014 a paragraph offers none. The theme decides what each name looks like, so a name means the same thing in every theme. Pass one as `variant`.",blocks:T().map(e=>({type:e.type,archetype:e.archetype,description:e.description,whenToUse:e.whenToUse,countRange:e.countRange,...e.colRange?{colRange:e.colRange}:{},renders:e.slots,variants:ce(e.variantSlots,q(),{core:!0}),...e.arrangements.length?{arrangements:e.arrangements.map(t=>t.name)}:{},...e.figures?.length?{figures:e.figures.map(t=>({name:t.name,countRange:t.countRange}))}:{},...e.marks.length?{marks:e.marks}:{}}))})),f("describe_block",{title:"Describe one block",description:"A worked example for one block type, ready to copy. Does NOT return a schema \u2014 the entry schema belongs to the archetype and is already on that archetype's add_ tool, identical for every block that shares it. Call this when you have chosen a block and want to see what good content looks like in it.",inputSchema:{type:o.string().describe('A block type from list_blocks, e.g. "timeline".')}},async({type:e})=>{const t=G(e);return t?Q({type:t.type,archetype:t.archetype,countRange:t.countRange,...t.colRange?{colRange:t.colRange}:{},renders:t.slots,whenToUse:t.whenToUse,...t.slots.includes("icon")?{icons:Nt}:{},...t.marks.length?{marks:t.marks}:{},variants:ce(t.variantSlots,q(),{core:!0}),...t.arrangements.length?{arrangements:t.arrangements.map(a=>({name:a.name,whenToUse:a.whenToUse,countRange:a.countRange}))}:{},...t.figures?.length?{figures:t.figures.map(a=>({name:a.name,whenToUse:`${a.whenToUse} It holds ${$n(a.countRange)} and a write outside that range is refused, unlike an arrangement whose range is guidance.`,countRange:a.countRange}))}:{},example:{tool:`add_${t.archetype}`,arguments:{pageId:"<a page id from get_document>",...$.get(`add_${t.archetype}`)?.includes("type")?{type:t.type}:{},[Ln[t.archetype]]:t.example}}}):z(e)}),f("get_document",{title:"Read the document",description:"The whole document: every page with its id, heading and blocks, and every block with its id, type and entries. Entry ids are what the update_ tools match on \u2014 echo one back to change that entry, omit it to add a new one, leave it out of the array to remove it. The reply also names the FILE this document is saved in, which defaults to the folder this server was started in. If the person named a file, or wants more than one document in that folder, call open_document BEFORE authoring."},async()=>re({file:n.path})),f("list_templates",{title:"List the templates",description:"The page designs available, each with the content it suits \u2014 the shelf to choose from before adding any page. Every one ADDS a single composed page; none replaces or alters what is already in the document. Add one with add_template_page.",inputSchema:{}},async()=>Q({templates:gt().map(e=>({id:e.id,name:e.name,whenToUse:e.whenToUse,look:Dt(e)}))})),f("add_template_page",{title:"Add a page from a template",description:"ADD ONE COMPOSED PAGE \u2014 heading, blocks, shape and design already chosen. This is how a page should normally be added; add_blank_page is for when no template suits. It INSERTS a page of its own, so do not add a page first. The text it arrives with is PLACEHOLDER, not content: fill every slot with set_page, which takes the whole page in one call.",inputSchema:{templateId:o.string().describe("A template id from list_templates."),before:o.string().optional().describe("Page id to insert before, for a PAGE template. Omit to append. Never an index.")}},async({templateId:e,before:t})=>{const a=ze(e);if(!a.ok)return d("unknownTemplate",`No template '${e}'. Templates are: ${gt().map(h=>h.id).join(", ")}. Call list_templates for the current ids.`);const r=a.value;if(t&&!k(t))return Y(t);const i=V(y());if(!n.editor.commands.applyTemplate(e,{before:t}))return A("apply_template",{templateId:e,before:t});n.save();const c=J(i,V(y())),l=c?k(c):null;return l?S(l,{pageId:c}):K({pageId:c})}),f("remove_page",{title:"Remove a page",description:"Delete a page and everything on it. Use it to clear a page you no longer want \u2014 including the blank one a document starts with, if you added your own pages instead of filling it. A document must keep at least one page, so removing the last one is refused.",inputSchema:{pageId:o.string().describe("A page id from get_document.")}},async({pageId:e})=>k(e)?V(y()).length===1?d("lastPage",`page '${e}' is the only page in this document and a document must hold at least one. Nothing was written. Put content on it instead, or add another page first.`):n.editor.commands.removePage(e)?(n.save(),re()):A("remove_page",{pageId:e}):Y(e)),f("open_document",{title:"Open or name the document file",description:"Choose the file this document is saved in. If the file exists it is OPENED and becomes the document you are working on; if it does not, it is where this document will be saved. Call it BEFORE authoring when the person names a file or wants more than one document in a folder \u2014 otherwise everything lands in the default the reply from get_document names. Pass a full path when you know the folder you are working in; a bare name is resolved against the folder this server was started in, which is not the same for every client.",inputSchema:{file:o.string().describe("Path to a .json document \u2014 absolute, or relative to the folder this server started in. The folder must already exist; this creates no directories.")}},async({file:e})=>{const t=fn(e);if(!t.ok)return d(t.code,t.message);try{n.openAt(t.path)}catch(a){return d("unreadableDocument",`${yn(e,a)} Nothing was opened; you are still working on the document you were before.`)}return console.error(`decaframe mcp \xB7 document: ${t.path}`),re({file:t.path,opened:t.existed})}),f("set_document",{title:"Set the document's title, shape or palette",description:"Change facts that belong to the WHOLE document rather than to one page: its title, its frame shape, where content sits in each frame, and which palette it is designed in. Omitted fields are left alone. Frame shape and content position live here, not on set_page, because a document is one shape and keeps one rhythm.",inputSchema:{title:o.string().optional().describe("The document's name. Not a heading on any page."),format:o.enum([...xt]).optional().describe("Frame shape for every page. Defaults to 16:9. A4 is a portrait sheet."),align:o.enum([...Et]).optional().describe("Where blocks sit in the leftover vertical space on EVERY page: top, middle or bottom. Defaults to middle."),mode:o.enum(["light","dark"]).optional().describe("Which palette the document is designed in: light or dark. A page may override it.")}},async e=>n.editor.commands.setDocument(e)?(n.save(),re()):A("set_document",e)),f("add_blank_page",{title:"Add a blank page",description:`Append a BLANK page, or insert one before an existing page. Reach for this only when no template suits the content \u2014 add_template_page adds a page that is already composed, and is the normal way to add one. A blank page holds nothing until you put blocks on it with ${we.map(e=>`add_${e}`).join(", ")}; set_page then fills every slot in one call.`,inputSchema:{...ut,...pt,before:o.string().optional().describe("Page id to insert before. Omit to append at the end. Never an index.")}},async e=>{if(e.before&&!k(e.before))return Y(e.before);const t=V(y());if(!n.editor.commands.addPage(e))return A("add_page",e);n.save();const a=J(t,V(y())),r=a?k(a):null;return r?S(r,{pageId:a}):K({pageId:a})}),f("import_markdown",{title:"Import a markdown document",description:'Write a whole markdown document at once \u2014 the fast path for anything already in markdown, and the ONE call that lays down a whole deck: pages, their look, and this catalogue\'s own blocks. WITHOUT pageId a NEW PAGE starts at every heading of `splitOn` (1 by default), that heading its title, and at every `::page\u2026::` line, so a page needs no heading; WITH pageId everything lands on that page and nothing splits. Ordinary markdown arrives as itself \u2014 paragraphs, headings, lists, tables, fenced code; a line that is only an image becomes a picture block; a ```mermaid fence becomes a DIAGRAM, and one that does not parse refuses the whole import. MARKERS say what markdown cannot, one per line, `::name: key=value \u2026::`, keys being this surface\'s own argument names: `::page: eyebrow="\u2026" subtitle="\u2026" accent=lead src=https://\u2026 alt="\u2026" surface=muted mode=dark::` right under a page\'s heading; `::layout: two-cols::` then `::col-1::`, `::col-2::` before each column; and a BLOCK marker over what builds it. `::box: variant=outlined arrangement=grid::`, `::steps: arrangement=funnel::`, `::timeline::`: the ITEMS are the headings under it, `### Title {icon=zap meta="\u2026" emphasis}`, the item\'s body beneath. `::stat-row::`: `### 84% {label="\u2026"}`, the note beneath. `::image::`, `::image-grid::`, `::people::`, `::image-with-text::`: one ` {caption="\u2026"}` line per picture, a heading after it its title, paragraphs its words; `::youtube::` over a bare address. `::chart: arrangement=line stack=stacked title="\u2026" axisX="\u2026" axisY="\u2026"::` over a table \u2014 header = series names, each row a category and its values, an empty cell a gap. `::text: arrangement=xl::` or `::table: variant=banded::` over the block beneath. `::end::` closes a block; the next marker or page closes it too, and nothing nests. A wrong name is REFUSED by name and nothing is written. It only creates: to change what exists, read it back and use the update_ tools.',inputSchema:{markdown:o.string().describe("The markdown document, as text."),pageId:o.string().optional().describe("A page id from get_document. Everything lands on that page and NOTHING is split. Omit to make a page per section instead."),splitOn:o.number().int().optional().describe("Which heading level starts a new page, 1 to 6. Default 1. Ignored when pageId is set."),before:o.string().optional().describe("Insert before this id \u2014 a block id when pageId is set, a page id otherwise. Omit to append at the end.")}},async({markdown:e,pageId:t,splitOn:a,before:r})=>{if(t&&!k(t))return Y(t);if(a!==void 0&&(a<1||a>6))return d("invalidLine",`splitOn is ${a}; markdown has six heading levels, so it must be 1 to 6. Nothing was written.`);const i=Ut(Ft(e),{theme:q(),pageless:!!t});if(i)return d("invalidLine",`${i} Nothing was written.`);const c=t?[{blocks:Ht(e,q())}]:Vt(e,a??1,q()),l=p=>!!p.text?.trim()||!!p.attrs?.src||!!p.attrs?.source||(p.content??[]).some(l);if(!c.some(p=>p.title?.trim()||p.blocks.some(l)))return d("invalidLine","that markdown draws nothing \u2014 no text, no pictures, no headings. Nothing was written. An empty document, one that is only whitespace, or one that is only HTML comments has nothing this can add.");const h=p=>p.flatMap(b=>b.type==="diagram"?[String(b.attrs?.source??"")]:h(b.content??[]));for(const p of c.flatMap(b=>h(b.blocks))){const b=await ye(p);if(b)return d("invalidSource",`In a \`\`\`mermaid fence: ${b} Nothing was written.`)}const u=V(y()),m=t?j(k(t)):[];if(!n.editor.commands.importMarkdown(c,t,r))return A("import_markdown",{pageId:t,before:r});if(n.save(),t){const p=j(k(t)),b=new Set(m);return S(k(t),{blockIds:p.filter(E=>!b.has(E))})}const g=new Set(u);return K({pageIds:V(y()).filter(p=>!g.has(p))})}),f("add_layout",{title:"Add a layout",description:`Put a LAYOUT on a page \u2014 a container that arranges other blocks side by side. It comes back with a region id per column; pass one of those to ${we.map(e=>`add_${e}`).join(", ")} instead of a page id to put a block inside that column. The layout holds no text of its own. For a picture running down one side of the page, this is NOT the tool \u2014 that is an ACCENT and it belongs to the page: use set_page.`,inputSchema:{pageId:o.string().describe("A page id from get_document. A layout only goes on a page."),layout:o.string().describe(ke.map(e=>`${e.name} \u2014 ${e.whenToUse}`).join(" | ")),before:o.string().optional().describe("Block id on that page to insert before. Omit to append.")}},async({pageId:e,layout:t,before:a})=>{const r=k(e);if(!r)return Y(e);if(!et(t))return Oe(t);if(a&&!_(a))return N(a);const i=he(r);if(!n.editor.commands.addLayout(e,t,a))return A("add_layout",{pageId:e,layout:t,before:a});n.save();const c=k(e),l=he(c).find(h=>!i.includes(h));return S(c,{blockId:l,regions:Ee(c,l)})}),f("set_layout",{title:"Change which layout a container uses",description:"Re-arrange a layout that already exists. Everything inside it is KEPT \u2014 every block and every region id survives, so this is safe to call on a full layout. If the new layout has fewer regions than the old one, the leftover blocks move into the last region rather than being deleted.",inputSchema:{blockId:o.string().describe("A layout id from get_document or add_layout."),layout:o.string().describe(`One of: ${ke.map(e=>e.name).join(", ")}.`)}},async({blockId:e,layout:t})=>{const a=ae(y(),e);if(!a||a.node.type.name!=="layout")return d("unknownBlock",`'${e}' is not a layout. set_layout takes the id add_layout returned; use set_block_type for an ordinary block.`);if(!et(t))return Oe(t);if(!n.editor.commands.setLayout(e,t))return A("set_layout",{blockId:e,layout:t});n.save();const r=H(y(),e);return S(r,{blockId:e,regions:Ee(r,e)})});const yt=async(e,t)=>{const a="Nothing was written \u2014 the whole call is refused so the page is never left half-filled.",r=c=>`blocks[${c}]`,i=[];for(const[c,l]of t.entries()){const h=String(l.blockId??""),u=_(h);if(!u)return{refusal:d("unknownBlock",`${r(c)} names block '${h}', which is not in this document. ${a} Block ids come back from apply_template and get_document.`)};const m=H(y(),h);if(m&&m.attrs.id!==e)return{refusal:d("wrongPage",`${r(c)} names block '${h}', which is on page '${m.attrs.id}', not the page '${e}' you are setting. ${a} Call set_page once per page.`)};const g=W(u.type.name),p=l.entries,b=l.content;if(g.archetype==="prose"){if(p!==void 0)return{refusal:d("wrongArchetype",`${r(c)} sent 'entries' to '${g.type}', which is a 'prose' block and takes 'content' \u2014 the same field update_prose takes. ${a}`)};if(b===void 0)return{refusal:d("invalidLine",`${r(c)} names no 'content'. ${a}`)};const X=Se(b,{kind:!1,type:g.type});if(X)return{refusal:d("invalidLine",`${r(c)}: ${X}`)};if(!Ke.has(u.type.name)&&b.length!==1)return{refusal:d("invalidLine",`${r(c)}: '${g.type}' holds exactly one line and you sent ${b.length}. ${a} Use add_prose to put the others on the page as their own blocks.`)};i.push(()=>n.editor.commands.updateBlock(h,{content:b}));continue}if(!ve[g.archetype])return{refusal:d("wrongArchetype",`${r(c)} names '${g.type}', a '${g.archetype}' block, which this array cannot fill. ${a} Use update_${g.archetype} for it.`)};if(b!==void 0)return{refusal:d("wrongArchetype",`${r(c)} sent 'content' to '${g.type}', which is a '${g.archetype}' block and takes 'entries'. ${a}`)};if(p===void 0)return{refusal:d("invalidEntry",`${r(c)} names no 'entries'. ${a}`)};const E=l.variant,P=l.arrangement,L=l.iconPosition,D=l.mark,M=_e(g,p),I=M??F(E,g)??te(P,g)??ue(P,g,p.length)??me(L,g)??ge(D,g);if(I)return{refusal:d(I===M?"invalidEntry":"invalidBlock",`${r(c)}: ${I} ${a}`)};i.push(()=>n.editor.commands.updateBlock(h,Te(g.archetype,Ae(p)))?(E!==void 0&&n.editor.commands.setVariant(h,E),P!==void 0&&pe(h,P??"",g),L!==void 0&&n.editor.commands.setIconPosition(h,L),D!==void 0&&n.editor.commands.setBlockMark(h,D),!0):!1)}return{run:i}};f("set_page",{title:"Set a page's heading, look or block content",description:"Change a page's own heading and look, and FILL ITS BLOCKS IN ONE CALL. Omitted fields are left alone; an empty string removes that heading slot. `blocks` takes the payload each block's archetype takes, so a template applied and then filled costs two calls rather than one per block; every block is checked before any is written, so a refusal changes nothing. The frame's SHAPE and where content sits in it are the document's \u2014 set_document.",inputSchema:{pageId:o.string().describe("A page id from get_document."),...ut,...pt,...jn,blocks:o.array(o.looseObject({blockId:o.string().describe("A block id, from add_template_page or get_document."),entries:o.array(o.looseObject({})).optional().describe("items, stats or media \u2014 what update_<archetype> takes."),content:o.array($e({existing:!0})).optional().describe("prose \u2014 what update_prose takes."),variant:o.string().nullable().optional().describe(`The block's style \u2014 a core name such as ${Ge.slice(1,4).join(", ")}, each glossed on add_items' own \`variant\`. Choose one; omit to keep the template's.`),arrangement:o.string().nullable().optional().describe("Its shape or figure \u2014 the names the archetype's add_ tool takes on `arrangement`. Omit to keep the template's."),iconPosition:o.string().nullable().optional().describe(`Where its icons sit: ${ie.join(", ")}. Only a block that draws icons takes it.`),mark:o.string().nullable().optional().describe(`What its items are marked with \u2014 ${lt("items")} \u2014 as add_items takes it.`)})).optional().describe("THE FAST PATH AFTER add_template_page \u2014 the page arrives composed and placeholder-written, and this replaces every slot in ONE call.")}},async({pageId:e,accent:t,accentSrc:a,accentAlt:r,blocks:i,...c})=>{if(!k(e))return Y(e);let l=[];if(i!==void 0){const u=await yt(e,i);if("refusal"in u)return u.refusal;l=u.run}if(t!==void 0||a!==void 0||r!==void 0){const u=a?Qe(a,"image"):null;if(u)return d("invalidSrc",u);if(!n.editor.commands.setAccent(e,{...t!==void 0?{position:t}:{},...a!==void 0?{src:a}:{},...r!==void 0?{alt:r}:{}}))return A("set_page",{pageId:e,accent:t,accentSrc:a})}if(Object.keys(c).length&&!n.editor.commands.setPage(e,c))return A("set_page",{pageId:e,...c});for(const[u,m]of l.entries())if(!m())return A("set_page",{pageId:e,block:`blocks[${u}]`});return n.save(),S(k(e))});const wt=e=>{const t=ve[e],a=T().filter(i=>i.archetype===e).map(i=>i.type),r=i=>d("wrongArchetype",`'${i.type}' is a '${i.archetype}' block, not '${e}'. Use add_${i.archetype} / update_${i.archetype} for it. This tool takes: ${a.join(", ")}.`);f(`add_${e}`,{title:`Add a ${e} block`,description:`Put ${/^[aeiou]/.test(e)?"an":"a"} '${e}' block on a page \u2014 ${a.join(", ")}. Every type here takes the same entries, so switching later with set_block_type keeps what you wrote; \`type\` names what each one is for.`,inputSchema:{pageId:o.string().describe(oe),type:o.string().describe(at(e)),entries:o.array(t).optional().describe(ot(e)),...B(e)?C():{},...de(e)?rt(e):{},...ct(e)?st:{},...ht(e)?dt(e):{},before:o.string().optional().describe("Block id on that page to insert before. Omit to append.")}},async({pageId:i,type:c,entries:l,variant:h,arrangement:u,iconPosition:m,mark:g,before:p})=>{if(l===void 0)return x(`add_${e}`,"entries",$.get(`add_${e}`).join(", "));const b=G(c);if(!b)return z(c);if(b.archetype!==e)return r(b);const E=R(i);if(!E)return ee(i);if(p&&!_(p))return N(p);const P=_e(b,l);if(P)return d("invalidEntry",P);const L=F(h,b);if(L)return d("unknownVariant",L);const D=te(u,b);if(D)return d("unknownArrangement",D);const M=ue(u,b,l.length);if(M)return d("countOutOfRange",M);const I=me(m,b);if(I)return d("unknownIconPosition",I);const X=ge(g,b);if(X)return d("unknownMark",X);const $t=j(E);if(!n.editor.commands.addBlock(i,c,Te(e,Ae(l)),p))return A(`add_${e}`,{pageId:i,type:c,before:p});const _t=R(i),U=J($t,j(_t));h!==void 0&&U&&n.editor.commands.setVariant(U,h),u!==void 0&&U&&pe(U,u??"",b),m!==void 0&&U&&n.editor.commands.setIconPosition(U,m),g!==void 0&&U&&n.editor.commands.setBlockMark(U,g),n.save();const At=Z(i);return S(At,{blockId:U})}),f(`update_${e}`,{title:`Replace a ${e} block's entries`,description:"Replace a block's content, matching entries by id: an entry carrying an id from get_document is updated in place, one with no id is added, and an id you leave out is removed. Slots you omit from an entry are cleared, so echo back the ones you want kept.",inputSchema:{blockId:o.string().describe(`A block id from get_document, of archetype '${e}'.`),entries:o.array(t).optional().describe(ot(e)),...B(e)?C():{},...de(e)?rt(e):{},...ct(e)?st:{},...ht(e)?dt(e):{}}},async({blockId:i,entries:c,variant:l,arrangement:h,iconPosition:u,mark:m})=>{if(c===void 0)return x(`update_${e}`,"entries",$.get(`update_${e}`).join(", "));const g=_(i);if(!g)return N(i);const p=W(g.type.name);if(p.archetype!==e)return r(p);const b=_e(p,c);if(b)return d("invalidEntry",b);const E=F(l,p);if(E)return d("unknownVariant",E);const P=te(h,p);if(P)return d("unknownArrangement",P);const L=ue(h,p,c.length);if(L)return d("countOutOfRange",L);const D=me(u,p);if(D)return d("unknownIconPosition",D);const M=ge(m,p);return M?d("unknownMark",M):n.editor.commands.updateBlock(i,Te(e,Ae(c)))?(l!==void 0&&n.editor.commands.setVariant(i,l),h!==void 0&&pe(i,h??"",p),u!==void 0&&n.editor.commands.setIconPosition(i,u),m!==void 0&&n.editor.commands.setBlockMark(i,m),n.save(),S(H(y(),i))):A(`update_${e}`,{blockId:i})})};for(const e of we)ve[e]&&wt(e);const kt=T().filter(e=>e.archetype==="prose").map(e=>e.type),vt=e=>d("wrongArchetype",`'${e.type}' is a '${e.archetype}' block, not 'prose'. Use add_${e.archetype} / update_${e.archetype} for it. This tool takes: ${kt.join(", ")}.`);f("add_prose",{title:"Add text to a page",description:"Write a passage onto a page \u2014 paragraphs, headings, lists, a quote and its source \u2014 in ONE call. Each line becomes its own block, so they can afterwards be reordered, restyled or deleted independently. This is how ordinary text gets onto a page.",inputSchema:{pageId:o.string().describe(oe),content:o.array($e()).optional().describe(Sn),...B("prose")?C():{},before:o.string().optional().describe("Block id on that page to insert before. Omit to append.")}},async({pageId:e,content:t,before:a})=>{if(t===void 0)return x("add_prose","content",$.get("add_prose").join(", "));const r=R(e);if(!r)return ee(e);if(a&&!_(a))return N(a);const i=Se(t);if(i)return d("invalidLine",i);const c=j(r);for(const m of Gt(t))if(!n.editor.commands.addBlock(e,m.kind,{content:m.lines},a))return A("add_prose",{pageId:e,kind:m.kind,before:a});const l=R(e),h=new Set(c),u=j(l).filter(m=>!h.has(m));return n.save(),S(Z(e),{blockIds:u})}),f("update_prose",{title:"Replace a prose block's text",description:"Rewrite one block's text. A line whose text you echo back exactly is not rewritten at all, so any formatting a person applied to it survives \u2014 read the block first and send back what you want kept. Adding a paragraph is add_prose; changing what a block IS is set_block_type.",inputSchema:{blockId:o.string().describe("A block id from get_document, of archetype 'prose'."),content:o.array($e({existing:!0})).optional().describe(En),...B("prose")?C():{}}},async({blockId:e,content:t})=>{if(t===void 0)return x("update_prose","content",$.get("update_prose").join(", "));const a=_(e);if(!a)return N(e);const r=W(a.type.name);if(r.archetype!=="prose")return vt(r);const i=Se(t,{kind:!1,type:r?.type});return i?d("invalidLine",i):!Ke.has(a.type.name)&&t.length!==1?d("invalidLine",`'${r.type}' holds exactly one line and you sent ${t.length}. Nothing was written. Send one line here, and use add_prose to put the others on the page as their own blocks.`):n.editor.commands.updateBlock(e,{content:t})?(n.save(),S(H(y(),e))):A("update_prose",{blockId:e})});const fe=T().filter(e=>e.archetype==="table").map(e=>e.type),Ne=e=>d("wrongArchetype",`'${e.type}' is a '${e.archetype}' block, not 'table'. Use add_${e.archetype} / update_${e.archetype} for it. This tool takes: ${fe.join(", ")}.`),Pe=o.looseObject({headers:o.array(o.string()).optional().describe("The top row of column headings, left to right. Send an empty array for a table with no header row. There is no header COLUMN here \u2014 a person can make the first column headings in the editor, but this payload has one axis for them, so write those cells as ordinary text in each row."),rows:o.array(o.array(o.string())).optional().describe("The body rows, each an array of cell text in the SAME ORDER as `headers`. Every row needs the same number of cells; use '' for a blank one. Plain text only \u2014 no markdown.")}),je="The whole table as {headers, rows}. `rows` is a list of rows, each a list of cell text. Echo back the shape get_document gave you and change the cells you mean: a cell whose text is unchanged is not rewritten, so formatting a person applied to it survives. Adding or removing a row or a column is just sending a longer or shorter list.",Ie=(e,t)=>{const a=ne("table");for(const r of Object.keys(e??{}))if(!a.includes(r))return`a table has no field '${r}'. It takes exactly: ${a.join(", ")}. \`headers\` is the top row of headings and \`rows\` is a list of rows of cell text.`;return Qt(e,t)};f("add_table",{title:"Add a table block",description:`Put a 'table' block on a page \u2014 ${fe.join(", ")}. Use it for values that only mean something at the crossing of two things. `+(fe.length>1?"Every type here takes the same {headers, rows}, so the choice is about how it looks: see list_blocks.":"It takes {headers, rows}."),inputSchema:{pageId:o.string().describe(oe),type:o.string().describe(at("table")),table:Pe.describe(je),...B("table")?C():{},before:o.string().optional().describe("Block id on that page to insert before. Omit to append.")}},async({pageId:e,type:t,table:a,variant:r,before:i})=>{const c=G(t);if(!c)return z(t);if(c.archetype!=="table")return Ne(c);const l=R(e);if(!l)return ee(e);if(i&&!_(i))return N(i);const h=Ie(a);if(h)return d("invalidTable",h);const u=F(r,c);if(u)return d("unknownVariant",u);const m=j(l);if(!n.editor.commands.addBlock(e,t,{table:a},i))return A("add_table",{pageId:e,type:t,before:i});const g=R(e),p=J(m,j(g));return r!==void 0&&p&&n.editor.commands.setVariant(p,r),n.save(),S(Z(e),{blockId:p})}),f("update_table",{title:"Replace a table's contents",description:"Rewrite a table. It is patched cell by cell rather than rebuilt, so a cell whose text you echo back unchanged keeps any formatting a person applied to it \u2014 and a person may have JOINED two cells into one, which this cannot describe and does not disturb. A joined cell's text belongs to the row and column it starts at; leave the cells it covers empty.",inputSchema:{blockId:o.string().describe("A block id from get_document, of archetype 'table'."),table:Pe.describe(je),...B("table")?C():{}}},async({blockId:e,table:t,variant:a})=>{const r=_(e);if(!r)return N(e);const i=W(r.type.name);if(i.archetype!=="table")return Ne(i);const c=Ie(t,r);if(c)return d("invalidTable",c);const l=F(a,i);return l?d("unknownVariant",l):n.editor.commands.updateBlock(e,{table:t})?(a!==void 0&&n.editor.commands.setVariant(e,a),n.save(),S(H(y(),e))):A("update_table",{blockId:e})});const be=T().filter(e=>e.archetype==="chart").map(e=>e.type),Le=e=>d("wrongArchetype",`'${e.type}' is a '${e.archetype}' block, not a chart. Use add_${e.archetype} / update_${e.archetype} for it. This tool takes: ${be.join(", ")}.`),De=o.looseObject({categories:o.array(o.string()).optional().describe("The labels along the axis, in the order they should read \u2014 one per column of numbers. These are what a reader compares: product lines, quarters, teams."),series:o.array(o.looseObject({name:o.string().optional().describe('What this line of numbers IS. It labels the series in the legend, so name the measure and its unit \u2014 "Revenue ($M)", not "Series 1".'),values:o.array(o.number().nullable()).optional().describe('The numbers, in the SAME ORDER as `categories` and EXACTLY as many. Use null for a gap \u2014 a short list is refused, because "the rest are missing" and "I mis-pasted a column" cannot be told apart.'),as:o.literal("line").optional().describe("Draw this one series as a line over the bars. That combination IS a combo chart; omit it for the block's own drawing.")})).optional().describe(`At most ${en} \u2014 that is how many colours a theme is required to answer for, so a seventh would repeat one.`)}),Me={title:o.string().optional().describe("The chart's title, as ordinary editable text above the plot. Omit to leave it alone; pass '' to clear it."),axisX:o.string().optional().describe("What the categories ARE \u2014 the label under the axis, like 'Product line'. Omit to leave it alone; '' to clear."),axisY:o.string().optional().describe("What the numbers ARE, including the unit \u2014 like 'Revenue ($M)'. Reads up the side of the plot. Omit to leave it alone; '' to clear.")},Ce={stack:o.enum(tn).nullable().optional().describe("How several series share a bar. 'none' puts them side by side; 'stacked' puts them in one bar so the height is the total; 'proportional' makes every bar the same height so what is read is the SHARE rather than the total. It is a SEPARATE axis from the drawing itself, so it combines with whatever `arrangement` names. Omit to leave it alone; 'none' for side by side.")},Be="The whole chart as {categories, series}. `categories` are the axis labels and each entry in `series` is one named line of numbers lining up with them exactly. Echo back the shape get_document gave you and change what you mean. The chart's TITLE and its two AXIS TITLES are not in here \u2014 they are three arguments of their own on this same tool.",He=e=>{const t=ne("chart");for(const a of Object.keys(e??{}))if(!t.includes(a))return`a chart has no field '${a}'. It takes exactly: ${t.join(", ")}. \`categories\` are the axis labels and \`series\` is a list of {name, values}.`;return Jt(e)};f("add_chart",{title:"Add a chart block",description:`Put a chart on a page \u2014 ${be.join(", ")}. Use it for numbers that are COMPARED rather than listed: a trend, a split, a ranking. It takes {categories, series}. For numbers that are only being listed use add_table; for two or three headline figures use add_stats.`,inputSchema:{pageId:o.string().describe(oe),type:o.string().describe(`One of: ${be.join(", ")}.`),chart:De.describe(Be),...Me,...de("chart")?it():{},...Ce,...B("chart")?C():{},before:o.string().optional().describe("Block id on that page to insert before. Omit to append.")}},async({pageId:e,type:t,chart:a,title:r,axisX:i,axisY:c,arrangement:l,stack:h,variant:u,before:m})=>{const g=G(t);if(!g)return z(t);if(g.archetype!=="chart")return Le(g);const p=R(e);if(!p)return ee(e);if(m&&!_(m))return N(m);const b=He(a);if(b)return d("invalidChart",b);const E=F(u,g);if(E)return d("unknownVariant",E);const P=te(l,g);if(P)return d("unknownArrangement",P);const L=j(p),D={chart:a,chartText:{title:r,axisX:i,axisY:c}};if(!n.editor.commands.addBlock(e,t,D,m))return A("add_chart",{pageId:e,type:t,before:m});const M=R(e),I=J(L,j(M));return I&&(u!==void 0&&n.editor.commands.setVariant(I,u),l!==void 0&&n.editor.commands.setArrangement(I,l||null),h!==void 0&&n.editor.commands.setStack(I,h||null)),n.save(),S(Z(e),{blockId:I})}),f("update_chart",{title:"Replace a chart's numbers",description:"Rewrite the whole payload of a chart. The three text slots \u2014 its title and both axis titles \u2014 are left alone unless you pass them, and every design choice on the block survives, so a person who restyled the chart keeps their work. To change the DRAWING rather than the numbers, pass `arrangement` or `stack` here.",inputSchema:{blockId:o.string().describe("A block id from get_document, of archetype 'chart'."),chart:De.describe(Be),...Me,...de("chart")?it():{},...Ce,...B("chart")?C():{}}},async({blockId:e,chart:t,title:a,axisX:r,axisY:i,arrangement:c,stack:l,variant:h})=>{const u=_(e);if(!u)return N(e);const m=W(u.type.name);if(m.archetype!=="chart")return Le(m);const g=He(t);if(g)return d("invalidChart",g);const p=F(h,m);if(p)return d("unknownVariant",p);const b=te(c,m);if(b)return d("unknownArrangement",b);const E={chart:t,chartText:{title:a,axisX:r,axisY:i}};return n.editor.commands.updateBlock(e,E)?(h!==void 0&&n.editor.commands.setVariant(e,h),c!==void 0&&n.editor.commands.setArrangement(e,c||null),l!==void 0&&n.editor.commands.setStack(e,l||null),n.save(),S(H(y(),e))):A("update_chart",{blockId:e})});const Fe=T().filter(e=>e.archetype==="diagram").map(e=>e.type),Ue=e=>d("wrongArchetype",`'${e.type}' is a '${e.archetype}' block, not a diagram. Use add_${e.archetype} / update_${e.archetype} for it. This tool takes: ${Fe.join(", ")}.`),Ve=o.looseObject({source:o.string().optional().describe("The mermaid text. The whole diagram, as one string with real newlines.")}),We=`Mermaid text. The FIRST LINE names the kind of diagram and is the commonest thing to get wrong: ${nn}. After that, write the RELATIONSHIPS and let the layout engine place everything: there are no coordinates to set. Labels are plain TEXT, with no HTML tags and nothing that loads a picture. Newlines are real, so send the source exactly as it should read.`,ye=async e=>{if(typeof e!="string")return"a diagram's 'source' must be a string of mermaid text.";const t=on(e);if(t)return t;const a=await sn(e);return!a||a.ok?null:`mermaid could not parse this source${a.line?` (line ${a.line})`:""}. ${an(a.message??"",a.line??null)} Fix that line rather than rewriting the diagram; if the message mentions the very first line, the opening keyword is probably wrong \u2014 describe_block lists the kinds.`};return f("add_diagram",{title:"Add a diagram block",description:"Put a diagram on a page \u2014 a flowchart, an org chart, a sequence, a state machine, an entity diagram and about a dozen more. Use it when the meaning is in WHAT CONNECTS TO WHAT. The payload is one string of mermaid text and you place nothing: the layout engine decides where every box goes. For a chart of NUMBERS use add_chart; for a handful of steps with real prose in them use add_items with type `steps`, which is themed and reads better on a slide than a flowchart of the same three boxes. A title or caption is an ordinary `heading` or `text` block above or below it \u2014 this block is only the drawing.",inputSchema:{pageId:o.string().describe(oe),type:o.string().describe(`One of: ${Fe.join(", ")}.`),diagram:Ve.describe(We),...B("diagram")?C():{},before:o.string().optional().describe("Block id on that page to insert before. Omit to append.")}},async({pageId:e,type:t,diagram:a,variant:r,before:i})=>{const c=a?.source,l=G(t);if(!l)return z(t);if(l.archetype!=="diagram")return Ue(l);const h=R(e);if(!h)return ee(e);if(i&&!_(i))return N(i);const u=await ye(c);if(u)return d("invalidSource",u);const m=F(r,l);if(m)return d("unknownVariant",m);const g=j(h);if(!n.editor.commands.addBlock(e,t,{diagram:{source:c}},i))return A("add_diagram",{pageId:e,type:t,before:i});const p=J(g,j(R(e)));p&&r!==void 0&&n.editor.commands.setVariant(p,r),n.save();const b=tt(c);return S(Z(e),b?{blockId:p,hint:b}:{blockId:p})}),f("update_diagram",{title:"Replace a diagram's source",description:"Rewrite the mermaid text of a diagram. The whole source at once \u2014 there is no partial edit, because a diagram is one string. Every design choice on the block survives, so a person who resized or magnified it keeps their work.",inputSchema:{blockId:o.string().describe("A block id from get_document, of archetype 'diagram'."),diagram:Ve.describe(We),...B("diagram")?C():{}}},async({blockId:e,diagram:t,variant:a})=>{const r=t?.source,i=_(e);if(!i)return N(e);const c=W(i.type.name);if(c.archetype!=="diagram")return Ue(c);const l=await ye(r);if(l)return d("invalidSource",l);const h=F(a,c);if(h)return d("unknownVariant",h);if(!n.editor.commands.updateBlock(e,{diagram:{source:r}}))return A("update_diagram",{blockId:e});a!==void 0&&n.editor.commands.setVariant(e,a),n.save();const u=tt(r);return S(H(y(),e),u?{hint:u}:{})}),f("set_block_type",{title:"Swap a block's rendering",description:"Render the same content as a different block. Free WITHIN an archetype \u2014 a timeline becomes a box keeping every entry and its id. Across archetypes there is no meaning-preserving mapping, so it is refused; remove the block and add the other instead.",inputSchema:{blockId:o.string().describe("A block id from get_document."),type:o.string().describe("The block type to switch to. Must share the current archetype.")}},async({blockId:e,type:t})=>{const a=_(e);if(!a)return N(e);const r=G(t);if(!r)return z(t);const i=W(a.type.name);if(i.archetype!==r.archetype)return d("archetypeMismatch",`Cannot turn '${i.type}' (${i.archetype}) into '${t}' (${r.archetype}) \u2014 the entries mean different things. Types you can swap to: ${T().filter(h=>h.archetype===i.archetype&&h.type!==i.type).map(h=>h.type).join(", ")||"none yet"}.`);const c=H(y(),e);if(!n.editor.commands.setBlockType(e,t))return A("set_block_type",{blockId:e,type:t});n.save();const l=c?k(c.attrs.id):null;return l?S(l):K()}),f("remove_block",{title:"Remove a block",description:"Delete a block and everything in it \u2014 a layout included, with every block in its columns. A page may be left empty \u2014 add its replacement afterwards rather than before.",inputSchema:{blockId:o.string().describe("A block id from get_document.")}},async({blockId:e})=>{const t=ae(y(),e);if(!t||!Mt(t.node.type.name))return N(e);const a=H(y(),e);if(!n.editor.commands.removeBlock(e))return A("remove_block",{blockId:e});n.save();const r=a?k(a.attrs.id):null;return r?S(r):K()}),f("export_html",{title:"Export the document as one HTML file",description:"Write the document to a single HTML file that opens in any browser by double-click, no hosting. Styles and every diagram's drawing are carried inside it; pictures, fonts and a video's player are LINKS (only a video's poster is embedded), so the file expects the internet and degrades without it \u2014 a system font, a glyph where a picture was, the video's still frame. It is a presentation viewer \u2014 swipe or use the arrow keys to move between pages. Returns the path, not the file; the path must be an .html file in a folder that exists. Export reflects the document as it is now, so fix any diagnostics first, and read the reply: it names any picture address the file could not use.",inputSchema:{path:o.string().optional().describe("Where to write it: an .html path in a folder that exists. Defaults to the document's own path with a .html suffix.")}},async({path:e})=>{const t=bn(e??n.path.replace(/\.json$/,"")+".html");if(!t.ok)return d(t.code,t.message);const a=t.path;let r;try{r=await gn(y().toJSON(),wn(n.path))}catch(i){return d("exportFailed",`Export aborted, nothing was written: ${i.message}`)}return Tt(a,r),K({path:a,pages:y().childCount,bytes:Buffer.byteLength(r),...mn(r)})}),w}function H(n,s){let w=null;return n.forEach(v=>{if(!w){if(v.attrs.id===s){w=v;return}v.descendants($=>w?!1:$.attrs?.id===s?(w=v,!1):!0)}}),w}export{Dn as INSTRUCTIONS,zn as createServer};
|