215 lines
5.5 KiB
JavaScript
215 lines
5.5 KiB
JavaScript
/**
|
|
* Base View Interface for ComfyUI Viewer
|
|
* All view extensions should extend or follow this interface pattern.
|
|
*/
|
|
|
|
import { escapeHtml } from "../utils/helpers.js";
|
|
|
|
export { escapeHtml };
|
|
|
|
export class BaseView {
|
|
/**
|
|
* Unique identifier for this view type
|
|
* @type {string}
|
|
*/
|
|
static id = "base";
|
|
|
|
/**
|
|
* Display name for this view type
|
|
* @type {string}
|
|
*/
|
|
static displayName = "Base";
|
|
|
|
/**
|
|
* Priority for content type detection (higher = checked first)
|
|
* @type {number}
|
|
*/
|
|
static priority = 0;
|
|
|
|
/**
|
|
* Scripts loaded state
|
|
* @type {object}
|
|
*/
|
|
static scripts = {};
|
|
|
|
/**
|
|
* Check if this view can handle the given content
|
|
* @param {string} content - The content to check
|
|
* @returns {number} - Score indicating how well this view can handle the content (0 = cannot handle)
|
|
*/
|
|
static detect(content) {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Load any required scripts for this view
|
|
* @param {string} basePath - Base path for loading scripts
|
|
* @returns {Promise<boolean>} - True if scripts loaded successfully
|
|
*/
|
|
static async loadScripts(basePath) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Render the content to HTML
|
|
* @param {string} content - The content to render
|
|
* @param {object} theme - Theme tokens { bg, fg, border, accent }
|
|
* @returns {string} - Rendered HTML body content
|
|
*/
|
|
static render(content, theme) {
|
|
return content;
|
|
}
|
|
|
|
/**
|
|
* Get additional styles required for this view
|
|
* @param {object} theme - Theme tokens
|
|
* @returns {string} - CSS styles
|
|
*/
|
|
static getStyles(theme) {
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Get scripts to inject into the iframe
|
|
* @returns {string} - Script tags/content
|
|
*/
|
|
static getScripts() {
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Check if scripts are ready
|
|
* @returns {boolean}
|
|
*/
|
|
static isReady() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get message types this view handles from iframe postMessage
|
|
* @returns {string[]} - Array of message type strings
|
|
*/
|
|
static getMessageTypes() {
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Handle a message from the iframe
|
|
* @param {string} messageType - The message type
|
|
* @param {object} data - The message data
|
|
* @param {object} node - The LiteGraph node
|
|
* @param {object} app - The ComfyUI app instance
|
|
* @returns {boolean} - True if message was handled
|
|
*/
|
|
static handleMessage(messageType, data, node, app) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get view-specific state from node widgets
|
|
* @param {object} node - The LiteGraph node
|
|
* @returns {object|null} - State object or null
|
|
*/
|
|
static getStateFromWidget(node) {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Inject saved state into content before rendering
|
|
* @param {string} content - Original content
|
|
* @param {object} state - State from getStateFromWidget
|
|
* @returns {string} - Modified content with state injected
|
|
*/
|
|
static injectState(content, state) {
|
|
return content;
|
|
}
|
|
|
|
/**
|
|
* Content marker prefix for this view type (if any)
|
|
* @returns {string|null}
|
|
*/
|
|
static getContentMarker() {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Whether this view uses the base iframe styles (iframe-base.css)
|
|
* Views that provide their own complete styling should return false
|
|
* @returns {boolean}
|
|
*/
|
|
static usesBaseStyles() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to load a script file as text
|
|
* @param {string} url - URL to fetch
|
|
* @returns {Promise<string>}
|
|
*/
|
|
export async function loadScriptText(url) {
|
|
try {
|
|
const res = await fetch(url);
|
|
return res.ok ? await res.text() : "";
|
|
} catch (e) {
|
|
console.error(`[WAS Viewer] Failed to load script: ${url}`, e);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Custom syntax highlighter using regex rules
|
|
* Rules are applied in order; earlier rules take precedence
|
|
* @param {string} content - The content to highlight
|
|
* @param {Array<{name: string, pattern: RegExp, className: string}>} rules - Highlighting rules
|
|
* @returns {string} - HTML with syntax highlighting spans
|
|
*/
|
|
export function highlightSyntax(content, rules) {
|
|
const tokens = [];
|
|
const escaped = escapeHtml(content);
|
|
|
|
// Find all matches for all rules
|
|
for (const rule of rules) {
|
|
const regex = new RegExp(rule.pattern.source, rule.pattern.flags.includes('g') ? rule.pattern.flags : rule.pattern.flags + 'g');
|
|
let match;
|
|
while ((match = regex.exec(content)) !== null) {
|
|
tokens.push({
|
|
start: match.index,
|
|
end: match.index + match[0].length,
|
|
text: match[0],
|
|
className: rule.className,
|
|
priority: rules.indexOf(rule)
|
|
});
|
|
}
|
|
}
|
|
|
|
// Sort by start position, then by priority (lower = higher priority)
|
|
tokens.sort((a, b) => a.start - b.start || a.priority - b.priority);
|
|
|
|
// Remove overlapping tokens (keep higher priority)
|
|
const filtered = [];
|
|
let lastEnd = 0;
|
|
for (const token of tokens) {
|
|
if (token.start >= lastEnd) {
|
|
filtered.push(token);
|
|
lastEnd = token.end;
|
|
}
|
|
}
|
|
|
|
// Build highlighted output
|
|
let result = '';
|
|
let pos = 0;
|
|
for (const token of filtered) {
|
|
if (token.start > pos) {
|
|
result += escapeHtml(content.slice(pos, token.start));
|
|
}
|
|
result += `<span class="${token.className}">${escapeHtml(token.text)}</span>`;
|
|
pos = token.end;
|
|
}
|
|
if (pos < content.length) {
|
|
result += escapeHtml(content.slice(pos));
|
|
}
|
|
|
|
return result;
|
|
}
|