From 8b9e09232906de651bfa70e2e85e489e3cdfebf0 Mon Sep 17 00:00:00 2001 From: Olivier Date: Fri, 26 Sep 2025 21:59:20 -0400 Subject: [PATCH] all should work now --- README.md | 219 ++++++++++++++++++++++++++++++++++------------------- main.js | 94 ++++++++++++++++++++--- main.ts | 131 ++++++++++++++++++++++++-------- styles.css | 91 +++++++++++----------- 4 files changed, 367 insertions(+), 168 deletions(-) diff --git a/README.md b/README.md index fae8131..1e3d188 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,20 @@ -# Menu Plugin for Obsidian +# Obsidian Menus -Create custom navigation menus in Obsidian using simple code blocks. Supports internal links, external URLs, and local file links with built-in themes and full customization. +Create simple, stylable menus from code blocks. You write links inside a `menu` code block, and the plugin renders: +- a single div with class `menu-container` +- a set of anchor tags inside it + +You can: +- Pick a built-in template (default, minimal, slate, horizon, aether) +- Use your own CSS class (no plugin CSS applied) +- Override styles using a small, safe set of YAML-like variables +- Mix template + extra classes (e.g., `layout: horizon wide`) + +CSS stays clean and predictable. ## Basic Usage -Create a menu using a `menu` code block with one of the built-in layouts: +Create a menu using a `menu` code block with one of the built-in templates: ````markdown ```menu @@ -16,32 +26,21 @@ layout: default ``` ```` -## Built-in Layouts +- `[[Note]]` or `[[Note|Alias]]` creates internal links. +- `[Text](https://example.com)` creates web links. +- `[Text](file:///C:/path/to/file)` opens local files/folders. -### `default` -Standard buttons with borders and backgrounds -````markdown -```menu -layout: default -[[Dashboard]] -[GitHub](https://github.com) -[Files](file:///C:/Users/Documents) -``` -```` +## Built-in Templates -### `minimal` -Clean text links with subtle colors -````markdown -```menu -layout: minimal -[[Notes]] -[Web](https://example.com) -[Folder](file:///C:/Projects) -``` -```` +Available out of the box: +- default +- minimal +- slate +- horizon +- aether + +Example: -### `slate` -Solid background buttons ````markdown ```menu layout: slate @@ -50,37 +49,95 @@ layout: slate ``` ```` -### `horizon` -Modern outlined style +You can also add extra classes like `wide` next to the layout: + ````markdown ```menu -layout: horizon +layout: horizon wide [[Dashboard]] [Resources](https://example.com) ``` ```` -### `aether` -Grid layout with equal-width items +Note: Built-in template CSS only applies when a built-in layout is selected. Internally, the container gets a `data-layout="..."` attribute which gates the plugin CSS. If you don’t select a built-in layout, none of the plugin CSS will affect your menu. + +## Custom CSS Class Mode (No Plugin CSS) + +Use your own CSS by providing a custom class via `class:` or `layout:` with a non-built-in value. In this mode, the plugin does not apply any of its CSS. It only renders the HTML and exposes inline CSS variables from the YAML-like overrides. + ````markdown ```menu -layout: aether -[[Projects]] -[GitHub](https://github.com) -[Documents](file:///C:/Users/Documents) +class: my-menu wide +bg: #111 +text: #eee +hover-text: #0af +internal-hover-text: orange + +[[Home]] +[Web](https://example.com) +[Folder](file:///C:/Projects) ``` ```` -## Link Types +Then, in a CSS snippet: -- **Internal**: `[[Note Name]]` or `[[Note Name|Display Text]]` -- **External**: `[Display Text](https://example.com)` -- **Files**: `[Display Text](file:///C:/path/to/file)` +```css +/* Example custom styling */ +.menu-container.my-menu { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} -## Color Customization +.menu-container.my-menu a { + text-decoration: none; + padding: 0.5rem 0.8rem; + border-radius: 6px; + color: var(--text, var(--text-normal)); + background: var(--bg, transparent); + border: 1px solid var(--border, var(--background-modifier-border)); +} -Add color properties using YAML syntax: +.menu-container.my-menu a:hover { + color: var(--hover-text, var(--text-accent)); + background: var(--hover-bg, transparent); + border-color: var(--hover-border, var(--text-accent)); +} +``` +## YAML-like Overrides (Safe Whitelist) + +The code block supports a small set of variables. No raw CSS properties are accepted in the code block. If you need full CSS power, use a CSS snippet in your IDE. + +Global variables: +- bg +- text +- border +- font +- hover-text +- hover-bg +- hover-border +- hover-font + +Per link type variants (prefix with internal-, external-, file-): +- internal-bg, internal-text, internal-border, internal-font +- internal-hover-text, internal-hover-bg, internal-hover-border, internal-hover-font +- external-... (same set) +- file-... (same set) + +Naming aliases supported: +- text-hover -> hover-text +- bg-hover -> hover-bg +- border-hover -> hover-border +- font-hover -> hover-font +- accent -> hover-text (and internal-accent/external-accent/file-accent -> corresponding -hover-text) + +Behavior of keys: +- bg applies to the buttons (anchors), not the container +- hover-* applies to the hover state of the buttons +- type-specific keys (e.g., internal-text) override the global ones for that link type + +Examples (template mode): ````markdown ```menu layout: default @@ -88,49 +145,51 @@ bg: #1a1a1a text: #ffffff hover-text: #ff6b6b border: #333333 + [[Home]] [GitHub](https://github.com) ``` ```` -### Global Color Variables -- `bg`: Background color -- `text`: Text color -- `border`: Border color -- `hover-text`: Hover text color -- `hover-bg`: Hover background -- `hover-border`: Hover border color -- `font`: Font family - -### Link-Type Specific Colors -Customize each link type individually: - +Per-link-type overrides: ````markdown ```menu layout: minimal internal-text: #00ff00 external-text: #ff6600 file-text: #0066ff -internal-font: "Arial" +internal-font: "Fira Code" external-font: "Georgia" +file-font: "Arial" + [[Internal Link]] [External Link](https://example.com) [File Link](file:///C:/Documents) ``` ```` -**Available for each type** (`internal`, `external`, `file`): -- `{type}-text`: Text color -- `{type}-bg`: Background color -- `{type}-border`: Border color -- `{type}-font`: Font family -- `{type}-hover-text`: Hover text color -- `{type}-hover-bg`: Hover background -- `{type}-hover-border`: Hover border color +## Link Types -## Advanced Examples +- Internal: `[[Note Name]]` or `[[Note Name|Display Text]]` +- External: `[Display Text](https://example.com)` +- Files: `[Display Text](file:///C:/path/to/file)` -### Gradient Background +Behavior: +- Internal links open within Obsidian. +- External links open in your browser. +- File links open via the OS. + +## Notes + +- Use either `layout:` or `class:`. If the value contains a built-in template, the template is applied. Otherwise, it’s treated as a pure CSS class (no plugin CSS). +- If no `layout:` or `class:` is provided, `layout: default` is assumed. +- Colors support hex, rgb, hsl, CSS variables, and gradients. +- Font names with spaces need quotes: `font: "Work Sans"`. +- You can append additional classes after the layout or class: `layout: horizon wide my-extra`. + +## Examples + +Built-in template with overrides: ````markdown ```menu layout: horizon @@ -142,23 +201,25 @@ hover-text: #ffd700 ``` ```` -### Different Fonts Per Link Type +Custom class with overrides: ````markdown ```menu -layout: default -font: "Inter" -internal-font: "Fira Code" -external-font: "Georgia" -file-font: "Arial" -[[Code Notes]] -[Articles](https://medium.com) -[Local Files](file:///C:/Documents) +class: my-toolbar +text: var(--text-normal) +hover-text: var(--text-accent) +[[Inbox]] +[Wiki](https://example.com) ``` ```` -## Notes - -- Use either `layout:` or `class:` (both work the same) -- File paths use `file://` protocol -- Colors support hex, rgb, hsl, CSS variables, and gradients -- Font names with spaces need quotes: `font: "Work Sans"` \ No newline at end of file +Minimal template focusing on text colors: +````markdown +```menu +layout: minimal +internal-text: var(--text-accent) +external-text: var(--text-faint) +file-text: var(--text-muted) +[[Notes]] +[Web](https://example.com) +[Folder](file:///C:/Projects) +``` diff --git a/main.js b/main.js index f693d30..c42db84 100644 --- a/main.js +++ b/main.js @@ -33,14 +33,14 @@ var MenuPlugin = class extends import_obsidian.Plugin { async onload() { this.registerMarkdownCodeBlockProcessor("menu", (source, el, ctx) => { const lines = source.trim().split("\n"); - let layout = ""; + let layoutOrClass = ""; let colors = {}; const links = []; for (const line of lines) { 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("[[")) { const [key, ...valueParts] = trimmed.split(":"); const value = valueParts.join(":").trim(); @@ -53,17 +53,84 @@ var MenuPlugin = class extends import_obsidian.Plugin { links.push(trimmed); } } - const finalLayout = layout || "default"; - const container = el.createEl("div", { cls: `menu-container ${finalLayout}` }); - if (Object.keys(colors).length > 0) { - for (const [key, value] of Object.entries(colors)) { - let cssKey = key.replace(/accent/g, "hover-text"); - container.style.setProperty(`--${cssKey}`, value); + const builtInLayouts = /* @__PURE__ */ new Set(["default", "minimal", "slate", "horizon", "aether"]); + const container = el.createEl("div", { cls: "menu-container" }); + let selectedLayout = ""; + let extraClasses = []; + if (layoutOrClass) { + const tokens = layoutOrClass.split(/\s+/).filter(Boolean); + if (tokens.length) { + const builtInIndex = tokens.findIndex((t) => builtInLayouts.has(t)); + if (builtInIndex !== -1) { + selectedLayout = tokens[builtInIndex]; + extraClasses = tokens.filter((_, i) => i !== builtInIndex); + } else { + extraClasses = tokens; + } } - if (colors["hover-text"]) { - container.style.setProperty("--hover-text", colors["hover-text"]); + } else { + selectedLayout = "default"; + } + if (selectedLayout) { + container.setAttr("data-layout", selectedLayout); + } + for (const cls of extraClasses) { + container.addClass(cls); + } + if (Object.keys(colors).length > 0) { + const baseKeys = /* @__PURE__ */ new Set([ + "bg", + "text", + "border", + "font", + "hover-text", + "hover-bg", + "hover-border", + "hover-font" + ]); + const normalizeKey = (raw) => { + let s = raw.trim().toLowerCase(); + s = s.replace(/\btext-hover\b/g, "hover-text").replace(/\bbg-hover\b/g, "hover-bg").replace(/\bborder-hover\b/g, "hover-border").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").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) => { + 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); } } + const applyInlineBaseStyles = (a, variant) => { + const prefix = variant === "generic" ? "" : `${variant}-`; + const get = (k) => { + var _a; + return (_a = colors[`${prefix}${k}`]) != null ? _a : colors[k]; + }; + const bgVal = get("bg"); + if (bgVal) + a.style.background = bgVal; + const textVal = get("text"); + if (textVal) + a.style.color = textVal; + const borderVal = get("border"); + if (borderVal) + a.style.borderColor = borderVal; + const fontVal = get("font"); + if (fontVal) + a.style.fontFamily = fontVal; + 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); + } + }; for (const link of links) { if (link.startsWith("[[") && link.endsWith("]]")) { const linkContent = link.slice(2, -2); @@ -77,6 +144,8 @@ var MenuPlugin = class extends import_obsidian.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(); @@ -97,8 +166,12 @@ var MenuPlugin = class extends import_obsidian.Plugin { a.style.cursor = "pointer"; 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(); @@ -127,4 +200,3 @@ var MenuPlugin = class extends import_obsidian.Plugin { }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = {}); -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsibWFpbi50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiaW1wb3J0IHsgUGx1Z2luIH0gZnJvbSAnb2JzaWRpYW4nO1xyXG5pbXBvcnQgeyBmaWxlVVJMVG9QYXRoIH0gZnJvbSAndXJsJztcclxuXHJcbmNvbnN0IHsgc2hlbGwgfSA9IHJlcXVpcmUoJ2VsZWN0cm9uJyk7XHJcblxyXG4vLyBTaW1wbGUgWUFNTCBwYXJzZXIgZm9yIGNvbG9yIHByb3BlcnRpZXNcclxuZnVuY3Rpb24gcGFyc2VZQU1MKHRleHQ6IHN0cmluZykge1xyXG5cdGNvbnN0IHJlc3VsdDogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHt9O1xyXG5cdGNvbnN0IGxpbmVzID0gdGV4dC5zcGxpdCgnXFxuJyk7XHJcblx0Zm9yIChjb25zdCBsaW5lIG9mIGxpbmVzKSB7XHJcblx0XHRjb25zdCB0cmltbWVkID0gbGluZS50cmltKCk7XHJcblx0XHRpZiAodHJpbW1lZCAmJiB0cmltbWVkLmluY2x1ZGVzKCc6JykpIHtcclxuXHRcdFx0Y29uc3QgW2tleSwgLi4udmFsdWVQYXJ0c10gPSB0cmltbWVkLnNwbGl0KCc6Jyk7XHJcblx0XHRcdGNvbnN0IHZhbHVlID0gdmFsdWVQYXJ0cy5qb2luKCc6JykudHJpbSgpO1xyXG5cdFx0XHRpZiAoa2V5ICYmIHZhbHVlKSB7XHJcblx0XHRcdFx0cmVzdWx0W2tleS50cmltKCldID0gdmFsdWU7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHR9XHJcblx0cmV0dXJuIHJlc3VsdDtcclxufVxyXG5cclxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgTWVudVBsdWdpbiBleHRlbmRzIFBsdWdpbiB7XHJcblx0YXN5bmMgb25sb2FkKCkge1xyXG5cdFx0dGhpcy5yZWdpc3Rlck1hcmtkb3duQ29kZUJsb2NrUHJvY2Vzc29yKCdtZW51JywgKHNvdXJjZSwgZWwsIGN0eCkgPT4ge1xyXG5cdFx0XHRjb25zdCBsaW5lcyA9IHNvdXJjZS50cmltKCkuc3BsaXQoJ1xcbicpO1xyXG5cdFx0XHRsZXQgbGF5b3V0ID0gJyc7XHJcblx0XHRcdGxldCBjb2xvcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7fTtcclxuXHRcdFx0Y29uc3QgbGlua3M6IHN0cmluZ1tdID0gW107XHJcblx0XHRcdFxyXG5cdFx0XHQvLyBQYXJzZSBZQU1MLWxpa2UgcHJvcGVydGllcyBhbmQgbGlua3NcclxuXHRcdFx0Zm9yIChjb25zdCBsaW5lIG9mIGxpbmVzKSB7XHJcblx0XHRcdFx0Y29uc3QgdHJpbW1lZCA9IGxpbmUudHJpbSgpO1xyXG5cdFx0XHRcdGlmICh0cmltbWVkLnN0YXJ0c1dpdGgoJ2xheW91dDonKSB8fCB0cmltbWVkLnN0YXJ0c1dpdGgoJ2NsYXNzOicpKSB7XHJcblx0XHRcdFx0XHRjb25zdCBjb2xvbkluZGV4ID0gdHJpbW1lZC5pbmRleE9mKCc6Jyk7XHJcblx0XHRcdFx0XHRsYXlvdXQgPSB0cmltbWVkLnN1YnN0cmluZyhjb2xvbkluZGV4ICsgMSkudHJpbSgpO1xyXG5cdFx0XHRcdH0gZWxzZSBpZiAodHJpbW1lZC5pbmNsdWRlcygnOicpICYmICF0cmltbWVkLnN0YXJ0c1dpdGgoJ1snKSAmJiAhdHJpbW1lZC5zdGFydHNXaXRoKCdbWycpKSB7XHJcblx0XHRcdFx0XHQvLyBQYXJzZSBjb2xvciBwcm9wZXJ0aWVzXHJcblx0XHRcdFx0XHRjb25zdCBba2V5LCAuLi52YWx1ZVBhcnRzXSA9IHRyaW1tZWQuc3BsaXQoJzonKTtcclxuXHRcdFx0XHRcdGNvbnN0IHZhbHVlID0gdmFsdWVQYXJ0cy5qb2luKCc6JykudHJpbSgpO1xyXG5cdFx0XHRcdFx0aWYgKGtleSAmJiB2YWx1ZSAmJiAha2V5LmluY2x1ZGVzKCcvLycpICYmICFrZXkuaW5jbHVkZXMoJ2h0dHAnKSkge1xyXG5cdFx0XHRcdFx0XHRjb2xvcnNba2V5LnRyaW0oKV0gPSB2YWx1ZTtcclxuXHRcdFx0XHRcdH1cclxuXHRcdFx0XHR9IGVsc2UgaWYgKHRyaW1tZWQgJiYgIXRyaW1tZWQuaW5jbHVkZXMoJzonKSkge1xyXG5cdFx0XHRcdFx0bGlua3MucHVzaCh0cmltbWVkKTtcclxuXHRcdFx0XHR9IGVsc2UgaWYgKHRyaW1tZWQuc3RhcnRzV2l0aCgnWycpKSB7XHJcblx0XHRcdFx0XHRsaW5rcy5wdXNoKHRyaW1tZWQpO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cdFx0XHRcclxuXHRcdFx0Y29uc3QgZmluYWxMYXlvdXQgPSBsYXlvdXQgfHwgJ2RlZmF1bHQnO1xyXG5cdFx0XHRjb25zdCBjb250YWluZXIgPSBlbC5jcmVhdGVFbCgnZGl2JywgeyBjbHM6IGBtZW51LWNvbnRhaW5lciAke2ZpbmFsTGF5b3V0fWAgfSk7XHJcblx0XHRcdFxyXG5cdFx0XHQvLyBBcHBseSBjdXN0b20gY29sb3JzIGFzIENTUyB2YXJpYWJsZXNcclxuXHRcdFx0aWYgKE9iamVjdC5rZXlzKGNvbG9ycykubGVuZ3RoID4gMCkge1xyXG5cdFx0XHRcdGZvciAoY29uc3QgW2tleSwgdmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKGNvbG9ycykpIHtcclxuXHRcdFx0XHRcdC8vIFJlcGxhY2UgJ2FjY2VudCcgd2l0aCAnaG92ZXItdGV4dCcgZm9yIGNvbnNpc3RlbmN5XHJcblx0XHRcdFx0XHRsZXQgY3NzS2V5ID0ga2V5LnJlcGxhY2UoL2FjY2VudC9nLCAnaG92ZXItdGV4dCcpO1xyXG5cdFx0XHRcdFx0Y29udGFpbmVyLnN0eWxlLnNldFByb3BlcnR5KGAtLSR7Y3NzS2V5fWAsIHZhbHVlKTtcclxuXHRcdFx0XHR9XHJcblx0XHRcdFx0Ly8gRXhhbXBsZTogZW5zdXJlIGhvdmVyLXRleHQgaXMgc2V0IGFuZCBhZGQgYSBjb21tZW50IGZvciB1c2FnZVxyXG5cdFx0XHRcdGlmIChjb2xvcnNbJ2hvdmVyLXRleHQnXSkge1xyXG5cdFx0XHRcdFx0Y29udGFpbmVyLnN0eWxlLnNldFByb3BlcnR5KCctLWhvdmVyLXRleHQnLCBjb2xvcnNbJ2hvdmVyLXRleHQnXSk7XHJcblx0XHRcdFx0XHQvLyBUbyB1c2UgaW4gQ1NTOiBhOmhvdmVyIHsgY29sb3I6IHZhcigtLWhvdmVyLXRleHQpOyB9XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcbiBcclxuXHRcdFx0Ly8gUHJvY2VzcyBlYWNoIGxpbmtcclxuXHRcdFx0Zm9yIChjb25zdCBsaW5rIG9mIGxpbmtzKSB7XHJcblx0XHRcdFx0aWYgKGxpbmsuc3RhcnRzV2l0aCgnW1snKSAmJiBsaW5rLmVuZHNXaXRoKCddXScpKSB7XHJcblx0XHRcdFx0XHQvLyBJbnRlcm5hbCBsaW5rXHJcblx0XHRcdFx0XHRjb25zdCBsaW5rQ29udGVudCA9IGxpbmsuc2xpY2UoMiwgLTIpO1xyXG5cdFx0XHRcdFx0bGV0IGhyZWYgPSBsaW5rQ29udGVudDtcclxuXHRcdFx0XHRcdGxldCB0ZXh0ID0gbGlua0NvbnRlbnQ7XHJcblx0XHRcdFx0XHRpZiAobGlua0NvbnRlbnQuaW5jbHVkZXMoJ3wnKSkge1xyXG5cdFx0XHRcdFx0XHRbaHJlZiwgdGV4dF0gPSBsaW5rQ29udGVudC5zcGxpdCgnfCcpO1xyXG5cdFx0XHRcdFx0fVxyXG5cdFx0XHRcdFx0Y29uc3QgYSA9IGNvbnRhaW5lci5jcmVhdGVFbCgnYScsIHtcclxuXHRcdFx0XHRcdFx0dGV4dDogdGV4dCxcclxuXHRcdFx0XHRcdFx0YXR0cjogeyAnZGF0YS1ocmVmJzogaHJlZiB9XHJcblx0XHRcdFx0XHR9KTtcclxuXHRcdFx0XHRcdGEuYWRkQ2xhc3MoJ21lbnUtaW50ZXJuYWwtbGluaycpO1xyXG5cdFx0XHRcdFx0YS5zdHlsZS5jdXJzb3IgPSAncG9pbnRlcic7XHJcblx0XHRcdFx0XHRhLmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgKGUpID0+IHtcclxuXHRcdFx0XHRcdFx0ZS5wcmV2ZW50RGVmYXVsdCgpO1xyXG5cdFx0XHRcdFx0XHRjb25zdCB2YXVsdE5hbWUgPSB0aGlzLmFwcC52YXVsdC5nZXROYW1lKCk7XHJcblx0XHRcdFx0XHRcdGNvbnN0IGVuY29kZWRGaWxlID0gZW5jb2RlVVJJQ29tcG9uZW50KGhyZWYpO1xyXG5cdFx0XHRcdFx0XHRjb25zdCB1cmkgPSBgb2JzaWRpYW46Ly9vcGVuP3ZhdWx0PSR7ZW5jb2RlVVJJQ29tcG9uZW50KHZhdWx0TmFtZSl9JmZpbGU9JHtlbmNvZGVkRmlsZX1gO1xyXG5cdFx0XHRcdFx0XHR3aW5kb3cub3Blbih1cmkpO1xyXG5cdFx0XHRcdFx0fSk7XHJcblx0XHRcdFx0fSBlbHNlIGlmIChsaW5rLm1hdGNoKC9eXFxbLipcXF1cXCguKlxcKSQvKSkge1xyXG5cdFx0XHRcdFx0Ly8gRXh0ZXJuYWwgbGlua1xyXG5cdFx0XHRcdFx0Y29uc3QgbWF0Y2ggPSBsaW5rLm1hdGNoKC9eXFxbKC4qKVxcXVxcKCguKilcXCkkLyk7XHJcblx0XHRcdFx0XHRpZiAobWF0Y2gpIHtcclxuXHRcdFx0XHRcdFx0Y29uc3QgdGV4dCA9IG1hdGNoWzFdO1xyXG5cdFx0XHRcdFx0XHRjb25zdCB1cmwgPSBtYXRjaFsyXTtcclxuXHRcdFx0XHRcdFx0Y29uc3QgYSA9IGNvbnRhaW5lci5jcmVhdGVFbCgnYScsIHtcclxuXHRcdFx0XHRcdFx0XHR0ZXh0OiB0ZXh0LFxyXG5cdFx0XHRcdFx0XHRcdGF0dHI6IHVybC5zdGFydHNXaXRoKCdmaWxlOi8vJykgPyB7fSA6IHsgaHJlZjogdXJsLCB0YXJnZXQ6ICdfYmxhbmsnLCByZWw6ICdub29wZW5lciBub3JlZmVycmVyJyB9XHJcblx0XHRcdFx0XHRcdH0pO1xyXG5cdFx0XHRcdFx0XHRhLnN0eWxlLmN1cnNvciA9ICdwb2ludGVyJztcclxuXHRcdFx0XHRcdFx0XHJcblx0XHRcdFx0XHRcdC8vIEFkZCBhcHByb3ByaWF0ZSBjbGFzcyBiYXNlZCBvbiBsaW5rIHR5cGVcclxuXHRcdFx0XHRcdFx0aWYgKHVybC5zdGFydHNXaXRoKCdmaWxlOi8vJykpIHtcclxuXHRcdFx0XHRcdFx0XHRhLmFkZENsYXNzKCdtZW51LWZpbGUtbGluaycpO1xyXG5cdFx0XHRcdFx0XHR9IGVsc2Uge1xyXG5cdFx0XHRcdFx0XHRcdGEuYWRkQ2xhc3MoJ21lbnUtZXh0ZXJuYWwtbGluaycpO1xyXG5cdFx0XHRcdFx0XHR9XHJcblx0XHRcdFx0XHRcdGEuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoZSkgPT4ge1xyXG5cdFx0XHRcdFx0XHRcdGUucHJldmVudERlZmF1bHQoKTtcclxuXHRcdFx0XHRcdFx0XHRpZiAodXJsLnN0YXJ0c1dpdGgoJ2ZpbGU6Ly8nKSkge1xyXG5cdFx0XHRcdFx0XHRcdFx0dHJ5IHtcclxuXHRcdFx0XHRcdFx0XHRcdFx0Ly8gQ29udmVydCBmaWxlIFVSTCB0byBwYXRoIGFuZCBoYW5kbGUgV2luZG93cyBwYXRoc1xyXG5cdFx0XHRcdFx0XHRcdFx0XHRsZXQgZmlsZVBhdGggPSBkZWNvZGVVUklDb21wb25lbnQodXJsLnN1YnN0cmluZyg3KSk7IC8vIFJlbW92ZSAnZmlsZTovLydcclxuXHRcdFx0XHRcdFx0XHRcdFx0Ly8gSGFuZGxlIFdpbmRvd3MgcGF0aHMgdGhhdCBzdGFydCB3aXRoIC9DOlxyXG5cdFx0XHRcdFx0XHRcdFx0XHRpZiAoZmlsZVBhdGguc3RhcnRzV2l0aCgnLycpICYmIGZpbGVQYXRoLmNoYXJBdCgyKSA9PT0gJzonKSB7XHJcblx0XHRcdFx0XHRcdFx0XHRcdFx0ZmlsZVBhdGggPSBmaWxlUGF0aC5zdWJzdHJpbmcoMSk7XHJcblx0XHRcdFx0XHRcdFx0XHRcdH1cclxuXHRcdFx0XHRcdFx0XHRcdFx0Y29uc29sZS5sb2coJ09wZW5pbmcgZmlsZSBwYXRoOicsIGZpbGVQYXRoKTtcclxuXHRcdFx0XHRcdFx0XHRcdFx0c2hlbGwub3BlblBhdGgoZmlsZVBhdGgpO1xyXG5cdFx0XHRcdFx0XHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcclxuXHRcdFx0XHRcdFx0XHRcdFx0Y29uc29sZS5lcnJvcignRmFpbGVkIHRvIG9wZW4gZmlsZTonLCBlcnJvcik7XHJcblx0XHRcdFx0XHRcdFx0XHR9XHJcblx0XHRcdFx0XHRcdFx0fSBlbHNlIHtcclxuXHRcdFx0XHRcdFx0XHRcdHdpbmRvdy5vcGVuKHVybCwgJ19ibGFuaycsICdub29wZW5lcixub3JlZmVycmVyJyk7XHJcblx0XHRcdFx0XHRcdFx0fVxyXG5cdFx0XHRcdFx0XHR9KTtcclxuXHRcdFx0XHRcdH1cclxuXHRcdFx0XHR9XHJcblx0XHRcdH1cclxuXHRcdH0pO1xyXG5cdH1cclxuXHJcblx0b251bmxvYWQoKSB7XHJcblx0XHQvLyBDbGVhbnVwIGlmIG5lZWRlZFxyXG5cdH1cclxufVxyXG4iXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUEsc0JBQXVCO0FBR3ZCLElBQU0sRUFBRSxNQUFNLElBQUksUUFBUSxVQUFVO0FBbUJwQyxJQUFxQixhQUFyQixjQUF3Qyx1QkFBTztBQUFBLEVBQzlDLE1BQU0sU0FBUztBQUNkLFNBQUssbUNBQW1DLFFBQVEsQ0FBQyxRQUFRLElBQUksUUFBUTtBQUNwRSxZQUFNLFFBQVEsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJO0FBQ3RDLFVBQUksU0FBUztBQUNiLFVBQUksU0FBaUMsQ0FBQztBQUN0QyxZQUFNLFFBQWtCLENBQUM7QUFHekIsaUJBQVcsUUFBUSxPQUFPO0FBQ3pCLGNBQU0sVUFBVSxLQUFLLEtBQUs7QUFDMUIsWUFBSSxRQUFRLFdBQVcsU0FBUyxLQUFLLFFBQVEsV0FBVyxRQUFRLEdBQUc7QUFDbEUsZ0JBQU0sYUFBYSxRQUFRLFFBQVEsR0FBRztBQUN0QyxtQkFBUyxRQUFRLFVBQVUsYUFBYSxDQUFDLEVBQUUsS0FBSztBQUFBLFFBQ2pELFdBQVcsUUFBUSxTQUFTLEdBQUcsS0FBSyxDQUFDLFFBQVEsV0FBVyxHQUFHLEtBQUssQ0FBQyxRQUFRLFdBQVcsSUFBSSxHQUFHO0FBRTFGLGdCQUFNLENBQUMsS0FBSyxHQUFHLFVBQVUsSUFBSSxRQUFRLE1BQU0sR0FBRztBQUM5QyxnQkFBTSxRQUFRLFdBQVcsS0FBSyxHQUFHLEVBQUUsS0FBSztBQUN4QyxjQUFJLE9BQU8sU0FBUyxDQUFDLElBQUksU0FBUyxJQUFJLEtBQUssQ0FBQyxJQUFJLFNBQVMsTUFBTSxHQUFHO0FBQ2pFLG1CQUFPLElBQUksS0FBSyxDQUFDLElBQUk7QUFBQSxVQUN0QjtBQUFBLFFBQ0QsV0FBVyxXQUFXLENBQUMsUUFBUSxTQUFTLEdBQUcsR0FBRztBQUM3QyxnQkFBTSxLQUFLLE9BQU87QUFBQSxRQUNuQixXQUFXLFFBQVEsV0FBVyxHQUFHLEdBQUc7QUFDbkMsZ0JBQU0sS0FBSyxPQUFPO0FBQUEsUUFDbkI7QUFBQSxNQUNEO0FBRUEsWUFBTSxjQUFjLFVBQVU7QUFDOUIsWUFBTSxZQUFZLEdBQUcsU0FBUyxPQUFPLEVBQUUsS0FBSyxrQkFBa0IsY0FBYyxDQUFDO0FBRzdFLFVBQUksT0FBTyxLQUFLLE1BQU0sRUFBRSxTQUFTLEdBQUc7QUFDbkMsbUJBQVcsQ0FBQyxLQUFLLEtBQUssS0FBSyxPQUFPLFFBQVEsTUFBTSxHQUFHO0FBRWxELGNBQUksU0FBUyxJQUFJLFFBQVEsV0FBVyxZQUFZO0FBQ2hELG9CQUFVLE1BQU0sWUFBWSxLQUFLLFVBQVUsS0FBSztBQUFBLFFBQ2pEO0FBRUEsWUFBSSxPQUFPLFlBQVksR0FBRztBQUN6QixvQkFBVSxNQUFNLFlBQVksZ0JBQWdCLE9BQU8sWUFBWSxDQUFDO0FBQUEsUUFFakU7QUFBQSxNQUNEO0FBR0EsaUJBQVcsUUFBUSxPQUFPO0FBQ3pCLFlBQUksS0FBSyxXQUFXLElBQUksS0FBSyxLQUFLLFNBQVMsSUFBSSxHQUFHO0FBRWpELGdCQUFNLGNBQWMsS0FBSyxNQUFNLEdBQUcsRUFBRTtBQUNwQyxjQUFJLE9BQU87QUFDWCxjQUFJLE9BQU87QUFDWCxjQUFJLFlBQVksU0FBUyxHQUFHLEdBQUc7QUFDOUIsYUFBQyxNQUFNLElBQUksSUFBSSxZQUFZLE1BQU0sR0FBRztBQUFBLFVBQ3JDO0FBQ0EsZ0JBQU0sSUFBSSxVQUFVLFNBQVMsS0FBSztBQUFBLFlBQ2pDO0FBQUEsWUFDQSxNQUFNLEVBQUUsYUFBYSxLQUFLO0FBQUEsVUFDM0IsQ0FBQztBQUNELFlBQUUsU0FBUyxvQkFBb0I7QUFDL0IsWUFBRSxNQUFNLFNBQVM7QUFDakIsWUFBRSxpQkFBaUIsU0FBUyxDQUFDLE1BQU07QUFDbEMsY0FBRSxlQUFlO0FBQ2pCLGtCQUFNLFlBQVksS0FBSyxJQUFJLE1BQU0sUUFBUTtBQUN6QyxrQkFBTSxjQUFjLG1CQUFtQixJQUFJO0FBQzNDLGtCQUFNLE1BQU0seUJBQXlCLG1CQUFtQixTQUFTLFVBQVU7QUFDM0UsbUJBQU8sS0FBSyxHQUFHO0FBQUEsVUFDaEIsQ0FBQztBQUFBLFFBQ0YsV0FBVyxLQUFLLE1BQU0sZ0JBQWdCLEdBQUc7QUFFeEMsZ0JBQU0sUUFBUSxLQUFLLE1BQU0sb0JBQW9CO0FBQzdDLGNBQUksT0FBTztBQUNWLGtCQUFNLE9BQU8sTUFBTSxDQUFDO0FBQ3BCLGtCQUFNLE1BQU0sTUFBTSxDQUFDO0FBQ25CLGtCQUFNLElBQUksVUFBVSxTQUFTLEtBQUs7QUFBQSxjQUNqQztBQUFBLGNBQ0EsTUFBTSxJQUFJLFdBQVcsU0FBUyxJQUFJLENBQUMsSUFBSSxFQUFFLE1BQU0sS0FBSyxRQUFRLFVBQVUsS0FBSyxzQkFBc0I7QUFBQSxZQUNsRyxDQUFDO0FBQ0QsY0FBRSxNQUFNLFNBQVM7QUFHakIsZ0JBQUksSUFBSSxXQUFXLFNBQVMsR0FBRztBQUM5QixnQkFBRSxTQUFTLGdCQUFnQjtBQUFBLFlBQzVCLE9BQU87QUFDTixnQkFBRSxTQUFTLG9CQUFvQjtBQUFBLFlBQ2hDO0FBQ0EsY0FBRSxpQkFBaUIsU0FBUyxDQUFDLE1BQU07QUFDbEMsZ0JBQUUsZUFBZTtBQUNqQixrQkFBSSxJQUFJLFdBQVcsU0FBUyxHQUFHO0FBQzlCLG9CQUFJO0FBRUgsc0JBQUksV0FBVyxtQkFBbUIsSUFBSSxVQUFVLENBQUMsQ0FBQztBQUVsRCxzQkFBSSxTQUFTLFdBQVcsR0FBRyxLQUFLLFNBQVMsT0FBTyxDQUFDLE1BQU0sS0FBSztBQUMzRCwrQkFBVyxTQUFTLFVBQVUsQ0FBQztBQUFBLGtCQUNoQztBQUNBLDBCQUFRLElBQUksc0JBQXNCLFFBQVE7QUFDMUMsd0JBQU0sU0FBUyxRQUFRO0FBQUEsZ0JBQ3hCLFNBQVMsT0FBUDtBQUNELDBCQUFRLE1BQU0sd0JBQXdCLEtBQUs7QUFBQSxnQkFDNUM7QUFBQSxjQUNELE9BQU87QUFDTix1QkFBTyxLQUFLLEtBQUssVUFBVSxxQkFBcUI7QUFBQSxjQUNqRDtBQUFBLFlBQ0QsQ0FBQztBQUFBLFVBQ0Y7QUFBQSxRQUNEO0FBQUEsTUFDRDtBQUFBLElBQ0QsQ0FBQztBQUFBLEVBQ0Y7QUFBQSxFQUVBLFdBQVc7QUFBQSxFQUVYO0FBQ0Q7IiwKICAibmFtZXMiOiBbXQp9Cg== diff --git a/main.ts b/main.ts index 1bf7e6c..a3f04f6 100644 --- a/main.ts +++ b/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 = {}; - 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 = {}; 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(); diff --git a/styles.css b/styles.css index 400bc5c..32fa7f0 100644 --- a/styles.css +++ b/styles.css @@ -1,6 +1,6 @@ /* Special styles to hide the border and icon on hover */ -.markdown-source-view.mod-cm6 .cm-embed-block:not(.cm-table-widget, .cm-lang-base):has(.menu-container):hover { +.markdown-source-view.mod-cm6 .cm-embed-block:not(.cm-table-widget, .cm-lang-base):has(.menu-container[data-layout]):hover { border-width: 0; outline: 0; box-shadow: var(--background-primary) 0 0 0 1px inset !important; @@ -22,8 +22,8 @@ } } -.menu-container, -.menu-container a { +.menu-container[data-layout], +.menu-container[data-layout] a { transition: 125ms; } @@ -35,12 +35,11 @@ /* Default style variant */ -.menu-container.default { +.menu-container[data-layout='default'] { display: flex; - gap: 1em; + gap: 0.7em; flex-wrap: wrap; border-radius: 6px; - background-color: var(--bg, var(--background-primary)); font-family: var(--font, inherit); a { @@ -51,49 +50,51 @@ font-weight: 500; border: 1px solid var(--border, var(--background-modifier-border)); color: var(--text, var(--text-normal)); + background: var(--bg, var(--background-primary)); font-family: var(--font, inherit); &:hover { color: var(--hover-text, var(--accent, var(--text-accent))); border-color: var(--hover-border, var(--accent, var(--text-accent))); + background: var(--hover-bg, transparent); } &.menu-internal-link { color: var(--internal-text, var(--text, var(--text-normal))); border-color: var(--internal-border, var(--border, var(--background-modifier-border))); - background-color: var(--internal-bg, transparent); + background: var(--internal-bg, var(--bg, var(--background-primary))); font-family: var(--internal-font, var(--font, inherit)); &:hover { color: var(--internal-hover-text, var(--internal-accent, var(--hover-text, var(--accent, var(--text-accent))))); border-color: var(--internal-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); - background-color: var(--internal-hover-bg, var(--hover-bg, transparent)); + background: var(--internal-hover-bg, var(--hover-bg, transparent)); } } &.menu-external-link { color: var(--external-text, var(--text, var(--text-normal))); border-color: var(--external-border, var(--border, var(--background-modifier-border))); - background-color: var(--external-bg, transparent); + background: var(--external-bg, var(--bg, var(--background-primary))); font-family: var(--external-font, var(--font, inherit)); &:hover { color: var(--external-hover-text, var(--external-accent, var(--hover-text, var(--accent, var(--text-accent))))); border-color: var(--external-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); - background-color: var(--external-hover-bg, var(--hover-bg, transparent)); + background: var(--external-hover-bg, var(--hover-bg, transparent)); } } &.menu-file-link { color: var(--file-text, var(--text, var(--text-normal))); border-color: var(--file-border, var(--border, var(--background-modifier-border))); - background-color: var(--file-bg, transparent); + background: var(--file-bg, var(--bg, var(--background-primary))); font-family: var(--file-font, var(--font, inherit)); &:hover { color: var(--file-hover-text, var(--file-accent, var(--hover-text, var(--accent, var(--text-accent))))); border-color: var(--file-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); - background-color: var(--file-hover-bg, var(--hover-bg, transparent)); + background: var(--file-hover-bg, var(--hover-bg, transparent)); } } } @@ -112,12 +113,11 @@ /* Minimal style variant */ -.menu-container.minimal { +.menu-container[data-layout='minimal'] { display: flex; gap: 0.2em; flex-wrap: wrap; transition: 125ms; - background-color: var(--bg, transparent); font-family: var(--font, inherit); a { @@ -128,10 +128,11 @@ font-size: 0.9em; font-weight: normal; color: var(--text, var(--text-normal)); + background: var(--bg, transparent); font-family: var(--font, inherit); &:hover { - background-color: var(--hover-bg, var(--background-secondary)); + background: var(--hover-bg, var(--background-secondary)); color: var(--hover-text, var(--accent, var(--text-accent))); } @@ -141,7 +142,7 @@ &:hover { color: var(--internal-hover-text, var(--internal-accent, var(--hover-text, var(--internal-hover, var(--text-accent-hover))))); - background-color: var(--internal-hover-bg, var(--hover-bg, var(--background-secondary))); + background: var(--internal-hover-bg, var(--hover-bg, var(--background-secondary))); } } @@ -151,7 +152,7 @@ &:hover { color: var(--external-hover-text, var(--external-accent, var(--hover-text, var(--external-hover, var(--text-normal))))); - background-color: var(--external-hover-bg, var(--hover-bg, var(--background-secondary))); + background: var(--external-hover-bg, var(--hover-bg, var(--background-secondary))); } } @@ -161,18 +162,17 @@ &:hover { color: var(--file-hover-text, var(--file-accent, var(--hover-text, var(--file-hover, var(--text-normal))))); - background-color: var(--file-hover-bg, var(--hover-bg, var(--background-secondary))); + background: var(--file-hover-bg, var(--hover-bg, var(--background-secondary))); } } } } /* Minimal style variant */ -.menu-container.slate { +.menu-container[data-layout='slate'] { display: flex; gap: 0.2em; flex-wrap: wrap; - background-color: var(--bg, transparent); font-family: var(--font, inherit); a { @@ -182,13 +182,13 @@ transition: color 0.15s ease; font-size: 0.9em; font-weight: normal; - background-color: var(--item-bg, var(--background-secondary)); + background: var(--bg, var(--background-secondary)); border: 1px solid var(--border, var(--background-secondary)); color: var(--text, var(--text-faint)); font-family: var(--font, inherit); &:hover { - background-color: var(--hover-bg, var(--background-secondary)); + background: var(--hover-bg, var(--background-secondary)); color: var(--hover-text, var(--accent, var(--text-accent))); border: 1px solid var(--hover-border, var(--accent, var(--text-accent))); } @@ -196,51 +196,50 @@ &.menu-internal-link { color: var(--internal-text, var(--text, var(--text-faint))); border-color: var(--internal-border, var(--border, var(--background-secondary))); - background-color: var(--internal-bg, var(--item-bg, var(--background-secondary))); + background: var(--internal-bg, var(--bg, var(--background-secondary))); font-family: var(--internal-font, var(--font, inherit)); &:hover { color: var(--internal-hover-text, var(--internal-accent, var(--hover-text, var(--accent, var(--text-accent))))); border-color: var(--internal-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); - background-color: var(--internal-hover-bg, var(--hover-bg, var(--background-secondary))); + background: var(--internal-hover-bg, var(--hover-bg, var(--background-secondary))); } } &.menu-external-link { color: var(--external-text, var(--text, var(--text-faint))); border-color: var(--external-border, var(--border, var(--background-secondary))); - background-color: var(--external-bg, var(--item-bg, var(--background-secondary))); + background: var(--external-bg, var(--bg, var(--background-secondary))); font-family: var(--external-font, var(--font, inherit)); &:hover { color: var(--external-hover-text, var(--external-accent, var(--hover-text, var(--accent, var(--text-accent))))); border-color: var(--external-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); - background-color: var(--external-hover-bg, var(--hover-bg, var(--background-secondary))); + background: var(--external-hover-bg, var(--hover-bg, var(--background-secondary))); } } &.menu-file-link { color: var(--file-text, var(--text, var(--text-faint))); border-color: var(--file-border, var(--border, var(--background-secondary))); - background-color: var(--file-bg, var(--item-bg, var(--background-secondary))); + background: var(--file-bg, var(--bg, var(--background-secondary))); font-family: var(--file-font, var(--font, inherit)); &:hover { color: var(--file-hover-text, var(--file-accent, var(--hover-text, var(--accent, var(--text-accent))))); border-color: var(--file-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); - background-color: var(--file-hover-bg, var(--hover-bg, var(--background-secondary))); + background: var(--file-hover-bg, var(--hover-bg, var(--background-secondary))); } } } } -.menu-container.horizon { +.menu-container[data-layout='horizon'] { display: flex; gap: 0.5em; flex-wrap: wrap; border-radius: 8px; - background-color: var(--bg, var(--background-primary)); font-family: var(--font, 'Space Grotesk', Inter, sans-serif); a { @@ -254,49 +253,51 @@ border-radius: 3px; padding-block: var(--size-2, 0.5em); padding-inline: var(--size-3, 2em); + background: var(--bg, transparent); transition: 250ms; &:hover { color: var(--hover-text, var(--accent, var(--orange-500, darkorange))); border: 1px solid var(--hover-border, var(--accent, var(--orange-500, darkorange))); + background: var(--hover-bg, transparent); } &.menu-internal-link { color: var(--internal-text, var(--text, var(--cyan-500, darkcyan))); border-color: var(--internal-border, var(--border, var(--cyan-500, darkcyan))); - background-color: var(--internal-bg, transparent); + background: var(--internal-bg, var(--bg, transparent)); font-family: var(--internal-font, var(--font, 'Space Grotesk', Inter, sans-serif)); &:hover { color: var(--internal-hover-text, var(--internal-accent, var(--hover-text, var(--accent, var(--orange-500, darkorange))))); border-color: var(--internal-hover-border, var(--hover-border, var(--accent, var(--orange-500, darkorange)))); - background-color: var(--internal-hover-bg, var(--hover-bg, transparent)); + background: var(--internal-hover-bg, var(--hover-bg, transparent)); } } &.menu-external-link { color: var(--external-text, var(--text, var(--cyan-500, darkcyan))); border-color: var(--external-border, var(--border, var(--cyan-500, darkcyan))); - background-color: var(--external-bg, transparent); + background: var(--external-bg, var(--bg, transparent)); font-family: var(--external-font, var(--font, 'Space Grotesk', Inter, sans-serif)); &:hover { color: var(--external-hover-text, var(--external-accent, var(--hover-text, var(--accent, var(--orange-500, darkorange))))); border-color: var(--external-hover-border, var(--hover-border, var(--accent, var(--orange-500, darkorange)))); - background-color: var(--external-hover-bg, var(--hover-bg, transparent)); + background: var(--external-hover-bg, var(--hover-bg, transparent)); } } &.menu-file-link { color: var(--file-text, var(--text, var(--cyan-500, darkcyan))); border-color: var(--file-border, var(--border, var(--cyan-500, darkcyan))); - background-color: var(--file-bg, transparent); + background: var(--file-bg, var(--bg, transparent)); font-family: var(--file-font, var(--font, 'Space Grotesk', Inter, sans-serif)); &:hover { color: var(--file-hover-text, var(--file-accent, var(--hover-text, var(--accent, var(--orange-500, darkorange))))); border-color: var(--file-hover-border, var(--hover-border, var(--accent, var(--orange-500, darkorange)))); - background-color: var(--file-hover-bg, var(--hover-bg, transparent)); + background: var(--file-hover-bg, var(--hover-bg, transparent)); } } } @@ -308,11 +309,10 @@ } } -.menu-container.aether { +.menu-container[data-layout='aether'] { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.5em; - background-color: var(--bg, transparent); font-family: var(--font, inherit); a { @@ -323,50 +323,51 @@ border-radius: 8px; color: var(--text, var(--text-normal)); border: 1px solid var(--border, var(--background-modifier-border)); + background: var(--bg, transparent); font-family: var(--font, inherit); &:hover { color: var(--hover-text, var(--accent, var(--text-accent))); border-color: var(--hover-border, var(--accent, var(--text-accent))); - background-color: var(--hover-bg, var(--background-modifier-hover)); + background: var(--hover-bg, var(--background-modifier-hover)); } &.menu-internal-link { color: var(--internal-text, var(--text, var(--text-normal))); border-color: var(--internal-border, var(--border, var(--background-modifier-border))); - background-color: var(--internal-bg, transparent); + background: var(--internal-bg, var(--bg, transparent)); font-family: var(--internal-font, var(--font, inherit)); &:hover { color: var(--internal-hover-text, var(--internal-accent, var(--hover-text, var(--accent, var(--text-accent))))); border-color: var(--internal-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); - background-color: var(--internal-hover-bg, var(--hover-bg, var(--background-modifier-hover))); + background: var(--internal-hover-bg, var(--hover-bg, var(--background-modifier-hover))); } } &.menu-external-link { color: var(--external-text, var(--text, var(--text-normal))); border-color: var(--external-border, var(--border, var(--background-modifier-border))); - background-color: var(--external-bg, transparent); + background: var(--external-bg, var(--bg, transparent)); font-family: var(--external-font, var(--font, inherit)); &:hover { color: var(--external-hover-text, var(--external-accent, var(--hover-text, var(--accent, var(--text-accent))))); border-color: var(--external-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); - background-color: var(--external-hover-bg, var(--hover-bg, var(--background-modifier-hover))); + background: var(--external-hover-bg, var(--hover-bg, var(--background-modifier-hover))); } } &.menu-file-link { color: var(--file-text, var(--text, var(--text-normal))); border-color: var(--file-border, var(--border, var(--background-modifier-border))); - background-color: var(--file-bg, transparent); + background: var(--file-bg, var(--bg, transparent)); font-family: var(--file-font, var(--font, inherit)); &:hover { color: var(--file-hover-text, var(--file-accent, var(--hover-text, var(--accent, var(--text-accent))))); border-color: var(--file-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); - background-color: var(--file-hover-bg, var(--hover-bg, var(--background-modifier-hover))); + background: var(--file-hover-bg, var(--hover-bg, var(--background-modifier-hover))); } } }