@alwith/module-editor 0.0.0-stage → 0.1.0-rc.12

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.
Files changed (64) hide show
  1. package/LICENSE +94 -0
  2. package/README.md +47 -2
  3. package/THIRD_PARTY_NOTICES.md +3 -0
  4. package/dist/core.d.ts +56 -0
  5. package/dist/core.js +8 -0
  6. package/dist/index.d.ts +86 -0
  7. package/dist/index.js +1 -0
  8. package/dist/monaco.d.ts +16 -0
  9. package/dist/monaco.js +1 -0
  10. package/dist/new-tab.d.ts +43 -0
  11. package/dist/new-tab.js +1 -0
  12. package/dist/preview-source-mode-toggle.d.ts +8 -0
  13. package/dist/preview-source-mode-toggle.js +1 -0
  14. package/dist/previews/excel-data.d.ts +20 -0
  15. package/dist/previews/excel-data.js +3 -0
  16. package/dist/previews/excel-navigation.d.ts +4 -0
  17. package/dist/previews/excel-navigation.js +1 -0
  18. package/dist/previews/excel.d.ts +3 -0
  19. package/dist/previews/excel.js +1 -0
  20. package/dist/previews/html-resources.d.ts +3 -0
  21. package/dist/previews/html-resources.js +2 -0
  22. package/dist/previews/html-session.d.ts +8 -0
  23. package/dist/previews/html-session.js +1 -0
  24. package/dist/previews/html.d.ts +3 -0
  25. package/dist/previews/html.js +1 -0
  26. package/dist/previews/index.d.ts +83 -0
  27. package/dist/previews/index.js +1 -0
  28. package/dist/previews/markdown-outline.d.ts +6 -0
  29. package/dist/previews/markdown-outline.js +1 -0
  30. package/dist/previews/markdown-readonly.d.ts +4 -0
  31. package/dist/previews/markdown-readonly.js +2 -0
  32. package/dist/previews/markdown.css +918 -0
  33. package/dist/previews/markdown.d.ts +5 -0
  34. package/dist/previews/markdown.js +1 -0
  35. package/dist/previews/media-types.d.ts +1 -0
  36. package/dist/previews/media-types.js +1 -0
  37. package/dist/previews/media.d.ts +2 -0
  38. package/dist/previews/media.js +1 -0
  39. package/dist/previews/pdf-controls.d.ts +1 -0
  40. package/dist/previews/pdf-controls.js +1 -0
  41. package/dist/previews/pdf.d.ts +2 -0
  42. package/dist/previews/pdf.js +1 -0
  43. package/dist/previews/shared.d.ts +7 -0
  44. package/dist/previews/shared.js +1 -0
  45. package/dist/previews/styles.css +142 -0
  46. package/dist/previews/tiff.d.ts +2 -0
  47. package/dist/previews/tiff.js +1 -0
  48. package/dist/previews/viewers.css +221 -0
  49. package/dist/previews/word.d.ts +2 -0
  50. package/dist/previews/word.js +1 -0
  51. package/dist/quick-start-assets.d.ts +4 -0
  52. package/dist/quick-start-assets.js +1 -0
  53. package/dist/quick-start-data.d.ts +11 -0
  54. package/dist/quick-start-data.js +1 -0
  55. package/dist/quick-start-icons.d.ts +9 -0
  56. package/dist/quick-start-icons.js +1 -0
  57. package/dist/styles.css +726 -0
  58. package/dist/workspace-assets.d.ts +2 -0
  59. package/dist/workspace-assets.js +1 -0
  60. package/dist/workspace-tabs.d.ts +27 -0
  61. package/dist/workspace-tabs.js +1 -0
  62. package/dist/workspace.d.ts +31 -0
  63. package/dist/workspace.js +1 -0
  64. package/package.json +163 -4
package/LICENSE ADDED
@@ -0,0 +1,94 @@
1
+ ALwith Developer and Redistribution License, Version 1.0
2
+ Copyright 2026 ALWITHAI PTE. LTD. All rights reserved, except as expressly granted below.
3
+
4
+ 1. Scope
5
+ This license applies only to ALWITHAI PTE. LTD.'s ("ALwith") own material in
6
+ the npm package accompanying this license (the "Software"). It is a proprietary
7
+ license, not an open-source license. Third-party material is governed by its
8
+ respective licenses, as described in THIRD_PARTY_NOTICES.md. No restriction here
9
+ reduces rights granted by those licenses or by mandatory applicable law.
10
+
11
+ 2. Permission to develop and distribute applications
12
+ Subject to this license, ALwith grants you a worldwide, non-exclusive, royalty-free
13
+ copyright license to use, reproduce, integrate and modify the supplied Software
14
+ to develop, test, host and operate your own applications, for personal,
15
+ internal business or commercial use. You may brand,
16
+ sell and distribute those applications, including the Software embedded in them,
17
+ to your customers and end users. No per-application registration, approval or
18
+ royalty is required by this software license.
19
+
20
+ You may bundle, minify, transform and otherwise adapt the supplied JavaScript,
21
+ styles and declarations for those applications. You may include the supplied
22
+ Software in an application source distribution when necessary to build or use
23
+ that application, provided it remains separately identified under this license.
24
+ You may combine it with separately licensed application code, including MPL-2.0
25
+ code, while complying with both licenses. Your own independent code remains yours;
26
+ this license does not require disclosure of your application source code.
27
+
28
+ Recipients may use the embedded Software to run the application and exercise the
29
+ permissions above under this same license. You may allow your contractors and
30
+ hosting providers to use it for those purposes. You must supply this license and
31
+ all applicable third-party notices with each redistribution (for example in an
32
+ included licenses directory or an accessible legal-notices page), identify your
33
+ modifications, and not impose terms that purport to change ALwith's license.
34
+
35
+ 3. Rights reserved
36
+ No right to ALwith's unpublished source code, private repositories, trademarks,
37
+ or separately distributed Runtime binaries, modules or services is granted here.
38
+ You may not relicense the Software as your own framework or redistribute it as
39
+ a standalone SDK/package for others, except for private development/build caches
40
+ used for applications permitted by Section 2. No restriction is imposed on
41
+ independently developed competing applications or on third-party licensed code.
42
+ You must not misrepresent authorship or imply ALwith endorses your application.
43
+
44
+ 4. Accounts and services
45
+ This license does not supply platform credentials, paid entitlements, hosting,
46
+ support, updates or a service-availability guarantee. Use of ALwith accounts,
47
+ pets, relay or other hosted services is separately subject to the applicable
48
+ service terms and authorization. Do not bypass service access controls or use
49
+ another person's credentials without authorization. Choosing or replacing an
50
+ authentication provider for your own application is not prohibited by this
51
+ software license. Distributing an Auth client does not authorize its recipients
52
+ to access another user's account, data or entitlements.
53
+
54
+ 5. Lawful use and independent responsibility
55
+ You must not use the Software to commit or knowingly facilitate unlawful acts.
56
+ You are responsible for your application's operation, permissions, business
57
+ practices, data processing, content and compliance with applicable law. You must
58
+ not make warranties or commitments on ALwith's behalf. ALwith does not endorse
59
+ your application merely because it uses the Software or connects to ALwith.
60
+ These allocations do not remove either party's own mandatory legal obligations.
61
+
62
+ 6. Disclaimer and limitation of liability
63
+ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED
64
+ "AS IS" AND "AS AVAILABLE", WITHOUT EXPRESS OR IMPLIED WARRANTIES, INCLUDING
65
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ACCURACY,
66
+ SECURITY OR UNINTERRUPTED OPERATION. AI OUTPUTS AND AUTOMATED ACTIONS MAY BE
67
+ INCORRECT OR HARMFUL; YOU MUST DETERMINE APPROPRIATE REVIEW AND SAFEGUARDS.
68
+
69
+ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, ALWITH AND ITS CONTRIBUTORS
70
+ ARE NOT LIABLE FOR DAMAGES ARISING FROM USE, MODIFICATION OR DISTRIBUTION OF
71
+ THE SOFTWARE, INCLUDING LOST PROFITS, LOST DATA, BUSINESS INTERRUPTION AND
72
+ INDIRECT OR CONSEQUENTIAL LOSS. NOTHING EXCLUDES OR LIMITS LIABILITY THAT
73
+ CANNOT LAWFULLY BE EXCLUDED OR LIMITED, INCLUDING APPLICABLE MANDATORY CONSUMER
74
+ RIGHTS AND LIABILITY FOR FRAUD, WILLFUL MISCONDUCT OR GROSS NEGLIGENCE WHERE
75
+ SUCH EXCLUSION OR LIMITATION IS PROHIBITED. THIS LICENSE DOES NOT EXCLUDE
76
+ CRIMINAL OR REGULATORY RESPONSIBILITY IMPOSED BY LAW.
77
+
78
+ 7. Duration and termination
79
+ The grant for this version continues while you comply with this license. If
80
+ ALwith gives written notice of a material breach, your rights terminate if you
81
+ do not cure it within 30 days; no cure period authorizes continuing unlawful
82
+ conduct. On termination, cease new use and distribution of ALwith's proprietary
83
+ Software except as required by law. Independently compliant recipients retain
84
+ their own grants. Sections concerning reserved rights, notices, disclaimers and
85
+ mandatory-law exceptions survive as applicable. Third-party licenses are not
86
+ terminated by this provision.
87
+
88
+ 8. Versions and severability
89
+ This license governs the accompanying version. A later license or publication
90
+ of source code does not retroactively replace this grant or revoke rights in
91
+ earlier lawfully obtained versions. Any additional open-source license must be
92
+ expressly identified by ALwith. If a provision is unenforceable, the remaining
93
+ provisions continue to the extent permitted by law. A separate written agreement
94
+ signed by you and ALwith may expressly override these terms for its stated scope.
package/README.md CHANGED
@@ -1,3 +1,48 @@
1
- # Temporary Holding Version
1
+ # @alwith/module-editor
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Document state, saving, tabs and extensible previews. The package does not depend on the file-tree package, a session store or Tauri.
4
+
5
+ ```tsx
6
+ import { createEditorController, EditorWorkbench, imagePreview } from "@alwith/module-editor"
7
+ import "@alwith/module-editor/styles.css"
8
+ const editor = createEditorController({ fs, confirmClose, reportError })
9
+ await editor.open("/project/readme.md")
10
+ <EditorWorkbench controller={editor} previews={[imagePreview]}
11
+ onError={reportError} />
12
+ ```
13
+
14
+ ## Lifecycle and safety
15
+
16
+ Stable document IDs survive path changes. One FS instance serializes saves, moves, deletions, closes and reloads. Directory moves rebase descendant paths. Dirty documents veto deletion when confirmation returns cancel. Approved deletions temporarily lock their documents until the native operation finishes or fails. Custom engines must respect `readOnly`. Failed saves keep dirty state; a disk version mismatch never silently overwrites the disk. Editing while a save/reload is awaiting I/O cannot silently close or replace the newer edit.
17
+
18
+ Call `requestClose(id)` to close a tab and `prepareShutdown()` before leaving the workspace/app. Both can return false. `dispose()` releases subscriptions only; it does not ask, save or discard on the user's behalf. The host is responsible for crash recovery/draft persistence if required; this release retains drafts in memory while switching projects, not across process crashes.
19
+
20
+ UTF-8 and BOM-marked UTF-16 LE/BE are decoded strictly. Invalid bytes remain binary/read-only. Saving retains BOM and a document's newline convention. Text is not guessed from malformed bytes. The default file size limit is 16 MiB; pass `maxFileBytes` for adapters that support a different limit. Clean documents have an LRU limit (default 12); dirty documents are not evicted.
21
+
22
+ `saveAs` uses create-only writes. The host obtains a destination and grants access; it must not assume an arbitrary path is writable. `onOpenExternal` is a host action for formats that need another application.
23
+
24
+ ## Rendering and extensions
25
+
26
+ `/core` contains no React. Root exports `EditorWorkbench`, `PreviewProvider`, `TextEditorProps` and `imagePreview`. Previews are ordered: the first provider whose `supports(document)` returns true renders the document. Text files can switch between edit and preview. SVG/HTML are not executed by the built-in image preview. HTML/PDF/office/media services belong in explicit optional providers with the required sandbox and resource disposal; unsupported binaries are not decoded as editable text.
27
+
28
+ The default text renderer is a textarea. `/monaco` exports `createMonacoEngine(monaco, controller)`: Monaco is an optional peer and never loaded by the default entry. The host imports Monaco lazily and configures workers. The engine retains undo/models and view state by document ID, releases closed models and provides `dispose`. The host supplies light/dark theme. U uses Monaco for code/Markdown editing and injects its shared Markdown renderer for preview.
29
+
30
+ All toolbar text is configured by `EditorLabels`; theme colors use semantic CSS variables. Hosts may provide `renderTextEditor`, `requestSaveAs`, `onOpenExternal`, and arbitrary preview providers. Business HTML/Drive/account/session integrations stay in the host.
31
+
32
+ See `examples/files/editor-only.tsx` and `combined.tsx` for independent and composed consumers.
33
+
34
+ `compactToolbar` keeps the file path visible and moves editor actions into an
35
+ overflow menu; `actionsLabel` localizes its trigger. The default toolbar remains
36
+ unchanged for other hosts. Tabs accept trackpad and wheel scrolling with hidden
37
+ scrollbars; activating a document brings its tab into view. Preview scrollbars
38
+ use thin, low-contrast thumbs based on the host foreground token.
39
+
40
+ ## Optional local previews
41
+
42
+ `@alwith/module-editor/previews` exports `createPreviewProviders(host)`. Load this entry lazily and import `@alwith/module-editor/previews/styles.css` alongside the workbench stylesheet. PDF, OOXML Word, spreadsheets, Markdown, HTML, audio/video and TIFF decoders load independently when their provider is selected. These portable viewers are reduced from ALwith Desktop; account, collaboration and native filesystem integrations remain in the host.
43
+
44
+ The host supplies `pdfWorkerSrc` and `resolveResource(documentPath, reference)`, returning `{ bytes, mimeType, path }`. Resolve references against the supplied base path, return a canonical resource path and enforce the host's filesystem permissions. `parseSpreadsheet(bytes, signal)` can delegate SheetJS decoding to a worker; honor cancellation by terminating or cancelling the worker. `labels` and `imageLabels` localize controls. `openExternal` handles legacy `.doc`/`.dot` files and unsupported media codecs.
45
+
46
+ HTML uses an opaque sandbox with scripts enabled and no parent access, navigation permissions or network requests. Relative stylesheets, CSS assets and literal JavaScript imports are resolved before rendering. Remote HTTPS assets require explicit `allowRemoteResources: true`; privileged schemes and plain HTTP stay blocked. Computed resource paths and circular local script dependencies cannot be resolved ahead of rendering. Preview-owned object URLs are released on replacement/unmount; resource loading and generated content are bounded.
47
+
48
+ Markdown uses Desktop's Milkdown CommonMark/GFM presets with an outline and formatting controls. It publishes edits synchronously through the workbench's document context and never writes files directly. Programmatic replacements retain their clean state. Set `autoSaveDelay` to debounce the host's save action, or leave it unset for manual saving. The host remains authoritative for read-only state, dirty state, save failures and external changes.
@@ -0,0 +1,3 @@
1
+ # Third-party notices
2
+
3
+ React is supplied by the host under the MIT license. Native filesystem dependencies retain their original licenses.
package/dist/core.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { type FileSystem, type FsTransaction, type Mutation, type Unsubscribe } from "@alwith/module-fs/core";
2
+ export type CloseDecision = "save" | "discard" | "cancel";
3
+ export type TextEncoding = "utf-8" | "utf-16le" | "utf-16be";
4
+ export interface EditorDocument {
5
+ id: string;
6
+ path: string;
7
+ kind: "text" | "binary";
8
+ text: string;
9
+ savedText: string;
10
+ bytes: Uint8Array;
11
+ encoding: TextEncoding;
12
+ bom: boolean;
13
+ eol: "\n" | "\r\n";
14
+ version: string;
15
+ dirty: boolean;
16
+ saving: boolean;
17
+ locked: boolean;
18
+ externalChange: boolean;
19
+ status: "loaded" | "deferred" | "loading" | "error";
20
+ loadError: string | null;
21
+ pinned: boolean;
22
+ contentRevision: number;
23
+ }
24
+ export interface EditorSnapshot {
25
+ selectionRevision?: number;
26
+ documents: readonly EditorDocument[];
27
+ activeId: string | null;
28
+ }
29
+ export interface EditorOptions {
30
+ fs: FileSystem;
31
+ confirmClose: (document: EditorDocument, reason: "close" | "delete" | "shutdown" | "reload") => Promise<CloseDecision>;
32
+ reportError: (error: unknown) => void;
33
+ maxFileBytes?: number;
34
+ maxCleanDocuments?: number;
35
+ }
36
+ export interface EditorController {
37
+ getSnapshot: () => EditorSnapshot;
38
+ subscribe: (listener: () => void) => Unsubscribe;
39
+ open: (path: string) => Promise<EditorDocument>;
40
+ activate: (id: string) => void;
41
+ ensureLoaded: (id: string) => Promise<void>;
42
+ pin: (id: string) => void;
43
+ reorder: (id: string, beforeId: string | null) => void;
44
+ requestCloseMany: (ids: readonly string[]) => Promise<boolean>;
45
+ update: (id: string, text: string) => void;
46
+ save: (id: string) => Promise<void>;
47
+ saveAs: (id: string, path: string) => Promise<void>;
48
+ reload: (id: string) => Promise<boolean>;
49
+ requestClose: (id: string) => Promise<boolean>;
50
+ prepareMutation: (operation: Mutation, transaction: FsTransaction) => Promise<boolean>;
51
+ prepareShutdown: () => Promise<boolean>;
52
+ dispose: () => void;
53
+ }
54
+ export declare function decodeText(bytes: Uint8Array): Pick<EditorDocument, "kind" | "text" | "encoding" | "bom" | "eol">;
55
+ export declare function encodeText(document: Pick<EditorDocument, "text" | "encoding" | "bom" | "eol">): Uint8Array;
56
+ export declare function createEditorController(options: EditorOptions): EditorController;
package/dist/core.js ADDED
@@ -0,0 +1,8 @@
1
+ import{containsPath as x,normalizePath as A,rebasePath as q}from"@alwith/module-fs/core";function V(d){let a="utf-8",f=0;if(d[0]===255&&d[1]===254)a="utf-16le",f=2;else if(d[0]===254&&d[1]===255)a="utf-16be",f=2;else if(d[0]===239&&d[1]===187&&d[2]===191)f=3;try{let p=new TextDecoder(a,{fatal:!0}).decode(d.subarray(f));if(p.includes("\x00"))return{kind:"binary",text:"",encoding:a,bom:f>0,eol:`
2
+ `};let h=p.includes(`\r
3
+ `)?`\r
4
+ `:`
5
+ `;return{kind:"text",text:p,encoding:a,bom:f>0,eol:h}}catch(p){if(!(p instanceof TypeError))throw p;return{kind:"binary",text:"",encoding:a,bom:f>0,eol:`
6
+ `}}}function j(d){let a=d.eol===`\r
7
+ `?d.text.replaceAll(/(?<!\r)\n/g,`\r
8
+ `):d.text;if(d.encoding==="utf-8"){let r=new TextEncoder().encode(a);if(!d.bom)return r;let w=new Uint8Array(r.length+3);return w.set([239,187,191]),w.set(r,3),w}let f=d.bom?2:0,p=new Uint8Array(a.length*2+f),h=new DataView(p.buffer),g=d.encoding==="utf-16le";if(d.bom)h.setUint16(0,65279,g);for(let r=0;r<a.length;r++)h.setUint16(f+r*2,a.charCodeAt(r),g);return p}function O(d){let{fs:a,confirmClose:f,reportError:p}=d,h=d.maxFileBytes??16777216,g=d.maxCleanDocuments??12,r={documents:[],activeId:null,selectionRevision:0},w=!1,E=new Set,b=new Map,k=new Map,R=new Map,z=0;function m(){for(let e of E)e()}function c(e){let n=r.documents.find((t)=>t.id===e);if(!n)throw Error(`Unknown document: ${e}`);return n}function C(e,n){let t=e.status==="loaded"?e.text!==n.text||e.bytes.length!==n.bytes.length||e.bytes.some((o,s)=>o!==n.bytes[s]):e.version!==n.version;return e.contentRevision+(t?1:0)}function u(e,n){r={...r,documents:r.documents.map((t)=>t.id===e?{...t,...n}:t)},m()}function v(){let e=r.documents.filter((t)=>t.status==="loaded"&&!t.dirty&&!t.saving&&!t.locked).toSorted((t,o)=>R.get(t.id)-R.get(o.id)),n=e.length-Math.max(1,g);for(let t of e){if(n<=0)break;if(t.id===r.activeId)continue;u(t.id,{status:"deferred",text:"",savedText:"",bytes:new Uint8Array,loadError:null}),n--}}function F(e){if(w)return Promise.reject(Error("Editor is disposed"));let n=c(e),t=k.get(e);if(t)return t;if(n.status==="loaded")return Promise.resolve();u(e,{status:"loading",loadError:null});let o=a.transaction(async()=>{try{let s=await y(c(e).path);if(w)throw Error("Editor was disposed while loading");let i=r.documents.find((l)=>l.id===e);if(!i)return;u(e,{...s,id:e,path:i.path,pinned:i.pinned,contentRevision:C(i,s),locked:i.locked,saving:i.saving}),v()}catch(s){if(!w&&r.documents.some((i)=>i.id===e))u(e,{status:"error",loadError:s instanceof Error?s.message:String(s)});throw s}});return k.set(e,o),o.then(()=>k.delete(e),()=>k.delete(e)),o}function M(e,n=!0){if(c(e),R.set(e,++z),r={...r,activeId:e,selectionRevision:(r.selectionRevision??0)+(n?1:0)},m(),v(),c(e).status!=="loaded")F(e).catch(p)}function U(e){let n=r.documents.findIndex((o)=>o.id===e),t=r.documents.filter((o)=>o.id!==e);if(r={...r,documents:t,activeId:r.activeId===e?t.length===0?null:t[Math.min(n,t.length-1)].id:r.activeId},R.delete(e),m(),r.activeId!==null)M(r.activeId,!1)}async function y(e){let n=await a.stat(e);if(n?.kind!=="file")throw Error("File is unavailable");if(n.size>h)throw Error(`File exceeds the ${Math.round(h/1024/1024)} MiB limit`);let t=await a.readFile(e),o=await a.stat(e);if(o?.version!==n.version)throw Error("File changed while opening; reopen it");let s=V(t);if(/\.(pdf|docx?|xlsx?|pptx?|zip|gz|tar|7z|rar|mp[34]|wav|ogg|webm|mov|exe|dll|wasm)$/i.test(e))s.kind="binary",s.text="";return{...s,id:crypto.randomUUID(),path:e,bytes:t,savedText:s.text,version:o.version,dirty:!1,saving:!1,locked:!1,externalChange:!1,status:"loaded",loadError:null,pinned:!1,contentRevision:0}}async function I(e,n,t){if(c(e).locked)throw Error("Document is locked by a file operation");if(c(e).status!=="loaded"){u(e,{status:"loading",loadError:null});try{let l=await y(c(e).path),T=c(e);u(e,{...l,id:e,pinned:T.pinned,contentRevision:C(T,l)})}catch(l){throw u(e,{status:"error",loadError:l instanceof Error?l.message:String(l)}),l}}let o=c(e);if(o.locked)throw Error("Document is locked by a file operation");if(o.kind!=="text")throw Error("Binary documents cannot be saved as text");if(!o.dirty&&t===void 0){v();return}let s=t===void 0?o.path:A(t),i=j(o);u(e,{saving:!0});try{await n.writeFile(s,i,t===void 0?{mode:"replace",expectedVersion:o.version}:{mode:"create"});let l=await n.stat(s);if(!l)throw Error("Saved file is no longer available");u(e,{path:s,version:l.version,savedText:o.text,dirty:c(e).text!==o.text,bytes:i,externalChange:!1})}finally{u(e,{saving:!1}),v()}}async function D(e,n,t){let o=c(e);if(!o.dirty)return!0;let s=await f(o,n);if(s==="cancel")return!1;if(s==="save")if(t)await I(e,t);else await S.save(e);let i=c(e);return i.contentRevision===o.contentRevision&&(s!=="save"||!i.dirty)}let P=a.subscribe((e)=>{if(e.type==="move")r={...r,documents:r.documents.map((n)=>({...n,path:q(n.path,e.path,e.to)}))},m();else if(e.type==="remove"){for(let n of r.documents)if(x(e.path,n.path))U(n.id)}else{let n=e.type==="change"?e.paths:[e.path];a.transaction(async()=>{for(let t of r.documents){if(!n.some((i)=>x(i,t.path)))continue;let o=await a.stat(t.path),s=r.documents.find((i)=>i.id===t.id);if(!s||s.saving||o!==null&&o.version===s.version)continue;if(s.dirty||o===null)u(s.id,{externalChange:!0});else if(s.status!=="loaded")u(s.id,{version:o.version,externalChange:!1,contentRevision:s.contentRevision+1});else{let i=await y(s.path),l=r.documents.find((T)=>T.id===s.id);if(!l)continue;if(l.text!==s.text||l.dirty)u(l.id,{externalChange:!0});else u(s.id,{...i,id:s.id,pinned:l.pinned,contentRevision:C(l,i)})}}}).catch(p)}}),B=a.beforeMutation(async(e,n)=>{if(!await S.prepareMutation(e,n))return!1;if(e.type!=="remove")return!0;let t=new Set(r.documents.filter((o)=>x(e.path,o.path)).map((o)=>o.id));return r={...r,documents:r.documents.map((o)=>t.has(o.id)?{...o,locked:!0}:o)},m(),{allow:!0,release(){r={...r,documents:r.documents.map((o)=>t.has(o.id)?{...o,locked:!1}:o)},m()}}}),S={getSnapshot:()=>r,subscribe(e){return E.add(e),()=>{E.delete(e)}},async open(e){if(w)throw Error("Editor is disposed");let n=A(e),t=r.documents.find((i)=>i.path===n);if(t)return M(t.id),await F(t.id),c(t.id);let o=b.get(n);if(o)return o;let s=a.transaction(async()=>{let i=await y(n);if(w)throw Error("Editor was disposed while opening");return r={...r,documents:[...r.documents,i]},M(i.id),i});b.set(n,s);try{return await s}finally{b.delete(n)}},activate:M,ensureLoaded:F,pin(e){c(e),u(e,{pinned:!0})},reorder(e,n){let t=c(e);if(n===e)return;if(n!==null)c(n);let o=r.documents.filter((i)=>i.id!==e),s=n===null?o.length:o.findIndex((i)=>i.id===n);o.splice(s,0,t),r={...r,documents:o},m()},async requestCloseMany(e){for(let n of[...new Set(e)]){if(!r.documents.some((t)=>t.id===n))continue;if(!await S.requestClose(n))return!1}return!0},update(e,n){let t=c(e);if(t.status!=="loaded")throw Error("Document content is not loaded");if(t.kind!=="text")throw Error("Document is read-only");if(t.locked)throw Error("Document is locked by a file operation");u(e,{text:n,dirty:n!==t.savedText,pinned:!0,contentRevision:t.contentRevision+(n!==t.text?1:0)}),v()},async save(e){await a.transaction((n)=>I(e,n))},async saveAs(e,n){await a.transaction((t)=>I(e,t,n))},async reload(e){return a.transaction(async(n)=>{if(!await D(e,"reload",n))return!1;let t=c(e),o=await y(t.path);if(c(e).contentRevision!==t.contentRevision)return!1;return u(e,{...o,id:e,pinned:t.pinned,contentRevision:C(t,o)}),v(),!0})},async requestClose(e){return a.transaction(async(n)=>{if(c(e).locked||c(e).saving)return!1;if(!await D(e,"close",n))return!1;return U(e),!0})},async prepareMutation(e,n){if(e.type!=="remove")return!0;let t=r.documents.filter((s)=>x(e.path,s.path)),o=new Map;for(let s of t){let i=c(s.id);if(!await D(s.id,"delete",n))return!1;o.set(s.id,i.contentRevision)}return r.documents.filter((s)=>x(e.path,s.path)).every((s)=>o.get(s.id)===s.contentRevision)},async prepareShutdown(){return a.transaction(async(e)=>{let n=new Map;for(let t of r.documents){let o=c(t.id);if(!await D(t.id,"shutdown",e))return!1;n.set(t.id,o.contentRevision)}return r.documents.every((t)=>n.get(t.id)===t.contentRevision)})},dispose(){w=!0,P(),B(),E.clear(),b.clear()}};return S}export{O as createEditorController,V as decodeText,j as encodeText};
@@ -0,0 +1,86 @@
1
+ import { type ReactNode } from "react";
2
+ import type { EditorController, EditorDocument } from "./core.js";
3
+ export * from "./core.js";
4
+ export * from "./workspace.js";
5
+ export * from "./workspace-tabs.js";
6
+ export interface TextEditorProps {
7
+ document: EditorDocument;
8
+ onChange: (text: string) => void;
9
+ onSave: () => void;
10
+ readOnly: boolean;
11
+ }
12
+ export interface PreviewContext {
13
+ setToolbarActions?: (actions: ReactNode | null) => void;
14
+ readOnly: boolean;
15
+ onChange: (text: string) => void;
16
+ onSave: () => void;
17
+ onError: (error: unknown) => void;
18
+ }
19
+ export interface PreviewProvider {
20
+ id: string;
21
+ supports: (document: EditorDocument) => boolean;
22
+ render: (document: EditorDocument, context: PreviewContext) => ReactNode;
23
+ }
24
+ export interface EditorLabels {
25
+ source?: string;
26
+ viewMode?: string;
27
+ loading?: string;
28
+ loadFailed?: string;
29
+ retry?: string;
30
+ empty: string;
31
+ save: string;
32
+ saveAs: string;
33
+ saving: string;
34
+ close: string;
35
+ edit: string;
36
+ preview: string;
37
+ reload: string;
38
+ changed: string;
39
+ unsupported: string;
40
+ openExternal: string;
41
+ }
42
+ export declare const defaultEditorLabels: EditorLabels;
43
+ export interface EditorTabLabels {
44
+ closeOthers: string;
45
+ closeRight: string;
46
+ closeSaved: string;
47
+ closeAll: string;
48
+ copyPath: string;
49
+ reveal: string;
50
+ }
51
+ export declare const defaultEditorTabLabels: EditorTabLabels;
52
+ export interface EditorWorkbenchProps {
53
+ tabBar?: (prefix: string) => ReactNode;
54
+ contentOverride?: (prefix: string) => ReactNode;
55
+ tabLabels?: Partial<EditorTabLabels>;
56
+ rootPath?: string;
57
+ onRevealPath?: (path: string) => Promise<void>;
58
+ onReveal?: (document: EditorDocument) => Promise<void>;
59
+ onCopyPath?: (document: EditorDocument) => Promise<void>;
60
+ controller: EditorController;
61
+ compactToolbar?: boolean;
62
+ actionsLabel?: string;
63
+ labels?: EditorLabels;
64
+ previews?: readonly PreviewProvider[];
65
+ renderTextEditor?: (props: TextEditorProps) => ReactNode;
66
+ onOpenExternal?: (document: EditorDocument) => Promise<void>;
67
+ requestSaveAs?: (document: EditorDocument) => Promise<string | null>;
68
+ readOnly?: boolean;
69
+ sourceReadOnly?: boolean;
70
+ onError: (error: unknown) => void;
71
+ }
72
+ export declare function EditorWorkbench({ controller, tabBar, contentOverride, tabLabels: tabLabelOverrides, rootPath, onRevealPath, onReveal, onCopyPath, compactToolbar, actionsLabel, labels, previews, renderTextEditor, requestSaveAs, onOpenExternal, readOnly, sourceReadOnly, onError }: EditorWorkbenchProps): ReactNode;
73
+ export interface ImagePreviewLabels {
74
+ failed: string;
75
+ zoomIn: string;
76
+ zoomOut: string;
77
+ resetZoom: string;
78
+ }
79
+ export declare function ImagePreview({ document, labels: overrides, onError }: {
80
+ document: EditorDocument;
81
+ labels?: Partial<ImagePreviewLabels>;
82
+ onError?: (error: unknown) => void;
83
+ }): ReactNode;
84
+ export declare const imagePreview: PreviewProvider;
85
+ export * from "./new-tab.js";
86
+ export { quickStartAssets } from "./quick-start-assets.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{jsx as a,jsxs as u,Fragment as Q}from"react/jsx-runtime";import{basename as H}from"@alwith/module-fs/core";import{Button as S}from"@base-ui/react/button";import{ContextMenu as h}from"@base-ui/react/context-menu";import{Menu as j}from"@base-ui/react/menu";import{Component as te,Suspense as ie,useCallback as ae,useEffect as _,useEffectEvent as re,useId as ne,useMemo as O,useRef as F,useState as Z,useSyncExternalStore as se}from"react";import{PreviewSourceModeToggle as le}from"./preview-source-mode-toggle.js";export*from"./core.js";export*from"./workspace.js";export*from"./workspace-tabs.js";export*from"./new-tab.js";import{quickStartAssets as Se}from"./quick-start-assets.js";var oe={empty:"Select a file to open",save:"Save",saveAs:"Save as",saving:"Saving…",close:"Close",edit:"Edit",preview:"Preview",reload:"Reload",changed:"This file changed on disk. Reload before saving.",unsupported:"No preview is available for this file.",openExternal:"Open externally"},de={closeOthers:"Close others",closeRight:"Close to the right",closeSaved:"Close saved",closeAll:"Close all",copyPath:"Copy path",reveal:"Reveal in file tree"};class ee extends te{state={error:null};static getDerivedStateFromError(t){return{error:t instanceof Error?t.message:String(t)}}componentDidCatch(t){this.props.onError(t)}componentDidUpdate(t){if(t.resetKey!==this.props.resetKey&&this.state.error!==null)this.setState({error:null})}render(){return this.state.error===null?this.props.children:u("div",{className:"alwith-editor-empty",role:"alert",children:[this.props.label,a("br",{}),this.state.error]})}}function ce({provider:t,document:b,context:y}){return t.render(b,y)}function ke({controller:t,tabBar:b,contentOverride:y,tabLabels:g,rootPath:v,onRevealPath:x,onReveal:R,onCopyPath:N,compactToolbar:I=!1,actionsLabel:A="More actions",labels:n=oe,previews:T=[],renderTextEditor:k,requestSaveAs:z,onOpenExternal:L,readOnly:C=!1,sourceReadOnly:w=!1,onError:P}){let s=se(t.subscribe,t.getSnapshot,t.getSnapshot),i=s.documents.find((e)=>e.id===s.activeId),K=F(null),D=ne(),l=F(null),c={...de,...g};_(()=>{let e=K.current;if(!e)return;let m=(r)=>{if(r.ctrlKey||r.metaKey||e.scrollWidth<=e.clientWidth)return;let M=Math.abs(r.deltaX)>Math.abs(r.deltaY)?r.deltaX:r.deltaY,B=r.deltaMode===1?M*16:r.deltaMode===2?M*e.clientWidth:M,Y=e.scrollLeft;if(e.scrollLeft+=B,Y!==e.scrollLeft)r.preventDefault()};return e.addEventListener("wheel",m,{passive:!1}),()=>e.removeEventListener("wheel",m)},[]),_(()=>{K.current?.querySelector('[aria-selected="true"]')?.scrollIntoView({block:"nearest",inline:"nearest"})},[s.activeId]);let[o,p]=Z({}),E=i?.id,[W,f]=Z(null),X=ae((e)=>{if(!E)return;f((m)=>e===null?m?.id===E?null:m:{id:E,node:e})},[E]);function d(e){e().catch(P)}let U=i?.status==="loaded"?T.find((e)=>e.supports(i)):void 0,q=i?o[i.id]??(U?"preview":"edit"):"edit",V=C||q==="edit"&&w;function G(e,m,r=!1){return I?a(j.Item,{className:"alwith-editor-menu-item",disabled:r,onClick:m,children:e}):a(S,{type:"button",disabled:r,onClick:m,children:e})}function J(){if(!i)return null;return u(Q,{children:[i.kind==="text"&&G(i.saving?n.saving:n.save,()=>d(()=>t.save(i.id)),V||i.locked||i.status!=="loaded"||!i.dirty||i.saving),L&&G(n.openExternal,()=>d(()=>L(i))),z&&i.kind==="text"&&G(n.saveAs,()=>d(async()=>{let e=await z(i);if(e!==null)await t.saveAs(i.id,e)}),V||i.locked||i.status!=="loaded"||i.saving)]})}return u("section",{className:"alwith-editor",onKeyDown:(e)=>{if((e.metaKey||e.ctrlKey)&&e.key.toLowerCase()==="s"&&i&&!y){if(e.preventDefault(),!V&&!i.locked&&i.status==="loaded")d(()=>t.save(i.id))}},children:[b?b(D):a("div",{className:"alwith-editor-tabs",role:"tablist",ref:K,onDragOver:(e)=>{if(l.current)e.preventDefault()},onDrop:(e)=>{if(!l.current)return;e.preventDefault(),t.reorder(l.current,null),l.current=null},children:s.documents.map((e,m)=>u(h.Root,{children:[u(h.Trigger,{className:"alwith-editor-tab","data-active":e.id===s.activeId,"data-transient":!e.pinned,draggable:!0,onDragStart:(r)=>{l.current=e.id,r.dataTransfer.effectAllowed="move",r.dataTransfer.setData("text/plain",e.path)},onDragEnd:()=>{l.current=null},onDragOver:(r)=>{if(l.current)r.preventDefault()},onDrop:(r)=>{if(!l.current)return;r.preventDefault(),r.stopPropagation();let M=r.currentTarget.getBoundingClientRect(),B=r.clientX<M.left+M.width/2?e.id:s.documents[m+1]?.id??null;t.reorder(l.current,B),l.current=null},children:[u(S,{type:"button",role:"tab",id:`${D}-tab-${e.id}`,"aria-controls":`${D}-panel-${e.id}`,"aria-selected":e.id===s.activeId,tabIndex:e.id===s.activeId?0:-1,title:e.path,onClick:()=>t.activate(e.id),onDoubleClick:()=>t.pin(e.id),onKeyDown:(r)=>{let M=r.key==="ArrowRight"?1:r.key==="ArrowLeft"?-1:0,B=r.key==="Home"?0:r.key==="End"?s.documents.length-1:M?(m+M+s.documents.length)%s.documents.length:null;if(B===null)return;r.preventDefault();let Y=s.documents[B];if(Y)t.activate(Y.id),globalThis.document.getElementById(`${D}-tab-${Y.id}`)?.focus()},children:[H(e.path),e.dirty?" •":""]}),a(S,{type:"button","aria-label":`${n.close} ${H(e.path)}`,onClick:()=>d(()=>t.requestClose(e.id)),children:"×"})]}),a(h.Portal,{children:a(h.Positioner,{children:u(h.Popup,{className:"alwith-editor-menu",children:[a(h.Item,{className:"alwith-editor-menu-item",onClick:()=>d(()=>t.requestClose(e.id)),children:n.close}),a(h.Item,{className:"alwith-editor-menu-item",disabled:s.documents.length<2,onClick:()=>d(()=>t.requestCloseMany(s.documents.filter((r)=>r.id!==e.id).map((r)=>r.id))),children:c.closeOthers}),a(h.Item,{className:"alwith-editor-menu-item",disabled:m===s.documents.length-1,onClick:()=>d(()=>t.requestCloseMany(s.documents.slice(m+1).map((r)=>r.id))),children:c.closeRight}),a(h.Item,{className:"alwith-editor-menu-item",disabled:!s.documents.some((r)=>!r.dirty),onClick:()=>d(()=>t.requestCloseMany(s.documents.filter((r)=>!r.dirty).map((r)=>r.id))),children:c.closeSaved}),a(h.Item,{className:"alwith-editor-menu-item",onClick:()=>d(()=>t.requestCloseMany(s.documents.map((r)=>r.id))),children:c.closeAll}),N&&a(h.Item,{className:"alwith-editor-menu-item",onClick:()=>d(()=>N(e)),children:c.copyPath}),R&&a(h.Item,{className:"alwith-editor-menu-item",onClick:()=>d(()=>R(e)),children:c.reveal}),a(h.Separator,{className:"alwith-editor-menu-separator"}),e.kind==="text"&&a(h.Item,{className:"alwith-editor-menu-item",disabled:C||w||e.locked||e.status!=="loaded"||!e.dirty||e.saving,onClick:()=>d(()=>t.save(e.id)),children:e.saving?n.saving:n.save}),z&&e.kind==="text"&&a(h.Item,{className:"alwith-editor-menu-item",disabled:C||w||e.locked||e.status!=="loaded"||e.saving,onClick:()=>d(async()=>{let r=await z(e);if(r!==null)await t.saveAs(e.id,r)}),children:n.saveAs}),L&&a(h.Item,{className:"alwith-editor-menu-item",onClick:()=>d(()=>L(e)),children:n.openExternal})]})})})]},e.id))}),y?y(D):i?u(Q,{children:[u("div",{className:"alwith-editor-toolbar",children:[a(ue,{path:i.path,rootPath:v,onRevealPath:x,onError:P}),u("div",{className:"alwith-editor-toolbar-actions",children:[q==="preview"&&W?.id===i.id&&W.node,U&&i.kind==="text"&&a(le,{mode:q,preview:n.preview,source:n.source??"Source",label:n.viewMode??"View mode",onModeChange:(e)=>p((m)=>({...m,[i.id]:e}))}),I?U&&i.kind==="text"?null:u(j.Root,{children:[a(j.Trigger,{"aria-label":A,title:A,children:"⋯"}),a(j.Portal,{children:a(j.Positioner,{sideOffset:4,children:a(j.Popup,{className:"alwith-editor-menu",children:J()})})})]}):J()]})]}),i.externalChange&&u("div",{className:"alwith-editor-notice",role:"status",children:[n.changed,a(S,{type:"button",onClick:()=>d(()=>t.reload(i.id)),children:n.reload})]}),a("div",{className:"alwith-editor-content",role:"tabpanel",id:`${D}-panel-${i.id}`,"aria-labelledby":`${D}-tab-${i.id}`,children:i.status==="error"?u("div",{className:"alwith-editor-empty",role:"alert",children:[a("p",{children:n.loadFailed??"Unable to load file"}),a("p",{children:i.loadError}),a(S,{onClick:()=>d(()=>t.ensureLoaded(i.id)),children:n.retry??"Retry"})]}):i.status!=="loaded"?a("div",{className:"alwith-editor-empty",role:"status","aria-busy":"true",children:n.loading??"Loading…"}):U&&(q==="preview"||i.kind==="binary")?a(ee,{resetKey:i.version,label:n.unsupported,onError:P,children:a(ie,{fallback:a("div",{"aria-busy":"true"}),children:a(ce,{provider:U,document:i,context:{setToolbarActions:X,readOnly:C||i.locked,onChange:(e)=>t.update(i.id,e),onSave:()=>d(()=>t.save(i.id)),onError:P}})})},`${i.id}:${U.id}`):i.kind==="text"?k?k({document:i,readOnly:C||w||i.locked,onChange:(e)=>t.update(i.id,e),onSave:()=>{if(!C&&!w&&!i.locked)d(()=>t.save(i.id))}}):a("textarea",{"aria-label":H(i.path),className:"alwith-editor-text",spellCheck:!1,readOnly:C||w||i.locked,value:i.text,onChange:(e)=>t.update(i.id,e.target.value)}):a("div",{className:"alwith-editor-empty",children:n.unsupported})},i.id)]}):a("div",{className:"alwith-editor-empty",children:n.empty})]})}function ue({path:t,rootPath:b,onRevealPath:y,onError:g}){let v=b?.replace(/\/$/,""),R=(v&&t.startsWith(`${v}/`)?t.slice(v.length+1):t).split("/").filter(Boolean),N=v&&t.startsWith(`${v}/`)?v:t.startsWith("/")?"":void 0;return a("nav",{className:"alwith-editor-breadcrumbs","aria-label":"File path",title:t,children:R.map((I,A)=>{N=N===void 0?I:`${N}/${I}`;let n=N;return u("span",{children:[A>0&&a("span",{"aria-hidden":"true",className:"alwith-editor-breadcrumb-separator",children:"›"}),y?a(S,{type:"button",onClick:()=>{y(n).catch(g)},children:I}):I]},n)})})}var he={failed:"Unable to display this image",zoomIn:"Zoom in",zoomOut:"Zoom out",resetZoom:"Reset zoom"};function me({document:t,labels:b,onError:y}){let g={...he,...b},v=O(()=>/\.svg$/i.test(t.path)&&t.kind==="text"?new TextEncoder().encode(t.text):t.bytes,[t.text,t.bytes,t.path,t.kind]),x=O(()=>({bytes:v,path:t.path,id:t.id,version:t.version}),[v,t.path,t.id,t.version]),[R,N]=Z(null),[I,A]=Z(null),n=R?.source===x?R.url:null,T=I===x,[k,z]=Z(1),[L,C]=Z({width:0,height:0}),w=F(null),P=F(null),s=F(1);function i(l,c){let o=Math.min(4,Math.max(0.25,l)),p=w.current,E=P.current?.getBoundingClientRect(),W=p?.getBoundingClientRect(),f=p&&E&&W?{x:c?.x??W.left+p.clientWidth/2,y:c?.y??W.top+p.clientHeight/2,imageX:0,imageY:0}:null;if(f&&E)f.imageX=(f.x-E.left)/s.current,f.imageY=(f.y-E.top)/s.current;if(s.current=o,z(o),f&&p)requestAnimationFrame(()=>{let X=P.current?.getBoundingClientRect();if(!X)return;p.scrollLeft+=X.left+f.imageX*o-f.x,p.scrollTop+=X.top+f.imageY*o-f.y})}let K=F(i);K.current=i,_(()=>{let l=w.current;if(!l)return;let c=(o)=>{if(!o.ctrlKey&&!o.metaKey)return;o.preventDefault(),K.current(s.current*Math.exp(-o.deltaY*0.002),{x:o.clientX,y:o.clientY})};return l.addEventListener("wheel",c,{passive:!1}),()=>l.removeEventListener("wheel",c)},[]);let D=re(()=>t.path);return _(()=>{let l=D().split(".").pop()?.toLowerCase(),c=l==="svg"?"image/svg+xml":l==="ico"?"image/x-icon":void 0,o=URL.createObjectURL(new Blob([new Uint8Array(x.bytes)],{type:c}));return N({source:x,url:o}),A(null),C({width:0,height:0}),z(1),s.current=1,()=>URL.revokeObjectURL(o)},[x]),u("div",{className:"alwith-editor-image-preview",children:[u("div",{className:"alwith-editor-image-controls",children:[a(S,{"aria-label":g.zoomOut,title:g.zoomOut,disabled:T||k<=0.25,onClick:()=>i(k/1.25),children:"−"}),u(S,{"aria-label":g.resetZoom,title:g.resetZoom,disabled:T,onClick:()=>i(1),children:[Math.round(k*100),"%"]}),a(S,{"aria-label":g.zoomIn,title:g.zoomIn,disabled:T||k>=4,onClick:()=>i(k*1.25),children:"+"})]}),a("div",{className:"alwith-editor-image-viewport",ref:w,onDoubleClick:()=>i(1),children:T?a("div",{className:"alwith-editor-empty",role:"alert",children:g.failed}):n&&a("div",{className:"alwith-editor-image-canvas",children:a("img",{ref:P,className:"alwith-editor-image",src:n,alt:H(t.path),style:L.width?{width:L.width*k,height:L.height*k}:void 0,onLoad:(l)=>{let c=w.current,o=l.currentTarget,p=c?Math.min(1,(c.clientWidth-40)/o.naturalWidth,(c.clientHeight-40)/o.naturalHeight):1;C({width:o.naturalWidth*Math.max(0.01,p),height:o.naturalHeight*Math.max(0.01,p)})},onError:()=>{A(x),y?.(Error(`Unable to decode image: ${t.path}`))}})})})]})}var Ce={id:"image",supports:(t)=>/\.(png|jpe?g|webp|gif|bmp|avif|svg|ico)$/i.test(t.path),render:(t,b)=>a(me,{document:t,onError:b.onError})};export{ke as EditorWorkbench,me as ImagePreview,oe as defaultEditorLabels,de as defaultEditorTabLabels,Ce as imagePreview,Se as quickStartAssets};
@@ -0,0 +1,16 @@
1
+ import type * as Monaco from "monaco-editor";
2
+ import { type ReactNode } from "react";
3
+ import type { EditorController } from "./core.js";
4
+ import type { TextEditorProps } from "./index.js";
5
+ export type MonacoAction = "find" | "replace" | "goto-line" | "undo" | "redo";
6
+ export type MonacoEditorProps = TextEditorProps & {
7
+ theme: "light" | "dark";
8
+ options?: Monaco.editor.IStandaloneEditorConstructionOptions;
9
+ position?: Monaco.IPosition;
10
+ };
11
+ export interface MonacoEngine {
12
+ Editor: (props: MonacoEditorProps) => ReactNode;
13
+ runAction: (action: MonacoAction) => Promise<void>;
14
+ dispose: () => void;
15
+ }
16
+ export declare function createMonacoEngine(monaco: typeof Monaco, controller: EditorController): MonacoEngine;
package/dist/monaco.js ADDED
@@ -0,0 +1 @@
1
+ import{jsx as O}from"react/jsx-runtime";import{useEffect as m,useRef as g}from"react";function V(c,h){let w=new WeakSet,r=new Map,u=new Map,i=null,E=h.subscribe(()=>{let e=h.getSnapshot().documents;for(let[o,n]of r)if(e.find((a)=>a.id===o)?.status!=="loaded"){if(i?.getModel()===n){let a=i.saveViewState();if(a)u.set(o,a);i.setModel(null)}n.dispose(),r.delete(o)}for(let o of u.keys())if(!e.some((n)=>n.id===o))u.delete(o)});function x(e){let o=g(null),n=g(e),l=g(null),a=g(!1);return n.current=e,m(()=>{if(!o.current)throw Error("Monaco container is missing");let{document:t}=n.current;if(t.status!=="loaded")throw Error("Monaco document content is not loaded");let d=r.get(t.id);if(!d){let f=t.path.split("/").at(-1),M=c.languages.getLanguages().find((v)=>v.filenames?.includes(f)||v.extensions?.some((S)=>f.endsWith(S)));d=c.editor.createModel(t.text,M?M.id:"plaintext",c.Uri.parse(`alwith-document:///${t.id}/${encodeURIComponent(f)}`)),r.set(t.id,d)}let s=c.editor.create(o.current,{automaticLayout:!0,minimap:{enabled:!1},fontSize:13,lineHeight:22,padding:{top:16},scrollBeyondLastLine:!1,wordWrap:"on",...n.current.options,model:d,readOnly:n.current.readOnly,theme:n.current.theme==="dark"?"vs-dark":"vs"});l.current=s,i=s;let y=u.get(t.id);if(y)s.restoreViewState(y);let C=d.onDidChangeContent(()=>{if(!a.current&&!d.isDisposed())n.current.onChange(d.getValue())});return s.addCommand(c.KeyMod.CtrlCmd|c.KeyCode.KeyS,()=>{if(!n.current.readOnly)n.current.onSave()}),()=>{let f=s.saveViewState();if(f&&r.has(t.id))u.set(t.id,f);if(C.dispose(),s.dispose(),l.current=null,i===s)i=null}},[e.document.id]),m(()=>{let t=r.get(e.document.id);if(t&&t.getValue()!==e.document.text){a.current=!0;try{t.setValue(e.document.text)}finally{a.current=!1}}},[e.document.id,e.document.text]),m(()=>{if(!e.position||w.has(e.position))return;let t=l.current;if(!t)throw Error("No Monaco editor is mounted");let d=t.getModel();if(!d)throw Error("Monaco model is missing");let s=d.validatePosition(e.position);t.setPosition(s),t.revealPositionInCenter(s),w.add(e.position)},[e.document.id,e.position]),m(()=>{if(l.current)l.current.updateOptions({...e.options,readOnly:e.readOnly});c.editor.setTheme(e.theme==="dark"?"vs-dark":"vs")},[e.readOnly,e.theme,e.options]),O("div",{ref:o,style:{height:"100%",minHeight:160}})}return{Editor:x,async runAction(e){if(!i)throw Error("No Monaco editor is mounted");let o={find:"actions.find",replace:"editor.action.startFindReplaceAction","goto-line":"editor.action.gotoLine",undo:"undo",redo:"redo"};if(i.focus(),e==="undo"||e==="redo")i.trigger("alwith",o[e],null);else{let n=i.getAction(o[e]);if(!n)throw Error(`Monaco action is unavailable: ${e}`);await n.run()}},dispose(){E();for(let e of r.values())e.dispose();r.clear(),u.clear()}}}export{V as createMonacoEngine};
@@ -0,0 +1,43 @@
1
+ import { type ReactNode } from "react";
2
+ import { type QuickStartRole } from "./quick-start-data.js";
3
+ import { workspaceLogo } from "./workspace-assets.js";
4
+ export { workspaceLogo };
5
+ export type { QuickStartRole };
6
+ export declare function NewTabPage({ title, link, onTutorial }: {
7
+ title: string;
8
+ link: string;
9
+ onTutorial: () => void;
10
+ }): ReactNode;
11
+ export interface QuickStartLabels {
12
+ skip: string;
13
+ back: string;
14
+ continue: string;
15
+ start: string;
16
+ next: string;
17
+ step1: {
18
+ title: string;
19
+ subtitle: string;
20
+ };
21
+ step2: {
22
+ title: string;
23
+ subtitle: string;
24
+ };
25
+ step3: {
26
+ title: string;
27
+ subtitle: string;
28
+ };
29
+ roles: Record<QuickStartRole, string>;
30
+ makes: Record<string, string>;
31
+ }
32
+ export interface QuickStartTip {
33
+ title: string;
34
+ description: string;
35
+ image: string;
36
+ decoration: string;
37
+ }
38
+ export declare function QuickStart({ labels, tips, onClose, onComplete }: {
39
+ labels: QuickStartLabels;
40
+ tips: readonly QuickStartTip[];
41
+ onClose: () => void;
42
+ onComplete: (role: QuickStartRole) => void;
43
+ }): ReactNode;
@@ -0,0 +1 @@
1
+ import{jsx as e,jsxs as c}from"react/jsx-runtime";import{Button as l}from"@base-ui/react/button";import{useMemo as y,useState as d}from"react";import{roleMakes as m}from"./quick-start-data.js";import{BusinessOwnerIcon as M,DataAnalystIcon as j,DesignerIcon as C,DeveloperIcon as D,InvestorIcon as A,MarketerIcon as O,OtherRoleIcon as S,ProductManagerIcon as E}from"./quick-start-icons.js";import{tutorialImage as R,workspaceLogo as b}from"./workspace-assets.js";var T={businessOwner:M,productManager:E,developer:D,dataAnalyst:j,marketer:O,investor:A,designer:C,other:S};function z({title:r,link:h,onTutorial:p}){return e("div",{className:"alwith-workspace-new-content",children:c(l,{className:"alwith-workspace-tutorial",onClick:p,children:[e("span",{className:"alwith-workspace-tutorial-hero",children:e("img",{src:R,alt:""})}),c("span",{className:"alwith-workspace-tutorial-copy",children:[e("strong",{children:r})," ",e("span",{children:h})]})]})})}function F({labels:r,tips:h,onClose:p,onComplete:N}){let[s,k]=d(1),[o,v]=d([]),[x,w]=d([]),[n,u]=d(0),q=y(()=>{let i=(o.length?o:["other"]).flatMap((a)=>o.length>1?m[a].slice(0,3):m[a]);return[...new Set(i.filter((a)=>a!=="justExploring")),"justExploring"]},[o]);function f(){if(n<h.length-1)u(n+1);else N(o.length===1?o[0]:"other")}if(s===4){let t=h[n];if(!t)throw Error("Quick start requires at least one tip");return c("div",{className:"alwith-quick-start-tip","data-tip":n,onKeyDown:(i)=>{if(i.key==="ArrowRight")i.preventDefault(),f();if(i.key==="ArrowLeft")i.preventDefault(),u(Math.max(0,n-1))},children:[e("img",{className:"alwith-quick-start-decoration",src:t.decoration,alt:""}),e("div",{className:"alwith-quick-start-progress",children:h.map((i,a)=>e("span",{"data-complete":a<=n},i.title))}),c("div",{className:"alwith-quick-start-tip-copy",children:[e("img",{src:t.image,alt:""}),e("h2",{children:t.title}),e("p",{children:t.description})]}),e(l,{className:"alwith-quick-start-previous","aria-label":r.back,disabled:n===0,onClick:()=>u(n-1)}),e(l,{className:"alwith-quick-start-next","aria-label":r.next,onClick:f})]})}let g=s===1?r.step1:s===2?r.step2:r.step3;return c("div",{className:"alwith-quick-start",children:[c("div",{className:s===3?"alwith-quick-start-ready":void 0,children:[e("h2",{children:g.title}),e("p",{children:g.subtitle})]}),s===1&&e("div",{className:"alwith-quick-start-roles",children:Object.keys(m).map((t)=>{let i=T[t];return c(l,{"aria-pressed":o.includes(t),onClick:()=>v((a)=>a.includes(t)?a.filter((I)=>I!==t):[...a,t]),children:[e(i,{}),r.roles[t]]},t)})}),s===2&&e("div",{className:"alwith-quick-start-makes",children:q.map((t)=>e(l,{"aria-pressed":x.includes(t),onClick:()=>w((i)=>i.includes(t)?i.filter((a)=>a!==t):[...i,t]),children:r.makes[t]},t))}),c("div",{className:"alwith-quick-start-footer",children:[e(l,{onClick:()=>{if(s===1)p();else{if(s===2)w([]);k(s-1)}},children:s===1?r.skip:r.back}),e(l,{onClick:()=>k(s+1),children:s===3?r.start:r.continue})]})]})}export{z as NewTabPage,F as QuickStart,b as workspaceLogo};
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from "react";
2
+ export declare function PreviewSourceModeToggle({ mode, onModeChange, preview, source, label }: {
3
+ mode: "preview" | "edit";
4
+ onModeChange: (mode: "preview" | "edit") => void;
5
+ preview: string;
6
+ source: string;
7
+ label: string;
8
+ }): ReactNode;
@@ -0,0 +1 @@
1
+ import{jsx as e,jsxs as m}from"react/jsx-runtime";import{Toggle as o}from"@base-ui/react/toggle";import{ToggleGroup as s}from"@base-ui/react/toggle-group";import{Code2Icon as u,EyeIcon as g}from"lucide-react";function v({mode:l,onModeChange:t,preview:a,source:i,label:d}){return m(s,{className:"alwith-editor-mode-toggle",value:[l],"aria-label":d,onValueChange:(n)=>{let r=n[0];if(r==="preview"||r==="edit")t(r)},children:[e(o,{value:"preview","aria-label":a,title:a,children:e(g,{"aria-hidden":"true"})}),e(o,{value:"edit","aria-label":i,title:i,children:e(u,{"aria-hidden":"true"})})]})}export{v as PreviewSourceModeToggle};
@@ -0,0 +1,20 @@
1
+ import type Spreadsheet from "x-data-spreadsheet";
2
+ import * as XLSX from "xlsx";
3
+ export interface PreviewSheet {
4
+ name: string;
5
+ rows: {
6
+ len: number;
7
+ [row: number]: {
8
+ cells: Record<number, {
9
+ text: string;
10
+ }>;
11
+ };
12
+ };
13
+ cols: {
14
+ len: number;
15
+ };
16
+ merges: string[];
17
+ }
18
+ export declare function workbookSheets(book: XLSX.WorkBook): PreviewSheet[];
19
+ export declare function truncateTextWithEllipsis(text: string, maxWidth: number, ctx: CanvasRenderingContext2D): string;
20
+ export declare function patchSpreadsheetTextOverflow(spreadsheet: InstanceType<typeof Spreadsheet> | null): void;
@@ -0,0 +1,3 @@
1
+ import*as h from"xlsx";function _(n){return n.SheetNames.map((t)=>{let i=n.Sheets[t];if(!i)throw Error(`Worksheet data missing: ${t}`);let e=i["!ref"]?h.utils.decode_range(i["!ref"]):null,u={len:e?e.e.r+1:0};for(let s of Object.keys(i)){if(s.startsWith("!"))continue;let r=i[s];if(!r)continue;let{r:c,c:o}=h.utils.decode_cell(s),l=u[c]??={cells:{}};l.cells[o]={text:h.utils.format_cell(r)}}return{name:t,rows:u,cols:{len:Math.max(26,e?e.e.c+1:0)},merges:[]}})}function X(n,t,i){if(n.length===0)return n;if(i.measureText(n).width<=t)return n;let e="...";if(i.measureText(e).width>=t)return e;let s=0,r=n.length,c="";while(s<=r){let o=Math.floor((s+r)/2),l=n.slice(0,o)+e;if(i.measureText(l).width<=t){c=l,s=o+1;continue}r=o-1}return c||e}function E(n){if(!n)return;let t=n?.sheet?.table?.draw;if(!t)return;let i=t.text;if(i.__excelEllipsisPatched)return;let e=i.bind(t),u=(s,r,c,o=!0)=>{if(o)return e(s,r,c,o);let l=String(s);if(l.length===0)return e(s,r,c,o);let a=r.innerWidth();if(a<=0)return e(s,r,c,o);let f=t.ctx,d=c?.font,p=d?.italic?"italic ":"",w=d?.bold?"bold ":"",g=d?.size||10,m=d?.name||"Helvetica";f.save(),f.font=`${p}${w}${g}px ${m}`;let S=l.split(`
2
+ `).map((T)=>X(T,a,f)).join(`
3
+ `);return f.restore(),e(S,r,c,o)};u.__excelEllipsisPatched=!0,t.text=u}export{E as patchSpreadsheetTextOverflow,X as truncateTextWithEllipsis,_ as workbookSheets};
@@ -0,0 +1,4 @@
1
+ export declare function installSheetNavigation(container: HTMLElement, labels: {
2
+ previousSheet: string;
3
+ nextSheet: string;
4
+ }): () => void;
@@ -0,0 +1 @@
1
+ function x(L,h){let c=L.querySelector(".x-spreadsheet-bottombar"),n=c?.querySelector(".x-spreadsheet-menu");if(!c||!n)throw Error("Spreadsheet navigation is missing");let o=document.createElement("button");o.type="button",o.className="excel-sheet-nav-button excel-sheet-nav-button-left",o.setAttribute("aria-label",h.previousSheet),o.textContent="‹";let s=document.createElement("button");s.type="button",s.className="excel-sheet-nav-button excel-sheet-nav-button-right",s.setAttribute("aria-label",h.nextSheet),s.textContent="›";let a=()=>Math.max(n.scrollWidth-n.clientWidth,0),g=(t)=>Math.min(Math.max(t,0),a()),f=()=>[...n.querySelectorAll(":scope > li")].map((t)=>t).filter((t)=>t.offsetWidth>0).map((t)=>{let e=t.offsetLeft,r=e+t.offsetWidth;return{left:e,right:r}}).toSorted((t,e)=>t.left-e.left),l=(t)=>{let e=Math.round(g(t));n.scrollTo({left:e,behavior:"smooth"})},i=()=>{let t=n.scrollLeft,e=a();o.disabled=t<=1,s.disabled=t>=e-1},u=()=>{let t=a()>1;return o.hidden=!t,s.hidden=!t,c.classList.toggle("excel-bottom-nav-enabled",t),t},v=1,b=()=>{let t=n.scrollLeft,e=f();if(e.length===0){l(t-160);return}let r=[...e].toReversed().find((d)=>d.left<t-v)??e[0];l(r.left)},m=()=>{let t=n.scrollLeft,e=t+n.clientWidth,r=f();if(r.length===0){l(t+160);return}let d=r.find((p)=>p.right>e+v)??r.at(-1);l(d.right-n.clientWidth)};o.addEventListener("click",b),s.addEventListener("click",m),n.addEventListener("scroll",i,{passive:!0}),c.append(o,s),u(),i();let E=new ResizeObserver(()=>{u(),i()});return E.observe(n),()=>{o.removeEventListener("click",b),s.removeEventListener("click",m),n.removeEventListener("scroll",i),E.disconnect(),o.remove(),s.remove(),c.classList.remove("excel-bottom-nav-enabled")}}export{x as installSheetNavigation};
@@ -0,0 +1,3 @@
1
+ import "x-data-spreadsheet/dist/xspreadsheet.css";
2
+ import { type PreviewProps } from "./index.js";
3
+ export default function Excel({ document, context, host }: PreviewProps): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ import{jsx as d,jsxs as k}from"react/jsx-runtime";import{useEffect as b,useEffectEvent as F,useRef as y,useState as L}from"react";import X from"x-data-spreadsheet";import*as N from"xlsx";import"x-data-spreadsheet/dist/xspreadsheet.css";import{patchSpreadsheetTextOverflow as C,workbookSheets as H}from"./excel-data.js";import{installSheetNavigation as R}from"./excel-navigation.js";import{previewLabel as u}from"./index.js";import{ErrorMessage as z,errorText as A}from"./shared.js";function B({document:n,context:m,host:a}){let[l,v]=L(null),[f,w]=L(null),x=y(null),o=y({context:m,host:a});o.current={context:m,host:a};let T=F((e,r)=>a.parseSpreadsheet?.(e,r));return b(()=>{w(null),v(null);let e=!1,r=new AbortController,i=/\.csv$/i.test(n.path)&&n.kind==="text"?new TextEncoder().encode(n.text):new Uint8Array(n.bytes);return(T(i,r.signal)??Promise.resolve().then(()=>N.read(i,{type:"array",cellHTML:!1,cellFormula:!1,cellStyles:!1}))).then((t)=>{if(!e)v(H(t))}).catch((t)=>{if(!e)w(A(t)),o.current.context.onError(t)}),()=>{e=!0,r.abort()}},[n.bytes,n.text,n.path,n.kind,a.parseSpreadsheet]),b(()=>{if(!l?.length)return;let e=x.current;if(!e)throw Error("Spreadsheet container is missing");let r=null,i=null,c=null,t=null,S=[],E=Reflect.get(window,"addEventListener"),j=(s,p,h)=>{S.push({type:s,listener:p,options:h}),E.call(window,s,p,h)},g=()=>{if(i?.(),c?.disconnect(),t!==null)cancelAnimationFrame(t);for(let{type:s,listener:p,options:h}of S)window.removeEventListener(s,p,h);e.replaceChildren(),r=null};try{window.addEventListener=j;try{r=new X(e,{mode:"read",showToolbar:!1,showContextmenu:!1,showBottomBar:!0,view:{height:()=>e.clientHeight||300,width:()=>e.clientWidth||320}})}finally{window.addEventListener=E}C(r),r.loadData(l),i=R(e,{previousSheet:u(o.current.host,"previousSheet"),nextSheet:u(o.current.host,"nextSheet")}),c=new ResizeObserver(()=>{if(t!==null)cancelAnimationFrame(t);t=requestAnimationFrame(()=>{t=null,r?.sheet.reload()})}),c.observe(e)}catch(s){g(),w(A(s)),o.current.context.onError(s)}return g},[l]),k("div",{className:"alwith-preview-excel excel-preview-wrapper",children:[d(z,{error:f}),!f&&!l&&d("div",{className:"alwith-preview-status",role:"status",children:u(a,"loading")}),!f&&l?.length===0&&d("div",{className:"alwith-preview-status",children:u(a,"emptySheet")}),d("div",{ref:x,className:"x-spreadsheet-container"})]})}export{B as default};
@@ -0,0 +1,3 @@
1
+ import type { PreviewHost } from "./index.js";
2
+ export declare const previewCsp = "default-src 'none'; img-src blob: data:; media-src blob: data:; font-src blob: data:; style-src 'unsafe-inline' blob:; script-src 'unsafe-inline' blob: data:; connect-src 'none'; frame-src 'none'; object-src 'none'; form-action 'none'; base-uri 'none'";
3
+ export declare function prepareHtmlPreview(text: string, path: string, host: PreviewHost, urls: Set<string>, signal?: AbortSignal): Promise<string>;
@@ -0,0 +1,2 @@
1
+ var q="default-src 'none'; img-src blob: data:; media-src blob: data:; font-src blob: data:; style-src 'unsafe-inline' blob:; script-src 'unsafe-inline' blob: data:; connect-src 'none'; frame-src 'none'; object-src 'none'; form-action 'none'; base-uri 'none'";async function M(j,p,y,T,E){let b=()=>{E?.throwIfAborted()};b();let x=new Map,L=0,A=0,S=0,h=async(e,a,s)=>{let t=[...e.matchAll(a)],r=await Promise.allSettled(t.map(s));b();let o=r.find((i)=>i.status==="rejected");if(o)throw o.reason;let c=r.map((i)=>{if(i.status!=="fulfilled")throw i.reason;return i.value}),n="",u=0;for(let i=0;i<t.length;i++){let f=t[i];n+=e.slice(u,f.index)+c[i],u=f.index+f[0].length}return n+e.slice(u)},w=async(e,a,s=new Set)=>{b();let t=a.trim();if(!t||t.startsWith("#"))return t;if(/^data:/i.test(t))return t;if(/^https:\/\//i.test(t)&&y.allowRemoteResources)return t;if(/^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(t))throw Error(`Remote or privileged preview resource is blocked: ${t}`);let r=`${e}\x00${t}`;if(s.has(r))throw Error(`Circular preview resource: ${t}`);let o=x.get(r);if(o)return o;if(++L>256||s.size>12)throw Error("HTML preview exceeds its resource limit");let c=await y.resolveResource(e,t);if(b(),A+=c.bytes.byteLength,A>67108864)throw Error("HTML preview resources exceed 64 MiB");let n=c.bytes,u=new Set(s).add(r);if(/text\/css/i.test(c.mimeType)||/\.css(?:[?#]|$)/i.test(t))n=new TextEncoder().encode(await v(new TextDecoder().decode(n),c.path,u));let i=/javascript/i.test(c.mimeType)||/\.(?:m?js)(?:[?#]|$)/i.test(t);if(i){let m=new TextDecoder().decode(n),d=await C(m,c.path,u);n=new TextEncoder().encode(d)}if(b(),S+=n.byteLength,S>67108864)throw Error("Generated HTML preview resources exceed 64 MiB");let f;if(i){let m="";for(let d=0;d<n.length;d+=8192)m+=String.fromCharCode(...n.subarray(d,d+8192));f=`data:text/javascript;base64,${btoa(m)}`}else f=URL.createObjectURL(new Blob([new Uint8Array(n).buffer],{type:c.mimeType})),T.add(f);return x.set(r,f),f},C=async(e,a,s)=>h(e,/\b((?:import|export)\s+(?:[^;\n]*?\s+from\s*)?|import\s*\(\s*)(["'])([^"']+)\2/g,async(t)=>`${t[1]}${t[2]}${await w(a,t[3],s)}${t[2]}`),v=async(e,a,s)=>{let t=await h(e,/@import\s+(["'])([^"']+)\1/g,async(r)=>`@import "${await w(a,r[2],s)}"`);return h(t,/url\(\s*(["']?)([^"')]+)\1\s*\)/g,async(r)=>{if(/^blob:/i.test(r[2]))return r[0];return`url("${await w(a,r[2],s)}")`})},l=new DOMParser().parseFromString(j,"text/html");for(let e of l.querySelectorAll("base,iframe,frame,object,embed,meta[http-equiv]"))e.remove();for(let e of l.querySelectorAll("link"))if(!e.rel.toLowerCase().split(/\s+/).includes("stylesheet"))e.remove();for(let e of l.querySelectorAll("[src],[poster],[href],[srcset],[style]")){for(let t of["src","poster","href"]){let r=e.getAttribute(t);if(r===null)continue;if(t==="href"&&e.tagName!=="LINK"){e.removeAttribute(t);continue}e.setAttribute(t,await w(p,r))}let a=e.getAttribute("srcset");if(a!==null&&!/^\s*data:/i.test(a)){let t=await Promise.allSettled(a.split(",").map(async(o)=>{let[c,...n]=o.trim().split(/\s+/);return[await w(p,c),...n].join(" ")})),r=t.find((o)=>o.status==="rejected");if(r)throw r.reason;e.setAttribute("srcset",t.map((o)=>{if(o.status!=="fulfilled")throw o.reason;return o.value}).join(", "))}let s=e.getAttribute("style");if(s!==null)e.setAttribute("style",await v(s,p,new Set))}for(let e of l.querySelectorAll("style"))e.textContent=await v(e.textContent||"",p,new Set);for(let e of l.querySelectorAll("script:not([src])"))e.textContent=await C(e.textContent||"",p,new Set);for(let e of l.querySelectorAll("form"))e.removeAttribute("action"),e.removeAttribute("target");let g=l.createElement("meta");return g.httpEquiv="Content-Security-Policy",g.content=y.allowRemoteResources?"default-src 'none'; img-src blob: data:; media-src blob: data:; font-src blob: data:; style-src 'unsafe-inline' blob:; script-src 'unsafe-inline' blob: data:; connect-src 'none'; frame-src 'none'; object-src 'none'; form-action 'none'; base-uri 'none'".replace("img-src blob: data:","img-src blob: data: https:").replace("media-src blob: data:","media-src blob: data: https:").replace("font-src blob: data:","font-src blob: data: https:").replace("style-src 'unsafe-inline' blob:","style-src 'unsafe-inline' blob: https:").replace("script-src 'unsafe-inline' blob: data:","script-src 'unsafe-inline' blob: data: https:"):"default-src 'none'; img-src blob: data:; media-src blob: data:; font-src blob: data:; style-src 'unsafe-inline' blob:; script-src 'unsafe-inline' blob: data:; connect-src 'none'; frame-src 'none'; object-src 'none'; form-action 'none'; base-uri 'none'",l.head.prepend(g),`<!doctype html>
2
+ ${l.documentElement.outerHTML}`}export{M as prepareHtmlPreview,q as previewCsp};
@@ -0,0 +1,8 @@
1
+ import type { PreviewHost } from "./index.js";
2
+ export interface HtmlPreviewSession {
3
+ src?: string;
4
+ srcDoc?: string;
5
+ sandbox: string;
6
+ dispose: () => Promise<void>;
7
+ }
8
+ export declare function openHtmlPreview(text: string, path: string, host: PreviewHost, signal: AbortSignal): Promise<HtmlPreviewSession>;
@@ -0,0 +1 @@
1
+ import{prepareHtmlPreview as i}from"./html-resources.js";async function c(t,e,r,s){if(s.throwIfAborted(),r.htmlPreview){let o=r.htmlIntegration?.transform(t,e)??t,n=await r.htmlPreview.open(e,o);if(s.aborted)await n.dispose(),s.throwIfAborted();return{src:n.url,sandbox:"allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads",dispose:()=>n.dispose()}}let a=new Set,l=()=>Promise.try(()=>{for(let o of a)URL.revokeObjectURL(o)});try{let o=await i(t,e,r,a,s);return{srcDoc:r.htmlIntegration?.transform(o,e)??o,sandbox:"allow-scripts",dispose:l}}catch(o){throw await l(),o}}export{c as openHtmlPreview};
@@ -0,0 +1,3 @@
1
+ import { type ReactElement } from "react";
2
+ import type { PreviewProps } from "./index.js";
3
+ export default function Html({ document, context, host }: PreviewProps): ReactElement;
@@ -0,0 +1 @@
1
+ import{jsx as p,jsxs as g}from"react/jsx-runtime";import{useEffect as c,useEffectEvent as k,useRef as P,useState as n}from"react";import{openHtmlPreview as j}from"./html-session.js";import{ErrorMessage as v,errorText as D}from"./shared.js";function K({document:o,context:h,host:s}){let d=P(null),[i,f]=n(null),[w,m]=n(null),[b,x]=n(0),[l,y]=n(1),u=s.htmlPreview?o.savedText:o.text,a=k((t)=>h.onError(t));return c(()=>{let t,r=new AbortController;return f(null),m(null),j(u,o.path,s,r.signal).then(async(e)=>{if(r.signal.aborted)await e.dispose();else t=e,f(e)}).catch((e)=>{if(!r.signal.aborted)m(D(e));if(!r.signal.aborted||!(e instanceof Error&&e.name==="AbortError"))a(e)}),()=>{if(r.abort(),t)t.dispose().catch(a)}},[u,o.path,s,b]),c(()=>{if(!s.htmlPreview)return;let t=!1,r;return s.htmlPreview.subscribe(o.path,()=>{if(!t)x((e)=>e+1)},(e)=>a(e)).then((e)=>{if(t)e();else r=e}).catch(a),()=>{t=!0,r?.()}},[o.path,s]),c(()=>{let t=(r)=>{if(!r.metaKey&&!r.ctrlKey||r.altKey)return;let e=r.key==="+"||r.key==="="||r.code==="Equal"||r.code==="NumpadAdd"?0.1:r.key==="-"||r.code==="Minus"||r.code==="NumpadSubtract"?-0.1:0;if(!e)return;r.preventDefault(),y((E)=>Number(Math.max(0.3,Math.min(3,E+e)).toFixed(2)))};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[]),g("div",{className:"alwith-preview-html",style:{overflow:"hidden"},children:[p(v,{error:w}),s.htmlIntegration?.render(d),i&&p("iframe",{ref:d,title:o.path.split("/").at(-1),sandbox:i.sandbox,referrerPolicy:"no-referrer",src:i.src,srcDoc:i.srcDoc,style:{flex:"none",transform:`scale(${l})`,transformOrigin:"left top",width:`${100/l}%`,height:`${100/l}%`}})]})}export{K as default};