diff --git a/docs/dist/v1/chartifact-toolbar.css b/docs/dist/v1/chartifact-toolbar.css index 15abb1d7..6000df51 100644 --- a/docs/dist/v1/chartifact-toolbar.css +++ b/docs/dist/v1/chartifact-toolbar.css @@ -24,8 +24,25 @@ /* Base styles for the toolbar */ .chartifact-toolbar { + display: flex; + align-items: center; + justify-content: space-between; padding: 0.5em 1em; - font-weight: bold; + font-family: sans-serif; +} + +.chartifact-toolbar a { + text-decoration: none; + color: inherit; +} + +.chartifact-toolbar button, .chartifact-toolbar select { + font-size: 9pt; +} + +/* Remove margin from all toolbar divs by default */ +.chartifact-toolbar > div { + display: inline-block; } /* Default (OS light theme) */ @@ -37,7 +54,7 @@ /* OS dark theme fallback */ @media (prefers-color-scheme: dark) { .chartifact-toolbar { - background-color: #1e1e1e; + background-color: #2e2e2e; color: #ffffff; border-bottom-color: rgba(255, 255, 255, 0.1); } diff --git a/docs/dist/v1/chartifact.host.umd.js b/docs/dist/v1/chartifact.host.umd.js index 40869953..08edcfa2 100644 --- a/docs/dist/v1/chartifact.host.umd.js +++ b/docs/dist/v1/chartifact.host.umd.js @@ -907,7 +907,7 @@ ${guardedJs} return; } let docIndex = 0; - const folderSpan = host.toolbar.toolbarElement.querySelector("#folderSpan"); + const { folderSpan } = host.toolbar; folderSpan.style.display = ""; folderSpan.innerText = `Folder: ${folder.title} (${folder.docs.length} documents)`; const prevBtn = document.createElement("button"); @@ -941,7 +941,8 @@ ${guardedJs} prevBtn.disabled = docIndex === 0; nextBtn.disabled = docIndex === folder.docs.length - 1; pageSelect.value = (docIndex + 1).toString(); - resolveUrl(folderUrl, folder.docs[docIndex].href, host); + const title = folder.docs[docIndex].title || `Page ${docIndex + 1}`; + resolveUrl(title, folderUrl, folder.docs[docIndex].href, host); } pageSelect.onchange = () => { const selectedPage = parseInt(pageSelect.value, 10); @@ -979,7 +980,7 @@ ${guardedJs} folderSpan.appendChild(pageSelect); folderSpan.appendChild(nextBtn); } - async function resolveUrl(base, relativeOrAbsolute, host) { + async function resolveUrl(title, base, relativeOrAbsolute, host) { let url; try { url = base ? new URL(relativeOrAbsolute, base).href : relativeOrAbsolute; @@ -999,11 +1000,11 @@ ${guardedJs} return; } if (result.idoc) { - host.render(void 0, result.idoc); + host.render(title, void 0, result.idoc); } else if (result.markdown) { - host.render(result.markdown, void 0); + host.render(title, result.markdown, void 0); } else if (result.folder) { - host.render("Nested folders are not supported", void 0); + host.render("Error", "Nested folders are not supported", void 0); } else { host.errorHandler( "Invalid document format", @@ -1011,7 +1012,7 @@ ${guardedJs} ); } } - function determineContent(url, content, host, handle) { + function determineContent(urlOrTitle, content, host, handle) { const result = _determineContent(content); if (handle) { if (result.error) { @@ -1021,16 +1022,16 @@ ${guardedJs} ); return; } else if (result.idoc) { - host.render(void 0, result.idoc); + host.render(urlOrTitle, void 0, result.idoc); } else if (result.folder) { - loadFolder(url, result.folder, host); + loadFolder(urlOrTitle, result.folder, host); } else if (result.markdown) { - host.render(result.markdown, void 0); + host.render(urlOrTitle, result.markdown, void 0); } } return result; } - function _determineContent(content, host) { + function _determineContent(content) { if (!content) { return { error: "Content is empty", @@ -1107,7 +1108,7 @@ ${guardedJs} ); return; } - determineContent(null, content, host, true); + determineContent(file.name, content, host, true); }; reader.onerror = (e) => { host.errorHandler( @@ -1152,7 +1153,7 @@ ${guardedJs} ); return; } - determineContent(null, content, host, true); + determineContent("clipboard-content", content, host, true); }); handled = true; break; @@ -1206,7 +1207,7 @@ ${guardedJs} ); return; } - determineContent(null, content, host, true); + determineContent("dropped-content", content, host, true); } else { host.errorHandler( "Unsupported drop content", @@ -1259,9 +1260,9 @@ ${guardedJs} const message = event.data; if (message.type == "hostRenderRequest") { if (message.markdown) { - host.render(message.markdown, void 0); + host.render(message.title, message.markdown, void 0); } else if (message.interactiveDocument) { - host.render(void 0, message.interactiveDocument); + host.render(message.title, void 0, message.interactiveDocument); } else { } } @@ -1394,7 +1395,7 @@ ${guardedJs} ${message} ${details}`; - this.render(markdown, void 0); + this.render("Error", markdown, void 0); } else { this.previewDiv.innerHTML = ""; const h1 = document.createElement("h1"); @@ -1408,7 +1409,10 @@ ${details}`; this.previewDiv.appendChild(pDetails); } } - render(markdown, interactiveDocument) { + render(title, markdown, interactiveDocument) { + if (this.toolbar) { + this.toolbar.filename = title; + } if (interactiveDocument) { this.onSetMode("json", null, interactiveDocument); this.renderInteractiveDocument(interactiveDocument); @@ -1461,26 +1465,69 @@ ${details}`; constructor(toolbarElementOrSelector, options = {}) { __publicField(this, "options"); __publicField(this, "toolbarElement"); + __publicField(this, "folderSpan"); + __publicField(this, "tweakButton"); + __publicField(this, "downloadButton"); + __publicField(this, "mode"); + __publicField(this, "filename"); + var _a, _b; this.options = options; + this.filename = options.filename || "sample"; + this.mode = options.mode || "markdown"; this.toolbarElement = typeof toolbarElementOrSelector === "string" ? document.querySelector(toolbarElementOrSelector) : toolbarElementOrSelector; if (!this.toolbarElement) { throw new Error("Toolbar element not found"); } - const html = `Chartifact viewer - - + const html = ` +
+ Chartifact viewer +
+ +
+ + +
`; this.toolbarElement.innerHTML = html; + this.tweakButton = this.toolbarElement.querySelector("#tweak"); + this.folderSpan = this.toolbarElement.querySelector("#folderSpan"); + this.downloadButton = this.toolbarElement.querySelector("#download"); if (this.options.tweakButton) { this.showTweakButton(); } - } - showTweakButton() { - const tweakButton = this.toolbarElement.querySelector("#tweak"); - tweakButton.style.display = ""; - tweakButton == null ? void 0 : tweakButton.addEventListener("click", () => { + if (this.options.downloadButton) { + this.showDownloadButton(); + } + (_a = this.tweakButton) == null ? void 0 : _a.addEventListener("click", () => { this.options.textarea.style.display = this.options.textarea.style.display === "none" ? "" : "none"; }); + (_b = this.downloadButton) == null ? void 0 : _b.addEventListener("click", () => { + const textarea = this.options.textarea; + if (!textarea) + return; + const content = textarea.value; + const ext = this.mode === "markdown" ? "idoc.md" : "idoc.json"; + const filename = `${filenameWithoutPathOrExtension(this.filename)}.${ext}`; + const blob = new Blob([content], { + type: this.mode === "markdown" ? "text/markdown" : "application/json" + }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 0); + }); + } + showTweakButton() { + this.tweakButton.style.display = ""; + } + showDownloadButton() { + this.downloadButton.style.display = ""; } manageTextareaVisibilityForAgents() { const { textarea } = this.options; @@ -1498,6 +1545,18 @@ ${details}`; }, 300); } } + function filenameWithoutPathOrExtension(filename) { + const base = filename.split(/[\\/]/).pop() || filename; + const idocIdx = base.indexOf(".idoc"); + if (idocIdx !== -1) { + return base.substring(0, idocIdx); + } + const lastDot = base.lastIndexOf("."); + if (lastDot > 0) { + return base.substring(0, lastDot); + } + return base; + } function create(toolbarElementOrSelector, options = {}) { const toolbar = new Toolbar(toolbarElementOrSelector, options); toolbar.manageTextareaVisibilityForAgents(); diff --git a/docs/dist/v1/chartifact.markdown.umd.js b/docs/dist/v1/chartifact.markdown.umd.js index 13d144d6..0831c7de 100644 --- a/docs/dist/v1/chartifact.markdown.umd.js +++ b/docs/dist/v1/chartifact.markdown.umd.js @@ -2202,6 +2202,7 @@ ${reconstitutedRules.join("\n\n")} } } async beginListening() { + this.active = true; this.log("beginListening", "begin initial batch", this.signalDeps); for (const peer of this.peers) { const batch = {}; @@ -2246,7 +2247,6 @@ ${reconstitutedRules.join("\n\n")} this.log(peer.id, "No shared signals"); } } - this.active = true; } deactivate() { if (this.signalDeps) { diff --git a/docs/dist/v1/chartifact.sandbox.umd.js b/docs/dist/v1/chartifact.sandbox.umd.js index f4a5a234..7479aa32 100644 --- a/docs/dist/v1/chartifact.sandbox.umd.js +++ b/docs/dist/v1/chartifact.sandbox.umd.js @@ -322,26 +322,69 @@ document.addEventListener('DOMContentLoaded', () => { constructor(toolbarElementOrSelector, options = {}) { __publicField(this, "options"); __publicField(this, "toolbarElement"); + __publicField(this, "folderSpan"); + __publicField(this, "tweakButton"); + __publicField(this, "downloadButton"); + __publicField(this, "mode"); + __publicField(this, "filename"); + var _a, _b; this.options = options; + this.filename = options.filename || "sample"; + this.mode = options.mode || "markdown"; this.toolbarElement = typeof toolbarElementOrSelector === "string" ? document.querySelector(toolbarElementOrSelector) : toolbarElementOrSelector; if (!this.toolbarElement) { throw new Error("Toolbar element not found"); } - const html = `Chartifact viewer - - + const html = ` +
+ Chartifact viewer +
+ +
+ + +
`; this.toolbarElement.innerHTML = html; + this.tweakButton = this.toolbarElement.querySelector("#tweak"); + this.folderSpan = this.toolbarElement.querySelector("#folderSpan"); + this.downloadButton = this.toolbarElement.querySelector("#download"); if (this.options.tweakButton) { this.showTweakButton(); } - } - showTweakButton() { - const tweakButton = this.toolbarElement.querySelector("#tweak"); - tweakButton.style.display = ""; - tweakButton == null ? void 0 : tweakButton.addEventListener("click", () => { + if (this.options.downloadButton) { + this.showDownloadButton(); + } + (_a = this.tweakButton) == null ? void 0 : _a.addEventListener("click", () => { this.options.textarea.style.display = this.options.textarea.style.display === "none" ? "" : "none"; }); + (_b = this.downloadButton) == null ? void 0 : _b.addEventListener("click", () => { + const textarea = this.options.textarea; + if (!textarea) + return; + const content = textarea.value; + const ext = this.mode === "markdown" ? "idoc.md" : "idoc.json"; + const filename = `${filenameWithoutPathOrExtension(this.filename)}.${ext}`; + const blob = new Blob([content], { + type: this.mode === "markdown" ? "text/markdown" : "application/json" + }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 0); + }); + } + showTweakButton() { + this.tweakButton.style.display = ""; + } + showDownloadButton() { + this.downloadButton.style.display = ""; } manageTextareaVisibilityForAgents() { const { textarea } = this.options; @@ -359,6 +402,18 @@ document.addEventListener('DOMContentLoaded', () => { }, 300); } } + function filenameWithoutPathOrExtension(filename) { + const base = filename.split(/[\\/]/).pop() || filename; + const idocIdx = base.indexOf(".idoc"); + if (idocIdx !== -1) { + return base.substring(0, idocIdx); + } + const lastDot = base.lastIndexOf("."); + if (lastDot > 0) { + return base.substring(0, lastDot); + } + return base; + } function create(toolbarElementOrSelector, options = {}) { const toolbar = new Toolbar(toolbarElementOrSelector, options); toolbar.manageTextareaVisibilityForAgents(); diff --git a/packages/common/src/messages.ts b/packages/common/src/messages.ts index feb53550..3cbb23a7 100644 --- a/packages/common/src/messages.ts +++ b/packages/common/src/messages.ts @@ -31,6 +31,7 @@ export type SandboxApprovalMessage = { export interface HostRenderRequestMessage { type: 'hostRenderRequest'; + title: string; markdown?: string; interactiveDocument?: InteractiveDocument } diff --git a/packages/host-test/src/index.ts b/packages/host-test/src/index.ts index e5b53519..78c8c03a 100644 --- a/packages/host-test/src/index.ts +++ b/packages/host-test/src/index.ts @@ -9,6 +9,7 @@ window.addEventListener('message', (event) => { if (hostStatusMessage.type === 'hostStatus' && hostStatusMessage.hostStatus === 'ready') { const message: Chartifact.common.HostRenderRequestMessage = { type: 'hostRenderRequest', + title: 'sample', markdown: `# Auto-loaded Content This markdown was automatically sent when the iframe became ready. diff --git a/packages/host/dev/index.ts b/packages/host/dev/index.ts index 0b1ee11d..2e624bbe 100644 --- a/packages/host/dev/index.ts +++ b/packages/host/dev/index.ts @@ -50,6 +50,7 @@ const host = new Listener({ onSetMode: (mode, markdown, interactiveDocument) => { switch (mode) { case 'json': + toolbar.mode = 'json'; textarea.value = JSON.stringify(interactiveDocument, null, 2); render = () => { const json = textarea.value; @@ -72,6 +73,7 @@ const host = new Listener({ }; break; case 'markdown': + toolbar.mode = 'markdown'; textarea.value = markdown; render = () => { const markdown = textarea.value; @@ -82,5 +84,6 @@ const host = new Listener({ return; } toolbar.showTweakButton(); + toolbar.showDownloadButton(); }, }); diff --git a/packages/host/src/clipboard.ts b/packages/host/src/clipboard.ts index 4ee4e023..3dbdb8ed 100644 --- a/packages/host/src/clipboard.ts +++ b/packages/host/src/clipboard.ts @@ -38,7 +38,7 @@ export function setupClipboardHandling(host: Listener) { ); return; } - determineContent(null, content, host, true); + determineContent('clipboard-content', content, host, true); }); handled = true; break; diff --git a/packages/host/src/dragdrop.ts b/packages/host/src/dragdrop.ts index 113ce1b5..3650d639 100644 --- a/packages/host/src/dragdrop.ts +++ b/packages/host/src/dragdrop.ts @@ -39,7 +39,7 @@ export function setupDragDropHandling(host: Listener) { ); return; } - determineContent(null, content, host, true); + determineContent('dropped-content', content, host, true); } else { host.errorHandler( 'Unsupported drop content', diff --git a/packages/host/src/file.ts b/packages/host/src/file.ts index 6f9ea48a..8ad16653 100644 --- a/packages/host/src/file.ts +++ b/packages/host/src/file.ts @@ -25,7 +25,7 @@ export function readFile(file: File, host: Listener) { ); return; } - determineContent(null, content, host, true); + determineContent(file.name, content, host, true); }; reader.onerror = (e) => { host.errorHandler( diff --git a/packages/host/src/folder.ts b/packages/host/src/folder.ts index 075ea4e1..76cb8567 100644 --- a/packages/host/src/folder.ts +++ b/packages/host/src/folder.ts @@ -34,7 +34,7 @@ export function loadFolder(folderUrl: string, folder: Folder, host: Listener) { let docIndex = 0; - const folderSpan = host.toolbar.toolbarElement.querySelector('#folderSpan') as HTMLSpanElement; + const { folderSpan } = host.toolbar; folderSpan.style.display = ''; folderSpan.innerText = `Folder: ${folder.title} (${folder.docs.length} documents)`; @@ -77,7 +77,9 @@ export function loadFolder(folderUrl: string, folder: Folder, host: Listener) { prevBtn.disabled = docIndex === 0; nextBtn.disabled = docIndex === folder.docs.length - 1; pageSelect.value = (docIndex + 1).toString(); - resolveUrl(folderUrl, folder.docs[docIndex].href, host); + + const title = folder.docs[docIndex].title || `Page ${docIndex + 1}`; + resolveUrl(title, folderUrl, folder.docs[docIndex].href, host); } pageSelect.onchange = () => { @@ -129,7 +131,7 @@ export function loadFolder(folderUrl: string, folder: Folder, host: Listener) { //resolveUrl(folderUrl, folder.docUrls[docIndex], host); } -async function resolveUrl(base: string, relativeOrAbsolute: string, host: Listener) { +async function resolveUrl(title: string, base: string, relativeOrAbsolute: string, host: Listener) { let url: string; try { url = base ? new URL(relativeOrAbsolute, base).href : relativeOrAbsolute; @@ -149,11 +151,11 @@ async function resolveUrl(base: string, relativeOrAbsolute: string, host: Listen return; } if (result.idoc) { - host.render(undefined, result.idoc); + host.render(title, undefined, result.idoc); } else if (result.markdown) { - host.render(result.markdown, undefined); + host.render(title, result.markdown, undefined); } else if (result.folder) { - host.render('Nested folders are not supported', undefined); + host.render('Error', 'Nested folders are not supported', undefined); } else { host.errorHandler( 'Invalid document format', diff --git a/packages/host/src/listener.ts b/packages/host/src/listener.ts index 6d50a850..d66e0690 100644 --- a/packages/host/src/listener.ts +++ b/packages/host/src/listener.ts @@ -165,7 +165,7 @@ export class Listener { //try to show the message in the sandbox, since it works well with paging folder content if (this.sandboxReady) { const markdown = `# Error:\n${message}\n\n${details}`; - this.render(markdown, undefined); + this.render('Error', markdown, undefined); } else { // Clear previous content this.previewDiv.innerHTML = ''; @@ -181,7 +181,10 @@ export class Listener { } } - public render(markdown?: string, interactiveDocument?: InteractiveDocument) { + public render(title: string, markdown: string | null, interactiveDocument: InteractiveDocument | null) { + if (this.toolbar) { + this.toolbar.filename = title; + } if (interactiveDocument) { this.onSetMode('json', null, interactiveDocument); this.renderInteractiveDocument(interactiveDocument); diff --git a/packages/host/src/post-receive.ts b/packages/host/src/post-receive.ts index f5d095f2..6abb1a82 100644 --- a/packages/host/src/post-receive.ts +++ b/packages/host/src/post-receive.ts @@ -20,9 +20,9 @@ export function setupPostMessageHandling(host: Listener) { const message = event.data as HostRenderRequestMessage; if (message.type == 'hostRenderRequest') { if (message.markdown) { - host.render(message.markdown, undefined); + host.render( message.title, message.markdown, undefined); } else if (message.interactiveDocument) { - host.render(undefined, message.interactiveDocument); + host.render( message.title, undefined, message.interactiveDocument); } else { //do nothing, as messages may be directed to the page for other purposes } diff --git a/packages/host/src/string.ts b/packages/host/src/string.ts index 87e560d1..459f8ed6 100644 --- a/packages/host/src/string.ts +++ b/packages/host/src/string.ts @@ -15,8 +15,8 @@ export interface ContentResult { errorDetail?: string; } -export function determineContent(url:string, content: string, host: Listener, handle: boolean): ContentResult { - const result = _determineContent(content, host); +export function determineContent(urlOrTitle: string, content: string, host: Listener, handle: boolean): ContentResult { + const result = _determineContent(content); if (handle) { if (result.error) { host.errorHandler( @@ -25,17 +25,17 @@ export function determineContent(url:string, content: string, host: Listener, ha ); return; } else if (result.idoc) { - host.render(undefined, result.idoc); + host.render(urlOrTitle, undefined, result.idoc); } else if (result.folder) { - loadFolder(url, result.folder, host); + loadFolder(urlOrTitle, result.folder, host); } else if (result.markdown) { - host.render(result.markdown, undefined); + host.render(urlOrTitle, result.markdown, undefined); } } return result; } -function _determineContent(content: string, host: Listener): ContentResult { +function _determineContent(content: string): ContentResult { if (!content) { return { error: 'Content is empty', diff --git a/packages/toolbar/chartifact-toolbar.css b/packages/toolbar/chartifact-toolbar.css index 15abb1d7..6000df51 100644 --- a/packages/toolbar/chartifact-toolbar.css +++ b/packages/toolbar/chartifact-toolbar.css @@ -24,8 +24,25 @@ /* Base styles for the toolbar */ .chartifact-toolbar { + display: flex; + align-items: center; + justify-content: space-between; padding: 0.5em 1em; - font-weight: bold; + font-family: sans-serif; +} + +.chartifact-toolbar a { + text-decoration: none; + color: inherit; +} + +.chartifact-toolbar button, .chartifact-toolbar select { + font-size: 9pt; +} + +/* Remove margin from all toolbar divs by default */ +.chartifact-toolbar > div { + display: inline-block; } /* Default (OS light theme) */ @@ -37,7 +54,7 @@ /* OS dark theme fallback */ @media (prefers-color-scheme: dark) { .chartifact-toolbar { - background-color: #1e1e1e; + background-color: #2e2e2e; color: #ffffff; border-bottom-color: rgba(255, 255, 255, 0.1); } diff --git a/packages/toolbar/src/toolbar.ts b/packages/toolbar/src/toolbar.ts index 11d4b8d6..449b8ada 100644 --- a/packages/toolbar/src/toolbar.ts +++ b/packages/toolbar/src/toolbar.ts @@ -5,37 +5,85 @@ export interface ToolbarOptions { tweakButton?: boolean; + downloadButton?: boolean; textarea?: HTMLTextAreaElement; + mode?: 'markdown' | 'json'; + filename?: string; } export class Toolbar { public toolbarElement: HTMLElement; + public folderSpan: HTMLElement; + public tweakButton: HTMLButtonElement; + public downloadButton: HTMLButtonElement; + public mode: 'markdown' | 'json'; + public filename: string; constructor(toolbarElementOrSelector: HTMLElement | string, public options: ToolbarOptions = {}) { + this.filename = options.filename || 'sample'; + this.mode = options.mode || 'markdown'; this.toolbarElement = typeof toolbarElementOrSelector === 'string' ? document.querySelector(toolbarElementOrSelector) : toolbarElementOrSelector; if (!this.toolbarElement) { throw new Error('Toolbar element not found'); } - const html = `Chartifact viewer - - + const html = ` +
+ Chartifact viewer +
+ +
+ + +
`; this.toolbarElement.innerHTML = html; + this.tweakButton = this.toolbarElement.querySelector('#tweak') as HTMLButtonElement; + this.folderSpan = this.toolbarElement.querySelector('#folderSpan') as HTMLElement; + this.downloadButton = this.toolbarElement.querySelector('#download') as HTMLButtonElement; + if (this.options.tweakButton) { this.showTweakButton(); } - } + if (this.options.downloadButton) { + this.showDownloadButton(); + } - showTweakButton() { - const tweakButton = this.toolbarElement.querySelector('#tweak') as HTMLButtonElement; - tweakButton.style.display = ''; - tweakButton?.addEventListener('click', () => { + this.tweakButton?.addEventListener('click', () => { this.options.textarea.style.display = this.options.textarea.style.display === 'none' ? '' : 'none'; }); + + this.downloadButton?.addEventListener('click', () => { + const textarea = this.options.textarea; + if (!textarea) return; + const content = textarea.value; + const ext = this.mode === 'markdown' ? 'idoc.md' : 'idoc.json'; + const filename = `${filenameWithoutPathOrExtension(this.filename)}.${ext}`; + const blob = new Blob([content], { + type: this.mode === 'markdown' ? 'text/markdown' : 'application/json' + }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, 0); + }); + } + + showTweakButton() { + this.tweakButton.style.display = ''; + } + + showDownloadButton() { + this.downloadButton.style.display = ''; } manageTextareaVisibilityForAgents() { @@ -62,3 +110,47 @@ export class Toolbar { } } + +function filenameWithoutPathOrExtension(filename: string) { + // Remove everything before the last slash or backslash + const base = filename.split(/[\\/]/).pop() || filename; + // If .idoc appears, remove it and everything after + const idocIdx = base.indexOf('.idoc'); + if (idocIdx !== -1) { + return base.substring(0, idocIdx); + } + // Otherwise, remove the last extension if present + const lastDot = base.lastIndexOf('.'); + if (lastDot > 0) { + return base.substring(0, lastDot); + } + return base; +} + +// --- TESTS for filenameWithoutPathOrExtension --- + +// const filenameTests: [string, string][] = [ +// // [input, expected] +// ["foo.md", "foo"], +// ["foo.idoc.md", "foo"], +// ["foo.idoc.json", "foo"], +// ["foo", "foo"], +// ["foo.bar.baz.md", "foo.bar.baz"], +// ["C:\\folder\\foo.md", "foo"], +// ["/home/user/foo.md", "foo"], +// ["folder/foo.idoc.md", "foo"], +// ["folder\\foo.idoc.json", "foo"], +// ["folder.with.dots/foo.bar.baz.md", "foo.bar.baz"], +// ["folder.with.dots\\foo.bar.baz.md", "foo.bar.baz"], +// ["", ""], +// ]; + +// filenameTests.forEach(([input, expected], i) => { +// const result = filenameWithoutPathOrExtension(input); +// const pass = result === expected; +// // eslint-disable-next-line no-console +// console.log( +// `${pass ? '✅' : '❌'} Test ${i + 1}: ${pass ? 'PASS' : `FAIL\n Input: ${input}\n Got: ${result}\n Expected: ${expected}`}` +// ); +// }); + diff --git a/packages/vscode/src/web/command-preview.ts b/packages/vscode/src/web/command-preview.ts index ea64dbf8..b2817961 100644 --- a/packages/vscode/src/web/command-preview.ts +++ b/packages/vscode/src/web/command-preview.ts @@ -111,11 +111,14 @@ export class PreviewManager { private getFileContentAndRender(fileUri: vscode.Uri, uriFsPath: string) { vscode.workspace.fs.readFile(fileUri).then(uint8array => { + const title = uriFsPath; + // If the file is a markdown file, we can send the markdown content if (uriFsPath.endsWith('.md')) { const markdown = new TextDecoder().decode(uint8array); this.render({ type: 'hostRenderRequest', + title, markdown, }); } else if (uriFsPath.endsWith('.json')) { @@ -125,6 +128,7 @@ export class PreviewManager { const interactiveDocument = JSON.parse(jsonContent); this.render({ type: 'hostRenderRequest', + title, interactiveDocument, }); } catch (error) {