├── .npmrc ├── .eslintignore ├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── icon.png ├── screenshot.png ├── .editorconfig ├── versions.json ├── .gitignore ├── manifest.json ├── tsconfig.json ├── version-bump.mjs ├── .eslintrc ├── .devcontainer └── devcontainer.json ├── LICENSE ├── esbuild.config.mjs ├── package.json ├── examples ├── example3.md ├── example2.md └── example1.md ├── CHANGELOG.md ├── styles.css ├── main.ts └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | main.js 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | buy_me_a_coffee: binarynoir 2 | ko_fi: binarynoir 3 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binarynoir/obsidian-markdown-tags/HEAD/icon.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binarynoir/obsidian-markdown-tags/HEAD/screenshot.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | tab_width = 4 11 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.12.0", 3 | "1.0.1": "0.12.0", 4 | "1.0.2": "0.12.0", 5 | "1.1.0": "0.12.0", 6 | "1.1.1": "0.12.0", 7 | "1.2.0": "0.12.0", 8 | "1.2.1": "0.12.0", 9 | "1.2.2": "0.12.0", 10 | "1.2.3": "0.12.0", 11 | "1.3.0": "0.12.0", 12 | "1.3.1": "0.12.0" 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "markdown-tags", 3 | "name": "Tags for Markdown", 4 | "version": "1.3.1", 5 | "minAppVersion": "0.12.0", 6 | "description": "Enhance your documents with custom tags. Use predefined or custom labels, customizable colors, and arrow indicators to visually track tasks and statuses.", 7 | "author": "John Smith III", 8 | "authorUrl": "https://binarynoir.tech", 9 | "fundingUrl": { 10 | "Buy Me a Coffee": "https://buymeacoffee.com/binarynoir" 11 | }, 12 | "isDesktopOnly": false 13 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES2022", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7", 19 | "ES2022" 20 | ] 21 | }, 22 | "include": [ 23 | "**/*.ts" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Obsidian Plugin 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set up Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: "20.x" 19 | 20 | - name: Build plugin 21 | run: | 22 | npm install 23 | npm run build 24 | 25 | - name: Install GitHub CLI 26 | run: sudo apt-get install -y gh 27 | 28 | - name: Create release 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | run: | 32 | tag="${GITHUB_REF#refs/tags/}" 33 | 34 | gh release create "$tag" \ 35 | --title="$tag" \ 36 | --generate-notes \ 37 | main.js manifest.json styles.css 38 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node 3 | { 4 | "name": "Node.js & TypeScript", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm", 7 | "mounts": [ 8 | "source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/node/.ssh,type=bind,consistency=cached" 9 | ] 10 | // Features to add to the dev container. More info: https://containers.dev/features. 11 | // "features": {}, 12 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 13 | // "forwardPorts": [], 14 | // Use 'postCreateCommand' to run commands after the container is created. 15 | // "postCreateCommand": "yarn install", 16 | // Configure tool-specific properties. 17 | // "customizations": {}, 18 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 19 | // "remoteUser": "root" 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 BinaryNoir 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === "production"); 13 | 14 | const context = await esbuild.context({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ["main.ts"], 19 | bundle: true, 20 | external: [ 21 | "obsidian", 22 | "electron", 23 | "@codemirror/autocomplete", 24 | "@codemirror/collab", 25 | "@codemirror/commands", 26 | "@codemirror/language", 27 | "@codemirror/lint", 28 | "@codemirror/search", 29 | "@codemirror/state", 30 | "@codemirror/view", 31 | "@lezer/common", 32 | "@lezer/highlight", 33 | "@lezer/lr", 34 | ...builtins], 35 | format: "cjs", 36 | target: "es2018", 37 | logLevel: "info", 38 | sourcemap: prod ? false : "inline", 39 | treeShaking: true, 40 | outfile: "main.js", 41 | minify: prod, 42 | }); 43 | 44 | if (prod) { 45 | await context.rebuild(); 46 | process.exit(0); 47 | } else { 48 | await context.watch(); 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-markdown-tags", 3 | "version": "1.3.1", 4 | "description": "Enhance your documents with custom tags. Use predefined or custom labels, customizable colors, and arrow indicators to visually track tasks and statuses.", 5 | "main": "main.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/binarynoir/obsidian-markdown-tags" 9 | }, 10 | "scripts": { 11 | "dev": "node esbuild.config.mjs", 12 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 13 | "version": "node version-bump.mjs && git add manifest.json versions.json", 14 | "release": "git pull && git tag -a $npm_package_version -m \"$npm_package_version\" && git push --tags" 15 | }, 16 | "keywords": ["obsidian", "markdown", "tags", "plugin", "customization"], 17 | "author": "John Smith III", 18 | "license": "MIT", 19 | "publisher": "BinaryNoir", 20 | "devDependencies": { 21 | "@types/node": "^16.11.6", 22 | "@typescript-eslint/eslint-plugin": "5.29.0", 23 | "@typescript-eslint/parser": "5.29.0", 24 | "builtin-modules": "3.3.0", 25 | "esbuild": "0.17.3", 26 | "obsidian": "latest", 27 | "tslib": "2.4.0", 28 | "typescript": "4.7.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/example3.md: -------------------------------------------------------------------------------- 1 | # Research Notes 2 | 3 | ## Project Overview 4 | 5 | ### Research Topics 6 | 7 | 8 | ((tag|todo)) ((tag|in-progress)) ((tag|done)) 9 | 10 | - **Topic 1**: Literature review on AI ethics ((tag|in-progress)) 11 | - **Topic 2**: Data collection methods ((tag|todo)) 12 | - **Topic 3**: Analysis of survey results ((tag|done)) 13 | 14 | ### Reading List 15 | 16 | 17 | ((tag|to-read)) ((tag|reading)) ((tag|read)) 18 | 19 | - **Book 1**: "Artificial Intelligence: A Modern Approach" ((tag|to-read)) 20 | - **Book 2**: "Data Science for Business" ((tag|reading)) 21 | - **Book 3**: "Deep Learning" ((tag|read)) 22 | 23 | ## Research Plan 24 | 25 | ### Phases 26 | 27 | 28 | (( 39 | ((tag|high-importance|#ff4500)) ((tag|medium-importance|#ffd700)) ((tag|low-importance|#32cd32)) 40 | 41 | - **Urgent Task**: Submit research proposal ((tag|high-importance|#ff4500)) 42 | - **Regular Task**: Schedule meetings with advisor ((tag|medium-importance|#ffd700)) 43 | - **Optional Task**: Attend AI conference ((tag|low-importance|#32cd32)) 44 | -------------------------------------------------------------------------------- /examples/example2.md: -------------------------------------------------------------------------------- 1 | # Project Management 2 | 3 | ## Task Status 4 | 5 | ### Current Sprint 6 | 7 | 8 | ((tag|todo)) ((tag|in-progress)) ((tag|done)) ((tag|blocked)) 9 | 10 | - **Task 1**: Implement user authentication ((tag|in-progress)) 11 | - **Task 2**: Design landing page ((tag|todo)) 12 | - **Task 3**: Fix login bug ((tag|blocked)) 13 | - **Task 4**: Write unit tests ((tag|done)) 14 | 15 | ### Backlog 16 | 17 | 18 | ((tag|planned)) ((tag|proposed)) ((tag|on-hold)) 19 | 20 | - **Feature 1**: Add user profile page ((tag|planned)) 21 | - **Feature 2**: Integrate payment gateway ((tag|proposed)) 22 | - **Feature 3**: Optimize database queries ((tag|on-hold)) 23 | 24 | ## Milestones 25 | 26 | ### Upcoming Releases 27 | 28 | 29 | (( 40 | ((tag|high-priority|#ff0000)) ((tag|medium-priority|#ffa500)) ((tag|low-priority|#008000)) 41 | 42 | - **Critical Bug**: Fix security vulnerability ((tag|high-priority|#ff0000)) 43 | - **Enhancement**: Improve UI/UX ((tag|medium-priority|#ffa500)) 44 | - **Minor Issue**: Update documentation ((tag|low-priority|#008000)) 45 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | - none 11 | 12 | ## [1.3.1] - 2025-08-19 13 | 14 | ### Fixed 15 | 16 | - Support for markdown tables 17 | 18 | ## [1.3.0] - 2025-08-19 19 | 20 | ### Added 21 | 22 | - Support for using either `|` or `/` as seperators 23 | 24 | ## [1.2.3] - 2024-12-13 25 | 26 | ### Fixed 27 | 28 | - Extension name inconsistencies 29 | 30 | ## [1.2.2] - 2024-12-13 31 | 32 | ### Fixed 33 | 34 | - Extension breaks other elemets in Preview Mode 35 | - Some elements in Preview Mode and Edit mode did not support tags 36 | 37 | 38 | ## [1.2.1] - 2024-11-28 39 | 40 | ### Fixed 41 | 42 | - Issues with callout box conflicts 43 | 44 | ## [1.2.0] - 2024-11-26 45 | 46 | ### Added 47 | 48 | - Support for reader view 49 | - Example markdown documents 50 | 51 | ### Changed 52 | 53 | - Improved arrow tag styling 54 | 55 | ## [1.1.1] - 2024-11-15 56 | 57 | ### Fixed 58 | 59 | - README.md had some arrow tags that were not opened correctly 60 | - Code comments had the old tag format 61 | 62 | ## [1.1.0] - 2024-11-15 63 | 64 | ### Changed 65 | 66 | - Tags are now formatted using `((tag|label|bgcolor|fgcolor))` format so as not to conflict with other common markdown 67 | - CSS class is now bn-tags amd bn-arrow-tags so as not to conflict with other common classes 68 | - README.md reflects new tag format 69 | 70 | ## [1.0.2] - 2024-11-11 71 | 72 | ### Changed 73 | 74 | - Updated name to remove obsidian from prefix 75 | 76 | ## [1.0.1] - 2024-11-11 77 | 78 | ### Added 79 | 80 | - Release prep and workflows 81 | 82 | ## [1.0.0] - 2024-11-08 83 | 84 | ### Added 85 | 86 | - Initial release checkin 87 | -------------------------------------------------------------------------------- /examples/example1.md: -------------------------------------------------------------------------------- 1 | # Test Document 2 | 3 | ## Status Tags Demo 4 | 5 | ### Basic Status Tags 6 | 7 | 8 | 9 | ((tag|todo)) ((tag|planned)) ((tag|in-progress)) ((tag|doing)) ((tag|done)) ((tag|tip)) ((tag|on-hold)) ((tag|tbd)) ((tag|proposed)) ((tag|draft)) ((tag|wip)) ((tag|mvp)) ((tag|blocked)) ((tag|canceled)) ((tag|error)) ((tag|warning)) ((tag|warn)) 10 | 11 | ### Custom Label Tags 12 | 13 | 14 | 15 | ((tag|custom label)) ((tag|custom label|grey)) ((tag|custom label|orange)) ((tag|custom label|green)) ((tag|custom label|blue)) ((tag|custom label|purple)) ((tag|custom label|red)) ((tag|custom label|yellow)) 16 | 17 | ## Arrow Tags Demo 18 | 19 | ### Arrow Status Tags 20 | 21 | 22 | 23 | (( 28 | 29 | (( 36 | 37 | ((tag|mvp|#008000)) ((tag|mvp||#90ee90)) ((tag|mvp|#008000|#90ee90)) 38 | 39 | ### Mixed Color Customizations 40 | 41 | 42 | 43 | ((tag|custom bg|#007bff)) ((tag|custom fg||#ff1493)) ((tag|custom colors|#007bff|#ff1493)) 44 | 45 | ### Complex Tag Styling 46 | 47 | 48 | 49 | ((tag|long label for testing|#222222|#ffffff)) 50 | ((tag|no-bg|)) ((tag|only-fg||#ff6347)) 51 | 52 | ## Mixed Usage and Custom Labels 53 | 54 | ### Combination of Styles and Labels 55 | 56 | 57 | 58 | ((tag|normal)) (([^\|\/\)]+)(?:[\|\/](?[^\|\/\)]*))?(?:[\|\/](?[^\|\/\)]*))?\)\)/g; 16 | 17 | const isValidHexColor = (color: string): boolean => /^#([0-9A-Fa-f]{3}){1,2}$/.test(color); 18 | 19 | const isValidColor = (color: string): boolean => isValidHexColor(color) || colorMap.includes(color.toLowerCase()); 20 | 21 | const escapeHtml = (str: string): string => str.replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char] || char)); 22 | 23 | function generateTagDecoration(label: string, bgcolor?: string, fgcolor?: string, arrow = false): Decoration { 24 | 25 | // Determine if the label is in the predefined label map 26 | const labelClass = labelMap.includes(label.toLowerCase()) ? label.toLowerCase() : ''; 27 | // Determine the background color class from the color map, defaulting to 'grey' 28 | const bgColorClass = bgcolor && colorMap.includes(bgcolor.toLowerCase()) ? bgcolor.toLowerCase() : 'grey'; 29 | // Check if a valid custom background color is provided 30 | const bgCustomColor = bgcolor && isValidHexColor(bgcolor) ? bgcolor : null; 31 | // Check if a valid custom foreground color is provided 32 | const fgCustomColor = fgcolor && isValidHexColor(fgcolor) ? fgcolor : null; 33 | 34 | // Combine classes, adding 'bn-arrow-tags' if the arrow flag is true 35 | const combinedClasses = `bn-tags ${labelClass} ${bgColorClass} ${arrow ? 'bn-arrow-tags' : ''}`.trim(); 36 | 37 | // Build inline style if custom background or foreground colors are provided 38 | const style = `${bgCustomColor ? `background-color: ${bgCustomColor};` : ''}${fgCustomColor ? ` color: ${fgCustomColor};` : ''}`; 39 | 40 | // Return the decoration with the combined classes and inline style attributes 41 | return Decoration.mark({ 42 | class: combinedClasses, 43 | attributes: { 44 | style: style 45 | } 46 | }); 47 | } 48 | 49 | export default class tagsPlugin extends Plugin { 50 | async onload() { 51 | // Register the CodeMirror plugin for the editor 52 | this.registerEditorExtension(this.editModeTagsHighlighter()); 53 | 54 | // Register the MarkdownPostProcessor for reader view 55 | this.registerMarkdownPostProcessor(this.viewModeTagsHighlighter()); 56 | } 57 | 58 | private editModeTagsHighlighter() { 59 | // Define the ViewPlugin 60 | return ViewPlugin.fromClass(class { 61 | decorations: DecorationSet; 62 | 63 | constructor(view: EditorView) { 64 | this.decorations = this.buildDecorations(view); 65 | } 66 | 67 | // Update decorations on document or viewport change 68 | update(update: ViewUpdate) { 69 | if (update.docChanged || update.viewportChanged || update.selectionSet) { 70 | this.decorations = this.buildDecorations(update.view); 71 | } 72 | } 73 | 74 | // Build decorations for tag syntax patterns 75 | buildDecorations(view: EditorView): DecorationSet { 76 | const builder = new RangeSetBuilder(); 77 | const cursorPos = view.state.selection.main.head; 78 | 79 | for (const { from, to } of view.visibleRanges) { 80 | const text = view.state.doc.sliceString(from, to); 81 | let match; 82 | 83 | // Function to check if a position is inside a code block 84 | const isInCodeBlock = (pos: number): boolean => { 85 | const line = view.state.doc.lineAt(pos); 86 | let inCodeBlock = false; 87 | for (let i = 1; i <= view.state.doc.lines; i++) { 88 | const currentLine = view.state.doc.line(i).text.trim(); 89 | 90 | if (currentLine.startsWith("```")) { 91 | inCodeBlock = !inCodeBlock; 92 | } 93 | if (i === line.number) { 94 | return inCodeBlock; 95 | } 96 | } 97 | return false; 98 | }; 99 | 100 | while ((match = tagSyntaxRegex.exec(text)) !== null) { 101 | const start = from + (match.index ?? 0); // Start of the match 102 | const end = start + match[0].length; // End of the match 103 | 104 | // Check if the cursor is within the match range or if the match is inside a code block 105 | if (cursorPos >= start && cursorPos <= end || (isInCodeBlock(start))) { 106 | continue; // Skip adding decorations if the cursor is within the range or inside a code or callout block 107 | } 108 | 109 | // Extract named groups with default values 110 | const { label = '', bgcolor = '', fgcolor = '' } = match.groups ?? {}; 111 | 112 | const escapedLabel = escapeHtml(label); 113 | const validBgColor = bgcolor && isValidColor(bgcolor) ? bgcolor : ''; 114 | const validFgColor = fgcolor && isValidColor(fgcolor) ? fgcolor : ''; 115 | const arrow = match[0].startsWith("((<"); 116 | 117 | // Apply decoration to hide the leading part 118 | builder.add(start, start + match[0].indexOf(label), Decoration.mark({ class: "bn-hidden" })); 119 | 120 | // Apply decoration to the inner text (label) 121 | builder.add(start + match[0].indexOf(label), start + match[0].indexOf(label) + label.length, generateTagDecoration(escapedLabel, validBgColor, validFgColor, arrow)); 122 | 123 | // Apply decoration to hide the trailing part 124 | builder.add(start + match[0].indexOf(label) + label.length, end, Decoration.mark({ class: "bn-hidden" })); 125 | } 126 | } 127 | 128 | return builder.finish() as DecorationSet; 129 | } 130 | }, { decorations: v => v.decorations }); 131 | } 132 | 133 | private viewModeTagsHighlighter() { 134 | return (el: HTMLElement, ctx: MarkdownPostProcessorContext) => { 135 | const tags = Array.from(el.querySelectorAll("p, li, span, div, td, th")); 136 | 137 | tags.forEach(tagElement => { 138 | const originalText = tagElement.textContent; // Use textContent to get plain text 139 | if (!originalText) return; // Skip if there's no text 140 | 141 | let match: RegExpExecArray | null; 142 | let updatedHTML = tagElement.innerHTML; // Start with the existing HTML 143 | let matchFound = false; // Initialize the flag 144 | 145 | // Process matches in the text content 146 | while ((match = tagSyntaxRegex.exec(originalText)) !== null) { 147 | matchFound = true; 148 | // Extract named groups with default values 149 | const { label = "", bgcolor = "", fgcolor = "" } = match.groups ?? {}; 150 | const escapedLabel = escapeHtml(label); 151 | const validBgColor = bgcolor && isValidColor(bgcolor) ? bgcolor : ""; 152 | const validFgColor = fgcolor && isValidColor(fgcolor) ? fgcolor : ""; 153 | const arrow = match[0].startsWith("((<"); 154 | 155 | // Generate the styled decoration 156 | const decoration = generateTagDecoration(escapedLabel, validBgColor, validFgColor, arrow); 157 | 158 | // Replace tag syntax with styled span 159 | const replacement = `${escapedLabel}`; 160 | const escapedMatch = escapeHtml(match[0]); // Escape the match to handle HTML entities 161 | updatedHTML = updatedHTML.replace(escapedMatch, replacement); 162 | } 163 | 164 | if (matchFound) { 165 | tagElement.innerHTML = updatedHTML; // Process this line only if a match was found 166 | } 167 | }); 168 | }; 169 | } 170 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tags for Markdown: Enhanced Styled Labels for Obsidian 2 | 3 | Add visual flair to your Markdown documents with custom tag styles! **Tags for Markdown** lets you highlight and style labels within Markdown documents using simple syntax, customizable colors, and optional arrow indicators—all within Obsidian. If you like **Tags for Markdown**, get our extension for [Visual Studio Code](https://github.com/binarynoir/vscode-markdown-tags/)! 4 | 5 | [![Support me on Buy Me a Coffee](https://img.shields.io/badge/Support%20me-Buy%20Me%20a%20Coffee-orange?style=for-the-badge&logo=buy-me-a-coffee)](https://buymeacoffee.com/binarynoir) 6 | [![Support me on Ko-fi](https://img.shields.io/badge/Support%20me-Ko--fi-blue?style=for-the-badge&logo=ko-fi)](https://ko-fi.com/binarynoir) 7 | [![Visit my website](https://img.shields.io/badge/Website-binarynoir.tech-8c8c8c?style=for-the-badge)](https://binarynoir.tech) 8 | 9 | ![obsidian-markdown-tags](./screenshot.png) 10 | 11 | ## Features 12 | 13 | ### 🎨 Styled Tags 14 | 15 | Highlight and style tags with ease using predefined or custom styles. 16 | 17 | ### 🖌️ Customizable Colors 18 | 19 | Use predefined colors or specify custom hex codes for both background and foreground colors, enabling unlimited styling options. 20 | 21 | ### 📄 Flexible Syntax 22 | 23 | 24 | Simple, flexible syntax options: 25 | 26 | You can use either the `|` (pipe) or `/` (slash) character as a separator between tag components: 27 | 28 | ```markdown 29 | ((tag|label)) 30 | ((tag/label)) 31 | ((tag|label|background-color)) 32 | ((tag/label/background-color)) 33 | ((tag|label|background-color|foreground-color)) 34 | ((tag/label/background-color/foreground-color)) 35 | (( 36 | (( 37 | ``` 38 | 39 | > **Note:** Both `|` and `/` are supported as separators. Use whichever you prefer or fits your workflow. This is especially useful when using tags within markdown tables. 40 | 41 | ### 🌈 Supports a Variety of Colors 42 | 43 | Choose from predefined colors (`grey`, `green`, `orange`, etc.) or use custom hex codes to suit your design preferences. 44 | 45 | --- 46 | 47 | ## Getting Started 48 | 49 | 1. **Install** the plugin from the Obsidian Community Plugins. 50 | 2. **Enable** the plugin in the Obsidian settings. 51 | 3. **Add Tags** in your Markdown files using the syntax below. 52 | 53 | ### Basic Syntax Examples 54 | 55 | #### Status Tags 56 | 57 | ```markdown 58 | ((tag|todo)) ((tag|in-progress|#ffcc00)) ((tag|done|#28a745|#ffffff)) 59 | ``` 60 | 61 | #### Arrowed Tags 62 | 63 | ```markdown 64 | ((/.obsidian/plugins/` 183 | 184 | ### Manually installing the plugin 185 | 186 | Copy over main.js, styles.css, manifest.json to your vault VaultFolder/.obsidian/plugins/obsidian-markdown-tags/. 187 | 188 | --- 189 | 190 | ## Contributing 191 | 192 | > Feel free to submit issues, feature requests, or contribute code on GitHub. 193 | 194 | ### Development 195 | 196 | ```bash 197 | npm install 198 | npm run build 199 | cp main.js manifest.json /path/to/your/vault/.obsidian/plugins/obsidian-markdown-tags 200 | ``` 201 | 202 | ### Release 203 | 204 | ### Releasing new releases 205 | 206 | - Update the changelog with new features and fixes 207 | - Commit all changed files and create a pull request 208 | - Update the `manifest.json` with the new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release. 209 | - Update the `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible. 210 | - Create new GitHub release using the new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases 211 | - Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. Note: The manifest.json file must be in two places, first the root path of your repository and also in the release. 212 | - Publish the release. 213 | 214 | > You can simplify the version bump process by running `npm version patch`, `npm version minor` or `npm version major` after updating `minAppVersion` manually in `manifest.json`. 215 | > The command will bump version in `manifest.json` and `package.json`, and add the entry for the new version to `versions.json` 216 | 217 | ```bash 218 | git checkout main 219 | git pull 220 | git tag -a x.y.z -m "x.y.z" 221 | git push --tags 222 | ``` 223 | 224 | The release will automatically be drafted. 225 | 226 | ## License 227 | 228 | MIT License 229 | 230 | --- 231 | 232 | ## Support 233 | 234 | If you encounter any issues or have questions, please open an issue on GitHub. 235 | 236 | ## Author 237 | 238 | John Smith III 239 | 240 | ## Acknowledgments 241 | 242 | Thanks to all contributors and users for their support and feedback. 243 | --------------------------------------------------------------------------------