Merge branch 'develop'

# Conflicts:
#	manifest-beta.json
#	versions.json
This commit is contained in:
Simon Cambier
2023-05-27 12:46:19 +02:00
16 changed files with 153 additions and 48 deletions

View File

@@ -18,6 +18,7 @@
} }
.omnisearch-result__title { .omnisearch-result__title {
white-space: pre-wrap;
align-items: center; align-items: center;
display: flex; display: flex;
gap: 5px; gap: 5px;

View File

@@ -1,7 +1,7 @@
{ {
"id": "omnisearch", "id": "omnisearch",
"name": "Omnisearch", "name": "Omnisearch",
"version": "1.14.1-beta.3", "version": "1.14.1-beta.2",
"minAppVersion": "1.0.0", "minAppVersion": "1.0.0",
"description": "A search engine that just works", "description": "A search engine that just works",
"author": "Simon Cambier", "author": "Simon Cambier",

View File

@@ -1,6 +1,6 @@
{ {
"name": "scambier.obsidian-search", "name": "scambier.obsidian-search",
"version": "1.14.0", "version": "1.14.1-beta.3",
"description": "A search engine for Obsidian", "description": "A search engine for Obsidian",
"main": "dist/main.js", "main": "dist/main.js",
"scripts": { "scripts": {

6
pnpm-lock.yaml generated
View File

@@ -58,7 +58,7 @@ devDependencies:
esbuild-plugin-copy: 1.3.0_esbuild@0.14.0 esbuild-plugin-copy: 1.3.0_esbuild@0.14.0
esbuild-svelte: 0.7.1_wvi5wuag3veo5vm52k3h7pgaae esbuild-svelte: 0.7.1_wvi5wuag3veo5vm52k3h7pgaae
jest: 27.5.1 jest: 27.5.1
obsidian: 1.1.1 obsidian: 1.2.8
prettier: 2.8.1 prettier: 2.8.1
prettier-plugin-svelte: 2.8.1_sro2v6ld777payjtkjtiuogcxi prettier-plugin-svelte: 2.8.1_sro2v6ld777payjtkjtiuogcxi
svelte: 3.54.0 svelte: 3.54.0
@@ -4119,8 +4119,8 @@ packages:
object-keys: 1.1.1 object-keys: 1.1.1
dev: true dev: true
/obsidian/1.1.1: /obsidian/1.2.8:
resolution: {integrity: sha512-GcxhsHNkPEkwHEjeyitfYNBcQuYGeAHFs1pEpZIv0CnzSfui8p8bPLm2YKLgcg20B764770B1sYGtxCvk9ptxg==} resolution: {integrity: sha512-HrC+feA8o0tXspj4lEAqxb1btwLwHD2oHXSwbbN+CdRHURqbCkuIDLld+nkuyJ1w1c9uvVDRVk8BoeOnWheOrQ==}
peerDependencies: peerDependencies:
'@codemirror/state': ^6.0.0 '@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0 '@codemirror/view': ^6.0.0

View File

@@ -5,24 +5,26 @@
import { cacheManager } from '../cache-manager' import { cacheManager } from '../cache-manager'
export let initialValue = '' export let initialValue = ''
let initialSet = false
export let placeholder = '' export let placeholder = ''
let initialSet = false
let value = '' let value = ''
let elInput: HTMLInputElement let elInput: HTMLInputElement
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export function setInputValue(v:string): void { export function setInputValue(v: string): void {
value = v value = v
} }
$: { function watchInitialValue(v: string): void {
if (initialValue && !initialSet && !value) { if (v && !initialSet && !value) {
initialSet = true initialSet = true
value = initialValue value = v
selectInput() selectInput()
} }
} }
$: watchInitialValue(initialValue)
function selectInput(_?: HTMLElement): void { function selectInput(_?: HTMLElement): void {
tick() tick()
.then(() => { .then(() => {
@@ -39,14 +41,14 @@
// the next time we open the modal, the search field will be empty // the next time we open the modal, the search field will be empty
cacheManager.addToSearchHistory('') cacheManager.addToSearchHistory('')
dispatch('input', value) dispatch('input', value)
}, 250) }, 300)
</script> </script>
<div class="omnisearch-input-container"> <div class="omnisearch-input-container">
<div class="omnisearch-input-field"> <div class="omnisearch-input-field">
<input <input
bind:this="{elInput}" bind:this="{elInput}"
bind:value bind:value="{value}"
class="prompt-input" class="prompt-input"
use:selectInput use:selectInput
on:compositionend="{_ => toggleInputComposition(false)}" on:compositionend="{_ => toggleInputComposition(false)}"

View File

@@ -27,6 +27,7 @@
import * as NotesIndex from '../notes-index' import * as NotesIndex from '../notes-index'
import { cacheManager } from '../cache-manager' import { cacheManager } from '../cache-manager'
import { searchEngine } from 'src/search/omnisearch' import { searchEngine } from 'src/search/omnisearch'
import { cancelable, CancelablePromise } from 'cancelable-promise'
export let modal: OmnisearchVaultModal export let modal: OmnisearchVaultModal
export let previousQuery: string | undefined export let previousQuery: string | undefined
@@ -38,9 +39,24 @@
let indexingStepDesc = '' let indexingStepDesc = ''
let searching = true let searching = true
let refInput: InputSearch | undefined let refInput: InputSearch | undefined
let openInNewPaneKey: string
let openInCurrentPaneKey: string
let createInNewPaneKey: string
let createInCurrentPaneKey: string
$: selectedNote = resultNotes[selectedIndex] $: selectedNote = resultNotes[selectedIndex]
$: searchQuery = searchQuery ?? previousQuery $: searchQuery = searchQuery ?? previousQuery
$: if (settings.openInNewPane) {
openInNewPaneKey = '↵'
openInCurrentPaneKey = getCtrlKeyLabel() + ' ↵'
createInNewPaneKey = 'shift ↵'
createInCurrentPaneKey = getCtrlKeyLabel() + ' shift ↵'
} else {
openInNewPaneKey = getCtrlKeyLabel() + ' ↵'
openInCurrentPaneKey = '↵'
createInNewPaneKey = getCtrlKeyLabel() + ' shift ↵'
createInCurrentPaneKey = 'shift ↵'
}
$: if (searchQuery) { $: if (searchQuery) {
searching = true searching = true
updateResults().then(() => { updateResults().then(() => {
@@ -112,9 +128,20 @@
refInput?.setInputValue(searchQuery) refInput?.setInputValue(searchQuery)
} }
let cancelableQuery: CancelablePromise<ResultNote[]> | null = null
async function updateResults() { async function updateResults() {
// If search is already in progress, cancel it and start a new one
if (cancelableQuery) {
cancelableQuery.cancel()
cancelableQuery = null
}
query = new Query(searchQuery) query = new Query(searchQuery)
resultNotes = await searchEngine.getSuggestions(query) cancelableQuery = cancelable(
new Promise(resolve => {
resolve(searchEngine.getSuggestions(query))
})
)
resultNotes = await cancelableQuery
selectedIndex = 0 selectedIndex = 0
await scrollIntoView() await scrollIntoView()
} }
@@ -297,7 +324,7 @@
<span>to cycle history</span> <span>to cycle history</span>
</div> </div>
<div class="prompt-instruction"> <div class="prompt-instruction">
<span class="prompt-instruction-command"></span><span>to open</span> <span class="prompt-instruction-command">{openInCurrentPaneKey}</span><span>to open</span>
</div> </div>
<div class="prompt-instruction"> <div class="prompt-instruction">
<span class="prompt-instruction-command">tab</span> <span class="prompt-instruction-command">tab</span>
@@ -305,15 +332,15 @@
</div> </div>
<div class="prompt-instruction"> <div class="prompt-instruction">
<span class="prompt-instruction-command">{getCtrlKeyLabel()}</span> <span class="prompt-instruction-command">{openInNewPaneKey}</span>
<span>to open in a new pane</span> <span>to open in a new pane</span>
</div> </div>
<div class="prompt-instruction"> <div class="prompt-instruction">
<span class="prompt-instruction-command">shift ↵</span> <span class="prompt-instruction-command">{createInCurrentPaneKey}</span>
<span>to create</span> <span>to create</span>
</div> </div>
<div class="prompt-instruction"> <div class="prompt-instruction">
<span class="prompt-instruction-command">ctrl shift ↵</span> <span class="prompt-instruction-command">{createInNewPaneKey}</span>
<span>to create in a new pane</span> <span>to create in a new pane</span>
</div> </div>

View File

@@ -71,8 +71,9 @@
<span class="omnisearch-result__title"> <span class="omnisearch-result__title">
<span bind:this="{elFilePathIcon}"></span> <span bind:this="{elFilePathIcon}"></span>
<span>{@html title.replace(reg, highlighterGroups)}</span> <span>{@html title.replace(reg, highlighterGroups)}</span>
<span class="omnisearch-result__extension" <span class="omnisearch-result__extension">
>.{getExtension(note.path)}</span> .{getExtension(note.path)}
</span>
<!-- Counter --> <!-- Counter -->
{#if note.matches.length > 0} {#if note.matches.length > 0}

View File

@@ -1,7 +1,9 @@
import { App, Modal, TFile } from 'obsidian' import { App, Modal, TFile } from 'obsidian'
import type { Modifier } from 'obsidian'
import ModalVault from './ModalVault.svelte' import ModalVault from './ModalVault.svelte'
import ModalInFile from './ModalInFile.svelte' import ModalInFile from './ModalInFile.svelte'
import { eventBus, EventNames, isInputComposition } from '../globals' import { eventBus, EventNames, isInputComposition } from '../globals'
import { settings } from '../settings'
abstract class OmnisearchModal extends Modal { abstract class OmnisearchModal extends Modal {
protected constructor(app: App) { protected constructor(app: App) {
@@ -61,8 +63,24 @@ abstract class OmnisearchModal extends Modal {
// #endregion Up/Down navigation // #endregion Up/Down navigation
let openInCurrentPaneKey: Modifier[]
let openInNewPaneKey: Modifier[]
let createInCurrentPaneKey: Modifier[]
let createInNewPaneKey: Modifier[]
if (settings.openInNewPane) {
openInCurrentPaneKey = ['Mod']
openInNewPaneKey = []
createInCurrentPaneKey = ['Mod', 'Shift']
createInNewPaneKey = ['Shift']
} else {
openInCurrentPaneKey = []
openInNewPaneKey = ['Mod']
createInCurrentPaneKey = ['Shift']
createInNewPaneKey = ['Mod', 'Shift']
}
// Open in new pane // Open in new pane
this.scope.register(['Mod'], 'Enter', e => { this.scope.register(openInNewPaneKey, 'Enter', e => {
e.preventDefault() e.preventDefault()
eventBus.emit('open-in-new-pane') eventBus.emit('open-in-new-pane')
}) })
@@ -74,17 +92,17 @@ abstract class OmnisearchModal extends Modal {
}) })
// Create a new note // Create a new note
this.scope.register(['Shift'], 'Enter', e => { this.scope.register(createInCurrentPaneKey, 'Enter', e => {
e.preventDefault() e.preventDefault()
eventBus.emit('create-note') eventBus.emit('create-note')
}) })
this.scope.register(['Ctrl', 'Shift'], 'Enter', e => { this.scope.register(createInNewPaneKey, 'Enter', e => {
e.preventDefault() e.preventDefault()
eventBus.emit('create-note', { newLeaf: true }) eventBus.emit('create-note', { newLeaf: true })
}) })
// Open in current pane // Open in current pane
this.scope.register([], 'Enter', e => { this.scope.register(openInCurrentPaneKey, 'Enter', e => {
if (!isInputComposition()) { if (!isInputComposition()) {
// Check if the user is still typing // Check if the user is still typing
e.preventDefault() e.preventDefault()

View File

@@ -16,6 +16,7 @@ export const excerptAfter = 300
export const highlightClass = `suggestion-highlight omnisearch-highlight ${ export const highlightClass = `suggestion-highlight omnisearch-highlight ${
settings.highlight ? 'omnisearch-default-highlight' : '' settings.highlight ? 'omnisearch-default-highlight' : ''
}` }`
export const K_DISABLE_OMNISEARCH = 'omnisearch-disabled'
export const eventBus = new EventBus() export const eventBus = new EventBus()

View File

@@ -4,6 +4,7 @@ import {
OmnisearchVaultModal, OmnisearchVaultModal,
} from './components/modals' } from './components/modals'
import { import {
isPluginDisabled,
loadSettings, loadSettings,
saveSettings, saveSettings,
settings, settings,
@@ -29,6 +30,13 @@ export default class OmnisearchPlugin extends Plugin {
async onload(): Promise<void> { async onload(): Promise<void> {
await loadSettings(this) await loadSettings(this)
this.addSettingTab(new SettingsTab(this))
if (isPluginDisabled()) {
console.log('Omnisearch - Plugin disabled')
return
}
await cleanOldCacheFiles() await cleanOldCacheFiles()
await OmnisearchCache.clearOldDatabases() await OmnisearchCache.clearOldDatabases()
@@ -38,7 +46,6 @@ export default class OmnisearchPlugin extends Plugin {
this.addRibbonButton() this.addRibbonButton()
} }
this.addSettingTab(new SettingsTab(this))
eventBus.disable('vault') eventBus.disable('vault')
eventBus.disable('infile') eventBus.disable('infile')
eventBus.on('global', EventNames.ToggleExcerpts, () => { eventBus.on('global', EventNames.ToggleExcerpts, () => {
@@ -252,3 +259,4 @@ function registerAPI(plugin: OmnisearchPlugin): void {
// Deprecated // Deprecated
;(app as any).plugins.plugins.omnisearch.api = api ;(app as any).plugins.plugins.omnisearch.api = api
} }

View File

@@ -201,13 +201,16 @@ export class Omnisearch {
logDebug('Found', results.length, 'results') logDebug('Found', results.length, 'results')
// Filter query results to only keep files that match query.extensions (if any) // Filter query results to only keep files that match query.query.ext (if any)
if (query.extensions.length) { if (query.query.ext?.length) {
results = results.filter(r => { results = results.filter(r => {
// ".can" should match ".canvas" // ".can" should match ".canvas"
const ext = '.' + r.id.split('.').pop() const ext = '.' + r.id.split('.').pop()
return query.extensions.some(e => ext.startsWith(e)) return query.query.ext?.some(e =>
ext.startsWith(e.startsWith('.') ? e : '.' + e)
)
}) })
console.log(query.query.ext, results.length)
} }
// Filter query results that match the path // Filter query results that match the path
@@ -219,15 +222,14 @@ export class Omnisearch {
) )
} }
if (query.query.exclude.path) { if (query.query.exclude.path) {
results = results.filter(r => results = results.filter(
r =>
!query.query.exclude.path?.some(p => !query.query.exclude.path?.some(p =>
(r.id as string).toLowerCase().includes(p.toLowerCase()) (r.id as string).toLowerCase().includes(p.toLowerCase())
) )
) )
} }
// If the query does not return any result,
// retry but with a shorter prefix limit
if (!results.length) { if (!results.length) {
return [] return []
} }

View File

@@ -7,7 +7,7 @@ import {
} from 'obsidian' } from 'obsidian'
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
import { database } from './database' import { database } from './database'
import { getTextExtractor, isCacheEnabled } from './globals' import { K_DISABLE_OMNISEARCH, getTextExtractor, isCacheEnabled } from './globals'
import type OmnisearchPlugin from './main' import type OmnisearchPlugin from './main'
interface WeightingSettings { interface WeightingSettings {
@@ -47,6 +47,7 @@ export interface OmnisearchSettings extends WeightingSettings {
simpleSearch: boolean simpleSearch: boolean
highlight: boolean highlight: boolean
splitCamelCase: boolean splitCamelCase: boolean
openInNewPane: boolean
verboseLogging: boolean verboseLogging: boolean
} }
@@ -55,6 +56,8 @@ export interface OmnisearchSettings extends WeightingSettings {
*/ */
export const showExcerpt = writable(false) export const showExcerpt = writable(false)
const needsARestart = `<strong style="color: var(--text-accent)">Needs a restart to fully take effect.</strong>`
export class SettingsTab extends PluginSettingTab { export class SettingsTab extends PluginSettingTab {
plugin: OmnisearchPlugin plugin: OmnisearchPlugin
@@ -72,6 +75,11 @@ export class SettingsTab extends PluginSettingTab {
const { containerEl } = this const { containerEl } = this
containerEl.empty() containerEl.empty()
if (app.loadLocalStorage(K_DISABLE_OMNISEARCH) == '1') {
const span = containerEl.createEl('span')
span.innerHTML = `<strong style="color: var(--text-accent)">⚠️ OMNISEARCH IS DISABLED ⚠️</strong>`
}
// Settings main title // Settings main title
containerEl.createEl('h2', { text: 'Omnisearch' }) containerEl.createEl('h2', { text: 'Omnisearch' })
@@ -138,7 +146,7 @@ export class SettingsTab extends PluginSettingTab {
Add extensions separated by a space, without the dot. Example: "<code>txt org</code>".<br /> Add extensions separated by a space, without the dot. Example: "<code>txt org</code>".<br />
⚠️ <span style="color: var(--text-accent)">Using extensions of non-plaintext files (like .docx or .pptx) WILL cause crashes, ⚠️ <span style="color: var(--text-accent)">Using extensions of non-plaintext files (like .docx or .pptx) WILL cause crashes,
because Omnisearch will try to index their content.</span><br /> because Omnisearch will try to index their content.</span><br />
<strong style="color: var(--text-accent)">Needs a restart to fully take effect.</strong>` ${needsARestart}`
}) })
new Setting(containerEl) new Setting(containerEl)
.setName('Additional files to index') .setName('Additional files to index')
@@ -192,7 +200,7 @@ export class SettingsTab extends PluginSettingTab {
span.innerHTML = `Normalize diacritics in search terms. Words like "brûlée" or "žluťoučký" will be indexed as "brulee" and "zlutoucky".<br/> span.innerHTML = `Normalize diacritics in search terms. Words like "brûlée" or "žluťoučký" will be indexed as "brulee" and "zlutoucky".<br/>
⚠️ <span style="color: var(--text-accent)">You probably should <strong>NOT</strong> disable this.</span><br> ⚠️ <span style="color: var(--text-accent)">You probably should <strong>NOT</strong> disable this.</span><br>
⚠️ <span style="color: var(--text-accent)">Changing this setting will clear the cache.</span><br> ⚠️ <span style="color: var(--text-accent)">Changing this setting will clear the cache.</span><br>
<strong style="color: var(--text-accent)">Needs a restart to fully take effect.</strong> ${needsARestart}
` `
}) })
new Setting(containerEl) new Setting(containerEl)
@@ -211,7 +219,7 @@ export class SettingsTab extends PluginSettingTab {
camelCaseDesc.createSpan({}, span => { camelCaseDesc.createSpan({}, span => {
span.innerHTML = `Enable this if you want to be able to search for CamelCaseWords as separate words.<br/> span.innerHTML = `Enable this if you want to be able to search for CamelCaseWords as separate words.<br/>
⚠️ <span style="color: var(--text-accent)">Changing this setting will clear the cache.</span><br> ⚠️ <span style="color: var(--text-accent)">Changing this setting will clear the cache.</span><br>
<strong style="color: var(--text-accent)">Needs a restart to fully take effect.</strong> ${needsARestart}
` `
}) })
new Setting(containerEl) new Setting(containerEl)
@@ -239,6 +247,18 @@ export class SettingsTab extends PluginSettingTab {
}) })
) )
new Setting(containerEl)
.setName('Open in new pane')
.setDesc(
'Open and create files in a new pane instead of the current pane.'
)
.addToggle(toggle =>
toggle.setValue(settings.openInNewPane).onChange(async v => {
settings.openInNewPane = v
await saveSettings(this.plugin)
})
)
//#endregion Behavior //#endregion Behavior
//#region User Interface //#region User Interface
@@ -364,7 +384,9 @@ export class SettingsTab extends PluginSettingTab {
new Setting(containerEl) new Setting(containerEl)
.setName('Enable verbose logging') .setName('Enable verbose logging')
.setDesc('Adds a LOT of logs for debugging purposes. Don\'t forget to disable it.') .setDesc(
"Adds a LOT of logs for debugging purposes. Don't forget to disable it."
)
.addToggle(toggle => .addToggle(toggle =>
toggle.setValue(settings.verboseLogging).onChange(async v => { toggle.setValue(settings.verboseLogging).onChange(async v => {
settings.verboseLogging = v settings.verboseLogging = v
@@ -375,14 +397,33 @@ export class SettingsTab extends PluginSettingTab {
//#endregion Debugginh //#endregion Debugginh
//#region Danger Zone //#region Danger Zone
if (isCacheEnabled()) {
new Setting(containerEl).setName('Danger Zone').setHeading() new Setting(containerEl).setName('Danger Zone').setHeading()
const disableDesc = new DocumentFragment()
disableDesc.createSpan({}, span => {
span.innerHTML = `Disable Omnisearch on this device only.<br>
${needsARestart}`
})
new Setting(containerEl)
.setName('Disable on this device')
.setDesc(disableDesc)
.addToggle(toggle =>
toggle.setValue(isPluginDisabled()).onChange(async v => {
if (v) {
app.saveLocalStorage(K_DISABLE_OMNISEARCH, '1')
} else {
app.saveLocalStorage(K_DISABLE_OMNISEARCH) // No value = unset
}
new Notice('Omnisearch - Disabled. Please restart Obsidian.')
})
)
if (isCacheEnabled()) {
const resetCacheDesc = new DocumentFragment() const resetCacheDesc = new DocumentFragment()
resetCacheDesc.createSpan({}, span => { resetCacheDesc.createSpan({}, span => {
span.innerHTML = `Erase all Omnisearch cache data. span.innerHTML = `Erase all Omnisearch cache data.
Use this if Omnisearch results are inconsistent, missing, or appear outdated.<br> Use this if Omnisearch results are inconsistent, missing, or appear outdated.<br>
<strong style="color: var(--text-accent)">Needs a restart to fully take effect.</strong>` ${needsARestart}`
}) })
new Setting(containerEl) new Setting(containerEl)
.setName('Clear cache data') .setName('Clear cache data')
@@ -417,6 +458,7 @@ export const DEFAULT_SETTINGS: OmnisearchSettings = {
PDFIndexing: false, PDFIndexing: false,
imagesIndexing: false, imagesIndexing: false,
splitCamelCase: false, splitCamelCase: false,
openInNewPane: false,
ribbonIcon: true, ribbonIcon: true,
showExcerpt: true, showExcerpt: true,
@@ -446,3 +488,7 @@ export async function loadSettings(plugin: Plugin): Promise<void> {
export async function saveSettings(plugin: Plugin): Promise<void> { export async function saveSettings(plugin: Plugin): Promise<void> {
await plugin.saveData(settings) await plugin.saveData(settings)
} }
export function isPluginDisabled(): boolean {
return app.loadLocalStorage(K_DISABLE_OMNISEARCH) == '1'
}

View File

@@ -52,7 +52,7 @@ export async function createNote(name: string, newLeaf = false): Promise<void> {
let pathPrefix: string let pathPrefix: string
switch (app.vault.getConfig('newFileLocation')) { switch (app.vault.getConfig('newFileLocation')) {
case 'current': case 'current':
pathPrefix = (app.workspace.getActiveFile()?.parent.path ?? '') + '/' pathPrefix = (app.workspace.getActiveFile()?.parent?.path ?? '') + '/'
break break
case 'folder': case 'folder':
pathPrefix = app.vault.getConfig('newFileFolderPath') + '/' pathPrefix = app.vault.getConfig('newFileFolderPath') + '/'

View File

@@ -33,7 +33,7 @@ export function highlighterGroups(...args: any[]) {
args[2] !== null && args[2] !== null &&
args[2] !== undefined args[2] !== undefined
) )
return `${args[1]}<span class="${highlightClass}">${args[2]}</span>` return `<span>${args[1]}</span><span class="${highlightClass}">${args[2]}</span>`
return '&lt;no content&gt;' return '&lt;no content&gt;'
} }

View File

@@ -17,7 +17,7 @@ declare module 'obsidian' {
interface App { interface App {
appId: string appId: string
loadLocalStorage(key: string): string | null
saveLocalStorage(key: string, value?: string): void
} }
} }

View File

@@ -107,6 +107,5 @@
"1.14.0-beta.1": "1.0.0", "1.14.0-beta.1": "1.0.0",
"1.14.0": "1.0.0", "1.14.0": "1.0.0",
"1.14.1-beta.1": "1.0.0", "1.14.1-beta.1": "1.0.0",
"1.14.1-beta.2": "1.0.0", "1.14.1-beta.2": "1.0.0"
"1.14.1-beta.3": "1.0.0"
} }