all should work now

This commit is contained in:
2025-09-26 21:59:20 -04:00
parent e7dd34d323
commit 8b9e092329
4 changed files with 367 additions and 168 deletions

131
main.ts
View File

@@ -1,30 +1,13 @@
import { Plugin } from 'obsidian';
import { fileURLToPath } from 'url';
const { shell } = require('electron');
// Simple YAML parser for color properties
function parseYAML(text: string) {
const result: Record<string, string> = {};
const lines = text.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (trimmed && trimmed.includes(':')) {
const [key, ...valueParts] = trimmed.split(':');
const value = valueParts.join(':').trim();
if (key && value) {
result[key.trim()] = value;
}
}
}
return result;
}
export default class MenuPlugin extends Plugin {
async onload() {
this.registerMarkdownCodeBlockProcessor('menu', (source, el, ctx) => {
const lines = source.trim().split('\n');
let layout = '';
let layoutOrClass = '';
let colors: Record<string, string> = {};
const links: string[] = [];
@@ -33,7 +16,7 @@ export default class MenuPlugin extends Plugin {
const trimmed = line.trim();
if (trimmed.startsWith('layout:') || trimmed.startsWith('class:')) {
const colonIndex = trimmed.indexOf(':');
layout = trimmed.substring(colonIndex + 1).trim();
layoutOrClass = trimmed.substring(colonIndex + 1).trim();
} else if (trimmed.includes(':') && !trimmed.startsWith('[') && !trimmed.startsWith('[[')) {
// Parse color properties
const [key, ...valueParts] = trimmed.split(':');
@@ -48,23 +31,102 @@ export default class MenuPlugin extends Plugin {
}
}
const finalLayout = layout || 'default';
const container = el.createEl('div', { cls: `menu-container ${finalLayout}` });
// Apply custom colors as CSS variables
if (Object.keys(colors).length > 0) {
for (const [key, value] of Object.entries(colors)) {
// Replace 'accent' with 'hover-text' for consistency
let cssKey = key.replace(/accent/g, 'hover-text');
container.style.setProperty(`--${cssKey}`, value);
// Determine layout and classes
const builtInLayouts = new Set(['default', 'minimal', 'slate', 'horizon', 'aether']);
const container = el.createEl('div', { cls: 'menu-container' });
let selectedLayout = '';
let extraClasses: string[] = [];
if (layoutOrClass) {
const tokens = layoutOrClass.split(/\s+/).filter(Boolean);
if (tokens.length) {
// Allow built-in layout to appear anywhere in the list (e.g., "class: my-class horizon wide")
const builtInIndex = tokens.findIndex(t => builtInLayouts.has(t));
if (builtInIndex !== -1) {
selectedLayout = tokens[builtInIndex];
extraClasses = tokens.filter((_, i) => i !== builtInIndex);
} else {
// Custom class mode: do not apply plugin CSS (no data-layout)
extraClasses = tokens;
}
}
// Example: ensure hover-text is set and add a comment for usage
if (colors['hover-text']) {
container.style.setProperty('--hover-text', colors['hover-text']);
// To use in CSS: a:hover { color: var(--hover-text); }
} else {
// No layout/class provided: use default built-in template
selectedLayout = 'default';
}
// Apply selected built-in template via data attribute (gates plugin CSS)
if (selectedLayout) {
container.setAttr('data-layout', selectedLayout);
}
// Apply any extra classes (e.g., "wide" or user-provided classes)
for (const cls of extraClasses) {
container.addClass(cls);
}
// Apply custom properties (whitelist only: bg, text, border, font and their -hover variants,
// plus internal-, external-, file- prefixed versions). No raw CSS props allowed.
if (Object.keys(colors).length > 0) {
const baseKeys = new Set([
'bg','text','border','font',
'hover-text','hover-bg','hover-border','hover-font',
]);
const normalizeKey = (raw: string) => {
let s = raw.trim().toLowerCase();
// Normalize synonyms/order for hover variants
s = s
// Prefer "hover-*" (matches CSS)
.replace(/\btext-hover\b/g, 'hover-text')
.replace(/\bbg-hover\b/g, 'hover-bg')
.replace(/\bborder-hover\b/g, 'hover-border')
// Old naming from earlier versions -> new "hover-*" order
.replace(/\binternal-text-hover\b/g, 'internal-hover-text')
.replace(/\binternal-bg-hover\b/g, 'internal-hover-bg')
.replace(/\binternal-border-hover\b/g, 'internal-hover-border')
.replace(/\bexternal-text-hover\b/g, 'external-hover-text')
.replace(/\bexternal-bg-hover\b/g, 'external-hover-bg')
.replace(/\bexternal-border-hover\b/g, 'external-hover-border')
.replace(/\bfile-text-hover\b/g, 'file-hover-text')
.replace(/\bfile-bg-hover\b/g, 'file-hover-bg')
.replace(/\bfile-border-hover\b/g, 'file-hover-border')
// Back-compat for "accent" -> "hover-text"
.replace(/\baccent\b/g, 'hover-text')
.replace(/\binternal-accent\b/g, 'internal-hover-text')
.replace(/\bexternal-accent\b/g, 'external-hover-text')
.replace(/\bfile-accent\b/g, 'file-hover-text')
.replace(/\bbackground\b/g, 'bg');
return s;
};
const isAllowed = (key: string) => {
if (baseKeys.has(key)) return true;
const m = key.match(/^(internal|external|file)-(.*)$/);
return !!(m && baseKeys.has(m[2]));
};
for (const [rawKey, value] of Object.entries(colors)) {
const key = normalizeKey(rawKey);
if (!isAllowed(key)) continue;
container.style.setProperty(`--${key}`, value);
}
}
// In custom class mode (no built-in layout), apply base inline styles to anchors so whitelist vars work without CSS.
const applyInlineBaseStyles = (a: HTMLElement, variant: 'internal' | 'external' | 'file' | 'generic') => {
const prefix = variant === 'generic' ? '' : `${variant}-`;
const get = (k: string) => (colors[`${prefix}${k}`] ?? colors[k]);
const bgVal = get('bg'); if (bgVal) a.style.background = bgVal as string;
const textVal = get('text'); if (textVal) a.style.color = textVal as string;
const borderVal = get('border'); if (borderVal) a.style.borderColor = borderVal as string;
const fontVal = get('font'); if (fontVal) a.style.fontFamily = fontVal as string;
// Expose hover values as CSS variables on the anchor for user CSS to consume if desired
const hoverKeys = ['hover-bg','hover-text','hover-border','hover-font'];
for (const hk of hoverKeys) {
const v = get(hk);
if (v) a.style.setProperty(`--${hk}`, v as string);
}
};
// Process each link
for (const link of links) {
if (link.startsWith('[[') && link.endsWith(']]')) {
@@ -80,6 +142,7 @@ export default class MenuPlugin extends Plugin {
attr: { 'data-href': href }
});
a.addClass('menu-internal-link');
if (!selectedLayout) applyInlineBaseStyles(a, 'internal');
a.style.cursor = 'pointer';
a.addEventListener('click', (e) => {
e.preventDefault();
@@ -103,8 +166,10 @@ export default class MenuPlugin extends Plugin {
// Add appropriate class based on link type
if (url.startsWith('file://')) {
a.addClass('menu-file-link');
if (!selectedLayout) applyInlineBaseStyles(a, 'file');
} else {
a.addClass('menu-external-link');
if (!selectedLayout) applyInlineBaseStyles(a, 'external');
}
a.addEventListener('click', (e) => {
e.preventDefault();