- commit
- d213abe107df70ce72282d734f71fe08e2a73d92
- parent
- 9558440aa656833e8d9ca31a2f44eb948abd4cae
- author
- Dami <dami.etoile@gmail.com>
@@ -107,7 +107,7 @@ document.querySelector('#app').innerHTML = `
107107 <dialog id="details-dialog" aria-labelledby="details-title">108108 <form method="dialog" class="dialog-card">109109 <div class="dialog-header">110- <div><p class="dialog-eyebrow">Diagram</p><h1 id="details-title">Add some context.</h1></div>110+ <h1 id="details-title">Diagram details</h1>111111 <button class="icon-button" value="cancel" aria-label="Close">×</button>112112 </div>113113 <div class="details-fields">@@ -157,7 +157,7 @@ document.querySelector('#app').innerHTML = `
157157 <dialog id="share-dialog" aria-labelledby="share-title">158158 <form method="dialog" class="dialog-card">159159 <div class="dialog-header">160- <div><p class="dialog-eyebrow">Share</p><h1 id="share-title">Take the diagram with you.</h1></div>160+ <h1 id="share-title">Share diagram</h1>161161 <button class="icon-button" value="cancel" aria-label="Close">×</button>162162 </div>163163 <p class="share-status" id="share-status"></p>@@ -176,7 +176,7 @@ document.querySelector('#app').innerHTML = `
176176 <dialog id="conflict-dialog" aria-labelledby="conflict-title">177177 <form method="dialog" class="dialog-card">178178 <div class="dialog-header">179- <div><p class="dialog-eyebrow">Save conflict</p><h1 id="conflict-title">This diagram changed elsewhere.</h1></div>179+ <h1 id="conflict-title">Save conflict</h1>180180 <button class="icon-button" value="cancel" aria-label="Close">×</button>181181 </div>182182 <p class="dialog-copy">Reload the saved version, or keep your work by saving it as a new diagram.</p>@@ -190,7 +190,7 @@ document.querySelector('#app').innerHTML = `
190190 <dialog id="password-dialog" aria-labelledby="password-title">191191 <form class="dialog-card" id="password-form">192192 <div class="dialog-header">193- <div><p class="dialog-eyebrow">Encrypted diagram</p><h1 id="password-title">Enter password.</h1></div>193+ <h1 id="password-title">Enter password</h1>194194 <button class="icon-button" id="password-cancel" type="button" aria-label="Close">×</button>195195 </div>196196 <p class="dialog-copy" id="password-copy"></p>@@ -646,7 +646,7 @@ async function svgToPngBlob(svg) {
646646 647647 async function lockDiagram() {648648 const password = await askPassword({649- title: 'Lock this diagram.',649+ title: 'Lock diagram',650650 copy: 'The source, metadata, and saved render will be encrypted in this browser. The password cannot be recovered.',651651 submitLabel: 'Lock diagram',652652 confirm: true,@@ -671,7 +671,7 @@ async function lockDiagram() {
671671 async function changeDiagramPassword() {672672 if (remote.mode !== 'locked' || !(remote.bundleKey instanceof Uint8Array)) return673673 const password = await askPassword({674- title: 'Change the password.',674+ title: 'Change password',675675 copy: 'Only the encrypted bundle key will be rewrapped. The diagram content does not need to be re-encrypted.',676676 submitLabel: 'Change password',677677 confirm: true,@@ -699,7 +699,7 @@ function unlockDiagram() {
699699 async function promptToUnlock(alias) {700700 while (true) {701701 const password = await askPassword({702- title: 'Unlock this diagram.',702+ title: 'Unlock diagram',703703 copy: 'Decryption happens only in this browser. The password is never sent to diagram.zip.',704704 submitLabel: 'Unlock',705705 confirm: false,@@ -281,7 +281,7 @@ dialog {
281281 padding: 0;282282 color: var(--ink);283283 border: 1px solid var(--line);284- border-radius: 20px;284+ border-radius: 12px;285285 background: var(--surface-raised);286286 box-shadow: var(--shadow);287287 }@@ -291,37 +291,34 @@ dialog::backdrop {
291291 backdrop-filter: blur(6px);292292 }293293 294-.dialog-card { padding: 24px; }294+.dialog-card { padding: 20px; }295295 296296 .dialog-header {297297 display: flex;298- align-items: flex-start;298+ align-items: center;299299 justify-content: space-between;300- gap: 20px;301- margin-bottom: 24px;302-}303-304-.dialog-eyebrow {305- margin: 0 0 5px;306- color: var(--muted);307- font-size: 12px;308- font-weight: 800;309- letter-spacing: .12em;310- text-transform: uppercase;300+ gap: 12px;301+ margin: -4px -4px 20px;302+ padding: 0 0 12px;303+ border-bottom: 1px solid var(--line);311304 }312305 313306 .dialog-header h1 {314307 margin: 0;315- font-size: clamp(25px, 5vw, 38px);316- line-height: 1.05;317- letter-spacing: -.045em;308+ font-size: 17px;309+ font-weight: 750;310+ line-height: 1.25;311+ letter-spacing: -.01em;318312 }319313 320314 .icon-button {321315 flex: 0 0 auto;322- width: 38px;316+ width: 32px;317+ min-height: 32px;323318 padding: 0;324- font-size: 24px;319+ border-radius: 6px;320+ font-size: 20px;321+ line-height: 1;325322 }326323 327324 .share-options { display: grid; gap: 18px; }@@ -1687,7 +1687,7 @@ Rel(bob, tandem, "Completes tasks")
16871687 <dialog id="details-dialog" aria-labelledby="details-title">16881688 <form method="dialog" class="dialog-card">16891689 <div class="dialog-header">1690- <div><p class="dialog-eyebrow">Diagram</p><h1 id="details-title">Add some context.</h1></div>1690+ <h1 id="details-title">Diagram details</h1>16911691 <button class="icon-button" value="cancel" aria-label="Close">×</button>16921692 </div>16931693 <div class="details-fields">@@ -1737,7 +1737,7 @@ Rel(bob, tandem, "Completes tasks")
17371737 <dialog id="share-dialog" aria-labelledby="share-title">17381738 <form method="dialog" class="dialog-card">17391739 <div class="dialog-header">1740- <div><p class="dialog-eyebrow">Share</p><h1 id="share-title">Take the diagram with you.</h1></div>1740+ <h1 id="share-title">Share diagram</h1>17411741 <button class="icon-button" value="cancel" aria-label="Close">×</button>17421742 </div>17431743 <p class="share-status" id="share-status"></p>@@ -1756,7 +1756,7 @@ Rel(bob, tandem, "Completes tasks")
17561756 <dialog id="conflict-dialog" aria-labelledby="conflict-title">17571757 <form method="dialog" class="dialog-card">17581758 <div class="dialog-header">1759- <div><p class="dialog-eyebrow">Save conflict</p><h1 id="conflict-title">This diagram changed elsewhere.</h1></div>1759+ <h1 id="conflict-title">Save conflict</h1>17601760 <button class="icon-button" value="cancel" aria-label="Close">×</button>17611761 </div>17621762 <p class="dialog-copy">Reload the saved version, or keep your work by saving it as a new diagram.</p>@@ -1770,7 +1770,7 @@ Rel(bob, tandem, "Completes tasks")
17701770 <dialog id="password-dialog" aria-labelledby="password-title">17711771 <form class="dialog-card" id="password-form">17721772 <div class="dialog-header">1773- <div><p class="dialog-eyebrow">Encrypted diagram</p><h1 id="password-title">Enter password.</h1></div>1773+ <h1 id="password-title">Enter password</h1>17741774 <button class="icon-button" id="password-cancel" type="button" aria-label="Close">×</button>17751775 </div>17761776 <p class="dialog-copy" id="password-copy"></p>@@ -1786,4 +1786,4 @@ Rel(bob, tandem, "Completes tasks")
17861786 </dialog>17871787 17881788 <div class="toast" id="toast" role="status" aria-live="polite"></div>1789-`;var lT=document.querySelector(`#diagram-type`),uT=document.querySelector(`#diagram-title`),dT=document.querySelector(`#diagram-description`),fT=document.querySelector(`#diagram-background`),pT=document.querySelector(`#diagram-padding`),mT=document.querySelector(`#diagram-frame`);for(let{id:e,label:t}of mx)lT.add(new Option(t,e));var hT=await TT(),gT=iw(hT.meta),_T=aw(hT.presentation);lT.value=hT.type,uT.value=gT.title,dT.value=gT.description,fT.value=_T.background,pT.value=String(_T.padding),mT.checked=_T.frame,$T(),aT=hT.type,cT.set(hT.type,hT);var vT=cd.define([{tag:K.keyword,color:`var(--syntax-keyword)`,fontWeight:`650`},{tag:K.string,color:`var(--syntax-string)`},{tag:[K.comment,K.lineComment,K.blockComment],color:`var(--syntax-comment)`,fontStyle:`italic`},{tag:[K.number,K.bool],color:`var(--syntax-number)`},{tag:[K.operator,K.punctuation],color:`var(--syntax-operator)`},{tag:[K.variableName,K.typeName],color:`var(--syntax-variable)`}]),yT=new V({state:k.create({doc:hT.source,extensions:[j_,tT.of(yx(hT.type)),fd(vT),V.theme({"&":{height:`100%`,backgroundColor:`transparent`},".cm-scroller":{fontFamily:`var(--font-mono)`,lineHeight:`1.65`},".cm-content":{padding:`28px 10px 80px`},".cm-gutters":{backgroundColor:`transparent`,border:`0`,paddingLeft:`12px`},".cm-activeLine, .cm-activeLineGutter":{backgroundColor:`var(--editor-active-line)`},".cm-cursor, .cm-dropCursor":{borderLeftColor:`var(--ink)`},"&.cm-focused":{outline:`none`}}),V.updateListener.of(e=>{e.docChanged&&!iT&&ST()})]}),parent:document.querySelector(`#editor`)}),bT=new Zw({stage:document.querySelector(`#preview-stage`),image:document.querySelector(`#preview-image`),status:document.querySelector(`.render-status`),minimap:document.querySelector(`#minimap`),minimapImage:document.querySelector(`#minimap-image`),minimapViewport:document.querySelector(`#minimap-viewport`)});lT.addEventListener(`change`,()=>{cT.set(aT,xT());let e=lT.value;ET(cT.get(e)??fw(e))}),document.querySelector(`#details`).addEventListener(`click`,()=>{document.querySelector(`#details-dialog`).showModal(),uT.focus()});for(let e of[uT,dT,fT,pT,mT])e.addEventListener(`input`,()=>{$T(),clearTimeout(wT.timeout),wT.timeout=setTimeout(wT,220)});document.querySelector(`#details-dialog`).addEventListener(`close`,wT),document.querySelector(`#lock-diagram`).addEventListener(`click`,FT),document.querySelector(`#change-password`).addEventListener(`click`,IT),document.querySelector(`#unlock-diagram`).addEventListener(`click`,LT),document.querySelector(`#save`).addEventListener(`click`,()=>AT()),document.querySelector(`#share`).addEventListener(`click`,DT),document.querySelector(`#share-save`).addEventListener(`click`,async()=>{await AT(),OT()}),document.querySelector(`.brand`).addEventListener(`click`,e=>{e.preventDefault(),confirm(`Start a new diagram?`)&&QT()}),document.querySelectorAll(`[data-preview-action]`).forEach(e=>{e.addEventListener(`click`,()=>{let t=e.dataset.previewAction;t===`zoom-out`&&bT.zoom(.8),t===`zoom-in`&&bT.zoom(1.25),t===`fit`&&bT.fit(),t===`one-to-one`&&bT.oneToOne()})}),document.querySelectorAll(`.mobile-tabs button`).forEach(e=>{e.addEventListener(`click`,()=>{document.querySelector(`.workspace`).dataset.mobilePanel=e.dataset.panel,document.querySelectorAll(`.mobile-tabs button`).forEach(t=>{t.setAttribute(`aria-selected`,String(t===e))}),e.dataset.panel===`preview`&&bT.fit()})}),document.querySelectorAll(`[data-copy]`).forEach(e=>{e.addEventListener(`click`,async()=>{let t=document.querySelector(`#${e.dataset.copy}`);t.value&&(await eE(t.value),$(`Copied`))})}),window.addEventListener(`popstate`,()=>location.reload()),window.addEventListener(`keydown`,e=>{(e.metaKey||e.ctrlKey)&&e.key.toLowerCase()===`s`&&(e.preventDefault(),AT())});function xT(){let e=Math.min(256,Math.max(0,Number.parseInt(pT.value,10)||0));return{type:lT.value,source:yT.state.doc.toString(),options:{},meta:iw({title:uT.value,description:dT.value}),presentation:aw({background:fT.value,padding:e,frame:mT.checked})}}function ST(e=1200){clearTimeout(rT),rT=setTimeout(CT,e)}function CT(e=!0){let t=xT();GT(t),Q.dirty=Q.keyEnvelopeDirty||Q.savedSnapshot!==HT(t,Q.mode),XT(),ZT(),e&&bT.render(t)}function wT(){clearTimeout(wT.timeout),CT()}async function TT(){let e=vw(location.pathname);if(e){let t=Sw(location.hash);t&&qT(e,t),location.hash&&history.replaceState(null,``,`${location.pathname}${location.search}`);try{let n=await nT.getAlias(e),r=t??JT(e);if(n.mode===`locked`){let e=await RT(n);return e?(YT(n,r,e.state,e.bundleKey),e.state):(sT=`A password is required to open this diagram.`,fw($C))}YT(n,r,n.state);let i=WT(`${$w}${e}`);return i?.revision===n.revision?i.state:n.state}catch(e){sT=e instanceof Cw?e.message:`This saved diagram could not be loaded.`}}location.hash&&history.replaceState(null,``,`${location.pathname}${location.search}`);let t=WT(Qw);return t?t.state:fw($C)}function ET(e,t=!0){if(!bx(e.type))throw Error(`Unsupported diagram type.`);let n=iw(e.meta),r=aw(e.presentation);iT=!0,aT=e.type,cT.set(e.type,{...e,meta:n,presentation:r}),lT.value=e.type,uT.value=n.title,dT.value=n.description,fT.value=r.background,pT.value=String(r.padding),mT.checked=r.frame,$T(),yT.dispatch({changes:{from:0,to:yT.state.doc.length,insert:e.source},effects:tT.reconfigure(yx(e.type))}),iT=!1,t?CT():bT.render(xT())}function DT(){CT(),OT(),document.querySelector(`#share-dialog`).showModal()}function OT(){let e=!!Q.aliasId,t=e&&Q.savedMode===`locked`,n=e?yw(location.origin,Q.aliasId):``,r=e&&Q.writeCapability?bw(location.origin,Q.aliasId,Q.writeCapability):``,i=t?null:Q.savedState,a=i?xw(location.origin,Q.aliasId,`svg`):``,o=document.querySelector(`#image-link`),s=document.querySelector(`#markdown-link`),c=i?.meta.title.trim().replace(/[\[\]\r\n]/g,` `)||`Diagram`;kT(`viewer-link`,n,`Save to create a read link`),kT(`editor-link`,r,e?`No write capability on this device`:`Save to create an edit link`),kT(`image-link`,a,t?`Encrypted diagrams cannot be embedded`:e?`Unavailable for this diagram`:`Save to create an image link`),kT(`markdown-link`,a?``:``,t?`Encrypted diagrams cannot be embedded`:e?`Unavailable for this diagram`:`Save to create Markdown`),document.querySelector(`#editor-link-note`).textContent=r?`Anyone with this link can update the diagram.`:e?`This device has read-only access. Save a copy to create a new edit link.`:`The edit link is a bearer credential and appears after saving.`,document.querySelector(`#image-link-note`).textContent=t?`This diagram is encrypted in your browser. Image embeds are disabled because they cannot ask for its password.`:``,o.dataset.state=t?`unavailable`:`ready`,s.dataset.state=t?`unavailable`:`ready`;let l=document.querySelector(`#share-status`),u=document.querySelector(`#share-save`);u.hidden=e&&!Q.dirty,e?Q.dirty&&Q.writeCapability?(l.textContent=`These links show the last saved version. Save your changes before sharing.`,u.textContent=`Save changes`):Q.dirty?(l.textContent=`You are editing a read-only diagram. Save a copy to share your changes.`,u.textContent=`Save as new`):l.textContent=t?Q.writeCapability?`The read link asks for the password. The edit link also grants permission to save changes.`:`The read link asks for the password. This device has read-only access.`:Q.writeCapability?`Copy a read link, or share the edit capability carefully.`:`This diagram is read-only on this device.`:(l.textContent=`Save this diagram to create stable share links.`,u.textContent=`Save diagram`)}function kT(e,t,n){let r=document.querySelector(`#${e}`),i=document.querySelector(`[data-copy="${e}"]`);r.value=t,r.placeholder=t?``:n,i.disabled=!t}async function AT(){if(oT)return!1;CT(!1);let e=xT();if(Q.aliasId&&!Q.dirty)return $(`Already saved`),!0;oT=!0,XT();try{if(Q.aliasId&&Q.writeCapability)return YT(Q.mode===`locked`?await nT.updateLockedAlias(Q.aliasId,await MT(e),Q.revision,Q.writeCapability):await nT.updateAlias(Q.aliasId,e,Q.revision,Q.writeCapability),Q.writeCapability,e,Q.mode===`locked`?Q.bundleKey:null),KT(`${$w}${Q.aliasId}`),await NT(e),$(`Saved`),!0;let t=!!Q.aliasId;return await jT(e),$(t?`Saved as a new diagram`:`Saved`),!0}catch(t){return t instanceof Cw&&t.status===412&&Q.aliasId?await BT(e):($(t instanceof Error?t.message:`Could not save this diagram.`),!1)}finally{oT=!1,XT()}}async function jT(e){let t=Q.aliasId,n=t?`${$w}${t}`:Qw,r=Q.bundleKey,i=Q.mode===`locked`?await nT.createLockedAlias(await MT(e)):await nT.createAlias(e);YT(i,i.writeCapability,e,Q.mode===`locked`?r:null),qT(i.aliasId,i.writeCapability),KT(n),history.pushState(null,``,`/d/${i.aliasId}`),await NT(e)}async function MT(e){if(!(Q.bundleKey instanceof Uint8Array)||!Q.keyEnvelope)throw Error(`This diagram is missing its browser encryption key.`);return Q.savedMode===`locked`&&Q.savedSnapshot===HT(e,`locked`)&&Q.encryptedContent&&Q.encryptedMetadata?{mode:`locked`,encryptedContent:Q.encryptedContent,encryptedMetadata:Q.encryptedMetadata,keyEnvelope:Q.keyEnvelope}:Kw(e,Q.bundleKey,Q.keyEnvelope)}async function NT(e){if(!Q.aliasId||!Q.writeCapability)return;await bT.render(e);let t=bT.svgBlobFor(e);if(!t)return;let n=[[`svg`,t]];try{n.push([`png`,await PT(t)])}catch{}let r=n.map(async([e,t])=>{let n=Q.mode===`locked`?await Yw(t,Q.bundleKey,e):t;return nT.uploadRender({aliasId:Q.aliasId,renderId:Q.renderId,revision:Q.revision,writeCapability:Q.writeCapability,format:e,mode:Q.mode,render:n})});(await Promise.allSettled(r)).every(e=>e.status===`rejected`)&&bT.setStatus(`Saved; render cache unavailable`,`ready`)}async function PT(e){let t=URL.createObjectURL(e);try{let e=new Image;e.src=t,await e.decode();let n=Math.min(1,4096/Math.max(e.naturalWidth,e.naturalHeight)),r=Math.max(1,Math.round(e.naturalWidth*n)),i=Math.max(1,Math.round(e.naturalHeight*n)),a=document.createElement(`canvas`);a.width=r,a.height=i;let o=a.getContext(`2d`);if(!o)throw Error(`Canvas is unavailable.`);o.drawImage(e,0,0,r,i);let s=await new Promise(e=>a.toBlob(e,`image/png`));if(!s)throw Error(`Could not create a PNG render.`);return s}finally{URL.revokeObjectURL(t)}}async function FT(){let e=await zT({title:`Lock this diagram.`,copy:`The source, metadata, and saved render will be encrypted in this browser. The password cannot be recovered.`,submitLabel:`Lock diagram`,confirm:!0});if(e!==null)try{let{bundleKey:t,payload:n}=await qw(xT(),e);Q.mode=`locked`,Q.bundleKey=t,Q.keyEnvelope=n.keyEnvelope,Q.encryptedContent=n.encryptedContent,Q.encryptedMetadata=n.encryptedMetadata,Q.keyEnvelopeDirty=!0,KT(Q.aliasId?`${$w}${Q.aliasId}`:Qw),CT(!1),$(`Locked in this browser — save to persist`)}catch(e){$(e instanceof Error?e.message:`Could not lock this diagram.`)}}async function IT(){if(Q.mode!==`locked`||!(Q.bundleKey instanceof Uint8Array))return;let e=await zT({title:`Change the password.`,copy:`Only the encrypted bundle key will be rewrapped. The diagram content does not need to be re-encrypted.`,submitLabel:`Change password`,confirm:!0});if(e!==null)try{Q.keyEnvelope=await Ww(Q.bundleKey,e),Q.keyEnvelopeDirty=!0,CT(!1),$(`Password changed — save to persist`)}catch(e){$(e instanceof Error?e.message:`Could not change the password.`)}}function LT(){Q.mode===`locked`&&confirm(`Remove password protection? The next save will store the diagram in plaintext.`)&&(Q.mode=`open`,Q.keyEnvelopeDirty=!1,CT(!1),$(`Password removed — save to persist`))}async function RT(e){for(;;){let t=await zT({title:`Unlock this diagram.`,copy:`Decryption happens only in this browser. The password is never sent to diagram.zip.`,submitLabel:`Unlock`,confirm:!1});if(t===null)return null;try{return await Jw(e,t)}catch(e){$(e instanceof Error?e.message:`Could not unlock this diagram.`)}}}function zT({title:e,copy:t,submitLabel:n,confirm:r}){let i=document.querySelector(`#password-dialog`),a=document.querySelector(`#password-form`),o=document.querySelector(`#diagram-password`),s=document.querySelector(`#diagram-password-confirm`),c=document.querySelector(`#password-confirm-field`),l=document.querySelector(`#password-error`);return document.querySelector(`#password-title`).textContent=e,document.querySelector(`#password-copy`).textContent=t,document.querySelector(`#password-submit`).textContent=n,c.hidden=!r,o.autocomplete=r?`new-password`:`current-password`,o.value=``,s.value=``,l.textContent=``,new Promise(e=>{let t=!1,n=n=>{t||(t=!0,a.removeEventListener(`submit`,c),document.querySelector(`#password-cancel`).removeEventListener(`click`,u),i.removeEventListener(`cancel`,u),i.removeEventListener(`close`,d),e(n))},c=e=>{e.preventDefault();let t=o.value;if(r&&t.length<8){l.textContent=`Use at least 8 characters.`,o.focus();return}if(r&&t!==s.value){l.textContent=`The passwords do not match.`,s.focus();return}i.close(),n(t)},u=e=>{e.preventDefault(),i.close(),n(null)},d=()=>n(null);a.addEventListener(`submit`,c),document.querySelector(`#password-cancel`).addEventListener(`click`,u),i.addEventListener(`cancel`,u),i.addEventListener(`close`,d),i.showModal(),o.focus()})}async function BT(e){let t=await VT();if(t===`reload`){let e=Q.aliasId,t=Q.writeCapability;try{let n=await nT.getAlias(e),r=n.state,i=null;if(n.mode===`locked`){let e=await RT(n);if(!e)return!1;r=e.state,i=e.bundleKey}return YT(n,t,r,i),KT(`${$w}${e}`),ET(r,!1),XT(),$(`Reloaded saved diagram`),!0}catch(e){return $(e instanceof Error?e.message:`Could not reload this diagram.`),!1}}return t===`copy`&&(await jT(e),$(`Saved as a new diagram`),!0)}function VT(){let e=document.querySelector(`#conflict-dialog`);return e.returnValue=``,e.showModal(),new Promise(t=>{e.addEventListener(`close`,()=>t(e.returnValue),{once:!0})})}function HT(e,t=`open`){return JSON.stringify({mode:t,type:e.type,source:e.source,options:e.options??{},meta:iw(e.meta),presentation:aw(e.presentation)})}function UT(e){if(!e||typeof e!=`object`||!bx(e.type)||typeof e.source!=`string`)return null;try{return{type:e.type,source:e.source,options:e.options??{},meta:iw(e.meta),presentation:aw(e.presentation)}}catch{return null}}function WT(e){try{let t=JSON.parse(localStorage.getItem(e)),n=UT(t?.state);return n?{state:n,revision:t.revision??null}:null}catch{return null}}function GT(e){if(Q.mode===`locked`)return;let t=Q.aliasId?`${$w}${Q.aliasId}`:Qw;try{localStorage.setItem(t,JSON.stringify({state:e,revision:Q.revision}))}catch{}}function KT(e){try{localStorage.removeItem(e)}catch{}}function qT(e,t){try{localStorage.setItem(`${eT}${e}`,t)}catch{}}function JT(e){try{return Sw(`#w=${localStorage.getItem(`${eT}${e}`)??``}`)}catch{return null}}function YT(e,t,n,r=null){Q={aliasId:e.aliasId,contentId:e.contentId,renderId:e.renderId,revision:e.revision,mode:e.mode,savedMode:e.mode,writeCapability:t,savedState:n,savedSnapshot:HT(n,e.mode),bundleKey:r,keyEnvelope:e.mode===`locked`?e.keyEnvelope:null,encryptedContent:e.mode===`locked`?e.encryptedContent:null,encryptedMetadata:e.mode===`locked`?e.encryptedMetadata:null,keyEnvelopeDirty:!1,dirty:!1},ZT(),XT()}function XT(){let e=document.querySelector(`#save`);if(oT){e.textContent=`Saving…`,e.disabled=!0;return}if(Q.aliasId&&!Q.dirty){e.textContent=`Saved`,e.disabled=!0;return}e.textContent=Q.aliasId&&!Q.writeCapability?`Save copy`:`Save`,e.disabled=!1}function ZT(){let e=Q.mode===`locked`;document.querySelector(`#privacy-label`).textContent=e?`Password locked`:`Open`,document.querySelector(`#privacy-description`).textContent=e?`Source, metadata, and saved renders are encrypted in your browser. Password-locked diagrams cannot be embedded.`:`Anyone with the read link can open and embed this diagram.`,document.querySelector(`#lock-diagram`).hidden=e,document.querySelector(`#change-password`).hidden=!e,document.querySelector(`#unlock-diagram`).hidden=!e}function QT(){Q={aliasId:null,contentId:null,renderId:null,revision:null,mode:`open`,savedMode:null,writeCapability:null,savedState:null,savedSnapshot:null,bundleKey:null,keyEnvelope:null,encryptedContent:null,encryptedMetadata:null,keyEnvelopeDirty:!1,dirty:!0},cT.clear(),history.pushState(null,``,`/`),ET(fw($C))}function $T(){let e=uT.value.trim(),t=dT.value.trim();document.title=e?`${e} — diagram.zip`:`diagram.zip`,document.querySelector(`meta[name="description"]`).content=t||`Create diagrams from text and share them as a link.`}async function eE(e){if(navigator.clipboard)return navigator.clipboard.writeText(e);let t=document.createElement(`textarea`);t.value=e,document.body.append(t),t.select(),document.execCommand(`copy`),t.remove()}function $(e){let t=document.querySelector(`#toast`);t.textContent=e,t.dataset.visible=`true`,clearTimeout($.timeout),$.timeout=setTimeout(()=>delete t.dataset.visible,1800)}CT(),sT&&$(sT);\ No newline at end of file1789+`;var lT=document.querySelector(`#diagram-type`),uT=document.querySelector(`#diagram-title`),dT=document.querySelector(`#diagram-description`),fT=document.querySelector(`#diagram-background`),pT=document.querySelector(`#diagram-padding`),mT=document.querySelector(`#diagram-frame`);for(let{id:e,label:t}of mx)lT.add(new Option(t,e));var hT=await TT(),gT=iw(hT.meta),_T=aw(hT.presentation);lT.value=hT.type,uT.value=gT.title,dT.value=gT.description,fT.value=_T.background,pT.value=String(_T.padding),mT.checked=_T.frame,$T(),aT=hT.type,cT.set(hT.type,hT);var vT=cd.define([{tag:K.keyword,color:`var(--syntax-keyword)`,fontWeight:`650`},{tag:K.string,color:`var(--syntax-string)`},{tag:[K.comment,K.lineComment,K.blockComment],color:`var(--syntax-comment)`,fontStyle:`italic`},{tag:[K.number,K.bool],color:`var(--syntax-number)`},{tag:[K.operator,K.punctuation],color:`var(--syntax-operator)`},{tag:[K.variableName,K.typeName],color:`var(--syntax-variable)`}]),yT=new V({state:k.create({doc:hT.source,extensions:[j_,tT.of(yx(hT.type)),fd(vT),V.theme({"&":{height:`100%`,backgroundColor:`transparent`},".cm-scroller":{fontFamily:`var(--font-mono)`,lineHeight:`1.65`},".cm-content":{padding:`28px 10px 80px`},".cm-gutters":{backgroundColor:`transparent`,border:`0`,paddingLeft:`12px`},".cm-activeLine, .cm-activeLineGutter":{backgroundColor:`var(--editor-active-line)`},".cm-cursor, .cm-dropCursor":{borderLeftColor:`var(--ink)`},"&.cm-focused":{outline:`none`}}),V.updateListener.of(e=>{e.docChanged&&!iT&&ST()})]}),parent:document.querySelector(`#editor`)}),bT=new Zw({stage:document.querySelector(`#preview-stage`),image:document.querySelector(`#preview-image`),status:document.querySelector(`.render-status`),minimap:document.querySelector(`#minimap`),minimapImage:document.querySelector(`#minimap-image`),minimapViewport:document.querySelector(`#minimap-viewport`)});lT.addEventListener(`change`,()=>{cT.set(aT,xT());let e=lT.value;ET(cT.get(e)??fw(e))}),document.querySelector(`#details`).addEventListener(`click`,()=>{document.querySelector(`#details-dialog`).showModal(),uT.focus()});for(let e of[uT,dT,fT,pT,mT])e.addEventListener(`input`,()=>{$T(),clearTimeout(wT.timeout),wT.timeout=setTimeout(wT,220)});document.querySelector(`#details-dialog`).addEventListener(`close`,wT),document.querySelector(`#lock-diagram`).addEventListener(`click`,FT),document.querySelector(`#change-password`).addEventListener(`click`,IT),document.querySelector(`#unlock-diagram`).addEventListener(`click`,LT),document.querySelector(`#save`).addEventListener(`click`,()=>AT()),document.querySelector(`#share`).addEventListener(`click`,DT),document.querySelector(`#share-save`).addEventListener(`click`,async()=>{await AT(),OT()}),document.querySelector(`.brand`).addEventListener(`click`,e=>{e.preventDefault(),confirm(`Start a new diagram?`)&&QT()}),document.querySelectorAll(`[data-preview-action]`).forEach(e=>{e.addEventListener(`click`,()=>{let t=e.dataset.previewAction;t===`zoom-out`&&bT.zoom(.8),t===`zoom-in`&&bT.zoom(1.25),t===`fit`&&bT.fit(),t===`one-to-one`&&bT.oneToOne()})}),document.querySelectorAll(`.mobile-tabs button`).forEach(e=>{e.addEventListener(`click`,()=>{document.querySelector(`.workspace`).dataset.mobilePanel=e.dataset.panel,document.querySelectorAll(`.mobile-tabs button`).forEach(t=>{t.setAttribute(`aria-selected`,String(t===e))}),e.dataset.panel===`preview`&&bT.fit()})}),document.querySelectorAll(`[data-copy]`).forEach(e=>{e.addEventListener(`click`,async()=>{let t=document.querySelector(`#${e.dataset.copy}`);t.value&&(await eE(t.value),$(`Copied`))})}),window.addEventListener(`popstate`,()=>location.reload()),window.addEventListener(`keydown`,e=>{(e.metaKey||e.ctrlKey)&&e.key.toLowerCase()===`s`&&(e.preventDefault(),AT())});function xT(){let e=Math.min(256,Math.max(0,Number.parseInt(pT.value,10)||0));return{type:lT.value,source:yT.state.doc.toString(),options:{},meta:iw({title:uT.value,description:dT.value}),presentation:aw({background:fT.value,padding:e,frame:mT.checked})}}function ST(e=1200){clearTimeout(rT),rT=setTimeout(CT,e)}function CT(e=!0){let t=xT();GT(t),Q.dirty=Q.keyEnvelopeDirty||Q.savedSnapshot!==HT(t,Q.mode),XT(),ZT(),e&&bT.render(t)}function wT(){clearTimeout(wT.timeout),CT()}async function TT(){let e=vw(location.pathname);if(e){let t=Sw(location.hash);t&&qT(e,t),location.hash&&history.replaceState(null,``,`${location.pathname}${location.search}`);try{let n=await nT.getAlias(e),r=t??JT(e);if(n.mode===`locked`){let e=await RT(n);return e?(YT(n,r,e.state,e.bundleKey),e.state):(sT=`A password is required to open this diagram.`,fw($C))}YT(n,r,n.state);let i=WT(`${$w}${e}`);return i?.revision===n.revision?i.state:n.state}catch(e){sT=e instanceof Cw?e.message:`This saved diagram could not be loaded.`}}location.hash&&history.replaceState(null,``,`${location.pathname}${location.search}`);let t=WT(Qw);return t?t.state:fw($C)}function ET(e,t=!0){if(!bx(e.type))throw Error(`Unsupported diagram type.`);let n=iw(e.meta),r=aw(e.presentation);iT=!0,aT=e.type,cT.set(e.type,{...e,meta:n,presentation:r}),lT.value=e.type,uT.value=n.title,dT.value=n.description,fT.value=r.background,pT.value=String(r.padding),mT.checked=r.frame,$T(),yT.dispatch({changes:{from:0,to:yT.state.doc.length,insert:e.source},effects:tT.reconfigure(yx(e.type))}),iT=!1,t?CT():bT.render(xT())}function DT(){CT(),OT(),document.querySelector(`#share-dialog`).showModal()}function OT(){let e=!!Q.aliasId,t=e&&Q.savedMode===`locked`,n=e?yw(location.origin,Q.aliasId):``,r=e&&Q.writeCapability?bw(location.origin,Q.aliasId,Q.writeCapability):``,i=t?null:Q.savedState,a=i?xw(location.origin,Q.aliasId,`svg`):``,o=document.querySelector(`#image-link`),s=document.querySelector(`#markdown-link`),c=i?.meta.title.trim().replace(/[\[\]\r\n]/g,` `)||`Diagram`;kT(`viewer-link`,n,`Save to create a read link`),kT(`editor-link`,r,e?`No write capability on this device`:`Save to create an edit link`),kT(`image-link`,a,t?`Encrypted diagrams cannot be embedded`:e?`Unavailable for this diagram`:`Save to create an image link`),kT(`markdown-link`,a?``:``,t?`Encrypted diagrams cannot be embedded`:e?`Unavailable for this diagram`:`Save to create Markdown`),document.querySelector(`#editor-link-note`).textContent=r?`Anyone with this link can update the diagram.`:e?`This device has read-only access. Save a copy to create a new edit link.`:`The edit link is a bearer credential and appears after saving.`,document.querySelector(`#image-link-note`).textContent=t?`This diagram is encrypted in your browser. Image embeds are disabled because they cannot ask for its password.`:``,o.dataset.state=t?`unavailable`:`ready`,s.dataset.state=t?`unavailable`:`ready`;let l=document.querySelector(`#share-status`),u=document.querySelector(`#share-save`);u.hidden=e&&!Q.dirty,e?Q.dirty&&Q.writeCapability?(l.textContent=`These links show the last saved version. Save your changes before sharing.`,u.textContent=`Save changes`):Q.dirty?(l.textContent=`You are editing a read-only diagram. Save a copy to share your changes.`,u.textContent=`Save as new`):l.textContent=t?Q.writeCapability?`The read link asks for the password. The edit link also grants permission to save changes.`:`The read link asks for the password. This device has read-only access.`:Q.writeCapability?`Copy a read link, or share the edit capability carefully.`:`This diagram is read-only on this device.`:(l.textContent=`Save this diagram to create stable share links.`,u.textContent=`Save diagram`)}function kT(e,t,n){let r=document.querySelector(`#${e}`),i=document.querySelector(`[data-copy="${e}"]`);r.value=t,r.placeholder=t?``:n,i.disabled=!t}async function AT(){if(oT)return!1;CT(!1);let e=xT();if(Q.aliasId&&!Q.dirty)return $(`Already saved`),!0;oT=!0,XT();try{if(Q.aliasId&&Q.writeCapability)return YT(Q.mode===`locked`?await nT.updateLockedAlias(Q.aliasId,await MT(e),Q.revision,Q.writeCapability):await nT.updateAlias(Q.aliasId,e,Q.revision,Q.writeCapability),Q.writeCapability,e,Q.mode===`locked`?Q.bundleKey:null),KT(`${$w}${Q.aliasId}`),await NT(e),$(`Saved`),!0;let t=!!Q.aliasId;return await jT(e),$(t?`Saved as a new diagram`:`Saved`),!0}catch(t){return t instanceof Cw&&t.status===412&&Q.aliasId?await BT(e):($(t instanceof Error?t.message:`Could not save this diagram.`),!1)}finally{oT=!1,XT()}}async function jT(e){let t=Q.aliasId,n=t?`${$w}${t}`:Qw,r=Q.bundleKey,i=Q.mode===`locked`?await nT.createLockedAlias(await MT(e)):await nT.createAlias(e);YT(i,i.writeCapability,e,Q.mode===`locked`?r:null),qT(i.aliasId,i.writeCapability),KT(n),history.pushState(null,``,`/d/${i.aliasId}`),await NT(e)}async function MT(e){if(!(Q.bundleKey instanceof Uint8Array)||!Q.keyEnvelope)throw Error(`This diagram is missing its browser encryption key.`);return Q.savedMode===`locked`&&Q.savedSnapshot===HT(e,`locked`)&&Q.encryptedContent&&Q.encryptedMetadata?{mode:`locked`,encryptedContent:Q.encryptedContent,encryptedMetadata:Q.encryptedMetadata,keyEnvelope:Q.keyEnvelope}:Kw(e,Q.bundleKey,Q.keyEnvelope)}async function NT(e){if(!Q.aliasId||!Q.writeCapability)return;await bT.render(e);let t=bT.svgBlobFor(e);if(!t)return;let n=[[`svg`,t]];try{n.push([`png`,await PT(t)])}catch{}let r=n.map(async([e,t])=>{let n=Q.mode===`locked`?await Yw(t,Q.bundleKey,e):t;return nT.uploadRender({aliasId:Q.aliasId,renderId:Q.renderId,revision:Q.revision,writeCapability:Q.writeCapability,format:e,mode:Q.mode,render:n})});(await Promise.allSettled(r)).every(e=>e.status===`rejected`)&&bT.setStatus(`Saved; render cache unavailable`,`ready`)}async function PT(e){let t=URL.createObjectURL(e);try{let e=new Image;e.src=t,await e.decode();let n=Math.min(1,4096/Math.max(e.naturalWidth,e.naturalHeight)),r=Math.max(1,Math.round(e.naturalWidth*n)),i=Math.max(1,Math.round(e.naturalHeight*n)),a=document.createElement(`canvas`);a.width=r,a.height=i;let o=a.getContext(`2d`);if(!o)throw Error(`Canvas is unavailable.`);o.drawImage(e,0,0,r,i);let s=await new Promise(e=>a.toBlob(e,`image/png`));if(!s)throw Error(`Could not create a PNG render.`);return s}finally{URL.revokeObjectURL(t)}}async function FT(){let e=await zT({title:`Lock diagram`,copy:`The source, metadata, and saved render will be encrypted in this browser. The password cannot be recovered.`,submitLabel:`Lock diagram`,confirm:!0});if(e!==null)try{let{bundleKey:t,payload:n}=await qw(xT(),e);Q.mode=`locked`,Q.bundleKey=t,Q.keyEnvelope=n.keyEnvelope,Q.encryptedContent=n.encryptedContent,Q.encryptedMetadata=n.encryptedMetadata,Q.keyEnvelopeDirty=!0,KT(Q.aliasId?`${$w}${Q.aliasId}`:Qw),CT(!1),$(`Locked in this browser — save to persist`)}catch(e){$(e instanceof Error?e.message:`Could not lock this diagram.`)}}async function IT(){if(Q.mode!==`locked`||!(Q.bundleKey instanceof Uint8Array))return;let e=await zT({title:`Change password`,copy:`Only the encrypted bundle key will be rewrapped. The diagram content does not need to be re-encrypted.`,submitLabel:`Change password`,confirm:!0});if(e!==null)try{Q.keyEnvelope=await Ww(Q.bundleKey,e),Q.keyEnvelopeDirty=!0,CT(!1),$(`Password changed — save to persist`)}catch(e){$(e instanceof Error?e.message:`Could not change the password.`)}}function LT(){Q.mode===`locked`&&confirm(`Remove password protection? The next save will store the diagram in plaintext.`)&&(Q.mode=`open`,Q.keyEnvelopeDirty=!1,CT(!1),$(`Password removed — save to persist`))}async function RT(e){for(;;){let t=await zT({title:`Unlock diagram`,copy:`Decryption happens only in this browser. The password is never sent to diagram.zip.`,submitLabel:`Unlock`,confirm:!1});if(t===null)return null;try{return await Jw(e,t)}catch(e){$(e instanceof Error?e.message:`Could not unlock this diagram.`)}}}function zT({title:e,copy:t,submitLabel:n,confirm:r}){let i=document.querySelector(`#password-dialog`),a=document.querySelector(`#password-form`),o=document.querySelector(`#diagram-password`),s=document.querySelector(`#diagram-password-confirm`),c=document.querySelector(`#password-confirm-field`),l=document.querySelector(`#password-error`);return document.querySelector(`#password-title`).textContent=e,document.querySelector(`#password-copy`).textContent=t,document.querySelector(`#password-submit`).textContent=n,c.hidden=!r,o.autocomplete=r?`new-password`:`current-password`,o.value=``,s.value=``,l.textContent=``,new Promise(e=>{let t=!1,n=n=>{t||(t=!0,a.removeEventListener(`submit`,c),document.querySelector(`#password-cancel`).removeEventListener(`click`,u),i.removeEventListener(`cancel`,u),i.removeEventListener(`close`,d),e(n))},c=e=>{e.preventDefault();let t=o.value;if(r&&t.length<8){l.textContent=`Use at least 8 characters.`,o.focus();return}if(r&&t!==s.value){l.textContent=`The passwords do not match.`,s.focus();return}i.close(),n(t)},u=e=>{e.preventDefault(),i.close(),n(null)},d=()=>n(null);a.addEventListener(`submit`,c),document.querySelector(`#password-cancel`).addEventListener(`click`,u),i.addEventListener(`cancel`,u),i.addEventListener(`close`,d),i.showModal(),o.focus()})}async function BT(e){let t=await VT();if(t===`reload`){let e=Q.aliasId,t=Q.writeCapability;try{let n=await nT.getAlias(e),r=n.state,i=null;if(n.mode===`locked`){let e=await RT(n);if(!e)return!1;r=e.state,i=e.bundleKey}return YT(n,t,r,i),KT(`${$w}${e}`),ET(r,!1),XT(),$(`Reloaded saved diagram`),!0}catch(e){return $(e instanceof Error?e.message:`Could not reload this diagram.`),!1}}return t===`copy`&&(await jT(e),$(`Saved as a new diagram`),!0)}function VT(){let e=document.querySelector(`#conflict-dialog`);return e.returnValue=``,e.showModal(),new Promise(t=>{e.addEventListener(`close`,()=>t(e.returnValue),{once:!0})})}function HT(e,t=`open`){return JSON.stringify({mode:t,type:e.type,source:e.source,options:e.options??{},meta:iw(e.meta),presentation:aw(e.presentation)})}function UT(e){if(!e||typeof e!=`object`||!bx(e.type)||typeof e.source!=`string`)return null;try{return{type:e.type,source:e.source,options:e.options??{},meta:iw(e.meta),presentation:aw(e.presentation)}}catch{return null}}function WT(e){try{let t=JSON.parse(localStorage.getItem(e)),n=UT(t?.state);return n?{state:n,revision:t.revision??null}:null}catch{return null}}function GT(e){if(Q.mode===`locked`)return;let t=Q.aliasId?`${$w}${Q.aliasId}`:Qw;try{localStorage.setItem(t,JSON.stringify({state:e,revision:Q.revision}))}catch{}}function KT(e){try{localStorage.removeItem(e)}catch{}}function qT(e,t){try{localStorage.setItem(`${eT}${e}`,t)}catch{}}function JT(e){try{return Sw(`#w=${localStorage.getItem(`${eT}${e}`)??``}`)}catch{return null}}function YT(e,t,n,r=null){Q={aliasId:e.aliasId,contentId:e.contentId,renderId:e.renderId,revision:e.revision,mode:e.mode,savedMode:e.mode,writeCapability:t,savedState:n,savedSnapshot:HT(n,e.mode),bundleKey:r,keyEnvelope:e.mode===`locked`?e.keyEnvelope:null,encryptedContent:e.mode===`locked`?e.encryptedContent:null,encryptedMetadata:e.mode===`locked`?e.encryptedMetadata:null,keyEnvelopeDirty:!1,dirty:!1},ZT(),XT()}function XT(){let e=document.querySelector(`#save`);if(oT){e.textContent=`Saving…`,e.disabled=!0;return}if(Q.aliasId&&!Q.dirty){e.textContent=`Saved`,e.disabled=!0;return}e.textContent=Q.aliasId&&!Q.writeCapability?`Save copy`:`Save`,e.disabled=!1}function ZT(){let e=Q.mode===`locked`;document.querySelector(`#privacy-label`).textContent=e?`Password locked`:`Open`,document.querySelector(`#privacy-description`).textContent=e?`Source, metadata, and saved renders are encrypted in your browser. Password-locked diagrams cannot be embedded.`:`Anyone with the read link can open and embed this diagram.`,document.querySelector(`#lock-diagram`).hidden=e,document.querySelector(`#change-password`).hidden=!e,document.querySelector(`#unlock-diagram`).hidden=!e}function QT(){Q={aliasId:null,contentId:null,renderId:null,revision:null,mode:`open`,savedMode:null,writeCapability:null,savedState:null,savedSnapshot:null,bundleKey:null,keyEnvelope:null,encryptedContent:null,encryptedMetadata:null,keyEnvelopeDirty:!1,dirty:!0},cT.clear(),history.pushState(null,``,`/`),ET(fw($C))}function $T(){let e=uT.value.trim(),t=dT.value.trim();document.title=e?`${e} — diagram.zip`:`diagram.zip`,document.querySelector(`meta[name="description"]`).content=t||`Create diagrams from text and share them as a link.`}async function eE(e){if(navigator.clipboard)return navigator.clipboard.writeText(e);let t=document.createElement(`textarea`);t.value=e,document.body.append(t),t.select(),document.execCommand(`copy`),t.remove()}function $(e){let t=document.querySelector(`#toast`);t.textContent=e,t.dataset.visible=`true`,clearTimeout($.timeout),$.timeout=setTimeout(()=>delete t.dataset.visible,1800)}CT(),sT&&$(sT);\ No newline at end of file@@ -7,8 +7,8 @@
77 <meta name="description" content="Create diagrams from text and share them as a link.">88 <link rel="icon" href="/diagram.zip/icon.svg" type="image/svg+xml">99 <title>diagram.zip</title>10- <script type="module" crossorigin src="/diagram.zip/assets/index-CiEjbWbf.js"></script>11- <link rel="stylesheet" crossorigin href="/diagram.zip/assets/index-DysJyYXu.css">10+ <script type="module" crossorigin src="/diagram.zip/assets/index-DimbzOnT.js"></script>11+ <link rel="stylesheet" crossorigin href="/diagram.zip/assets/index-B2XsKz37.css">1212 </head>1313 <body>1414 <div id="app"></div>