// @ts-check
/**
* editor.js - Memory Bank Document Editor WebView Script
* This script handles the interactive behavior of the Memory Bank document editor webview.
*/
/* eslint-env browser */
/* global acquireVsCodeApi, mermaid */
(function () {
const vscode = acquireVsCodeApi();
let editor, previewContentDiv, errorMessageDiv;
let lastKnownText = '';
/**
* Initialize the editor functionality.
*/
function initialize() {
console.log('[Webview] Initializing editor script...'); // Add log at the start
// Get DOM elements
editor = document.getElementById('editor');
previewContentDiv = document.getElementById('preview-content');
errorMessageDiv = document.getElementById('error-message');
if (editor) {
lastKnownText = editor.value;
// Setup event listeners for editor changes
editor.addEventListener('input', handleEditorInput);
} else {
console.error('[Webview] Editor element not found');
}
// Handle messages from the extension
window.addEventListener('message', handleExtensionMessages);
// Setup mode toggle buttons
setupModeToggle();
console.log('[Webview] Script loaded and listeners attached.');
// Initialize Mermaid if available
initializeMermaid();
// Request initial preview
requestInitialPreview();
// Attempt initial Mermaid rendering after a delay
setTimeout(attemptInitialMermaidRender, 150);
console.log('[Webview] Editor script initialization complete.');
}
/**
* Initialize Mermaid library if available
*/
function initializeMermaid() {
try {
if (typeof mermaid !== 'undefined') {
console.log('[Webview] Mermaid library found, initializing...');
// Initialize with default config
mermaid.initialize({
startOnLoad: false, // We'll manually run rendering
theme: 'default',
securityLevel: 'loose', // Needed for webview rendering
flowchart: { useMaxWidth: true, htmlLabels: true },
logLevel: 3, // Error
});
console.log('[Webview] Mermaid initialized successfully');
return true;
} else {
console.warn('[Webview] Mermaid library not found during initialization.');
return false;
}
} catch (err) {
console.error('[Webview] Error initializing Mermaid:', err);
return false;
}
}
/**
* Request the initial preview from the extension
*/
function requestInitialPreview() {
console.log("[Webview] Sending 'requestPreview' message to extension.");
vscode.postMessage({ type: 'requestPreview', payload: lastKnownText });
}
/**
* Handle input events from the editor textarea
* @param {Event} e - The input event
*/
function handleEditorInput(e) {
const newText = e.target.value;
lastKnownText = newText;
errorMessageDiv.textContent = '';
// Basic JSON validation
try {
JSON.parse(newText);
// Send update to extension host for processing and preview generation
vscode.postMessage({ type: 'update', payload: newText });
} catch (err) {
errorMessageDiv.textContent = 'Invalid JSON: ' + err.message;
// Still send update so extension is aware of invalid state
vscode.postMessage({ type: 'update', payload: newText });
}
}
/**
* Handle messages received from the extension
* @param {MessageEvent} event - The message event
*/
function handleExtensionMessages(event) {
const message = event.data;
switch (message.type) {
case 'update': // Update editor content
handleEditorUpdate(message);
break;
case 'updatePreview': // Update preview content
handlePreviewUpdate(message);
break;
}
}
/**
* Handle update messages for the editor content
* @param {object} message - The update message
*/
function handleEditorUpdate(message) {
const newText = message.text;
if (newText !== lastKnownText) {
console.log('Webview received editor update from extension.');
editor.value = newText;
lastKnownText = newText;
errorMessageDiv.textContent = '';
}
}
/**
* Handle update messages for the preview content
* @param {object} message - The update message
*/
function handlePreviewUpdate(message) {
console.log("[Webview] Received 'updatePreview' from extension.");
if (message.html !== undefined) {
previewContentDiv.innerHTML = message.html; // Set rendered HTML
console.log('[Webview] Preview HTML updated (length: ' + message.html.length + ')');
// Attempt to render Mermaid diagrams after updating HTML
renderMermaidDiagrams();
} else {
console.error("[Webview] Received 'updatePreview' message without html content.");
}
}
/**
* Render Mermaid diagrams in the preview content
*/
function renderMermaidDiagrams() {
try {
if (typeof mermaid !== 'undefined') {
const mermaidElements = previewContentDiv.querySelectorAll('.mermaid');
if (mermaidElements && mermaidElements.length > 0) {
console.log(
'[Webview] Found ' +
mermaidElements.length +
' mermaid elements. Rendering individually...'
);
// Iterate through NodeList and render each element
Array.from(mermaidElements).forEach((element, index) => {
try {
// Remove any previously rendered SVG to prevent duplicates
const existingSvg = element.querySelector('svg');
if (existingSvg) {
element.innerHTML = element.textContent || ''; // Restore original text
}
mermaid.run({ nodes: [element] }); // Render one element
console.log('[Webview] Mermaid rendering attempted on element ' + index);
} catch (renderErr) {
console.error(
'[Webview] Mermaid rendering failed for element ' + index + ':',
renderErr
);
// Add null check before setting innerHTML in catch block
if (element) {
element.innerHTML = '<pre>Mermaid Error:' + renderErr + '</pre>';
}
}
});
}
} else {
console.warn('[Webview] Mermaid library not found when trying to render.');
}
} catch (err) {
console.error('[Webview] Error rendering Mermaid:', err);
}
}
/**
* Attempt initial rendering of Mermaid diagrams
*/
function attemptInitialMermaidRender() {
try {
if (typeof mermaid !== 'undefined') {
const initialMermaidElements = previewContentDiv.querySelectorAll('.mermaid');
if (initialMermaidElements && initialMermaidElements.length > 0) {
console.log(
'[Webview] Found ' +
initialMermaidElements.length +
' mermaid elements for initial render. Rendering individually...'
);
// Iterate through NodeList and render each element
Array.from(initialMermaidElements).forEach((element, index) => {
try {
// Check if already rendered (simple check)
if (!element.querySelector('svg')) {
mermaid.run({ nodes: [element] }); // Render one element
console.log('[Webview] Initial Mermaid rendering attempted on element ' + index);
} else {
console.log(
'[Webview] Skipping initial render for already rendered element ' + index
);
}
} catch (renderErr) {
console.error(
'[Webview] Initial Mermaid rendering failed for element ' + index + ':',
renderErr
);
// Add null check before setting innerHTML in catch block
if (element) {
element.innerHTML = '<pre>Mermaid Error:' + renderErr + '</pre>';
}
}
});
} else {
console.log('[Webview] No mermaid elements found for initial render.');
}
} else {
console.warn('[Webview] Mermaid library not found for initial render.');
}
} catch (err) {
console.error('[Webview] Error during initial Mermaid rendering:', err);
}
}
/**
* Sets up event listeners for the mode toggle buttons.
*/
function setupModeToggle() {
const controls = document.getElementById('controls');
if (controls) {
controls.addEventListener('click', (event) => {
// Check if the clicked target is an HTMLButtonElement
if (event.target instanceof HTMLButtonElement) {
const button = event.target; // Cast to button element
const mode = button.getAttribute('data-mode');
if (mode) {
updateMode(mode);
}
}
});
} else {
console.error('[Webview] Controls element not found');
}
}
/**
* Updates the body class and button states based on the selected mode.
* @param {string} newMode - The mode to switch to ('editor-only', 'split', 'preview-only').
*/
function updateMode(newMode) {
// Remove existing mode classes from body
document.body.classList.remove('show-editor-only', 'show-split', 'show-preview-only');
// Add the new mode class
document.body.classList.add(`show-${newMode}`);
// Update button active states
const buttons = document.querySelectorAll('#controls button');
buttons.forEach(button => {
if (button.getAttribute('data-mode') === newMode) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
});
console.log(`[Webview] Switched to mode: ${newMode}`);
// Persist mode preference if needed (using vscode.setState)
// vscode.setState({ viewMode: newMode });
}
// Initialize after the DOM is fully loaded
if (document.readyState === 'loading') { // Loading hasn't finished yet
window.addEventListener('DOMContentLoaded', initialize);
} else { // DOMContentLoaded has already fired
initialize();
}
})();