obsidian/.obsidian/plugins/word-frequency/main.js

365 lines
13 KiB
JavaScript

'use strict';
var obsidian = require('obsidian');
const DEFAULT_SETTINGS = {
blacklist: 'the,and,to,of,a,in,for,on,is,it,that,with,as,this,by,your,you',
threshold: 3,
};
const ELEMENT_CLASSES = {
containerButton: 'word-frequency-button-container',
containerContent: 'word-frequency-sidebar-content',
containerCount: 'word-frequency-count-container',
containerFilter: 'word-frequency-filter-container',
containerRow: 'word-frequency-row',
containerThreshold: 'word-frequency-threshold-display',
containerWordList: 'word-frequency-word-list',
filter: 'word-frequency-filter',
settingBlacklist: 'word-frequency-setting-blacklist',
settingInfoItem: 'word-frequency-setting-item-info',
settingItem: 'word-frequency-setting-item',
};
const COMMAND_ID_SHOW_SIDEBAR = 'word-frequency-show-sidebar';
const EVENT_UPDATE = 'word-frequency-update';
const FREQUENCY_ICON = 'file-chart-column-increasing';
const PLUGIN_NAME = 'Word frequency';
const SETTINGS_DESCRIPTIONS = {
blacklist: 'Comma-separated list of words to exclude.',
threshold: 'Only show words that appear at least this many times.',
};
const SETTINGS_NAMES = {
blacklist: 'Blacklist',
threshold: 'Word frequency threshold',
};
const VIEW_TYPE = 'word-frequency-view';
class ViewManager {
plugin;
constructor(plugin) {
this.plugin = plugin;
}
getOrCreateLeaf(workspace, viewType) {
const leaves = workspace.getLeavesOfType(viewType);
if (leaves.length > 0) {
return leaves[0];
}
return workspace.getRightLeaf(false);
}
async setViewState(leaf, viewType) {
await leaf.setViewState({
type: viewType,
active: true,
});
}
updateContent() {
const editor = this.plugin.app.workspace.getActiveViewOfType(obsidian.MarkdownView)?.editor;
this.plugin.frequencyCounter.triggerUpdateContent(editor);
}
}
function segmentText(content) {
const normalized = content.toLowerCase().normalize('NFKC');
const stripped = normalized.replace(/[^\p{L}\p{N}\s]+/gu, '');
return stripped
.trim()
.split(/\s+/)
.filter((word) => word.length > 0);
}
class WordFrequencyCounter {
debouncedEditorChange;
lastActiveEditor;
plugin;
constructor(plugin,
/* eslint-disable no-unused-vars */
debouncedEditorChange = obsidian.debounce((editor) => this.triggerUpdateContent(editor), 3000)
/* eslint-enable no-unused-vars */
) {
this.debouncedEditorChange = debouncedEditorChange;
this.plugin = plugin;
}
calculateWordFrequencies(content) {
if (content.length === 0) {
return [];
}
const words = segmentText(content);
const wordCounts = new Map();
for (const word of words) {
wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
}
return Array.from(wordCounts.entries()).sort(([, countA], [, countB]) => countB - countA);
}
handleActiveLeafChange(leaf, workspace) {
if (leaf === null) {
return;
}
if (!(leaf.view instanceof obsidian.MarkdownView)) {
return;
}
this.plugin.registerEvent(workspace.on('editor-change', (editor) => this.debouncedEditorChange(editor)));
const activeView = workspace.getActiveViewOfType(obsidian.MarkdownView);
if (activeView) {
this.lastActiveEditor = activeView.editor;
}
if (workspace.getLeavesOfType(VIEW_TYPE).length > 0) {
this.triggerUpdateContent(this.lastActiveEditor);
}
}
triggerUpdateContent(editor) {
if (editor === undefined) {
if (this.lastActiveEditor === undefined) {
return;
}
editor = this.lastActiveEditor;
}
try {
const wordCounts = this.calculateWordFrequencies(editor.getValue());
window.document.dispatchEvent(new CustomEvent(EVENT_UPDATE, { detail: { wordCounts } }));
}
catch (error) {
console.error('error in triggerUpdateContent', error);
}
}
}
class WordFrequencyDisplay {
getFilter;
debouncedFilterInput;
filter = '';
plugin;
view;
constructor(plugin, view,
/* eslint-disable no-unused-vars */
getFilter = () => this.filter, debouncedFilterInput = obsidian.debounce((event) => {
const target = event.target;
this.filter = target.value;
this.view.updateContent();
}, 500)
/* eslint-enable no-unused-vars */
) {
this.getFilter = getFilter;
this.debouncedFilterInput = debouncedFilterInput;
this.plugin = plugin;
this.view = view;
}
addWordToSidebar(blacklist, word, count, contentContainer) {
if (blacklist.has(word) ||
count < this.plugin.settings.threshold ||
!word.toLowerCase().includes(this.getFilter().toLowerCase())) {
return;
}
const row = contentContainer.createEl('div', {
cls: ELEMENT_CLASSES.containerRow,
});
const wordCountContainer = row.createEl('div', {
cls: ELEMENT_CLASSES.containerCount,
});
wordCountContainer.createEl('span', { text: word });
wordCountContainer.createEl('span', { text: count.toString() });
const buttonContainer = row.createEl('div', {
cls: ELEMENT_CLASSES.containerButton,
});
const button = buttonContainer.createEl('button');
obsidian.setIcon(button, 'trash-2');
this.plugin.registerDomEvent(button, 'click', () => {
this.saveWordToBlacklist(word);
});
}
createFilter(contentEl) {
const filterContainer = contentEl.createEl('div', {
cls: ELEMENT_CLASSES.containerFilter,
});
const filterInput = filterContainer.createEl('input', {
cls: ELEMENT_CLASSES.filter,
attr: {
type: 'text',
placeholder: 'Type to filter results',
},
});
this.plugin.registerDomEvent(filterInput, 'input', (event) => this.debouncedFilterInput(event));
}
createHeader(contentEl) {
const headerContainer = contentEl.createEl('div');
const headerElement = headerContainer.createEl('h4');
headerElement.setText(PLUGIN_NAME);
}
createThresholdDisplay(contentEl) {
const thresholdDisplay = contentEl.createEl('div', {
cls: ELEMENT_CLASSES.containerThreshold,
});
thresholdDisplay.setText(`Current frequency threshold is ${this.plugin.settings.threshold}.`);
thresholdDisplay.setAttr('title', 'Configure settings for this plugin to update the frequency threshold.');
}
saveWordToBlacklist(word) {
const settings = this.plugin.settings;
settings.blacklist += `,${word}`;
this.plugin.saveData(settings);
this.view.updateContent();
}
}
class WordFrequencyView extends obsidian.ItemView {
display;
eventListener = () => { };
plugin;
wordCountList = [];
wordListContainer;
constructor(leaf, plugin, display, divElement) {
super(leaf);
this.plugin = plugin;
this.display = display ?? new WordFrequencyDisplay(plugin, this);
// TODO: find a better way to set a default value
this.wordListContainer = divElement ?? this.containerEl.createDiv();
}
getDisplayText() {
return PLUGIN_NAME;
}
getIcon() {
return FREQUENCY_ICON;
}
getPlugin() {
return this.plugin;
}
getViewType() {
return VIEW_TYPE;
}
async onOpen() {
this.eventListener = (event) => {
if (event.type === EVENT_UPDATE) {
this.wordCountList = event.detail.wordCounts;
this.updateContent();
}
};
window.document.addEventListener(EVENT_UPDATE, this.eventListener);
this.contentEl.empty();
const contentContainer = this.contentEl.createDiv({
cls: ELEMENT_CLASSES.containerContent,
});
this.display.createHeader(contentContainer);
this.display.createFilter(contentContainer);
this.wordListContainer = contentContainer.createDiv({
cls: ELEMENT_CLASSES.containerWordList,
});
this.display.createThresholdDisplay(contentContainer);
this.updateContent();
}
async onClose() {
window.document.removeEventListener(EVENT_UPDATE, this.eventListener);
}
updateContent() {
this.wordListContainer.empty();
const blacklist = new Set(this.plugin.settings.blacklist.split(',').map((word) => word.trim()));
this.wordCountList.forEach(([word, count]) => {
this.display.addWordToSidebar(blacklist, word, count, this.wordListContainer);
});
}
}
class WordFrequencySettingTab extends obsidian.PluginSettingTab {
plugin;
settingFactory;
constructor(plugin, settingFactory = (element) => new obsidian.Setting(element)) {
super(plugin.app, plugin);
this.plugin = plugin;
this.settingFactory = settingFactory;
}
display() {
const { containerEl } = this;
containerEl.empty();
const blacklist = this.settingFactory(containerEl)
.setName(SETTINGS_NAMES.blacklist)
.setDesc(SETTINGS_DESCRIPTIONS.blacklist)
.setClass(ELEMENT_CLASSES.settingItem)
.addTextArea((text) => {
text.setValue(this.plugin.settings.blacklist)
.onChange(async (value) => {
await this.saveBlacklistValue(value);
})
.inputEl.classList.add(ELEMENT_CLASSES.settingBlacklist);
});
blacklist.infoEl.addClass(ELEMENT_CLASSES.settingInfoItem);
this.settingFactory(containerEl)
.setName(SETTINGS_NAMES.threshold)
.setDesc(SETTINGS_DESCRIPTIONS.threshold)
.addText((text) => text
.setPlaceholder('3')
.setValue(this.plugin.settings.threshold.toString())
.onChange(async (value) => {
await this.updateThreshold(value);
}));
}
async saveBlacklistValue(value) {
this.plugin.settings.blacklist = value;
await this.plugin.saveSettings();
}
async updateThreshold(value) {
const num = parseInt(value, 10);
if (isNaN(num)) {
return;
}
this.plugin.settings.threshold = num;
await this.plugin.saveSettings();
this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE).forEach((leaf) => {
if (leaf.view instanceof WordFrequencyView) {
leaf.view.updateContent();
}
});
}
}
class WordFrequencyPlugin extends obsidian.Plugin {
createView;
frequencyCounter;
settings = DEFAULT_SETTINGS;
settingTab;
viewManager;
constructor(app, manifest, viewManager, settingTab, frequencyCounter,
/* eslint-disable no-unused-vars */
createView = (leaf) => new WordFrequencyView(leaf, this)
/* eslint-enable no-unused-vars */
) {
super(app, manifest);
this.createView = createView;
this.settingTab = settingTab ?? new WordFrequencySettingTab(this);
this.viewManager = viewManager ?? new ViewManager(this);
this.frequencyCounter =
frequencyCounter ?? new WordFrequencyCounter(this);
}
async onload() {
const title = `Show ${PLUGIN_NAME.toLowerCase()} sidebar`;
await this.loadSettings();
this.registerView(VIEW_TYPE, this.createView);
this.addRibbonIcon(FREQUENCY_ICON, title, () => this.activateView());
this.addCommand({
id: COMMAND_ID_SHOW_SIDEBAR,
name: title,
callback: () => this.activateView(),
});
this.registerEvent(this.app.workspace.on('active-leaf-change', (leaf) => {
this.frequencyCounter.handleActiveLeafChange(leaf, this.app.workspace);
}));
this.addSettingTab(this.settingTab);
}
onunload() { }
async activateView() {
const { workspace } = this.app;
const leaf = this.viewManager.getOrCreateLeaf(workspace, VIEW_TYPE);
if (leaf === null) {
return;
}
await this.viewManager.setViewState(leaf, VIEW_TYPE);
await workspace.revealLeaf(leaf);
this.viewManager.updateContent();
}
async saveSettings() {
await this.saveData(this.settings);
}
async loadSettings() {
const settings = await this.loadData();
this.settings = Object.assign({}, DEFAULT_SETTINGS, settings);
}
}
module.exports = WordFrequencyPlugin;
/* nosourcemap */