all should work now
This commit is contained in:
131
main.ts
131
main.ts
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user