├── .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 | 
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 | [
](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 |
--------------------------------------------------------------------------------