collabmd 0.1.31 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/{drawio-editor-B4sHHwWJ.js → drawio-editor-Br4qyh3r.js} +1 -1
- package/dist/client/assets/drawioEditor-ClmnTx5J.js +2 -0
- package/dist/client/assets/{editor-session-BKHEYkbY.js → editor-session-Cf-iV5-3.js} +2 -2
- package/dist/client/assets/embedded-editor-base-CgEkjNYn.css +1 -0
- package/dist/client/assets/{excalidraw-editor-CHbeNZu6.js → excalidraw-editor-CXAxVhlw.js} +2 -2
- package/dist/client/assets/{excalidrawEditor-BhaTX2Ko.js → excalidrawEditor-DoH_kMdu.js} +2 -2
- package/dist/client/assets/exportDocument-9wTrBZNS.css +1 -0
- package/dist/client/assets/{index-kPgEYvEX.js → index-C8QZYHvV.js} +2 -2
- package/dist/client/assets/index-CxSbUTs2.css +1 -0
- package/dist/client/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/client/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/client/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/client/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/client/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/client/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/client/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/client/assets/main-BjHHMlv0.js +1249 -0
- package/dist/client/assets/vault-api-client-Bf9tB_GW.js +1 -0
- package/dist/client/assets/vault-api-client-D7Ck0CvR.css +1 -0
- package/dist/client/drawio-editor.html +1 -1
- package/dist/client/excalidraw-editor.html +1 -1
- package/dist/client/export-document.html +2 -2
- package/dist/client/index.html +2 -2
- package/package.json +4 -1
- package/src/client/app/main-entry.js +2 -0
- package/src/client/application/app-shell/git-feature.js +28 -36
- package/src/client/application/app-shell/presence-feature.js +3 -1
- package/src/client/application/diagram-preview-hydrator.js +24 -15
- package/src/client/application/mermaid-preview-hydrator.js +121 -4
- package/src/client/application/plantuml-preview-hydrator.js +6 -16
- package/src/client/application/preview-renderer.js +11 -2
- package/src/client/bootstrap/collabmd-app-shell.js +176 -50
- package/src/client/domain/excalidraw-document-switch.js +3 -0
- package/src/client/excalidraw-editor.js +187 -84
- package/src/client/infrastructure/auth-client.js +4 -4
- package/src/client/infrastructure/backlinks-api-client.js +18 -0
- package/src/client/infrastructure/comment-thread-store.js +4 -0
- package/src/client/infrastructure/editor-session.js +2 -0
- package/src/client/infrastructure/git-api-client.js +134 -0
- package/src/client/infrastructure/plantuml-api-client.js +21 -0
- package/src/client/infrastructure/vault-api-client.js +67 -0
- package/src/client/presentation/backlinks-panel.js +6 -9
- package/src/client/presentation/bases-preview-controller.js +26 -7
- package/src/client/presentation/excalidraw-embed-controller.js +251 -15
- package/src/client/presentation/excalidraw-embed-reconciler.js +0 -1
- package/src/client/presentation/file-action-controller.js +97 -0
- package/src/client/presentation/file-explorer-controller.js +2 -0
- package/src/client/presentation/file-explorer-view.js +368 -0
- package/src/client/presentation/file-history-view-controller.js +41 -40
- package/src/client/presentation/git-diff-view-controller.js +70 -53
- package/src/client/presentation/git-panel-controller.js +12 -30
- package/src/client/styles/export-document.css +45 -2
- package/src/client/styles/features/auth-gate.css +13 -24
- package/src/client/styles/features/comment-card.css +6 -0
- package/src/client/styles/features/comments-drawer.css +38 -18
- package/src/client/styles/features/diagram-preview.css +33 -0
- package/src/client/styles/features/embedded-preview.css +37 -11
- package/src/client/styles/features/preview-markdown.css +103 -28
- package/src/client/styles/foundation/themes.css +4 -0
- package/src/client/styles/foundation/tokens.css +4 -3
- package/src/client/styles/layout/sidebar.css +72 -0
- package/src/server/auth/auth-common.js +224 -0
- package/src/server/auth/auth-constants.js +30 -0
- package/src/server/auth/auth-strategies.js +320 -0
- package/src/server/auth/create-auth-service.js +24 -545
- package/src/server/domain/bases/base-definition.js +255 -0
- package/src/server/domain/bases/base-expression-runtime.js +1159 -0
- package/src/server/domain/bases/base-index-snapshot-store.js +418 -0
- package/src/server/domain/bases/base-query-results.js +205 -0
- package/src/server/domain/bases/base-query-service.js +68 -1965
- package/src/server/domain/collaboration/collaboration-room.js +36 -1
- package/src/server/domain/collaboration/room-client-state-store.js +1 -0
- package/src/server/domain/collaboration/room-persistence-controller.js +1 -1
- package/src/server/infrastructure/http/create-vault-api-query-handler.js +109 -1
- package/src/server/infrastructure/http/http-response.js +56 -0
- package/src/server/infrastructure/persistence/vault-file-store.js +110 -0
- package/dist/client/assets/drawioEditor-DGpo2hcx.js +0 -2
- package/dist/client/assets/embedded-editor-base-DgmRcsR8.css +0 -1
- package/dist/client/assets/exportDocument-DmV7Gngb.css +0 -1
- package/dist/client/assets/index-D69HOZHr.css +0 -1
- package/dist/client/assets/main-DSxNTtD-.js +0 -1243
- package/dist/client/assets/vault-api-client-BzHQseMN.js +0 -1
- package/dist/client/assets/vault-api-client-De7kMvrE.css +0 -1
- /package/dist/client/assets/{exportDocument-CtZKWE2c.js → exportDocument-Bia2hI25.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{l as e,n as t}from"./runtime-config-CqhchMC4.js";var n={enabled:!1,implemented:!0,loginEndpoint:`/api/auth/oidc/login`,passwordLabel:`Password`,provider:``,requiresLogin:!1,sessionEndpoint:`/api/auth/session`,statusEndpoint:`/api/auth/status`,strategy:`none`,submitLabel:`Continue`},r=`data:image/png;base64,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`;function i(){return{...n,...t().auth??{}}}function a(){let e=window.location.hash.startsWith(`#`)?window.location.hash.slice(1):window.location.hash;return new URLSearchParams(e)}function o(){return a().get(`auth_password`)||``}function s(){let e=a();if(!e.has(`auth_password`))return;e.delete(`auth_password`);let t=e.toString(),n=`${window.location.pathname}${window.location.search}${t?`#${t}`:``}`;window.history.replaceState(null,``,n)}function c(e){let t=a(),n=t.get(e)||``;if(!n)return``;t.delete(e);let r=t.toString(),i=`${window.location.pathname}${window.location.search}${r?`#${r}`:``}`;return window.history.replaceState(null,``,i),n}function l(){return`${window.location.pathname}${window.location.search}${window.location.hash}`}async function u(e){let t=await fetch(e.statusEndpoint,{headers:{Accept:`application/json`}}),n=await t.json().catch(()=>({}));if(!t.ok)throw Error(n.error||`Failed to read auth status`);return n}function d(e){let t=e?.auth??{};if(t.strategy!==`oidc`||t.provider!==`google`)return;let n=String(e?.user?.name??``).trim();if(n)try{window.localStorage.setItem(`collabmd-user-name`,n)}catch{}}async function f(e,t){let n=await fetch(e.sessionEndpoint,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({password:t})}),r=await n.json().catch(()=>({}));if(!n.ok)throw Error(r.error||`Authentication failed`);return r}function p(){let e=document.createElement(`div`);e.className=`auth-gate-overlay`;let t=document.createElement(`section`);return t.className=`auth-gate-card`,e.append(t),{card:t,overlay:e}}function m(){let e=document.createElement(`span`);e.className=`auth-gate-button__icon`,e.setAttribute(`aria-hidden`,`true`);let t=document.createElement(`img`);return t.alt=``,t.decoding=`async`,t.height=20,t.loading=`eager`,t.src=r,t.width=20,e.append(t),e}function h(e,t,{errorMessage:n=``}){e.replaceChildren();let r=document.createElement(`h1`);r.textContent=`Authentication required`;let i=document.createElement(`p`);i.textContent=`Sign in with Google to join this session.`;let a=document.createElement(`div`);a.className=`auth-gate-actions`;let o=document.createElement(`button`);o.className=`ui-button ui-button--secondary auth-gate-button auth-gate-button--google`,o.type=`button`,o.append(m(),document.createTextNode(t.submitLabel||`Continue with Google`));let s=document.createElement(`div`);s.className=`auth-gate-error`,s.textContent=n,a.append(o),e.append(r,i,s,a),o.addEventListener(`click`,()=>{let e=new URL(t.loginEndpoint,window.location.origin);e.searchParams.set(`returnTo`,l()),window.location.assign(e.toString())})}function g(e,{title:t,body:n,secondaryActionLabel:r=``,onSecondaryAction:i=null}){e.replaceChildren();let a=document.createElement(`h1`);a.textContent=t;let o=document.createElement(`p`);if(o.textContent=n,e.append(a,o),r&&typeof i==`function`){let t=document.createElement(`button`);t.className=`ui-button ui-button--secondary auth-gate-secondary-button`,t.type=`button`,t.textContent=r,t.addEventListener(`click`,()=>{i()}),e.append(t)}}function _(e,t,{onSubmit:n}){e.replaceChildren();let r=document.createElement(`h1`);r.textContent=`Authentication required`;let i=document.createElement(`p`);i.textContent=`Enter the host password to join this shared session.`;let a=document.createElement(`form`);a.className=`auth-gate-form`;let o=document.createElement(`label`);o.className=`auth-gate-label`,o.textContent=t.passwordLabel;let s=document.createElement(`input`);s.className=`ui-input auth-gate-input`,s.type=`password`,s.name=`password`,s.autocomplete=`current-password`,s.required=!0;let c=document.createElement(`div`);c.className=`auth-gate-error`;let l=document.createElement(`div`);l.className=`auth-gate-actions`;let u=document.createElement(`button`);u.className=`ui-button ui-button--primary auth-gate-button`,u.type=`submit`,u.textContent=t.submitLabel,l.append(u),o.append(s),a.append(r,i,o,c,l),e.append(a),a.addEventListener(`submit`,e=>{e.preventDefault();let t=s.value;s.disabled=!0,u.disabled=!0,c.textContent=``,n(t).catch(e=>{c.textContent=e instanceof Error?e.message:`Authentication failed`,s.disabled=!1,u.disabled=!1,s.select(),s.focus()})}),queueMicrotask(()=>{s.focus()})}async function v(){let e=i();if(!e.enabled||e.strategy===`none`)return{authenticated:!0,auth:e};let{card:t,overlay:n}=p();document.body.append(n);let r=c(`auth_error`);return new Promise(i=>{let a=(t=null)=>{s(),d(t),n.remove(),i({authenticated:!0,auth:t?.auth??e,user:t?.user??null})};(async()=>{g(t,{body:`Checking access to this session…`,title:`CollabMD`});try{let t=await u(e);if(t.authenticated){a(t);return}}catch(e){g(t,{body:e instanceof Error?`${e.message} Refresh the page and try again.`:`Failed to contact the auth service. Refresh the page and try again.`,title:`Cannot verify access`});return}if(e.strategy===`password`){let n=o();if(n)try{s(),await f(e,n),a();return}catch{}_(t,e,{onSubmit:async t=>{await f(e,t),a()}});return}if(e.strategy===`oidc`){h(t,e,{errorMessage:r}),r=``;return}g(t,{body:`This authentication strategy is not available.`,title:`Authentication unavailable`})})()})}function y(e){return encodeURIComponent(String(e??``))}function b(e){return String(e??``).replace(/\/+$/u,``).split(`/`).filter(Boolean).pop()||``}function x(e,t){let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=t,document.body.appendChild(r),r.click(),r.remove(),window.setTimeout(()=>URL.revokeObjectURL(n),0)}function S(e=``,t=`download`){let n=String(e).match(/filename\*=UTF-8''([^;]+)/iu);if(n)try{return decodeURIComponent(n[1])}catch{return t}return String(e).match(/filename="([^"]+)"/iu)?.[1]||t}function C(e,t={}){let n={...t};return e&&(n[`X-CollabMD-Request-Id`]=String(e)),n}async function w(e,t){let n=await e.json().catch(()=>({}));if(!e.ok||n.ok===!1){let r=Error(n.error||t);throw r.status=e.status,r.body=n,r}return n}async function T(e,{fallbackError:t,fallbackFileName:n}={}){let r=await fetch(e);if(!r.ok){let e=await r.json().catch(()=>({}));throw Error(e.error||t)}return x(await r.blob(),S(r.headers.get(`content-disposition`)||``,n)),r}var E=new class{async readTree(){return w(await fetch(e(`/files`)),`Failed to load file tree`)}async readFile(t){return w(await fetch(e(`/file?path=${encodeURIComponent(t)}`)),`Failed to read file`)}async queryBase({activeFilePath:t=``,path:n=``,search:r=``,source:i=null,sourcePath:a=``,view:o=``}={}){return w(await fetch(e(`/base/query`),{body:JSON.stringify({activeFilePath:t,path:n,search:r,source:i,sourcePath:a,view:o}),headers:{"Content-Type":`application/json`},method:`POST`}),`Failed to query base`)}async exportBaseCsv({activeFilePath:t=``,path:n=``,search:r=``,source:i=null,sourcePath:a=``,view:o=``}={}){let s=await fetch(e(`/base/export`),{body:JSON.stringify({activeFilePath:t,path:n,search:r,source:i,sourcePath:a,view:o}),headers:{"Content-Type":`application/json`},method:`POST`});if(!s.ok){let e=await s.json().catch(()=>({}));throw Error(e.error||`Failed to export base CSV`)}return{blob:await s.blob(),contentDisposition:s.headers.get(`content-disposition`)||``}}async createFile({content:t,path:n,requestId:r=null}){return w(await fetch(e(`/file`),{body:JSON.stringify({content:t,path:n}),headers:C(r,{"Content-Type":`application/json`}),method:`POST`}),`Failed to create file`)}async writeFile({content:t,path:n,requestId:r=null}){return w(await fetch(e(`/file`),{body:JSON.stringify({content:t,path:n}),headers:C(r,{"Content-Type":`application/json`}),method:`PUT`}),`Failed to write file`)}async renameFile({oldPath:t,newPath:n,requestId:r=null}){return w(await fetch(e(`/file`),{body:JSON.stringify({newPath:n,oldPath:t}),headers:C(r,{"Content-Type":`application/json`}),method:`PATCH`}),`Failed to rename file`)}async deleteFile(t,{requestId:n=null}={}){return w(await fetch(e(`/file?path=${encodeURIComponent(t)}`),{headers:n?C(n):void 0,method:`DELETE`}),`Failed to delete file`)}async createDirectory(t,{requestId:n=null}={}){return w(await fetch(e(`/directory`),{body:JSON.stringify({path:t}),headers:C(n,{"Content-Type":`application/json`}),method:`POST`}),`Failed to create folder`)}async renameDirectory({oldPath:t,newPath:n,requestId:r=null}){return w(await fetch(e(`/directory`),{body:JSON.stringify({newPath:n,oldPath:t}),headers:C(r,{"Content-Type":`application/json`}),method:`PATCH`}),`Failed to rename folder`)}async deleteDirectory(t,{recursive:n=!1,requestId:r=null}={}){let i=new URLSearchParams({path:String(t??``)});return n&&i.set(`recursive`,`1`),w(await fetch(e(`/directory?${i.toString()}`),{headers:r?C(r):void 0,method:`DELETE`}),`Failed to delete folder`)}async uploadImageAttachment({file:t,fileName:n=``,sourcePath:r}){return w(await fetch(e(`/attachments`),{body:t,headers:{"Content-Type":t?.type||`application/octet-stream`,"X-CollabMD-File-Name":y(n),"X-CollabMD-Source-Path":y(r)},method:`POST`}),`Failed to upload image`)}async downloadFile(t){let n=b(t)||`download`;return T(e(`/download/file?path=${encodeURIComponent(t)}`),{fallbackError:`Failed to download file`,fallbackFileName:n})}async downloadDirectory(t){let n=b(t)||`vault`;return T(e(`/download/directory?path=${encodeURIComponent(t)}`),{fallbackError:`Failed to download folder`,fallbackFileName:`${n}.zip`})}};export{v as n,E as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.auth-gate-overlay{z-index:5000;padding:var(--space-6);background:radial-gradient(circle at 50% 8%, oklch(from var(--color-primary) l c h / .18), transparent 28%), radial-gradient(circle at 60% 45%, oklch(from var(--color-primary) l c h / .08), transparent 18%), linear-gradient(180deg, oklch(from var(--color-bg) calc(l - .06) c h / .9), oklch(from var(--color-bg) calc(l - .08) c h / .96));-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.auth-gate-card{width:min(100%,440px);padding:var(--space-8);border:1px solid var(--border-accent-soft);border-radius:var(--radius-xl-plus);background:linear-gradient(180deg, var(--color-surface), var(--color-surface-2));color:var(--color-text);box-shadow:var(--shadow-card-strong), var(--shadow-sheet-inset);font-family:var(--font-body)}.auth-gate-card h1{font-family:var(--font-display);font-size:var(--text-xl);letter-spacing:-.04em;margin:0 0 10px;line-height:1.05}.auth-gate-card p{color:var(--color-text-muted);line-height:1.5;font-size:var(--text-sm);margin:0 0 22px}.auth-gate-form{gap:var(--space-3);display:grid}.auth-gate-label{gap:var(--space-2);color:var(--color-text-muted);font-size:var(--text-sm);font-weight:500;display:grid}.auth-gate-input{min-height:var(--control-h-lg);border-radius:var(--radius-xl);background:var(--surface-overlay);color:inherit}.auth-gate-input:focus{background:var(--surface-overlay-soft)}.auth-gate-actions{gap:var(--space-3);display:grid}.auth-gate-button,.auth-gate-secondary-button{width:100%;min-height:var(--control-h-lg);border-radius:var(--radius-xl);justify-content:center;transition:transform .16s,box-shadow .16s,border-color .16s,background-color .16s,color .16s}.auth-gate-button:hover,.auth-gate-secondary-button:hover{transform:translateY(-1px)}.auth-gate-button:focus-visible,.auth-gate-secondary-button:focus-visible{outline:3px solid oklch(from var(--color-primary) l c h / .3);outline-offset:2px}.auth-gate-button{box-shadow:var(--shadow-floating-soft);font-weight:600}.auth-gate-button:hover{box-shadow:var(--shadow-floating-hover)}.auth-gate-secondary-button{background:var(--surface-overlay)}.auth-gate-secondary-button:hover{background:var(--surface-overlay-soft)}.auth-gate-button[disabled],.auth-gate-secondary-button[disabled]{opacity:.7;cursor:wait;transform:none}.auth-gate-button--google{background:var(--color-surface-raised);color:var(--color-text);border-color:var(--color-border);box-shadow:var(--shadow-elevated-soft), var(--shadow-sheet-inset)}.auth-gate-button--google:hover{background:var(--surface-overlay-soft)}.auth-gate-button__icon{flex:none;justify-content:center;align-items:center;width:22px;height:22px;display:inline-flex}.auth-gate-button__icon img{object-fit:contain;width:100%;height:100%;display:block}.auth-gate-error{min-height:1.25rem;color:var(--color-error);font-size:var(--text-sm);margin-bottom:var(--space-1)}@media (width<=640px){.auth-gate-overlay{padding:var(--space-4)}.auth-gate-card{padding:var(--space-6);border-radius:var(--radius-xl)}}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>draw.io — CollabMD</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/drawioEditor-
|
|
7
|
+
<script type="module" crossorigin src="./assets/drawioEditor-ClmnTx5J.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="./assets/runtime-config-loader-_D2KZKn-.js">
|
|
9
9
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-Bo9GmnuQ.js">
|
|
10
10
|
</head>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Excalidraw — CollabMD</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/excalidrawEditor-
|
|
7
|
+
<script type="module" crossorigin src="./assets/excalidrawEditor-DoH_kMdu.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="./assets/runtime-config-loader-_D2KZKn-.js">
|
|
9
9
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-Bo9GmnuQ.js">
|
|
10
10
|
</head>
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>CollabMD Export</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/exportDocument-
|
|
7
|
+
<script type="module" crossorigin src="./assets/exportDocument-Bia2hI25.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="./assets/runtime-config-loader-_D2KZKn-.js">
|
|
9
9
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-Bo9GmnuQ.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="./assets/base-h1uscjTi.js">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="./assets/base-CaIEJUF9.css">
|
|
12
|
-
<link rel="stylesheet" crossorigin href="./assets/exportDocument-
|
|
12
|
+
<link rel="stylesheet" crossorigin href="./assets/exportDocument-9wTrBZNS.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body data-theme="light" data-surface="export-document">
|
|
15
15
|
<main class="export-page-shell">
|
package/dist/client/index.html
CHANGED
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
document.documentElement.setAttribute('data-initial-file-requested', 'true');
|
|
16
16
|
}
|
|
17
17
|
</script>
|
|
18
|
-
<script type="module" crossorigin src="./assets/index-
|
|
18
|
+
<script type="module" crossorigin src="./assets/index-C8QZYHvV.js"></script>
|
|
19
19
|
<link rel="modulepreload" crossorigin href="./assets/runtime-config-loader-_D2KZKn-.js">
|
|
20
20
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-Bo9GmnuQ.js">
|
|
21
21
|
<link rel="modulepreload" crossorigin href="./assets/base-h1uscjTi.js">
|
|
22
22
|
<link rel="stylesheet" crossorigin href="./assets/base-CaIEJUF9.css">
|
|
23
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
23
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CxSbUTs2.css">
|
|
24
24
|
</head>
|
|
25
25
|
<body>
|
|
26
26
|
<div id="appRoot"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "collabmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Collaborative markdown vault — like Obsidian, but online. Serve any directory of markdown files for realtime collaborative editing.",
|
|
6
6
|
"repository": {
|
|
@@ -71,7 +71,10 @@
|
|
|
71
71
|
"@codemirror/theme-one-dark": "^6.1.3",
|
|
72
72
|
"@codemirror/view": "^6.40.0",
|
|
73
73
|
"@excalidraw/excalidraw": "0.18.0-4a5c9e9",
|
|
74
|
+
"@fontsource-variable/inter": "^5.2.8",
|
|
75
|
+
"@fontsource/jetbrains-mono": "^5.2.8",
|
|
74
76
|
"@turbodocx/html-to-docx": "^1.20.1",
|
|
77
|
+
"archiver": "^7.0.1",
|
|
75
78
|
"highlight.js": "^11.11.1",
|
|
76
79
|
"js-yaml": "^4.1.1",
|
|
77
80
|
"lib0": "^0.2.117",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import darkHighlightThemeUrl from '../assets/vendor/highlight/github-dark.min.css?url';
|
|
2
2
|
import lightHighlightThemeUrl from '../assets/vendor/highlight/github.min.css?url';
|
|
3
|
+
import '@fontsource-variable/inter';
|
|
4
|
+
import '@fontsource/jetbrains-mono';
|
|
3
5
|
import '../styles/base.css';
|
|
4
6
|
import '../styles/style.css';
|
|
5
7
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createWorkspaceChange } from '../../../domain/workspace-change.js';
|
|
2
|
-
import { resolveApiUrl } from '../../domain/runtime-paths.js';
|
|
3
2
|
import { createWorkspaceRequestId } from '../../domain/workspace-request-id.js';
|
|
4
3
|
|
|
5
4
|
function normalizeWorkspaceChange(workspaceChange = {}) {
|
|
@@ -257,15 +256,10 @@ export const gitFeature = {
|
|
|
257
256
|
this.syncFileHistoryButton({ filePath: resolvedCurrentFilePath, mode: 'history-preview' });
|
|
258
257
|
|
|
259
258
|
try {
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const data = await response.json();
|
|
265
|
-
if (!response.ok) {
|
|
266
|
-
throw new Error(data.error || 'Failed to load historical file preview');
|
|
267
|
-
}
|
|
268
|
-
|
|
259
|
+
const data = await this.gitApiClient.readFileSnapshot({
|
|
260
|
+
hash,
|
|
261
|
+
path: filePath,
|
|
262
|
+
});
|
|
269
263
|
this.setStaticPreviewDocument?.({
|
|
270
264
|
...data,
|
|
271
265
|
currentFilePath: resolvedCurrentFilePath,
|
|
@@ -286,7 +280,7 @@ export const gitFeature = {
|
|
|
286
280
|
}
|
|
287
281
|
|
|
288
282
|
this.elements.outlineToggle?.classList.add('hidden');
|
|
289
|
-
this.renderTextFilePreview({
|
|
283
|
+
this.renderTextFilePreview?.({
|
|
290
284
|
content: data.content,
|
|
291
285
|
filePath: resolvedCurrentFilePath,
|
|
292
286
|
});
|
|
@@ -294,7 +288,7 @@ export const gitFeature = {
|
|
|
294
288
|
console.error('[git-preview] Failed to load file snapshot:', error);
|
|
295
289
|
this.clearStaticPreviewDocument?.();
|
|
296
290
|
this.toastController.show('Failed to load historical file preview');
|
|
297
|
-
this.renderTextFilePreview({
|
|
291
|
+
this.renderTextFilePreview?.({
|
|
298
292
|
content: 'Failed to load historical file preview.',
|
|
299
293
|
filePath: resolvedCurrentFilePath,
|
|
300
294
|
});
|
|
@@ -302,28 +296,26 @@ export const gitFeature = {
|
|
|
302
296
|
}
|
|
303
297
|
},
|
|
304
298
|
|
|
305
|
-
async postGitAction(
|
|
299
|
+
async postGitAction(actionName, payload) {
|
|
306
300
|
const requestId = createWorkspaceRequestId();
|
|
307
301
|
this.pendingWorkspaceRequestIds?.add(requestId);
|
|
308
302
|
try {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
throw error;
|
|
303
|
+
switch (actionName) {
|
|
304
|
+
case 'stage':
|
|
305
|
+
return await this.gitApiClient.stageFile({ path: payload.path, requestId });
|
|
306
|
+
case 'unstage':
|
|
307
|
+
return await this.gitApiClient.unstageFile({ path: payload.path, requestId });
|
|
308
|
+
case 'push':
|
|
309
|
+
return await this.gitApiClient.pushBranch({ requestId });
|
|
310
|
+
case 'pull':
|
|
311
|
+
return await this.gitApiClient.pullBranch({ requestId });
|
|
312
|
+
case 'reset-file':
|
|
313
|
+
return await this.gitApiClient.resetFile({ path: payload.path, requestId });
|
|
314
|
+
case 'commit':
|
|
315
|
+
return await this.gitApiClient.commit({ message: payload.message, requestId });
|
|
316
|
+
default:
|
|
317
|
+
throw new Error(`Unsupported git action: ${actionName}`);
|
|
325
318
|
}
|
|
326
|
-
return data;
|
|
327
319
|
} catch (error) {
|
|
328
320
|
this.pendingWorkspaceRequestIds?.delete(requestId);
|
|
329
321
|
throw error;
|
|
@@ -483,7 +475,7 @@ export const gitFeature = {
|
|
|
483
475
|
}
|
|
484
476
|
|
|
485
477
|
await this.runGitActionWithStatus('Staging changes...', async () => {
|
|
486
|
-
const result = await this.postGitAction(
|
|
478
|
+
const result = await this.postGitAction('stage', { path: filePath });
|
|
487
479
|
await this.finalizeGitAction({
|
|
488
480
|
action: 'stage',
|
|
489
481
|
filePath,
|
|
@@ -499,7 +491,7 @@ export const gitFeature = {
|
|
|
499
491
|
}
|
|
500
492
|
|
|
501
493
|
await this.runGitActionWithStatus('Unstaging changes...', async () => {
|
|
502
|
-
const result = await this.postGitAction(
|
|
494
|
+
const result = await this.postGitAction('unstage', { path: filePath });
|
|
503
495
|
await this.finalizeGitAction({
|
|
504
496
|
action: 'unstage',
|
|
505
497
|
filePath,
|
|
@@ -512,7 +504,7 @@ export const gitFeature = {
|
|
|
512
504
|
async pushGitBranch() {
|
|
513
505
|
await this.runGitActionWithStatus('Pushing branch...', async () => {
|
|
514
506
|
try {
|
|
515
|
-
const result = await this.postGitAction(
|
|
507
|
+
const result = await this.postGitAction('push', {});
|
|
516
508
|
await this.finalizeGitAction({
|
|
517
509
|
action: 'push',
|
|
518
510
|
result,
|
|
@@ -526,7 +518,7 @@ export const gitFeature = {
|
|
|
526
518
|
async pullGitBranch() {
|
|
527
519
|
await this.runGitActionWithStatus('Pulling branch...', async () => {
|
|
528
520
|
try {
|
|
529
|
-
const result = await this.postGitAction(
|
|
521
|
+
const result = await this.postGitAction('pull', {});
|
|
530
522
|
await this.finalizeGitAction({
|
|
531
523
|
action: 'pull',
|
|
532
524
|
result,
|
|
@@ -598,7 +590,7 @@ export const gitFeature = {
|
|
|
598
590
|
}
|
|
599
591
|
try {
|
|
600
592
|
await this.runGitActionWithStatus('Resetting file...', async () => {
|
|
601
|
-
const result = await this.postGitAction(
|
|
593
|
+
const result = await this.postGitAction('reset-file', { path: filePath });
|
|
602
594
|
await this.finalizeGitAction({
|
|
603
595
|
action: 'reset',
|
|
604
596
|
filePath,
|
|
@@ -676,7 +668,7 @@ export const gitFeature = {
|
|
|
676
668
|
}
|
|
677
669
|
try {
|
|
678
670
|
await this.runGitActionWithStatus('Committing changes...', async () => {
|
|
679
|
-
const result = await this.postGitAction(
|
|
671
|
+
const result = await this.postGitAction('commit', {
|
|
680
672
|
message,
|
|
681
673
|
});
|
|
682
674
|
await this.finalizeGitAction({
|
|
@@ -17,9 +17,11 @@ export const presenceFeature = {
|
|
|
17
17
|
if (!badge) return;
|
|
18
18
|
|
|
19
19
|
const hasEditorSession = Boolean(this.session);
|
|
20
|
+
const isDrawioRoute = Boolean(this.currentFilePath && this.isDrawioFile?.(this.currentFilePath));
|
|
20
21
|
const isExcalidrawRoute = Boolean(this.currentFilePath && this.isExcalidrawFile?.(this.currentFilePath));
|
|
21
22
|
const isImageRoute = Boolean(this.currentFilePath && this.isImageFile?.(this.currentFilePath));
|
|
22
|
-
const shouldUseLobbyState = !hasEditorSession
|
|
23
|
+
const shouldUseLobbyState = !hasEditorSession
|
|
24
|
+
&& (!this.currentFilePath || isDrawioRoute || isExcalidrawRoute || isImageRoute);
|
|
23
25
|
const effectiveConnectionState = shouldUseLobbyState
|
|
24
26
|
? this.lobby?.getConnectionState?.() ?? this.connectionState
|
|
25
27
|
: this.connectionState;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { resolveApiUrl } from '../domain/runtime-paths.js';
|
|
2
1
|
import {
|
|
3
2
|
cancelIdleRender,
|
|
4
3
|
IDLE_RENDER_TIMEOUT_MS,
|
|
@@ -7,6 +6,7 @@ import {
|
|
|
7
6
|
shouldPreserveHydratedDiagram,
|
|
8
7
|
syncAttribute,
|
|
9
8
|
} from './preview-diagram-utils.js';
|
|
9
|
+
import { resolveApiUrl } from '../domain/runtime-paths.js';
|
|
10
10
|
|
|
11
11
|
function datasetKeyToAttributeName(datasetKey) {
|
|
12
12
|
return `data-${datasetKey.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)}`;
|
|
@@ -16,7 +16,8 @@ export class DiagramPreviewHydrator {
|
|
|
16
16
|
constructor(renderer, {
|
|
17
17
|
batchSize,
|
|
18
18
|
datasetKeys,
|
|
19
|
-
fetchFn =
|
|
19
|
+
fetchFn = null,
|
|
20
|
+
loadFileSource = null,
|
|
20
21
|
filePathLabel,
|
|
21
22
|
requestIdleRenderFn = requestIdleRender,
|
|
22
23
|
cancelIdleRenderFn = cancelIdleRender,
|
|
@@ -29,7 +30,7 @@ export class DiagramPreviewHydrator {
|
|
|
29
30
|
this.renderer = renderer;
|
|
30
31
|
this.batchSize = batchSize;
|
|
31
32
|
this.datasetKeys = datasetKeys;
|
|
32
|
-
this.
|
|
33
|
+
this.loadFileSource = loadFileSource ?? this.createLegacyFileSourceLoader(fetchFn);
|
|
33
34
|
this.filePathLabel = filePathLabel;
|
|
34
35
|
this.requestIdleRenderFn = requestIdleRenderFn;
|
|
35
36
|
this.cancelIdleRenderFn = cancelIdleRenderFn;
|
|
@@ -53,6 +54,22 @@ export class DiagramPreviewHydrator {
|
|
|
53
54
|
this.fileInflightRequests = new Map();
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
createLegacyFileSourceLoader(fetchFn) {
|
|
58
|
+
if (typeof fetchFn !== 'function') {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return async (filePath) => {
|
|
63
|
+
const response = await fetchFn(resolveApiUrl(`/file?path=${encodeURIComponent(filePath)}`));
|
|
64
|
+
const payload = await response.json().catch(() => ({}));
|
|
65
|
+
if (!response.ok || payload?.ok === false) {
|
|
66
|
+
throw new Error(payload?.error || `Failed to load ${this.filePathLabel.toLowerCase()} source`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return String(payload?.content ?? '');
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
destroy() {
|
|
57
74
|
this.cancelHydration();
|
|
58
75
|
this.preservedShells.clear();
|
|
@@ -359,19 +376,11 @@ export class DiagramPreviewHydrator {
|
|
|
359
376
|
return this.fileInflightRequests.get(target);
|
|
360
377
|
}
|
|
361
378
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
},
|
|
366
|
-
})
|
|
367
|
-
.then(async (response) => {
|
|
368
|
-
const data = await response.json().catch(() => null);
|
|
369
|
-
if (!response.ok || typeof data?.content !== 'string') {
|
|
370
|
-
throw new Error(data?.error || `Failed to load ${target}`);
|
|
371
|
-
}
|
|
379
|
+
if (typeof this.loadFileSource !== 'function') {
|
|
380
|
+
throw new Error(`Missing ${this.filePathLabel} source loader`);
|
|
381
|
+
}
|
|
372
382
|
|
|
373
|
-
|
|
374
|
-
})
|
|
383
|
+
const request = this.loadFileSource(target)
|
|
375
384
|
.finally(() => {
|
|
376
385
|
this.fileInflightRequests.delete(target);
|
|
377
386
|
});
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from './preview-diagram-utils.js';
|
|
13
13
|
|
|
14
14
|
export class MermaidPreviewHydrator extends DiagramPreviewHydrator {
|
|
15
|
-
constructor(renderer) {
|
|
15
|
+
constructor(renderer, { fetchFn = null, loadFileSource = null } = {}) {
|
|
16
16
|
super(renderer, {
|
|
17
17
|
batchSize: MERMAID_BATCH_SIZE,
|
|
18
18
|
datasetKeys: {
|
|
@@ -26,7 +26,9 @@ export class MermaidPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
26
26
|
sourceLineEnd: 'sourceLineEnd',
|
|
27
27
|
target: 'mermaidTarget',
|
|
28
28
|
},
|
|
29
|
+
fetchFn,
|
|
29
30
|
filePathLabel: 'Mermaid',
|
|
31
|
+
loadFileSource,
|
|
30
32
|
shellClassName: 'mermaid-shell',
|
|
31
33
|
sourceClassName: 'mermaid-source',
|
|
32
34
|
});
|
|
@@ -35,6 +37,101 @@ export class MermaidPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
35
37
|
this.loader = null;
|
|
36
38
|
this.runtime = null;
|
|
37
39
|
this.shellRefits = new WeakMap();
|
|
40
|
+
this.activeMaximizedShell = null;
|
|
41
|
+
this.maximizedRoot = null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
destroy() {
|
|
45
|
+
this.cancelHydration();
|
|
46
|
+
this.preservedShells.clear();
|
|
47
|
+
this.clearActiveShell();
|
|
48
|
+
this.maximizedRoot?.remove();
|
|
49
|
+
this.maximizedRoot = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
cancelHydration() {
|
|
53
|
+
super.cancelHydration();
|
|
54
|
+
|
|
55
|
+
const activeShell = this.syncActiveShell();
|
|
56
|
+
if (!activeShell) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.restoreShellMount(activeShell);
|
|
61
|
+
activeShell.classList.remove('is-maximized');
|
|
62
|
+
this.clearActiveShell();
|
|
63
|
+
document.body.classList.remove('mermaid-maximized-open');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
clearActiveShell() {
|
|
67
|
+
this.activeMaximizedShell = null;
|
|
68
|
+
if (this.maximizedRoot && this.maximizedRoot.childElementCount === 0) {
|
|
69
|
+
this.maximizedRoot.hidden = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
syncActiveShell() {
|
|
74
|
+
if (
|
|
75
|
+
this.activeMaximizedShell?.isConnected
|
|
76
|
+
&& this.activeMaximizedShell.classList.contains('is-maximized')
|
|
77
|
+
) {
|
|
78
|
+
return this.activeMaximizedShell;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.clearActiveShell();
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
ensureMaximizedRoot() {
|
|
86
|
+
if (this.maximizedRoot?.isConnected && this.maximizedRoot.parentElement === document.body) {
|
|
87
|
+
return this.maximizedRoot;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let maximizedRoot = document.body.querySelector('[data-mermaid-maximized-root="true"]');
|
|
91
|
+
if (!maximizedRoot) {
|
|
92
|
+
maximizedRoot = document.createElement('div');
|
|
93
|
+
maximizedRoot.dataset.mermaidMaximizedRoot = 'true';
|
|
94
|
+
maximizedRoot.className = 'mermaid-maximized-root';
|
|
95
|
+
document.body.appendChild(maximizedRoot);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.maximizedRoot = maximizedRoot;
|
|
99
|
+
return maximizedRoot;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
mountShellInMaximizedRoot(shell) {
|
|
103
|
+
if (!shell) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const maximizedRoot = this.ensureMaximizedRoot();
|
|
108
|
+
maximizedRoot.hidden = false;
|
|
109
|
+
shell._mermaidRestoreParent = shell.parentElement || null;
|
|
110
|
+
shell._mermaidRestoreNextSibling = shell.nextSibling || null;
|
|
111
|
+
maximizedRoot.appendChild(shell);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
restoreShellMount(shell) {
|
|
115
|
+
if (!shell) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const restoreParent = shell._mermaidRestoreParent;
|
|
120
|
+
const restoreNextSibling = shell._mermaidRestoreNextSibling;
|
|
121
|
+
if (restoreParent?.isConnected) {
|
|
122
|
+
if (restoreNextSibling?.parentElement === restoreParent) {
|
|
123
|
+
restoreParent.insertBefore(shell, restoreNextSibling);
|
|
124
|
+
} else {
|
|
125
|
+
restoreParent.appendChild(shell);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
shell._mermaidRestoreParent = null;
|
|
130
|
+
shell._mermaidRestoreNextSibling = null;
|
|
131
|
+
|
|
132
|
+
if (this.maximizedRoot && this.maximizedRoot.childElementCount === 0) {
|
|
133
|
+
this.maximizedRoot.hidden = true;
|
|
134
|
+
}
|
|
38
135
|
}
|
|
39
136
|
|
|
40
137
|
applyTheme(theme) {
|
|
@@ -111,6 +208,7 @@ export class MermaidPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
111
208
|
if (restoredMaximizedShell) {
|
|
112
209
|
document.body.classList.add('mermaid-maximized-open');
|
|
113
210
|
}
|
|
211
|
+
this.syncActiveShell();
|
|
114
212
|
}
|
|
115
213
|
|
|
116
214
|
async prepareHydrationBatch() {
|
|
@@ -240,6 +338,10 @@ export class MermaidPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
240
338
|
}
|
|
241
339
|
|
|
242
340
|
const hydratedShells = Array.from(previewElement.querySelectorAll('.mermaid-shell[data-mermaid-hydrated="true"]'));
|
|
341
|
+
const activeShell = this.syncActiveShell();
|
|
342
|
+
if (activeShell && !hydratedShells.includes(activeShell)) {
|
|
343
|
+
hydratedShells.push(activeShell);
|
|
344
|
+
}
|
|
243
345
|
if (hydratedShells.length === 0) {
|
|
244
346
|
return;
|
|
245
347
|
}
|
|
@@ -447,11 +549,14 @@ export class MermaidPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
447
549
|
};
|
|
448
550
|
|
|
449
551
|
const setMaximizedState = (shouldMaximize) => {
|
|
450
|
-
const previewElement = this.renderer.previewElement;
|
|
451
552
|
if (shouldMaximize) {
|
|
452
|
-
const activeContainer =
|
|
553
|
+
const activeContainer = this.syncActiveShell();
|
|
453
554
|
if (activeContainer && activeContainer !== shell) {
|
|
555
|
+
this.restoreShellMount(activeContainer);
|
|
454
556
|
activeContainer.classList.remove('is-maximized');
|
|
557
|
+
if (this.activeMaximizedShell === activeContainer) {
|
|
558
|
+
this.clearActiveShell();
|
|
559
|
+
}
|
|
455
560
|
this.shellRefits.get(activeContainer)?.();
|
|
456
561
|
const activeButton = activeContainer.querySelector('.mermaid-maximize-btn');
|
|
457
562
|
if (activeButton) {
|
|
@@ -460,15 +565,21 @@ export class MermaidPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
460
565
|
activeButton.title = 'Maximize diagram';
|
|
461
566
|
}
|
|
462
567
|
}
|
|
568
|
+
this.mountShellInMaximizedRoot(shell);
|
|
463
569
|
shell.classList.add('is-maximized');
|
|
570
|
+
this.activeMaximizedShell = shell;
|
|
464
571
|
document.body.classList.add('mermaid-maximized-open');
|
|
465
572
|
syncMaximizeButtonState();
|
|
466
573
|
scheduleResetZoomToFit({ force: true });
|
|
467
574
|
return;
|
|
468
575
|
}
|
|
469
576
|
|
|
577
|
+
this.restoreShellMount(shell);
|
|
470
578
|
shell.classList.remove('is-maximized');
|
|
471
|
-
if (
|
|
579
|
+
if (this.activeMaximizedShell === shell) {
|
|
580
|
+
this.clearActiveShell();
|
|
581
|
+
}
|
|
582
|
+
if (!this.syncActiveShell()) {
|
|
472
583
|
document.body.classList.remove('mermaid-maximized-open');
|
|
473
584
|
}
|
|
474
585
|
syncMaximizeButtonState();
|
|
@@ -548,6 +659,12 @@ export class MermaidPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
548
659
|
}
|
|
549
660
|
|
|
550
661
|
scheduleActiveRefit() {
|
|
662
|
+
const activeShell = this.syncActiveShell();
|
|
663
|
+
if (activeShell) {
|
|
664
|
+
this.shellRefits.get(activeShell)?.();
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
551
668
|
const previewElement = this.renderer.previewElement;
|
|
552
669
|
if (!previewElement) {
|
|
553
670
|
return;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { clamp } from '../domain/vault-utils.js';
|
|
2
2
|
import { setDiagramActionButtonIcon } from '../domain/diagram-action-icons.js';
|
|
3
|
-
import { resolveApiUrl } from '../domain/runtime-paths.js';
|
|
4
3
|
import { DiagramPreviewHydrator } from './diagram-preview-hydrator.js';
|
|
5
4
|
import {
|
|
6
5
|
createPlantUmlPlaceholderCard,
|
|
@@ -13,7 +12,7 @@ import {
|
|
|
13
12
|
} from './preview-diagram-utils.js';
|
|
14
13
|
|
|
15
14
|
export class PlantUmlPreviewHydrator extends DiagramPreviewHydrator {
|
|
16
|
-
constructor(renderer) {
|
|
15
|
+
constructor(renderer, { loadFileSource = null, renderClient = null } = {}) {
|
|
17
16
|
super(renderer, {
|
|
18
17
|
batchSize: PLANTUML_BATCH_SIZE,
|
|
19
18
|
datasetKeys: {
|
|
@@ -28,10 +27,12 @@ export class PlantUmlPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
28
27
|
target: 'plantumlTarget',
|
|
29
28
|
},
|
|
30
29
|
filePathLabel: 'PlantUML',
|
|
30
|
+
loadFileSource,
|
|
31
31
|
shellClassName: 'plantuml-shell',
|
|
32
32
|
sourceClassName: 'plantuml-source',
|
|
33
33
|
});
|
|
34
34
|
this.renderer = renderer;
|
|
35
|
+
this.renderClient = renderClient;
|
|
35
36
|
this.svgCache = new Map();
|
|
36
37
|
this.svgInflightRequests = new Map();
|
|
37
38
|
this.shellRefits = new WeakMap();
|
|
@@ -206,20 +207,9 @@ export class PlantUmlPreviewHydrator extends DiagramPreviewHydrator {
|
|
|
206
207
|
return this.svgInflightRequests.get(cacheKey);
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
const request =
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
'Content-Type': 'application/json',
|
|
213
|
-
},
|
|
214
|
-
body: JSON.stringify({ source }),
|
|
215
|
-
})
|
|
216
|
-
.then(async (response) => {
|
|
217
|
-
const data = await response.json().catch(() => null);
|
|
218
|
-
if (!response.ok || !data?.ok || typeof data.svg !== 'string') {
|
|
219
|
-
throw new Error(data?.error || 'Failed to render PlantUML');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const sanitized = sanitizeSvgMarkup(data.svg);
|
|
210
|
+
const request = this.renderClient.renderSvg(source)
|
|
211
|
+
.then((svgMarkup) => {
|
|
212
|
+
const sanitized = sanitizeSvgMarkup(svgMarkup);
|
|
223
213
|
this.svgCache.set(cacheKey, sanitized);
|
|
224
214
|
return sanitized;
|
|
225
215
|
})
|
|
@@ -10,23 +10,27 @@ export class PreviewRenderer {
|
|
|
10
10
|
constructor({
|
|
11
11
|
getContent,
|
|
12
12
|
getFileList,
|
|
13
|
+
loadFileSource = null,
|
|
13
14
|
getSourceFilePath,
|
|
14
15
|
onAfterRenderCommit,
|
|
15
16
|
onBeforeRenderCommit,
|
|
16
17
|
onPreviewLayoutChange,
|
|
17
18
|
onRenderComplete,
|
|
18
19
|
outlineController,
|
|
20
|
+
plantUmlRenderClient = null,
|
|
19
21
|
previewContainer,
|
|
20
22
|
previewElement,
|
|
21
23
|
}) {
|
|
22
24
|
this.getContent = getContent;
|
|
23
25
|
this.getFileList = getFileList;
|
|
26
|
+
this.loadFileSource = loadFileSource;
|
|
24
27
|
this.getSourceFilePath = getSourceFilePath;
|
|
25
28
|
this.onAfterRenderCommit = onAfterRenderCommit;
|
|
26
29
|
this.onBeforeRenderCommit = onBeforeRenderCommit;
|
|
27
30
|
this.onPreviewLayoutChange = onPreviewLayoutChange;
|
|
28
31
|
this.onRenderComplete = onRenderComplete;
|
|
29
32
|
this.outlineController = outlineController;
|
|
33
|
+
this.plantUmlRenderClient = plantUmlRenderClient;
|
|
30
34
|
this.previewContainer = previewContainer;
|
|
31
35
|
this.previewElement = previewElement;
|
|
32
36
|
this.renderHost = null;
|
|
@@ -78,8 +82,13 @@ export class PreviewRenderer {
|
|
|
78
82
|
this.plantUmlHydrator.scheduleActiveRefit();
|
|
79
83
|
};
|
|
80
84
|
|
|
81
|
-
this.mermaidHydrator = new MermaidPreviewHydrator(this
|
|
82
|
-
|
|
85
|
+
this.mermaidHydrator = new MermaidPreviewHydrator(this, {
|
|
86
|
+
loadFileSource,
|
|
87
|
+
});
|
|
88
|
+
this.plantUmlHydrator = new PlantUmlPreviewHydrator(this, {
|
|
89
|
+
loadFileSource,
|
|
90
|
+
renderClient: plantUmlRenderClient,
|
|
91
|
+
});
|
|
83
92
|
this.renderScheduler = new PreviewRenderScheduler({ getRenderProfileFn: getRenderProfile });
|
|
84
93
|
this.renderExecutor = new PreviewRenderExecutor({
|
|
85
94
|
attachmentApiPath: resolveApiUrl('/attachment'),
|