├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierrc ├── LICENSE ├── README.md ├── assets ├── dict.js └── screenshot-1.png ├── esbuild.config.mjs ├── jest.config.ts ├── manifest.json ├── package-lock.json ├── package.json ├── src ├── Dictionary.ts ├── Highlighter.ts ├── Settings.ts ├── SettingsTab.ts ├── main.ts └── utils.ts ├── styles.css ├── tests └── lib │ └── DomUtil.test.ts ├── tsconfig.jest.json ├── tsconfig.json ├── version-bump.mjs └── versions.json /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | main.js 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": ["@typescript-eslint"], 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/eslint-recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "parserOptions": { 12 | "sourceType": "module" 13 | }, 14 | "rules": { 15 | "no-unused-vars": "off", 16 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 17 | "@typescript-eslint/ban-ts-comment": "off", 18 | "no-prototype-builtins": "off", 19 | "@typescript-eslint/no-empty-function": "off" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.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: Use Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: "18.x" 19 | 20 | - name: Build plugin 21 | run: | 22 | npm install 23 | npm run build 24 | 25 | - name: Create release 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | run: | 29 | tag="${GITHUB_REF#refs/tags/}" 30 | 31 | gh release create "$tag" \ 32 | --title="$tag" \ 33 | --draft \ 34 | main.js manifest.json styles.css 35 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-svelte"], 3 | "arrowParens": "always", 4 | "bracketSameLine": false, 5 | "bracketSpacing": true, 6 | "semi": false, 7 | "singleQuote": true, 8 | "jsxSingleQuote": false, 9 | "quoteProps": "as-needed", 10 | "trailingComma": "all", 11 | "singleAttributePerLine": false, 12 | "htmlWhitespaceSensitivity": "css", 13 | "proseWrap": "preserve", 14 | "insertPragma": false, 15 | "printWidth": 80, 16 | "requirePragma": false, 17 | "tabWidth": 4, 18 | "useTabs": false, 19 | "embeddedLanguageFormatting": "auto", 20 | "svelteBracketNewLine": false, 21 | "svelteIndentScriptAndStyle": false 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 eatgrass 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Vocabulary Highlighter for Obsidian

2 |

highlight English words according to the frequency

3 | 4 | ## Introduction 5 | 6 | Enhance your reading and learning experience in Obsidian with the Vocabulary Highlighter. This plugin highlights words based on their frequency, aiding in English language learning and content review. Perfect for students, writers, and English language enthusiasts. 7 | 8 | ## Screenshots 9 | 10 | ![Highlight](https://github.com/eatgrass/obsidian-vocab-highlighter/blob/487a3ac1b5023d9f8146c18e01099c4a15d83395/assets/screenshot-1.png) 11 | 12 | ## Settings 13 | 14 | 1. Navigate to the plugin's setting tab in Obsidian. 15 | 2. Adjust the highlight threshold to control frequency sensitivity and select your preferred colors for different frequency ranges. 16 | 3. Optionally disable certain highlight groups to tailor your focus. 17 | 4. Reopen your files in Reading View to see the changes. Words will be highlighted based on your settings. 18 | 5. Use the `Toggle Vocabulary Highlight` command to conveniently turn highlighting on or off as needed. 19 | 20 | ## Commands 21 | 22 | `Toggle Vocabulary Highlight` 23 | 24 | - - - 25 | 26 | [BuyMeACoffee](https://www.buymeacoffee.com/eatgrass) 27 | -------------------------------------------------------------------------------- /assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eatgrass/obsidian-vocab-highlighter/62188c9b4fe85b642b2086473b2da581acc315e5/assets/screenshot-1.png -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | import esbuildSvelte from "esbuild-svelte"; 5 | import sveltePreprocess from "svelte-preprocess"; 6 | 7 | const banner = `/* 8 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 9 | if you want to view the source, please visit the github repository of this plugin 10 | */ 11 | `; 12 | 13 | const prod = process.argv[2] === "production"; 14 | 15 | const context = await esbuild.context({ 16 | banner: { 17 | js: banner, 18 | }, 19 | entryPoints: ["src/main.ts"], 20 | bundle: true, 21 | plugins: [ 22 | esbuildSvelte({ 23 | compilerOptions: { css: "injected" }, 24 | preprocess: sveltePreprocess(), 25 | }), 26 | ], 27 | external: [ 28 | "obsidian", 29 | "electron", 30 | "@codemirror/autocomplete", 31 | "@codemirror/collab", 32 | "@codemirror/commands", 33 | "@codemirror/language", 34 | "@codemirror/lint", 35 | "@codemirror/search", 36 | "@codemirror/state", 37 | "@codemirror/view", 38 | "@lezer/common", 39 | "@lezer/highlight", 40 | "@lezer/lr", 41 | ...builtins, 42 | ], 43 | format: "cjs", 44 | target: "es2018", 45 | logLevel: "info", 46 | sourcemap: prod ? false : "inline", 47 | treeShaking: true, 48 | outfile: "main.js", 49 | }); 50 | 51 | if (prod) { 52 | await context.rebuild(); 53 | process.exit(0); 54 | } else { 55 | await context.watch(); 56 | } 57 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'jest' 2 | 3 | const config: Config = { 4 | verbose: true, 5 | preset: 'ts-jest', 6 | transform: { 7 | '.ts': [ 8 | 'ts-jest', 9 | { 10 | tsconfig: './tsconfig.jest.json', 11 | }, 12 | ], 13 | }, 14 | moduleFileExtensions: ['js', 'ts'], 15 | testTimeout: 1000, 16 | } 17 | 18 | module.exports = config 19 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "vocabulary-highlighter", 3 | "name": "Vocabulary Highlighter", 4 | "version": "1.0.8", 5 | "minAppVersion": "0.15.0", 6 | "description": "Hightlight vocabulary based on the word frequency", 7 | "author": "eatgrass", 8 | "repo": "eatgrass/obsidian-vocab-highlighter", 9 | "fundingUrl": "https://www.buymeacoffee.com/eatgrass", 10 | "authorUrl": "https://github.com/eatgrass", 11 | "isDesktopOnly": false 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-sample-plugin", 3 | "version": "1.0.0", 4 | "description": "This is a sample plugin for Obsidian (https://obsidian.md)", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "test": "jest", 10 | "test:dev": "jest --watch", 11 | "version": "node version-bump.mjs && git add manifest.json versions.json", 12 | "format": "prettier --write --plugin prettier-plugin-svelte ." 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "@tsconfig/svelte": "^5.0.2", 19 | "@types/jest": "^29.5.8", 20 | "@types/lodash": "^4.14.201", 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 | "esbuild-svelte": "^0.8.0", 27 | "jest": "^29.7.0", 28 | "jest-environment-jsdom": "^29.7.0", 29 | "obsidian": "latest", 30 | "prettier": "^3.0.3", 31 | "prettier-plugin-svelte": "^3.0.3", 32 | "svelte": "^4.2.2", 33 | "svelte-preprocess": "^5.0.4", 34 | "ts-jest": "^29.1.1", 35 | "ts-node": "^10.9.1", 36 | "tslib": "2.4.0", 37 | "typescript": "^5.2.2" 38 | }, 39 | "dependencies": { 40 | "lodash": "^4.17.21" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Dictionary.ts: -------------------------------------------------------------------------------- 1 | import dict from '../assets/dict' 2 | import { memoize } from 'lodash' 3 | 4 | export const query = memoize(async (word: string): Promise => { 5 | return _query(word) 6 | }) 7 | 8 | const _query = memoize((word: string) => (dict as any)[word]) 9 | -------------------------------------------------------------------------------- /src/Highlighter.ts: -------------------------------------------------------------------------------- 1 | import { query } from 'Dictionary' 2 | import { type HighlightSettings } from 'Settings' 3 | 4 | const pattern = /([A-Za-zÀ-ÿ-]+|[0-9._]+|.|!|\?|'|"|:|;|,|-)/i 5 | 6 | const tokenize = (text: string | null): string[] => { 7 | return text ? text.split(pattern).filter((s) => s !== '') : [] 8 | } 9 | 10 | export const wrapTokens = async ( 11 | el: Node, 12 | settings: HighlightSettings, 13 | ): Promise => { 14 | if (el.nodeType === Node.TEXT_NODE) { 15 | let tokens = tokenize(el.textContent) 16 | const nodes = await Promise.all( 17 | tokens.map((word) => createNodes(word, settings)), 18 | ) 19 | const fragment = new DocumentFragment() 20 | fragment.append(...nodes) 21 | fragment.normalize() 22 | el.parentElement?.replaceChild(fragment, el) 23 | } else if (el.nodeType === Node.ELEMENT_NODE) { 24 | const children = el.childNodes 25 | for (let i = 0; i < children.length; i++) { 26 | wrapTokens(children[i], settings) 27 | } 28 | } 29 | } 30 | 31 | const createNodes = async ( 32 | token: string, 33 | settings: HighlightSettings, 34 | ): Promise => { 35 | const rank = await query(token.toLowerCase()) 36 | 37 | // If word is not found, return a text node 38 | if (!rank) { 39 | return document.createTextNode(token) 40 | } 41 | // Create a span element 42 | const span = document.createElement('span') 43 | span.textContent = token 44 | span.className = 'vocab-hl' 45 | 46 | // Determine the class name based on the rank 47 | const getClassName = () => { 48 | const levels = [ 49 | settings.basic, 50 | settings.intermediate, 51 | settings.advanced, 52 | settings.specialized, 53 | settings.idiomatic, 54 | ] 55 | 56 | span.dataset.rank = `${rank}` 57 | for (let i = 0; i < levels.length; i++) { 58 | if (rank < levels[i].rank) { 59 | if (levels[i].enabled) { 60 | return `vocab-hl-${i + 1}` 61 | } else { 62 | return '' 63 | } 64 | } 65 | } 66 | 67 | return '' 68 | } 69 | span.className = `vocab-hl ${getClassName()}` 70 | 71 | return span 72 | } 73 | 74 | export const rerender = (settings: HighlightSettings) => { 75 | 76 | const levels: ( 77 | | 'basic' 78 | | 'intermediate' 79 | | 'advanced' 80 | | 'specialized' 81 | | 'idiomatic' 82 | )[] = ['basic', 'intermediate', 'advanced', 'specialized', 'idiomatic'] 83 | 84 | let hover = '140%' 85 | 86 | if (!settings.enabled) { 87 | hover = '100%' 88 | } 89 | 90 | document.documentElement.style.setProperty( 91 | '--voab-hl-hover-brightness', 92 | hover, 93 | ) 94 | 95 | for (let i = 0; i < levels.length; i++) { 96 | // set background color 97 | document.documentElement.style.setProperty( 98 | `--vocab-hl-${levels[i]}`, 99 | settings[levels[i]].bg, 100 | ) 101 | 102 | // set opacity 103 | document.documentElement.style.setProperty( 104 | `--vocab-hl-${levels[i]}-opacity`, 105 | `${settings.translucency}`, 106 | ) 107 | 108 | if (!settings[levels[i]].enabled || !settings.enabled) { 109 | document.documentElement.style.setProperty( 110 | `--vocab-hl-${levels[i]}-opacity`, 111 | '0', 112 | ) 113 | } 114 | } 115 | 116 | // FIXME: only rank changed 117 | const words = document.querySelectorAll('.vocab-hl[data-rank]') 118 | words.forEach((word) => { 119 | let rankstr = word.getAttribute('data-rank') 120 | for (let i = 0; i < levels.length; i++) { 121 | if (rankstr && parseInt(rankstr) < settings[levels[i]].rank) { 122 | word.className = `vocab-hl vocab-hl-${i + 1}` 123 | break 124 | } 125 | } 126 | }) 127 | } 128 | -------------------------------------------------------------------------------- /src/Settings.ts: -------------------------------------------------------------------------------- 1 | import { rerender } from "Highlighter" 2 | 3 | export interface HighlightSettings { 4 | basic: Highlight 5 | intermediate: Highlight 6 | advanced: Highlight 7 | specialized: Highlight 8 | idiomatic: Highlight 9 | translucency: number 10 | enabled: boolean 11 | globalProcessor: boolean, 12 | } 13 | 14 | interface Highlight { 15 | bg: string 16 | fg: string 17 | rank: number 18 | enabled: boolean 19 | } 20 | 21 | export const DEFAULT_SETTINGS: HighlightSettings = { 22 | 23 | translucency: 0.5, 24 | enabled: true, 25 | globalProcessor: true, 26 | basic: { 27 | bg: '48, 51, 64', 28 | fg: 'black', 29 | rank: 6000, 30 | enabled: true, 31 | }, 32 | intermediate: { 33 | bg: '208, 103, 157', 34 | fg: 'black', 35 | rank: 16500, 36 | enabled: true, 37 | }, 38 | advanced: { 39 | bg: '95, 179, 161', 40 | fg: 'black', 41 | rank: 30000, 42 | enabled: true, 43 | }, 44 | specialized: { 45 | bg: '173, 215, 255', 46 | fg: 'black', 47 | rank: 45000, 48 | enabled: true, 49 | }, 50 | idiomatic: { 51 | bg: '115, 144, 170', 52 | fg: 'black', 53 | rank: 240000, 54 | enabled: true, 55 | }, 56 | } 57 | 58 | let settings: HighlightSettings = { ...DEFAULT_SETTINGS } 59 | 60 | export const getSettings = (): HighlightSettings => settings 61 | 62 | export const updateSettings = ( 63 | updated: Partial, 64 | ): HighlightSettings => { 65 | settings = { ...settings, ...updated } 66 | rerender(settings) 67 | return settings 68 | } 69 | -------------------------------------------------------------------------------- /src/SettingsTab.ts: -------------------------------------------------------------------------------- 1 | import { Setting, PluginSettingTab } from 'obsidian' 2 | import type VocabHightlightPlugin from 'main' 3 | import { getSettings, updateSettings, DEFAULT_SETTINGS } from 'Settings' 4 | import { getSettingDesc } from 'utils' 5 | 6 | export default class HighlistSettingsTab extends PluginSettingTab { 7 | 8 | private plugin: VocabHightlightPlugin 9 | 10 | constructor({ plugin }: { plugin: VocabHightlightPlugin }) { 11 | super(plugin.app, plugin) 12 | this.plugin = plugin 13 | } 14 | 15 | public async saveSettings(update?: boolean): Promise { 16 | await this.plugin.saveSettings() 17 | if (update) { 18 | this.display() 19 | } 20 | } 21 | 22 | private createHighlightSetting( 23 | containerEl: HTMLElement, 24 | category: 25 | | 'basic' 26 | | 'intermediate' 27 | | 'advanced' 28 | | 'specialized' 29 | | 'idiomatic', 30 | ): void { 31 | new Setting(containerEl) 32 | .setName(category.charAt(0).toUpperCase() + category.slice(1)) 33 | .addSlider((slide) => { 34 | slide.setLimits(0, 240000, 100).setDynamicTooltip() 35 | slide.setValue(getSettings()[category].rank) 36 | slide.onChange((value) => { 37 | let s = getSettings() 38 | updateSettings({ 39 | [category]: { ...s[category], rank: value }, 40 | }) 41 | this.plugin.saveSettings() 42 | }) 43 | }) 44 | .addColorPicker((picker) => { 45 | let [r, g, b] = getSettings()[category].bg.split(',') 46 | picker.setValueRgb({ r: +r, g: +g, b: +b }) 47 | 48 | picker.onChange(() => { 49 | const { r, g, b } = picker.getValueRgb() 50 | let s = getSettings() 51 | updateSettings({ 52 | [category]: { ...s[category], bg: `${r}, ${g}, ${b}` }, 53 | }) 54 | this.plugin.saveSettings() 55 | }) 56 | }) 57 | .addToggle((toggle) => { 58 | toggle.setValue(getSettings()[category].enabled) 59 | toggle.onChange((value) => { 60 | let s = getSettings() 61 | updateSettings({ 62 | [category]: { ...s[category], enabled: value }, 63 | }) 64 | this.plugin.saveSettings() 65 | }) 66 | }) 67 | } 68 | 69 | public display() { 70 | const { containerEl } = this 71 | containerEl.empty() 72 | 73 | new Setting(containerEl) 74 | .setName('Global highlight processor') 75 | .setDesc(createFragment(getSettingDesc)) 76 | .addToggle((toggle) => { 77 | toggle.setValue(getSettings().globalProcessor) 78 | toggle.onChange((value) => { 79 | updateSettings({ 80 | globalProcessor: value, 81 | }) 82 | this.plugin.saveSettings() 83 | }) 84 | }) 85 | 86 | new Setting(containerEl).setName('Translucency').addSlider((slide) => { 87 | slide.setDynamicTooltip() 88 | slide.setLimits(0, 1, 0.05) 89 | slide.setValue(getSettings().translucency) 90 | slide.onChange((value) => { 91 | updateSettings({ 92 | translucency: value, 93 | }) 94 | this.plugin.saveSettings() 95 | }) 96 | }) 97 | 98 | this.createHighlightSetting(containerEl, 'basic') 99 | this.createHighlightSetting(containerEl, 'intermediate') 100 | this.createHighlightSetting(containerEl, 'advanced') 101 | this.createHighlightSetting(containerEl, 'specialized') 102 | this.createHighlightSetting(containerEl, 'idiomatic') 103 | 104 | new Setting(containerEl).addButton((button) => { 105 | button.setButtonText('Reset') 106 | button.onClick(() => { 107 | updateSettings(DEFAULT_SETTINGS) 108 | this.plugin.saveSettings() 109 | this.saveSettings(true) 110 | }) 111 | }) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Plugin, setTooltip } from 'obsidian' 2 | import { wrapTokens } from 'Highlighter' 3 | import HighlistSettingsTab from 'SettingsTab' 4 | import { getSettings, updateSettings } from 'Settings' 5 | import { Platform } from 'obsidian' 6 | 7 | export default class VocabHighlighterPlugin extends Plugin { 8 | async onload() { 9 | await this.loadSettings() 10 | 11 | this.registerMarkdownPostProcessor((element, ctx) => { 12 | const settings = getSettings() 13 | const { cssclasses } = ctx.frontmatter || { cssclasses: [] } 14 | const sholdProcess: boolean = 15 | settings.globalProcessor || 16 | !!cssclasses?.includes('enable-vocab-hl') 17 | if (sholdProcess && Platform.isDesktopApp) { 18 | wrapTokens(element, settings) 19 | element.addEventListener('mouseover', (e) => { 20 | console.log(settings.enabled) 21 | if (e.target instanceof HTMLElement && settings.enabled) { 22 | const ele = e.target as HTMLElement 23 | let { rank } = ele.dataset 24 | if (rank) { 25 | setTooltip(ele, rank, { 26 | delay: 500, 27 | placement: 'top', 28 | }) 29 | } 30 | } 31 | }) 32 | } 33 | }) 34 | 35 | this.addRibbonIcon('highlighter', 'Highlight vocabulary', () => { 36 | updateSettings({ enabled: !getSettings().enabled }) 37 | }) 38 | 39 | // toogle highlight command 40 | this.addCommand({ 41 | id: 'toggle-vocab-highlight', 42 | name: 'Toggle highlight', 43 | callback: () => { 44 | updateSettings({ enabled: !getSettings().enabled }) 45 | }, 46 | }) 47 | 48 | // setting tab 49 | this.addSettingTab(new HighlistSettingsTab({ plugin: this })) 50 | } 51 | 52 | onunload() {} 53 | 54 | async loadSettings() { 55 | updateSettings(await this.loadData()) 56 | } 57 | 58 | async saveSettings() { 59 | await this.saveData(getSettings()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export const getSettingDesc = (documentFragment: DocumentFragment) => { 2 | const div = documentFragment.createDiv() 3 | const p1 = document.createElement('p') 4 | p1.textContent = 5 | 'Whether highlight processor should be applied to all documents.' 6 | div.appendChild(p1) 7 | const p2 = document.createElement('p') 8 | p2.textContent = 'Disabling this then you can add ' 9 | const strong1 = document.createElement('strong') 10 | const i1 = document.createElement('i') 11 | i1.textContent = 'enable-vocab-hl' 12 | strong1.appendChild(i1) 13 | p2.appendChild(strong1) 14 | p2.appendChild(document.createTextNode(' to ')) 15 | const strong2 = document.createElement('strong') 16 | const i2 = document.createElement('i') 17 | i2.textContent = 'cssclasses' 18 | strong2.appendChild(i2) 19 | p2.appendChild(strong2) 20 | p2.appendChild(document.createTextNode(' in your frontmatter')) 21 | const br = document.createElement('br') 22 | p2.appendChild(br) 23 | p2.appendChild( 24 | document.createTextNode('to enable processor for certain documents.'), 25 | ) 26 | div.appendChild(p2) 27 | 28 | const p3 = document.createElement('p') 29 | p3.textContent = 30 | 'Sometimes, it may be necessary to reopen a file to allow the processor to reapply the highlights' 31 | div.appendChild(p3) 32 | 33 | const p4 = document.createElement('p') 34 | const b = document.createElement('b') 35 | b.textContent = 'Important: ' 36 | const code = document.createElement('code') 37 | code.textContent = 'toggle highlight' 38 | b.appendChild(code) 39 | b.appendChild( 40 | document.createTextNode( 41 | ' command can only affects the documents that have been processed.', 42 | ), 43 | ) 44 | p4.appendChild(b) 45 | div.appendChild(p4) 46 | } 47 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vocab-hl-basic-opacity: 0.5; 3 | --vocab-hl-intermediate-opacity: 0.5; 4 | --vocab-hl-advanced-opacity: 0.5; 5 | --vocab-hl-specialized-opacity: 0.5; 6 | --vocab-hl-idiomatic-opacity: 0.5; 7 | --vocab-hl-basic: 68, 207, 110; 8 | --vocab-hl-intermediate: 83, 223, 221; 9 | --vocab-hl-advanced: 233, 151, 63; 10 | --vocab-hl-specialized: 2, 122, 255; 11 | --vocab-hl-idiomatic: 168, 130, 255; 12 | --vocab-hl-tooltip: none; 13 | --voab-hl-hover-brightness: 140%; 14 | } 15 | 16 | .vocab-hl { 17 | border-radius: 3px; 18 | } 19 | 20 | .vocab-hl:hover { 21 | filter: brightness(var(--voab-hl-hover-brightness)); 22 | 23 | } 24 | 25 | .vocab-hl.vocab-hl-1 { 26 | background-color: rgba( 27 | var(--vocab-hl-basic), 28 | var(--vocab-hl-basic-opacity) 29 | ); 30 | } 31 | 32 | .vocab-hl.vocab-hl-2 { 33 | background-color: rgba( 34 | var(--vocab-hl-intermediate), 35 | var(--vocab-hl-intermediate-opacity) 36 | ); 37 | } 38 | 39 | .vocab-hl.vocab-hl-3 { 40 | background-color: rgba( 41 | var(--vocab-hl-advanced), 42 | var(--vocab-hl-advanced-opacity) 43 | ); 44 | } 45 | 46 | .vocab-hl.vocab-hl-4 { 47 | background-color: rgba( 48 | var(--vocab-hl-specialized), 49 | var(--vocab-hl-specialized-opacity) 50 | ); 51 | } 52 | 53 | .vocab-hl.vocab-hl-5 { 54 | background-color: rgba( 55 | var(--vocab-hl-idiomatic), 56 | var(--vocab-hl-idiomatic-opacity) 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /tests/lib/DomUtil.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | -------------------------------------------------------------------------------- /tsconfig.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "verbatimModuleSyntax": false 5 | } 6 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["svelte", "jest", "node"], 5 | "baseUrl": "./src", 6 | "inlineSources": true, 7 | "module": "ESNext", 8 | "target": "ES6", 9 | "allowJs": true, 10 | "noImplicitAny": true, 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "isolatedModules": true, 14 | "strictNullChecks": true, 15 | "resolveJsonModule": true, 16 | "lib": ["DOM", "ES5", "ES6", "ES7"] 17 | }, 18 | "include": ["src/**/*", "tests/**/*"] 19 | } 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.15.0" 3 | } 4 | --------------------------------------------------------------------------------