import {
Platform,
Plugin,
PluginSettingTab,
Setting,
SliderComponent,
} from 'obsidian'
import { writable } from 'svelte/store'
import type OmnisearchPlugin from './main'
interface WeightingSettings {
weightBasename: number
weightH1: number
weightH2: number
weightH3: number
}
export interface OmnisearchSettings extends WeightingSettings {
/** Respect the "excluded files" Obsidian setting by downranking results ignored files */
respectExcluded: boolean
/** Ignore diacritics when indexing files */
ignoreDiacritics: boolean
/** Extensions of plain text files to index, in addition to .md */
indexedFileTypes: string[]
/** Enable PDF indexing */
PDFIndexing: boolean
/** Max number of spawned processes for background tasks, such as extracting text from PDFs */
backgroundProcesses: number
/** Write cache files on disk (unrelated to PDFs) */
// persistCache: boolean
/** Display Omnisearch popup notices over Obsidian */
showIndexingNotices: boolean
/** Activate the small 🔍 button on Obsidian's ribbon */
ribbonIcon: boolean
/** Display short filenames in search results, instead of the full path */
showShortName: boolean
/** Display the small contextual excerpt in search results */
showExcerpt: boolean
/** Enable a "create note" button in the Vault Search modal */
showCreateButton: boolean
/** Vim mode shortcuts */
CtrlJK: boolean
/** Vim mode shortcuts */
CtrlNP: boolean
/** Key for the welcome message when Obsidian is updated. A message is only shown once. */
welcomeMessage: string
}
/**
* A store to reactively toggle the `showExcerpt` setting on the fly
*/
export const showExcerpt = writable(false)
export class SettingsTab extends PluginSettingTab {
plugin: OmnisearchPlugin
constructor(plugin: OmnisearchPlugin) {
super(app, plugin)
this.plugin = plugin
showExcerpt.subscribe(async v => {
settings.showExcerpt = v
await saveSettings(this.plugin)
})
}
display(): void {
const { containerEl } = this
containerEl.empty()
// Settings main title
containerEl.createEl('h2', { text: 'Omnisearch settings' })
// Sponsor link - Thank you!
const divSponsor = containerEl.createDiv()
divSponsor.innerHTML = `
`
// #region Behavior
new Setting(containerEl).setName('Behavior').setHeading()
// Respect excluded files
new Setting(containerEl)
.setName('Respect Obsidian\'s "Excluded Files"')
.setDesc(
'Files that are in Obsidian\'s "Options > Files & Links > Excluded Files" list will be downranked in results.'
)
.addToggle(toggle =>
toggle.setValue(settings.respectExcluded).onChange(async v => {
settings.respectExcluded = v
await saveSettings(this.plugin)
})
)
// Ignore diacritics
const diacriticsDesc = new DocumentFragment()
diacriticsDesc.createSpan({}, span => {
span.innerHTML = `Normalize diacritics in search terms. Words like "brûlée" or "žluťoučký" will be indexed as "brulee" and "zlutoucky".
Needs a restart to fully take effect.`
})
new Setting(containerEl)
.setName('Ignore diacritics')
.setDesc(diacriticsDesc)
.addToggle(toggle =>
toggle.setValue(settings.ignoreDiacritics).onChange(async v => {
settings.ignoreDiacritics = v
await saveSettings(this.plugin)
})
)
// Additional files to index
const indexedFileTypesDesc = new DocumentFragment()
indexedFileTypesDesc.createSpan({}, span => {
span.innerHTML = `In addition to standard md files, Omnisearch can also index other plain text files.
Add extensions separated by a space. Example: txt org.
Needs a restart to fully take effect.`
})
new Setting(containerEl)
.setName('Additional files to index')
.setDesc(indexedFileTypesDesc)
.addText(component => {
component
.setValue(settings.indexedFileTypes.join(' '))
.setPlaceholder('Example: txt org')
.onChange(async v => {
settings.indexedFileTypes = v.split(' ')
await saveSettings(this.plugin)
})
})
// // Background processes
// new Setting(containerEl)
// .setName(
// `Background processes (default: ${DEFAULT_SETTINGS.backgroundProcesses})`
// )
// .setDesc('The maximum number of processes for background work, like PDF indexing. This value should not be higher than your number of CPU cores.')
// .addSlider(cb => {
// cb.setLimits(1, 16, 1)
// .setValue(settings.backgroundProcesses)
// .setDynamicTooltip()
// .onChange(v => {
// settings.backgroundProcesses = v
// saveSettings(this.plugin)
// })
// })
// // Store index
// const serializedIndexDesc = new DocumentFragment()
// serializedIndexDesc.createSpan({}, span => {
// span.innerHTML = `This will speedup startup times after the initial indexing. Do not activate it unless indexing is too slow on your device:
//
.obsidian/plugins/omnisearch/*.data must not be synchronized between your devices.shift ↵ shortcut, can be useful for mobile device users.`
})
new Setting(containerEl)
.setName('Show "Create note" button')
.setDesc(createBtnDesc)
.addToggle(toggle =>
toggle.setValue(settings.showCreateButton).onChange(async v => {
settings.showCreateButton = v
await saveSettings(this.plugin)
})
)
// Show notices
new Setting(containerEl)
.setName('Show indexing notices')
.setDesc('Shows a notice when indexing is done, usually at startup.')
.addToggle(toggle =>
toggle.setValue(settings.showIndexingNotices).onChange(async v => {
settings.showIndexingNotices = v
await saveSettings(this.plugin)
})
)
// Display note names without the full path
new Setting(containerEl)
.setName('Hide full path in results list')
.setDesc(
'In the search results, only show the note name, without the full path.'
)
.addToggle(toggle =>
toggle.setValue(settings.showShortName).onChange(async v => {
settings.showShortName = v
await saveSettings(this.plugin)
})
)
// #endregion User Interface
// #region Results Weighting
new Setting(containerEl).setName('Results weighting').setHeading()
new Setting(containerEl)
.setName(
`File name & declared aliases (default: ${DEFAULT_SETTINGS.weightBasename})`
)
.addSlider(cb => this.weightSlider(cb, 'weightBasename'))
new Setting(containerEl)
.setName(`Headings level 1 (default: ${DEFAULT_SETTINGS.weightH1})`)
.addSlider(cb => this.weightSlider(cb, 'weightH1'))
new Setting(containerEl)
.setName(`Headings level 2 (default: ${DEFAULT_SETTINGS.weightH2})`)
.addSlider(cb => this.weightSlider(cb, 'weightH2'))
new Setting(containerEl)
.setName(`Headings level 3 (default: ${DEFAULT_SETTINGS.weightH3})`)
.addSlider(cb => this.weightSlider(cb, 'weightH3'))
// #endregion Results Weighting
// #region Shortcuts
new Setting(containerEl).setName('Shortcuts').setHeading()
new Setting(containerEl)
.setName(
'Use [Ctrl/Cmd]+j/k to navigate up/down in the results, if Vim mode is enabled'
)
.addToggle(toggle =>
toggle.setValue(settings.CtrlJK).onChange(async v => {
settings.CtrlJK = v
await saveSettings(this.plugin)
})
)
new Setting(containerEl)
.setName(
'Use [Ctrl/Cmd]+n/p to navigate up/down in the results, if Vim mode is enabled'
)
.addToggle(toggle =>
toggle.setValue(settings.CtrlNP).onChange(async v => {
settings.CtrlNP = v
await saveSettings(this.plugin)
})
)
// #endregion Shortcuts
}
weightSlider(cb: SliderComponent, key: keyof WeightingSettings): void {
cb.setLimits(1, 3, 0.1)
.setValue(settings[key])
.setDynamicTooltip()
.onChange(v => {
settings[key] = v
saveSettings(this.plugin)
})
}
}
// Determining the maximum concurrent processes/workers/promises for heavy work,
// without hogging all the resources.
const cpuCount = Platform.isMobileApp ? 1 : require('os').cpus().length
let backgroundProcesses = Math.max(1, Math.floor(cpuCount * 0.75))
if (backgroundProcesses == cpuCount) {
backgroundProcesses = 1
}
export const DEFAULT_SETTINGS: OmnisearchSettings = {
respectExcluded: true,
ignoreDiacritics: true,
indexedFileTypes: [] as string[],
PDFIndexing: false,
backgroundProcesses,
showIndexingNotices: false,
showShortName: false,
ribbonIcon: true,
showExcerpt: true,
showCreateButton: false,
weightBasename: 2,
weightH1: 1.5,
weightH2: 1.3,
weightH3: 1.1,
CtrlJK: false,
CtrlNP: false,
// persistCache: false,
welcomeMessage: '',
} as const
export let settings = Object.assign({}, DEFAULT_SETTINGS) as OmnisearchSettings
export async function loadSettings(plugin: Plugin): Promise