From 02c82a339e040d1d59cc6f70c81911de95fe3e99 Mon Sep 17 00:00:00 2001 From: "@gapmiss" Date: Fri, 29 May 2026 17:47:22 -0500 Subject: [PATCH] Add link support for badges using >> syntax Badges can now include clickable links with `[!!type:text>>link]` syntax. Supports both wikilinks (`[[Note]]`) and external URLs. Co-Authored-By: Claude Opus 4.5 --- README.md | 27 +++++++++++++++++++++++++++ src/main.ts | 39 ++++++++++++++++++++++++++++++++++++--- styles.css | 10 ++++++++++ 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b09752f..ccc1313 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ A light-weight plugin for displaying inline "badges" in [Obsidian.md](https://gi - [Github styled badges](#Github) - [Plain-text](#Plain-text) - [custom](#custom) + - [Links](#links) - [Usage in tables](#usage-in-tables) - [Installation](#Installation) - [CSS styles](#CSS) @@ -190,6 +191,32 @@ For example, `[!!success]` displays as "Success" with a checkmark icon. This wor ![](assets/Badges-demo-Obsidian-v1.3.7-20230709171541.png) ![](assets/Badges-demo-Obsidian-v1.3.7-20230709171534.png) +### Links + +Badges can be made clickable by adding a link using the `>>` syntax. + +###### syntax + +```markdown +`[!!KEY:VAL>>LINK]` +``` + +| syntax | details | +| ------ | ------- | +| `>>` | link delimiter | +| `LINK` | wikilink `[[Note]]` or external URL `https://...` | + +###### examples + +```markdown +`[!!info:Documentation>>https://obsidian.md]` +`[!!note:See also>>[[My Note]]]` +`[!!tip:Jump to section>>[[My Note#Heading]]]` +``` + +> [!NOTE] +> Links work with all badge types including custom badges. + ### Usage in tables When using badges inside Markdown tables, the `|` pipe character conflicts with the table cell separator. To work around this, use escaped pipes `\|` instead of `|` in your badge syntax. diff --git a/src/main.ts b/src/main.ts index 5e8d683..9bd795b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import { RangeSetBuilder } from "@codemirror/state" import { ViewPlugin, WidgetType, EditorView, ViewUpdate, Decoration, DecorationSet } from '@codemirror/view' import { BADGE_TYPES } from './constants'; -const REGEXP = /(`\[!!([^\]]*)\]`)/gm; +const REGEXP = /(`\[!!(.*?)\]`)/gm; const TAGS = 'code' export default class BadgesPlugin extends Plugin { @@ -131,7 +131,7 @@ const viewPlugin = ViewPlugin.fromClass(class { decorations: (v) => v.decorations, }) -function buildBadge(text: string): HTMLSpanElement { +function buildBadge(text: string): HTMLSpanElement | HTMLAnchorElement { const newEl = createSpan(); const iconEl = createSpan(); const titleEl = createSpan(); @@ -139,11 +139,25 @@ function buildBadge(text: string): HTMLSpanElement { let attrType = ""; const part = text.substring(2); // Support escaped pipes (\|) for use inside Markdown tables - const content = part.substring(part.length-1,1).trim().replace(/\\\|/g, '|'); + let content = part.substring(part.length-1,1).trim().replace(/\\\|/g, '|'); if (!content.length) { newEl.setText("Badges syntax error"); return newEl; } + // Parse optional link syntax: >>[[wikilink]] or >>https://... + let linkTarget: string | null = null; + let isWikilink = false; + const linkMatch = content.match(/>>(\[\[.+?\]\]|.+)$/); + if (linkMatch) { + const rawLink = linkMatch[1].trim(); + if (rawLink.startsWith('[[') && rawLink.endsWith(']]')) { + linkTarget = rawLink.slice(2, -2); + isWikilink = true; + } else { + linkTarget = rawLink; + } + content = content.slice(0, content.lastIndexOf('>>')).trim(); + } const parts = content.split(':'); let badgeType = parts[0].trim(); let badgeContent: string; @@ -225,5 +239,24 @@ function buildBadge(text: string): HTMLSpanElement { } newEl.appendChild(titleEl); } + // Wrap in anchor if link was specified + if (linkTarget) { + const anchor = createEl('a'); + if (isWikilink) { + anchor.addClass('internal-link'); + anchor.setAttr('data-href', linkTarget); + anchor.setAttr('href', linkTarget); + anchor.setAttr('data-tooltip-position', 'top'); + } else { + anchor.addClass('external-link'); + anchor.setAttr('href', linkTarget); + anchor.setAttr('target', '_blank'); + anchor.setAttr('rel', 'noopener'); + anchor.setAttr('aria-label', linkTarget); + anchor.setAttr('data-tooltip-position', 'top'); + } + anchor.appendChild(newEl); + return anchor; + } return newEl; } diff --git a/styles.css b/styles.css index 7de6476..8a3ec03 100644 --- a/styles.css +++ b/styles.css @@ -416,3 +416,13 @@ body { color: rgba(var(--badge-color), 1); background-color: rgba(var(--badge-color),.123); } +a:has(> .inline-badge) { + text-decoration: none; +} +a:has(> .inline-badge), +a:has(> .inline-badge) .inline-badge { + cursor: pointer; +} +a:has(> .inline-badge):hover .inline-badge { + filter: brightness(1.1); +}