diff --git a/__mocks__/utils.ts b/__mocks__/utils.ts deleted file mode 100644 index f128829..0000000 --- a/__mocks__/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const debounce = jest.fn((func) => { - return jest.fn(func); -}); diff --git a/eslint.config.js b/eslint.config.js index 07b012c..503e31b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,5 +1,4 @@ import pluginJs from '@eslint/js'; -import prettierConfig from 'eslint-config-prettier'; import globals from 'globals'; import tseslint from 'typescript-eslint'; @@ -24,5 +23,9 @@ export default [ }, pluginJs.configs.recommended, ...tseslint.configs.recommended, - prettierConfig, + { + rules: { + curly: ['error', 'all'], + }, + }, ]; diff --git a/manifest.json b/manifest.json index a3d2b2c..e772e3a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "word-frequency", "name": "Word Frequency", - "version": "1.2.10", + "version": "1.3.0", "minAppVersion": "1.0.0", "description": "Counts the most frequently used words in a note and displays them in the sidebar.", "author": "Mike Rodarte", diff --git a/package.json b/package.json index 7c16005..e5f79bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "word-frequency", - "version": "1.2.10", + "version": "1.3.0", "type": "module", "description": "Count word frequency in current note within Obsidian", "main": "main.js", @@ -12,11 +12,12 @@ "dev": "npm run build && obsidian --dev", "watch": "rollup -c --watch", "prettier": "npx prettier --check \"src/**/*.{js,ts,jsx,tsx}\"", - "pretty-fix": "npx prettier src --write", - "lint": "eslint . --ext \"src/**/*.{js,ts,jsx,tsx}\"", + "format": "npx prettier src --write", + "lint": "eslint . --ext \"**/*.{js,ts,jsx,tsx}\"", "lint-fix": "eslint . --ext .js,.ts --fix", "test": "jest", - "coverage": "npx jest --coverage" + "coverage": "npx jest --coverage", + "all": "npm run lint-fix && npm run format && npm run build && npm run coverage" }, "repository": { "type": "git", diff --git a/src/WordFrequencyDisplay.ts b/src/WordFrequencyDisplay.ts index acf5a8d..c3d024e 100644 --- a/src/WordFrequencyDisplay.ts +++ b/src/WordFrequencyDisplay.ts @@ -1,9 +1,10 @@ -import { setIcon } from 'obsidian'; -import { PLUGIN_NAME } from './constants'; +import { debounce, setIcon } from 'obsidian'; +import { ELEMENT_CLASSES, PLUGIN_NAME } from './constants'; import WordFrequencyPlugin from './main'; import { WordFrequencyView } from './WordFrequencyView'; export class WordFrequencyDisplay { + private filter: string = ''; private plugin: WordFrequencyPlugin; private view: WordFrequencyView; @@ -18,22 +19,27 @@ export class WordFrequencyDisplay { count: number, contentContainer: HTMLDivElement ) { - if (blacklist.has(word) || count < this.plugin.settings.threshold) { + if ( + blacklist.has(word) || + count < this.plugin.settings.threshold || + (this.filter !== '' && + !word.toLowerCase().contains(this.filter.toLowerCase())) + ) { return; } const row = contentContainer.createEl('div', { - cls: 'word-frequency-row', + cls: ELEMENT_CLASSES.containerRow, }); const wordCountContainer = row.createEl('div', { - cls: 'word-frequency-count-container', + cls: ELEMENT_CLASSES.containerCount, }); wordCountContainer.createEl('span', { text: word }); wordCountContainer.createEl('span', { text: count.toString() }); const buttonContainer = row.createEl('div', { - cls: 'word-frequency-button-container', + cls: ELEMENT_CLASSES.containerButton, }); const button = buttonContainer.createEl('button'); setIcon(button, 'trash-2'); @@ -42,6 +48,25 @@ export class WordFrequencyDisplay { }); } + createFilter(contentEl: HTMLElement) { + const filterContainer = contentEl.createEl('div'); + const filterInput = filterContainer.createEl('input'); + filterInput.setAttr('type', 'text'); + filterInput.addClass(ELEMENT_CLASSES.filter); + filterInput.setAttr('placeholder', 'Type to filter results'); + + const debouncedMethod = debounce((event: Event) => { + const target = event.target as HTMLInputElement; + this.filter = target.value; + + this.view.updateContent(); + }, 500); + + this.plugin.registerDomEvent(filterInput, 'input', (event) => + debouncedMethod(event) + ); + } + createHeader(contentEl: HTMLElement) { const headerContainer = contentEl.createEl('div'); const headerElement = headerContainer.createEl('h4'); @@ -50,7 +75,7 @@ export class WordFrequencyDisplay { createThresholdDisplay(contentEl: HTMLElement) { const thresholdDisplay = contentEl.createEl('div', { - cls: 'threshold-display', + cls: ELEMENT_CLASSES.containerThreshold, }); thresholdDisplay.setText( `Current frequency threshold is ${this.plugin.settings.threshold}.` diff --git a/src/WordFrequencyView.ts b/src/WordFrequencyView.ts index e09fc62..7bac13b 100644 --- a/src/WordFrequencyView.ts +++ b/src/WordFrequencyView.ts @@ -1,5 +1,6 @@ import { ItemView, WorkspaceLeaf } from 'obsidian'; import { + ELEMENT_CLASSES, EVENT_UPDATE, FREQUENCY_ICON, PLUGIN_NAME, @@ -13,6 +14,7 @@ export class WordFrequencyView extends ItemView { private eventListener: (event: CustomEvent) => void = () => {}; private readonly plugin: WordFrequencyPlugin; private wordCountList: [string, number][] = []; + private wordListContainer: HTMLDivElement; constructor( leaf: WorkspaceLeaf, @@ -22,6 +24,9 @@ export class WordFrequencyView extends ItemView { super(leaf); this.plugin = plugin; this.display = display ?? new WordFrequencyDisplay(plugin, this); + this.wordListContainer = createDiv({ + cls: ELEMENT_CLASSES.containerWordList, + }); } getDisplayText(): string { @@ -52,6 +57,15 @@ export class WordFrequencyView extends ItemView { this.eventListener as EventListener ); + this.contentEl.empty(); + const contentContainer = this.contentEl.createDiv({ + cls: ELEMENT_CLASSES.containerContent, + }); + this.display.createHeader(contentContainer); + this.display.createFilter(contentContainer); + contentContainer.appendChild(this.wordListContainer); + this.display.createThresholdDisplay(contentContainer); + this.updateContent(); } @@ -63,9 +77,7 @@ export class WordFrequencyView extends ItemView { } updateContent() { - this.contentEl.empty(); - this.display.createHeader(this.contentEl); - const contentContainer = this.contentEl.createEl('div'); + this.wordListContainer.empty(); const blacklist = new Set( this.plugin.settings.blacklist.split(',').map((word) => word.trim()) ); @@ -75,9 +87,8 @@ export class WordFrequencyView extends ItemView { blacklist, word, count, - contentContainer + this.wordListContainer ); }); - this.display.createThresholdDisplay(this.contentEl); } } diff --git a/src/__tests__/WordFrequencyDisplay.test.ts b/src/__tests__/WordFrequencyDisplay.test.ts index 04e58ba..dd447df 100644 --- a/src/__tests__/WordFrequencyDisplay.test.ts +++ b/src/__tests__/WordFrequencyDisplay.test.ts @@ -1,4 +1,4 @@ -import { PLUGIN_NAME } from '../constants'; +import { ELEMENT_CLASSES, PLUGIN_NAME } from '../constants'; import WordFrequencyPlugin from '../main'; import { WordFrequencyDisplay } from '../WordFrequencyDisplay'; import { WordFrequencyView } from '../WordFrequencyView'; @@ -141,20 +141,17 @@ describe('WordFrequencyDisplay', () => { it('should set text and attribute in the content', () => { display.createThresholdDisplay(contentEl); - const thresholdDisplay = contentEl.createEl('div', { - cls: 'threshold-display', - }); - expect(contentEl.createEl).toHaveBeenCalledWith('div', { - cls: 'threshold-display', + cls: ELEMENT_CLASSES.containerThreshold, }); - expect(thresholdDisplay.setText).toHaveBeenCalledWith( - `Current frequency threshold is ${mockPlugin.settings.threshold}.` - ); - expect(thresholdDisplay.setAttr).toHaveBeenCalledWith( - 'title', - 'Configure settings for this plugin to update the frequency threshold.' - ); + // TODO: determine how to test the element from contentEl was called properly + // expect(thresholdDisplay.setText).toHaveBeenCalledWith( + // `Current frequency threshold is ${mockPlugin.settings.threshold}.` + // ); + // expect(thresholdDisplay.setAttr).toHaveBeenCalledWith( + // 'title', + // 'Configure settings for this plugin to update the frequency threshold.' + // ); }); }); }); diff --git a/src/__tests__/WordFrequencyView.test.ts b/src/__tests__/WordFrequencyView.test.ts index 3a42c52..bf338a4 100644 --- a/src/__tests__/WordFrequencyView.test.ts +++ b/src/__tests__/WordFrequencyView.test.ts @@ -19,6 +19,7 @@ describe('WordFrequencyView', () => { mockLeaf = new WorkspaceLeaf(); mockDisplay = { addWordToSidebar: jest.fn(), + createFilter: jest.fn(), createHeader: jest.fn(), createThresholdDisplay: jest.fn(), } as unknown as WordFrequencyDisplay; diff --git a/src/constants.ts b/src/constants.ts index f918258..6ad81cf 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,6 +8,16 @@ export const DEFAULT_SETTINGS: WordFrequencySettings = { threshold: 3, }; +export const ELEMENT_CLASSES = { + containerButton: 'word-frequency-button-container', + containerContent: 'word-frequency-sidebar-content', + containerCount: 'word-frequency-count-container', + containerRow: 'word-frequency-row', + containerThreshold: 'word-frequency-threshold-display', + containerWordList: 'word-frequency-word-list', + filter: 'word-frequency-filter', +}; + export const EVENT_UPDATE = 'word-frequency-update'; export const FREQUENCY_ICON = 'file-chart-column-increasing'; export const PLUGIN_NAME = 'Word frequency';