fix: lots of missing pieces for python macro handling
This commit is contained in:
@@ -43,4 +43,5 @@ export {
|
||||
renderAllMacros,
|
||||
getEditorPreview,
|
||||
setPythonMacroResolver,
|
||||
refreshPythonMacroSlugs,
|
||||
} from './registry';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Macro Registry
|
||||
*
|
||||
*
|
||||
* Central registry for all macro definitions.
|
||||
* Macros self-register using registerMacro() function.
|
||||
*/
|
||||
@@ -21,10 +21,13 @@ const macroRegistry = new Map<string, MacroDefinition>();
|
||||
let pythonMacroResolverFn: PythonMacroResolver | null = null;
|
||||
let pythonMacroRendererFn: PythonMacroRendererFn | null = null;
|
||||
|
||||
// Python macro slugs for editor known/unknown detection
|
||||
const pythonMacroSlugs = new Set<string>();
|
||||
|
||||
/**
|
||||
* Register a macro definition.
|
||||
* Call this from each macro definition file.
|
||||
*
|
||||
*
|
||||
* @param macro - The macro definition to register
|
||||
* @throws Error if a macro with the same name is already registered
|
||||
*/
|
||||
@@ -48,9 +51,28 @@ export function setPythonMacroResolver(
|
||||
pythonMacroRendererFn = renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the set of known Python macro slugs from the backend.
|
||||
* Call on startup and when scripts change.
|
||||
*/
|
||||
export async function refreshPythonMacroSlugs(): Promise<void> {
|
||||
try {
|
||||
if (typeof window === 'undefined' || !window.electronAPI?.scripts?.getEnabledMacroSlugs) {
|
||||
return;
|
||||
}
|
||||
const slugs = await window.electronAPI.scripts.getEnabledMacroSlugs();
|
||||
pythonMacroSlugs.clear();
|
||||
for (const slug of slugs) {
|
||||
pythonMacroSlugs.add(slug.toLowerCase());
|
||||
}
|
||||
} catch {
|
||||
// Silently ignore — may be called before IPC bridge is ready
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a macro definition by name.
|
||||
*
|
||||
*
|
||||
* @param name - The macro name (case-insensitive)
|
||||
* @returns The macro definition or undefined if not found
|
||||
*/
|
||||
@@ -59,12 +81,13 @@ export function getMacro(name: string): MacroDefinition | undefined {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a macro is registered.
|
||||
*
|
||||
* Check if a macro is registered (JS registry or Python macro slug).
|
||||
*
|
||||
* @param name - The macro name (case-insensitive)
|
||||
*/
|
||||
export function hasMacro(name: string): boolean {
|
||||
return macroRegistry.has(name.toLowerCase());
|
||||
const lower = name.toLowerCase();
|
||||
return macroRegistry.has(lower) || pythonMacroSlugs.has(lower);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,6 +109,7 @@ export function getAllMacros(): MacroDefinition[] {
|
||||
*/
|
||||
export function clearMacros(): void {
|
||||
macroRegistry.clear();
|
||||
pythonMacroSlugs.clear();
|
||||
}
|
||||
|
||||
// Regex to match [[macroName param1="value1" param2='value2']]
|
||||
@@ -98,37 +122,37 @@ const PARAM_REGEX = /(\w+)=(?:["']([^"']*?)["']|([^\s\]]+))/g;
|
||||
|
||||
/**
|
||||
* Parse parameters from a macro parameter string.
|
||||
*
|
||||
*
|
||||
* @param paramString - The parameter string (e.g., 'link="file.jpg" caption="Hello"' or 'year=2016')
|
||||
* @returns Parsed key-value pairs
|
||||
*/
|
||||
export function parseParams(paramString: string | undefined): MacroParams {
|
||||
if (!paramString) return {};
|
||||
|
||||
|
||||
const params: MacroParams = {};
|
||||
let match;
|
||||
|
||||
|
||||
while ((match = PARAM_REGEX.exec(paramString)) !== null) {
|
||||
// match[1] = key, match[2] = quoted value, match[3] = unquoted value
|
||||
params[match[1]] = match[2] !== undefined ? match[2] : match[3];
|
||||
}
|
||||
|
||||
|
||||
// Reset regex lastIndex for next use
|
||||
PARAM_REGEX.lastIndex = 0;
|
||||
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse all macros from a markdown string.
|
||||
*
|
||||
*
|
||||
* @param markdown - The markdown content to parse
|
||||
* @returns Array of parsed macros with their positions
|
||||
*/
|
||||
export function parseMacros(markdown: string): ParsedMacro[] {
|
||||
const macros: ParsedMacro[] = [];
|
||||
let match;
|
||||
|
||||
|
||||
while ((match = MACRO_REGEX.exec(markdown)) !== null) {
|
||||
macros.push({
|
||||
name: match[1].toLowerCase(),
|
||||
@@ -138,17 +162,17 @@ export function parseMacros(markdown: string): ParsedMacro[] {
|
||||
end: match.index + match[0].length,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Reset regex lastIndex for next use
|
||||
MACRO_REGEX.lastIndex = 0;
|
||||
|
||||
|
||||
return macros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single macro to HTML.
|
||||
* First checks JS registry, then falls back to Python macro resolver.
|
||||
*
|
||||
*
|
||||
* @param macro - The parsed macro
|
||||
* @param context - Render context
|
||||
* @returns The rendered HTML or an error placeholder
|
||||
@@ -158,7 +182,7 @@ export async function renderMacro(
|
||||
context: MacroRenderContext
|
||||
): Promise<string> {
|
||||
const definition = getMacro(macro.name);
|
||||
|
||||
|
||||
if (definition) {
|
||||
// Validate if validator exists
|
||||
if (definition.validate) {
|
||||
@@ -167,7 +191,7 @@ export async function renderMacro(
|
||||
return `<span class="macro-error" title="${error}">${macro.rawText}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const result = definition.render(macro.params, context);
|
||||
return result instanceof Promise ? await result : result;
|
||||
@@ -195,7 +219,7 @@ export async function renderMacro(
|
||||
/**
|
||||
* Render all macros in a markdown string to HTML.
|
||||
* Returns the markdown with macros replaced by their rendered HTML.
|
||||
*
|
||||
*
|
||||
* @param markdown - The markdown content
|
||||
* @param context - Render context
|
||||
* @returns Markdown with macros replaced by rendered HTML
|
||||
@@ -205,41 +229,41 @@ export async function renderAllMacros(
|
||||
context: MacroRenderContext
|
||||
): Promise<string> {
|
||||
const macros = parseMacros(markdown);
|
||||
|
||||
|
||||
if (macros.length === 0) return markdown;
|
||||
|
||||
|
||||
// Render all macros in parallel
|
||||
const rendered = await Promise.all(
|
||||
macros.map(macro => renderMacro(macro, context))
|
||||
);
|
||||
|
||||
|
||||
// Replace macros from end to start to preserve positions
|
||||
let result = markdown;
|
||||
for (let i = macros.length - 1; i >= 0; i--) {
|
||||
const macro = macros[i];
|
||||
result = result.slice(0, macro.start) + rendered[i] + result.slice(macro.end);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor preview text for a macro.
|
||||
*
|
||||
*
|
||||
* @param name - The macro name
|
||||
* @param params - The macro parameters
|
||||
* @returns Preview text for the editor
|
||||
*/
|
||||
export function getEditorPreview(name: string, params: MacroParams): string {
|
||||
const definition = getMacro(name);
|
||||
|
||||
|
||||
if (!definition) {
|
||||
return `⚠ ${name}`;
|
||||
}
|
||||
|
||||
|
||||
if (definition.editorPreview) {
|
||||
return definition.editorPreview(params);
|
||||
}
|
||||
|
||||
|
||||
return `⬡ ${definition.name}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user