From 9e382a1c30eaa72e762943abedc557a9f1b0ff34 Mon Sep 17 00:00:00 2001 From: Kishan Patel Date: Tue, 14 Oct 2025 14:59:37 -0400 Subject: [PATCH 1/6] fix(exporters): added confimation dialog to show which cell output will be saved --- .../components/notebook/exporters/index.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/client/src/components/notebook/exporters/index.ts b/client/src/components/notebook/exporters/index.ts index e09cf463a..a949bcd83 100644 --- a/client/src/components/notebook/exporters/index.ts +++ b/client/src/components/notebook/exporters/index.ts @@ -44,6 +44,21 @@ export const saveOutput = async () => { return; } + // Show a message to clarify which cell's output will be saved + const proceed = await window.showInformationMessage( + l10n.t( + "This will save output from the selected cell (cell {0}). To save output from a different cell, please click on that cell first.", + cell.index + 1, + ), + { modal: false }, + l10n.t("Continue"), + l10n.t("Cancel"), + ); + + if (proceed !== l10n.t("Continue")) { + return; + } + let odsItem = null; let logItem = null; @@ -100,7 +115,7 @@ export const saveOutput = async () => { content = odsItem.data.toString(); fileExtension = "html"; fileName = `${path.basename(notebook.uri.path, ".sasnb")}_${l10n.t("output")}_${ - activeCell + 1 + cell.index + 1 }.html`; } else if (exportChoice.outputType === "log" && logItem) { const logs: Array<{ line: string; type: string }> = JSON.parse( @@ -109,7 +124,7 @@ export const saveOutput = async () => { content = logs.map((log) => log.line).join("\n"); fileExtension = "log"; fileName = `${path.basename(notebook.uri.path, ".sasnb")}_${l10n.t("output")}_${ - activeCell + 1 + cell.index + 1 }.log`; } } catch (error) { From 1aba51a16f1e2ed068e5f17c21dfa9b3aa60e0bf Mon Sep 17 00:00:00 2001 From: Kishan Patel Date: Wed, 15 Oct 2025 14:30:02 -0400 Subject: [PATCH 2/6] feat(renderer): refactored to render save output button for better ux Signed-off-by: Kishan Patel --- .../components/notebook/exporters/index.ts | 112 ++++-------------- .../notebook/renderers/HTMLRenderer.ts | 49 +++++++- .../notebook/renderers/LogRenderer.ts | 46 ++++++- client/src/node/extension.ts | 26 +++- package.json | 17 +-- package.nls.json | 1 - 6 files changed, 141 insertions(+), 110 deletions(-) diff --git a/client/src/components/notebook/exporters/index.ts b/client/src/components/notebook/exporters/index.ts index a949bcd83..1153e7c8f 100644 --- a/client/src/components/notebook/exporters/index.ts +++ b/client/src/components/notebook/exporters/index.ts @@ -1,6 +1,6 @@ // Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { Uri, l10n, window, workspace } from "vscode"; +import { NotebookDocument, Uri, l10n, window, workspace } from "vscode"; import type { LanguageClient } from "vscode-languageclient/node"; import path from "path"; @@ -31,101 +31,32 @@ export const exportNotebook = async (client: LanguageClient) => { workspace.fs.writeFile(uri, Buffer.from(content)); }; -export const saveOutput = async () => { - const notebook = window.activeNotebookEditor?.notebook; - const activeCell = window.activeNotebookEditor?.selection?.start; - - if (!notebook || activeCell === undefined) { - return; - } - - const cell = notebook.cellAt(activeCell); - if (!cell) { - return; - } - - // Show a message to clarify which cell's output will be saved - const proceed = await window.showInformationMessage( - l10n.t( - "This will save output from the selected cell (cell {0}). To save output from a different cell, please click on that cell first.", - cell.index + 1, - ), - { modal: false }, - l10n.t("Continue"), - l10n.t("Cancel"), - ); - - if (proceed !== l10n.t("Continue")) { - return; - } - - let odsItem = null; - let logItem = null; - - for (const output of cell.outputs) { - if (!odsItem) { - odsItem = output.items.find( - (item) => item.mime === "application/vnd.sas.ods.html5", - ); - } - if (!logItem) { - logItem = output.items.find( - (item) => item.mime === "application/vnd.sas.compute.log.lines", - ); - } +let timesOutputSaved = 0; - if (odsItem && logItem) { - break; - } - } - - const choices: Array<{ - label: string; +export const saveOutputFromRenderer = async ( + message: { outputType: "html" | "log"; - }> = []; - - if (odsItem) { - choices.push({ - label: l10n.t("Save ODS HTML"), - outputType: "html", - }); - } - - if (logItem) { - choices.push({ - label: l10n.t("Save Log"), - outputType: "log", - }); - } - - const exportChoice = await window.showQuickPick(choices, { - placeHolder: l10n.t("Choose output type to save"), - ignoreFocusOut: true, - }); - - if (!exportChoice) { - return; - } - - let content = ""; + content: unknown; + mime: string; + cellIndex?: number; + }, + notebook: NotebookDocument, +) => { + const { outputType, content } = message; + + let fileContent = ""; let fileExtension = ""; let fileName = ""; + try { - if (exportChoice.outputType === "html" && odsItem) { - content = odsItem.data.toString(); + if (outputType === "html" && typeof content === "string") { + fileContent = content; fileExtension = "html"; - fileName = `${path.basename(notebook.uri.path, ".sasnb")}_${l10n.t("output")}_${ - cell.index + 1 - }.html`; - } else if (exportChoice.outputType === "log" && logItem) { - const logs: Array<{ line: string; type: string }> = JSON.parse( - logItem.data.toString(), - ); - content = logs.map((log) => log.line).join("\n"); + fileName = `${path.basename(notebook.uri.path, ".sasnb")}_${l10n.t("output")}_${timesOutputSaved + 1}.html`; + } else if (outputType === "log" && Array.isArray(content)) { + fileContent = content.map((log: { line: string }) => log.line).join("\n"); fileExtension = "log"; - fileName = `${path.basename(notebook.uri.path, ".sasnb")}_${l10n.t("output")}_${ - cell.index + 1 - }.log`; + fileName = `${path.basename(notebook.uri.path, ".sasnb")}_${l10n.t("output")}_${timesOutputSaved + 1}.log`; } } catch (error) { window.showErrorMessage( @@ -146,7 +77,8 @@ export const saveOutput = async () => { return; } - await workspace.fs.writeFile(uri, Buffer.from(content)); + await workspace.fs.writeFile(uri, Buffer.from(fileContent)); + timesOutputSaved++; window.showInformationMessage(l10n.t("Saved to {0}", uri.fsPath)); }; diff --git a/client/src/components/notebook/renderers/HTMLRenderer.ts b/client/src/components/notebook/renderers/HTMLRenderer.ts index 3fde97365..0ac1ff94d 100644 --- a/client/src/components/notebook/renderers/HTMLRenderer.ts +++ b/client/src/components/notebook/renderers/HTMLRenderer.ts @@ -19,18 +19,63 @@ function replaceLast( ); } -export const activate: ActivationFunction = () => ({ +export const activate: ActivationFunction = (context) => ({ renderOutputItem(data, element) { const html = data.text(); let shadow = element.shadowRoot; if (!shadow) { shadow = element.attachShadow({ mode: "open" }); } - shadow.innerHTML = replaceLast( + + // Create a wrapper with a save button + const wrapper = document.createElement("div"); + wrapper.style.position = "relative"; + + // Add save button if messaging is available + if (context.postMessage) { + const saveButton = document.createElement("button"); + saveButton.textContent = "Save Output"; + saveButton.title = "Save this output to a file"; + saveButton.style.cssText = ` + position: absolute; + top: 8px; + right: 8px; + padding: 4px 8px; + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + border-radius: 2px; + cursor: pointer; + font-size: 12px; + z-index: 1000; + `; + saveButton.onmouseover = () => { + saveButton.style.background = "var(--vscode-button-hoverBackground)"; + }; + saveButton.onmouseout = () => { + saveButton.style.background = "var(--vscode-button-background)"; + }; + + saveButton.onclick = () => { + context.postMessage({ + command: "saveOutput", + outputType: "html", + content: html, + mime: data.mime, + }); + }; + wrapper.appendChild(saveButton); + } + + // Add the HTML content + const contentDiv = document.createElement("div"); + contentDiv.innerHTML = replaceLast( // it's not a whole webview, body not allowed html.replace("", "", ); + wrapper.appendChild(contentDiv); + shadow.replaceChildren(wrapper); }, }); diff --git a/client/src/components/notebook/renderers/LogRenderer.ts b/client/src/components/notebook/renderers/LogRenderer.ts index 0e9b3445f..2906bd05c 100644 --- a/client/src/components/notebook/renderers/LogRenderer.ts +++ b/client/src/components/notebook/renderers/LogRenderer.ts @@ -10,8 +10,49 @@ const colorMap = { note: "var(--vscode-editorInfo-foreground)", }; -export const activate: ActivationFunction = () => ({ +export const activate: ActivationFunction = (context) => ({ renderOutputItem(data, element) { + const wrapper = document.createElement("div"); + wrapper.style.position = "relative"; + + // Add save button if messaging is available + if (context.postMessage) { + const saveButton = document.createElement("button"); + saveButton.textContent = "Save Output"; + saveButton.title = "Save this output to a file"; + saveButton.style.cssText = ` + position: absolute; + top: 8px; + right: 8px; + padding: 4px 8px; + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + border-radius: 2px; + cursor: pointer; + font-size: 12px; + z-index: 1000; + `; + saveButton.onmouseover = () => { + saveButton.style.background = "var(--vscode-button-hoverBackground)"; + }; + saveButton.onmouseout = () => { + saveButton.style.background = "var(--vscode-button-background)"; + }; + + const logs: LogLine[] = data.json(); + saveButton.onclick = () => { + context.postMessage({ + command: "saveOutput", + outputType: "log", + content: logs, + mime: data.mime, + }); + }; + wrapper.appendChild(saveButton); + } + + // Add the log content const root = document.createElement("div"); root.style.whiteSpace = "pre"; root.style.fontFamily = "var(--vscode-editor-font-family)"; @@ -26,6 +67,7 @@ export const activate: ActivationFunction = () => ({ } root.append(div); } - element.replaceChildren(root); + wrapper.appendChild(root); + element.replaceChildren(wrapper); }, }); diff --git a/client/src/node/extension.ts b/client/src/node/extension.ts index c50becba0..76cb82476 100644 --- a/client/src/node/extension.ts +++ b/client/src/node/extension.ts @@ -8,6 +8,7 @@ import { commands, l10n, languages, + notebooks, tasks, window, workspace, @@ -54,7 +55,10 @@ import { LogTokensProvider, legend } from "../components/logViewer"; import { sasDiagnostic } from "../components/logViewer/sasDiagnostics"; import { NotebookController } from "../components/notebook/Controller"; import { NotebookSerializer } from "../components/notebook/Serializer"; -import { exportNotebook, saveOutput } from "../components/notebook/exporters"; +import { + exportNotebook, + saveOutputFromRenderer, +} from "../components/notebook/exporters"; import { ConnectionType } from "../components/profile"; import { SasTaskProvider } from "../components/tasks/SasTaskProvider"; import { SAS_TASK_TYPE } from "../components/tasks/SasTasks"; @@ -202,7 +206,6 @@ export function activate(context: ExtensionContext) { commands.registerCommand("SAS.notebook.export", () => exportNotebook(client), ), - commands.registerCommand("SAS.notebook.saveOutput", saveOutput), tasks.registerTaskProvider(SAS_TASK_TYPE, new SasTaskProvider()), ...sasDiagnostic.getSubscriptions(), commands.registerTextEditorCommand("SAS.toggleLineComment", (editor) => { @@ -210,6 +213,25 @@ export function activate(context: ExtensionContext) { }), ); + // Set up message handlers for notebook renderers + const htmlRendererMessaging = + notebooks.createRendererMessaging("sas-html-renderer"); + const logRendererMessaging = + notebooks.createRendererMessaging("sas-log-renderer"); + + context.subscriptions.push( + htmlRendererMessaging.onDidReceiveMessage((e) => { + if (e.message.command === "saveOutput") { + saveOutputFromRenderer(e.message, e.editor.notebook); + } + }), + logRendererMessaging.onDidReceiveMessage((e) => { + if (e.message.command === "saveOutput") { + saveOutputFromRenderer(e.message, e.editor.notebook); + } + }), + ); + // Reset first to set "No Active Profiles" resetStatusBarItem(); // Update status bar if profile is found diff --git a/package.json b/package.json index 75651447b..78c47b34e 100644 --- a/package.json +++ b/package.json @@ -848,11 +848,6 @@ "title": "%commands.SAS.notebook.export%", "category": "SAS Notebook" }, - { - "command": "SAS.notebook.saveOutput", - "title": "%commands.SAS.notebook.saveOutput%", - "category": "SAS Notebook" - }, { "command": "SAS.file.new", "shortTitle": "%commands.SAS.file.new.short%", @@ -1125,12 +1120,6 @@ "command": "SAS.notebook.export" } ], - "notebook/cell/title": [ - { - "command": "SAS.notebook.saveOutput", - "when": "notebookType == 'sas-notebook' && notebookCellHasOutputs" - } - ], "commandPalette": [ { "when": "editorLangId == sas && !SAS.hideRunMenuItem", @@ -1376,7 +1365,8 @@ "entrypoint": "./client/dist/notebook/LogRenderer.js", "mimeTypes": [ "application/vnd.sas.compute.log.lines" - ] + ], + "requiresMessaging": "optional" }, { "id": "sas-html-renderer", @@ -1384,7 +1374,8 @@ "entrypoint": "./client/dist/notebook/HTMLRenderer.js", "mimeTypes": [ "application/vnd.sas.ods.html5" - ] + ], + "requiresMessaging": "optional" } ] }, diff --git a/package.nls.json b/package.nls.json index 78ebdaf8c..996da0f2f 100644 --- a/package.nls.json +++ b/package.nls.json @@ -18,7 +18,6 @@ "commands.SAS.notebook.export": "Export", "commands.SAS.notebook.new": "New SAS Notebook", "commands.SAS.notebook.new.short": "SAS Notebook", - "commands.SAS.notebook.saveOutput": "Save Output", "commands.SAS.refresh": "Refresh", "commands.SAS.removeFromFavorites": "Remove from My Favorites", "commands.SAS.renameResource": "Rename...", From 54868210024f28f00e06a996f86747c9a0df6585 Mon Sep 17 00:00:00 2001 From: Kishan Patel Date: Wed, 15 Oct 2025 14:36:33 -0400 Subject: [PATCH 3/6] feat(exporters): linting DCO Remediation Commit for Kishan Patel I, Kishan Patel , hereby add my Signed-off-by to this commit: 9e382a1c30eaa72e762943abedc557a9f1b0ff34 Signed-off-by: Kishan Patel --- client/src/components/notebook/renderers/HTMLRenderer.ts | 2 -- client/src/components/notebook/renderers/LogRenderer.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/client/src/components/notebook/renderers/HTMLRenderer.ts b/client/src/components/notebook/renderers/HTMLRenderer.ts index 0ac1ff94d..767c18cc1 100644 --- a/client/src/components/notebook/renderers/HTMLRenderer.ts +++ b/client/src/components/notebook/renderers/HTMLRenderer.ts @@ -27,11 +27,9 @@ export const activate: ActivationFunction = (context) => ({ shadow = element.attachShadow({ mode: "open" }); } - // Create a wrapper with a save button const wrapper = document.createElement("div"); wrapper.style.position = "relative"; - // Add save button if messaging is available if (context.postMessage) { const saveButton = document.createElement("button"); saveButton.textContent = "Save Output"; diff --git a/client/src/components/notebook/renderers/LogRenderer.ts b/client/src/components/notebook/renderers/LogRenderer.ts index 2906bd05c..7e61b664b 100644 --- a/client/src/components/notebook/renderers/LogRenderer.ts +++ b/client/src/components/notebook/renderers/LogRenderer.ts @@ -15,7 +15,6 @@ export const activate: ActivationFunction = (context) => ({ const wrapper = document.createElement("div"); wrapper.style.position = "relative"; - // Add save button if messaging is available if (context.postMessage) { const saveButton = document.createElement("button"); saveButton.textContent = "Save Output"; @@ -52,7 +51,6 @@ export const activate: ActivationFunction = (context) => ({ wrapper.appendChild(saveButton); } - // Add the log content const root = document.createElement("div"); root.style.whiteSpace = "pre"; root.style.fontFamily = "var(--vscode-editor-font-family)"; From c16d75d934260ed2de75878c8d6d088ab0e28247 Mon Sep 17 00:00:00 2001 From: Kishan Patel Date: Thu, 16 Oct 2025 14:54:13 -0400 Subject: [PATCH 4/6] fix(renderer): changed save button to be in the corner and only show on hover Signed-off-by: Kishan Patel --- .../notebook/renderers/HTMLRenderer.ts | 73 +++++++++++++----- .../notebook/renderers/LogRenderer.ts | 75 ++++++++++++++----- 2 files changed, 110 insertions(+), 38 deletions(-) diff --git a/client/src/components/notebook/renderers/HTMLRenderer.ts b/client/src/components/notebook/renderers/HTMLRenderer.ts index 767c18cc1..716917b90 100644 --- a/client/src/components/notebook/renderers/HTMLRenderer.ts +++ b/client/src/components/notebook/renderers/HTMLRenderer.ts @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import type { ActivationFunction } from "vscode-notebook-renderer"; +let outputIndex = 0; + /** * Replace the last occurrence of a substring */ @@ -22,36 +24,61 @@ function replaceLast( export const activate: ActivationFunction = (context) => ({ renderOutputItem(data, element) { const html = data.text(); + const currentIndex = outputIndex++; + let shadow = element.shadowRoot; if (!shadow) { shadow = element.attachShadow({ mode: "open" }); } - const wrapper = document.createElement("div"); - wrapper.style.position = "relative"; + const container = document.createElement("div"); + container.style.position = "relative"; if (context.postMessage) { + const toolbar = document.createElement("div"); + toolbar.style.cssText = ` + position: absolute; + top: 4px; + display: flex; + gap: 4px; + opacity: 0; + transition: opacity 0.1s ease; + background: var(--vscode-editor-background); + border: 1px solid var(--vscode-widget-border); + border-radius: 4px; + padding: 2px; + z-index: 1000; + margin: 4px 4px 0 0; + `; + + // Save button with inline SVG icon const saveButton = document.createElement("button"); - saveButton.textContent = "Save Output"; - saveButton.title = "Save this output to a file"; + saveButton.title = "Save Output"; + saveButton.setAttribute("aria-label", "Save Output"); + saveButton.innerHTML = ` + + + + `; saveButton.style.cssText = ` - position: absolute; - top: 8px; - right: 8px; - padding: 4px 8px; - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + background: transparent; border: none; - border-radius: 2px; + color: var(--vscode-icon-foreground); cursor: pointer; - font-size: 12px; - z-index: 1000; + border-radius: 3px; `; + saveButton.onmouseover = () => { - saveButton.style.background = "var(--vscode-button-hoverBackground)"; + saveButton.style.background = "var(--vscode-toolbar-hoverBackground)"; }; saveButton.onmouseout = () => { - saveButton.style.background = "var(--vscode-button-background)"; + saveButton.style.background = "transparent"; }; saveButton.onclick = () => { @@ -60,9 +87,19 @@ export const activate: ActivationFunction = (context) => ({ outputType: "html", content: html, mime: data.mime, + cellIndex: currentIndex, }); }; - wrapper.appendChild(saveButton); + + toolbar.appendChild(saveButton); + container.onmouseenter = () => { + toolbar.style.opacity = "1"; + }; + container.onmouseleave = () => { + toolbar.style.opacity = "0"; + }; + + container.appendChild(toolbar); } // Add the HTML content @@ -73,7 +110,7 @@ export const activate: ActivationFunction = (context) => ({ "", "", ); - wrapper.appendChild(contentDiv); - shadow.replaceChildren(wrapper); + container.appendChild(contentDiv); + shadow.replaceChildren(container); }, }); diff --git a/client/src/components/notebook/renderers/LogRenderer.ts b/client/src/components/notebook/renderers/LogRenderer.ts index 7e61b664b..a75f5351e 100644 --- a/client/src/components/notebook/renderers/LogRenderer.ts +++ b/client/src/components/notebook/renderers/LogRenderer.ts @@ -4,6 +4,8 @@ import type { ActivationFunction } from "vscode-notebook-renderer"; import type { LogLine } from "../../../connection"; +let outputIndex = 0; + const colorMap = { error: "var(--vscode-editorError-foreground)", warning: "var(--vscode-editorWarning-foreground)", @@ -12,50 +14,83 @@ const colorMap = { export const activate: ActivationFunction = (context) => ({ renderOutputItem(data, element) { - const wrapper = document.createElement("div"); - wrapper.style.position = "relative"; + const logs: LogLine[] = data.json(); + const currentIndex = outputIndex++; + + const container = document.createElement("div"); + container.style.position = "relative"; if (context.postMessage) { + const toolbar = document.createElement("div"); + toolbar.style.cssText = ` + position: absolute; + top: 4px; + display: flex; + gap: 4px; + opacity: 0; + transition: opacity 0.1s ease; + background: var(--vscode-editor-background); + border: 1px solid var(--vscode-widget-border); + border-radius: 4px; + padding: 2px; + z-index: 1000; + margin: 4px 4px 0 0; + `; + const saveButton = document.createElement("button"); - saveButton.textContent = "Save Output"; - saveButton.title = "Save this output to a file"; + saveButton.title = "Save Output"; + saveButton.setAttribute("aria-label", "Save Output"); + saveButton.innerHTML = ` + + + + `; saveButton.style.cssText = ` - position: absolute; - top: 8px; - right: 8px; - padding: 4px 8px; - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + background: transparent; border: none; - border-radius: 2px; + color: var(--vscode-icon-foreground); cursor: pointer; - font-size: 12px; - z-index: 1000; + border-radius: 3px; `; + saveButton.onmouseover = () => { - saveButton.style.background = "var(--vscode-button-hoverBackground)"; + saveButton.style.background = "var(--vscode-toolbar-hoverBackground)"; }; saveButton.onmouseout = () => { - saveButton.style.background = "var(--vscode-button-background)"; + saveButton.style.background = "transparent"; }; - const logs: LogLine[] = data.json(); saveButton.onclick = () => { context.postMessage({ command: "saveOutput", outputType: "log", content: logs, mime: data.mime, + cellIndex: currentIndex, }); }; - wrapper.appendChild(saveButton); + + toolbar.appendChild(saveButton); + container.onmouseenter = () => { + toolbar.style.opacity = "1"; + }; + container.onmouseleave = () => { + toolbar.style.opacity = "0"; + }; + + container.appendChild(toolbar); } const root = document.createElement("div"); root.style.whiteSpace = "pre"; root.style.fontFamily = "var(--vscode-editor-font-family)"; - const logs: LogLine[] = data.json(); for (const line of logs) { const color = colorMap[line.type]; const div = document.createElement("div"); @@ -65,7 +100,7 @@ export const activate: ActivationFunction = (context) => ({ } root.append(div); } - wrapper.appendChild(root); - element.replaceChildren(wrapper); + container.appendChild(root); + element.replaceChildren(container); }, }); From 1f694d0bb6d4fa7a8e792730fdf414c867e2e447 Mon Sep 17 00:00:00 2001 From: Kishan Patel Date: Tue, 21 Oct 2025 22:58:18 -0400 Subject: [PATCH 5/6] bug(Renderer): fixed neg top Signed-off-by: Kishan Patel --- .../components/notebook/renderers/HTMLRenderer.ts | 12 +++++------- .../src/components/notebook/renderers/LogRenderer.ts | 8 ++++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/client/src/components/notebook/renderers/HTMLRenderer.ts b/client/src/components/notebook/renderers/HTMLRenderer.ts index 716917b90..44d9f7c93 100644 --- a/client/src/components/notebook/renderers/HTMLRenderer.ts +++ b/client/src/components/notebook/renderers/HTMLRenderer.ts @@ -38,7 +38,8 @@ export const activate: ActivationFunction = (context) => ({ const toolbar = document.createElement("div"); toolbar.style.cssText = ` position: absolute; - top: 4px; + top: -22px; + right: 8px; display: flex; gap: 4px; opacity: 0; @@ -48,16 +49,15 @@ export const activate: ActivationFunction = (context) => ({ border-radius: 4px; padding: 2px; z-index: 1000; - margin: 4px 4px 0 0; `; - // Save button with inline SVG icon const saveButton = document.createElement("button"); saveButton.title = "Save Output"; saveButton.setAttribute("aria-label", "Save Output"); saveButton.innerHTML = ` - + + `; saveButton.style.cssText = ` @@ -100,9 +100,7 @@ export const activate: ActivationFunction = (context) => ({ }; container.appendChild(toolbar); - } - - // Add the HTML content + } // Add the HTML content const contentDiv = document.createElement("div"); contentDiv.innerHTML = replaceLast( // it's not a whole webview, body not allowed diff --git a/client/src/components/notebook/renderers/LogRenderer.ts b/client/src/components/notebook/renderers/LogRenderer.ts index a75f5351e..52a81347f 100644 --- a/client/src/components/notebook/renderers/LogRenderer.ts +++ b/client/src/components/notebook/renderers/LogRenderer.ts @@ -24,7 +24,8 @@ export const activate: ActivationFunction = (context) => ({ const toolbar = document.createElement("div"); toolbar.style.cssText = ` position: absolute; - top: 4px; + top: -22px; + right: 8px; display: flex; gap: 4px; opacity: 0; @@ -34,7 +35,6 @@ export const activate: ActivationFunction = (context) => ({ border-radius: 4px; padding: 2px; z-index: 1000; - margin: 4px 4px 0 0; `; const saveButton = document.createElement("button"); @@ -42,7 +42,8 @@ export const activate: ActivationFunction = (context) => ({ saveButton.setAttribute("aria-label", "Save Output"); saveButton.innerHTML = ` - + + `; saveButton.style.cssText = ` @@ -86,7 +87,6 @@ export const activate: ActivationFunction = (context) => ({ container.appendChild(toolbar); } - const root = document.createElement("div"); root.style.whiteSpace = "pre"; root.style.fontFamily = "var(--vscode-editor-font-family)"; From d9efb508d08363b3fbc2b3e5a856075377db41a6 Mon Sep 17 00:00:00 2001 From: Kishan Patel Date: Wed, 22 Oct 2025 12:15:03 -0400 Subject: [PATCH 6/6] fix(Renderer): fixed log output negative top for save button Signed-off-by: Kishan Patel --- client/src/components/notebook/renderers/LogRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/notebook/renderers/LogRenderer.ts b/client/src/components/notebook/renderers/LogRenderer.ts index 52a81347f..355728904 100644 --- a/client/src/components/notebook/renderers/LogRenderer.ts +++ b/client/src/components/notebook/renderers/LogRenderer.ts @@ -24,7 +24,7 @@ export const activate: ActivationFunction = (context) => ({ const toolbar = document.createElement("div"); toolbar.style.cssText = ` position: absolute; - top: -22px; + top: -10px; right: 8px; display: flex; gap: 4px;