Compare commits

...

10 Commits

22 changed files with 106 additions and 644 deletions

7
.madgerc Normal file
View File

@ -0,0 +1,7 @@
{
"detectiveOptions": {
"ts": {
"skipTypeImports": true
}
}
}

View File

@ -1,223 +0,0 @@
<script lang="ts">
import InputSearch from './InputSearch.svelte'
import {
Action,
eventBus,
excerptAfter,
type ResultNote,
type SearchMatch,
} from '../globals'
import { getCtrlKeyLabel, loopIndex } from '../tools/utils'
import { onDestroy, onMount, tick } from 'svelte'
import { MarkdownView, Platform } from 'obsidian'
import ModalContainer from './ModalContainer.svelte'
import {
LocatorInFileModal,
LocatorVaultModal,
} from '../components/modals'
import ResultItemInFile from './ResultItemInFile.svelte'
import { Query } from '../search/query'
import { openNote } from '../tools/notes'
import type LocatorPlugin from '../main'
export let plugin: LocatorPlugin
export let modal: LocatorInFileModal
export let parent: LocatorVaultModal | null = null
export let singleFilePath = ''
export let previousQuery: string | undefined
let searchQuery: string
let groupedOffsets: number[] = []
let selectedIndex = 0
let note: ResultNote | undefined
let query: Query
$: searchQuery = previousQuery ?? ''
onMount(() => {
eventBus.enable('infile')
eventBus.on('infile', Action.Enter, openSelection)
eventBus.on('infile', Action.OpenInNewPane, openSelectionInNewTab)
eventBus.on('infile', Action.ArrowUp, () => moveIndex(-1))
eventBus.on('infile', Action.ArrowDown, () => moveIndex(1))
eventBus.on('infile', Action.Tab, switchToVaultModal)
})
onDestroy(() => {
eventBus.disable('infile')
})
$: (async () => {
if (searchQuery) {
query = new Query(searchQuery, {
ignoreDiacritics: plugin.settings.ignoreDiacritics,
ignoreArabicDiacritics: plugin.settings.ignoreArabicDiacritics,
})
note =
(
await plugin.searchEngine.getSuggestions(query, {
singleFilePath,
})
)[0] ?? null
}
selectedIndex = 0
await scrollIntoView()
})()
$: {
if (note) {
let groups = getGroups(note.matches)
// If there are quotes in the search,
// only show results that match at least one of the quotes
const exactTerms = query.getExactTerms()
if (exactTerms.length) {
groups = groups.filter(group =>
exactTerms.every(exact =>
group.some(match => match.match.includes(exact))
)
)
}
groupedOffsets = groups.map(group => Math.round(group.first()!.offset))
}
}
/**
* Group together close matches to reduce the number of results
*/
function getGroups(matches: SearchMatch[]): SearchMatch[][] {
const groups: SearchMatch[][] = []
let lastOffset = -1
let count = 0 // Avoid infinite loops
while (++count < 100) {
const group = getGroupedMatches(matches, lastOffset, excerptAfter)
if (!group.length) break
lastOffset = group.last()!.offset
groups.push(group)
}
return groups
}
function getGroupedMatches(
matches: SearchMatch[],
offsetFrom: number,
maxLen: number
): SearchMatch[] {
const first = matches.find(m => m.offset > offsetFrom)
if (!first) return []
return matches.filter(
m => m.offset > offsetFrom && m.offset <= first.offset + maxLen
)
}
function moveIndex(dir: 1 | -1): void {
selectedIndex = loopIndex(selectedIndex + dir, groupedOffsets.length)
scrollIntoView()
}
async function scrollIntoView(): Promise<void> {
await tick()
const elem = document.querySelector(`[data-result-id="${selectedIndex}"]`)
elem?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
}
async function openSelectionInNewTab(): Promise<void> {
return openSelection(true)
}
async function openSelection(newTab = false): Promise<void> {
if (note) {
modal.close()
if (parent) parent.close()
// Open (or switch focus to) the note
const reg = plugin.textProcessor.stringsToRegex(note.foundWords)
reg.exec(note.content)
await openNote(plugin.app, note, reg.lastIndex, newTab)
// Move cursor to the match
const view = plugin.app.workspace.getActiveViewOfType(MarkdownView)
if (!view) {
// Not an editable document, so no cursor to place
return
// throw new Error('OmniSearch - No active MarkdownView')
}
const offset = groupedOffsets[selectedIndex] ?? 0
const pos = view.editor.offsetToPos(offset)
pos.ch = 0
view.editor.setCursor(pos)
view.editor.scrollIntoView({
from: { line: pos.line - 10, ch: 0 },
to: { line: pos.line + 10, ch: 0 },
})
}
}
function switchToVaultModal(): void {
new LocatorVaultModal(plugin, searchQuery ?? previousQuery).open()
modal.close()
}
</script>
<InputSearch
plugin="{plugin}"
on:input="{e => (searchQuery = e.detail)}"
placeholder="Locator - File"
initialValue="{previousQuery}">
<div class="omnisearch-input-container__buttons">
{#if Platform.isMobile}
<button on:click="{switchToVaultModal}">Vault search</button>
{/if}
</div>
</InputSearch>
<ModalContainer>
{#if groupedOffsets.length && note}
{#each groupedOffsets as offset, i}
<ResultItemInFile
{plugin}
offset="{offset}"
note="{note}"
index="{i}"
selected="{i === selectedIndex}"
on:mousemove="{_e => (selectedIndex = i)}"
on:click="{evt => openSelection(evt.ctrlKey)}"
on:auxclick="{evt => {
if (evt.button == 1) openSelection(true)
}}" />
{/each}
{:else}
<div style="text-align: center;">
We found 0 results for your search here.
</div>
{/if}
</ModalContainer>
<div class="prompt-instructions">
<div class="prompt-instruction">
<span class="prompt-instruction-command">↑↓</span><span>to navigate</span>
</div>
<div class="prompt-instruction">
<span class="prompt-instruction-command"></span><span>to open</span>
</div>
<div class="prompt-instruction">
<span class="prompt-instruction-command">tab</span>
<span>to switch to Vault Search</span>
</div>
<div class="prompt-instruction">
<span class="prompt-instruction-command">esc</span>
{#if !!parent}
<span>to go back to Vault Search</span>
{:else}
<span>to close</span>
{/if}
</div>
<div class="prompt-instruction">
<span class="prompt-instruction-command">{getCtrlKeyLabel()}</span>
<span>to open in a new pane</span>
</div>
</div>

View File

@ -1,34 +1,33 @@
<script lang="ts">
import { cancelable, CancelablePromise } from 'cancelable-promise'
import { debounce } from 'lodash-es'
import { MarkdownView, Notice, Platform, TFile } from 'obsidian'
import { onDestroy, onMount, tick } from 'svelte'
import InputSearch from './InputSearch.svelte'
import ModalContainer from './ModalContainer.svelte'
import {
type LocatorVaultModal,
} from '../components/modals'
import {
Action,
eventBus,
indexingStep,
IndexingStepType,
type ResultNote,
SPACE_OR_PUNCTUATION,
Action,
} from '../globals'
import type LocatorPlugin from '../main'
import { Query } from '../search/query'
import { createNote, openNote } from '../tools/notes'
import {
getCtrlKeyLabel,
getAltKeyLabel,
getCtrlKeyLabel,
getExtension,
isFilePDF,
loopIndex,
} from '../tools/utils'
import {
LocatorInFileModal,
type LocatorVaultModal,
} from '../components/modals'
import ResultItemVault from './ResultItemVault.svelte'
import { Query } from '../search/query'
import { cancelable, CancelablePromise } from 'cancelable-promise'
import { debounce } from 'lodash-es'
import type LocatorPlugin from '../main'
import InputSearch from './InputSearch.svelte'
import LazyLoader from './lazy-loader/LazyLoader.svelte'
import ModalContainer from './ModalContainer.svelte'
import ResultItemVault from './ResultItemVault.svelte'
let {
modal,
@ -107,7 +106,6 @@
eventBus.on('vault', Action.CreateNote, createNoteAndCloseModal)
eventBus.on('vault', Action.OpenInNewPane, openNoteInNewPane)
eventBus.on('vault', Action.InsertLink, insertLink)
eventBus.on('vault', Action.Tab, switchToInFileModal)
eventBus.on('vault', Action.ArrowUp, () => moveIndex(-1))
eventBus.on('vault', Action.ArrowDown, () => moveIndex(1))
eventBus.on('vault', Action.PrevSearchHistory, prevSearchHistory)
@ -149,8 +147,8 @@
cancelableQuery = null
}
query = new Query(searchQuery, {
ignoreDiacritics: plugin.settings.ignoreDiacritics,
ignoreArabicDiacritics: plugin.settings.ignoreArabicDiacritics,
ignoreDiacritics: true,
ignoreArabicDiacritics: true,
})
cancelableQuery = cancelable(
new Promise(resolve => {
@ -271,34 +269,6 @@
modal.close()
}
function switchToInFileModal(): void {
// Do nothing if the selectedNote is a PDF,
// or if there is 0 match (e.g indexing in progress)
if (
selectedNote &&
(isFilePDF(selectedNote?.path) || !selectedNote?.matches.length)
) {
return
}
saveCurrentQuery()
modal.close()
if (selectedNote) {
// Open in-file modal for selected search result
const file = plugin.app.vault.getAbstractFileByPath(selectedNote.path)
if (file && file instanceof TFile) {
new LocatorInFileModal(plugin, file, searchQuery).open()
}
} else {
// Open in-file modal for active file
const view = plugin.app.workspace.getActiveViewOfType(MarkdownView)
if (view?.file) {
new LocatorInFileModal(plugin, view.file, searchQuery).open()
}
}
}
function moveIndex(dir: 1 | -1): void {
selectedIndex = loopIndex(selectedIndex + dir, resultNotes.length)
scrollIntoView()
@ -325,9 +295,6 @@
{#if plugin.settings.showCreateButton}
<button on:click={onClickCreateNote}>Create note</button>
{/if}
{#if Platform.isMobile}
<button on:click={switchToInFileModal}>In-File search</button>
{/if}
</div>
</InputSearch>

View File

@ -1,6 +1,8 @@
<script lang="ts">
import { showExcerpt } from '../settings/index'
import { setIcon, TFile } from 'obsidian'
import { onMount } from 'svelte'
import type { ResultNote } from '../globals'
import type LocatorPlugin from '../main'
import {
getExtension,
isFileCanvas,
@ -10,17 +12,14 @@
pathWithoutFilename,
} from '../tools/utils'
import ResultItemContainer from './ResultItemContainer.svelte'
import type LocatorPlugin from '../main'
import { setIcon, TFile } from 'obsidian'
import { onMount } from 'svelte'
// Import icon utility functions
import { showExcerpt } from 'src/settings/utils'
import {
loadIconData,
initializeIconPacks,
getIconNameForPath,
loadIconSVG,
getDefaultIconSVG,
getIconNameForPath,
initializeIconPacks,
loadIconData,
loadIconSVG,
} from '../tools/icon-utils'
export let selected = false
@ -148,25 +147,24 @@
</script>
<ResultItemContainer
glyph="{glyph}"
id="{note.path}"
{glyph}
id={note.path}
cssClass=" {note.isEmbed ? 'omnisearch-result__embed' : ''}"
on:auxclick
on:click
on:mousemove
selected="{selected}">
{selected}>
<div>
<div class="omnisearch-result__title-container">
<span class="omnisearch-result__title">
{#if note.isEmbed}
<span
bind:this="{elEmbedIcon}"
title="The document above is embedded in this note"></span>
bind:this={elEmbedIcon}
title="The document above is embedded in this note" />
{:else}
<!-- File Icon -->
{#if fileIconSVG}
<span class="omnisearch-result__icon" use:renderSVG="{fileIconSVG}"
></span>
<span class="omnisearch-result__icon" use:renderSVG={fileIconSVG} />
{/if}
{/if}
<span>
@ -194,8 +192,7 @@
<div class="omnisearch-result__folder-path">
<!-- Folder Icon -->
{#if folderIconSVG}
<span class="omnisearch-result__icon" use:renderSVG="{folderIconSVG}"
></span>
<span class="omnisearch-result__icon" use:renderSVG={folderIconSVG} />
{/if}
<span>
{@html plugin.textProcessor.highlightText(notePath, matchesNotePath)}
@ -218,7 +215,7 @@
<!-- Image -->
{#if imagePath}
<div class="omnisearch-result__image-container">
<img style="width: 100px" src="{imagePath}" alt="" />
<img style="width: 100px" src={imagePath} alt="" />
</div>
{/if}
</div>

View File

@ -1,10 +1,9 @@
import { MarkdownView, Modal, TFile } from 'obsidian'
import type { Modifier } from 'obsidian'
import ModalVault from './ModalVault.svelte'
import ModalInFile from './ModalInFile.svelte'
import { MarkdownView, Modal } from 'obsidian'
import { mount, unmount } from 'svelte'
import { Action, eventBus, EventNames, isInputComposition } from '../globals'
import type LocatorPlugin from '../main'
import { mount, unmount } from 'svelte'
import ModalVault from './ModalVault.svelte'
abstract class LocatorModal extends Modal {
protected constructor(plugin: LocatorPlugin) {
@ -188,36 +187,3 @@ export class LocatorVaultModal extends LocatorModal {
})
}
}
export class LocatorInFileModal extends LocatorModal {
constructor(
plugin: LocatorPlugin,
file: TFile,
searchQuery: string = '',
parent?: LocatorModal
) {
super(plugin)
const cmp = mount(ModalInFile, {
target: this.modalEl,
props: {
plugin,
modal: this,
singleFilePath: file.path,
parent: parent,
previousQuery: searchQuery,
},
})
if (parent) {
// Hide the parent vault modal, and show it back when this one is closed
parent.containerEl.toggleVisibility(false)
}
this.onClose = () => {
if (parent) {
parent.containerEl.toggleVisibility(true)
}
unmount(cmp)
}
}
}

View File

@ -11,8 +11,6 @@ export const regexExtensions = /(?:^|\s)\.(\w+)/g
export const excerptBefore = 100
export const excerptAfter = 300
export const K_DISABLE_OMNISEARCH = 'locator-disabled'
export const eventBus = new EventBus()
export const EventNames = {

View File

@ -6,37 +6,26 @@ import {
type PluginManifest,
TFile,
} from 'obsidian'
import {
LocatorInFileModal,
LocatorVaultModal,
} from './components/modals'
import {
getDefaultSettings,
loadSettings,
SettingsTab,
showExcerpt,
} from './settings'
import type { LocatorSettings } from './settings/utils'
import { isCacheEnabled } from './settings/utils'
import { saveSettings } from './settings/utils'
import { isPluginDisabled } from './settings/utils'
import { LocatorVaultModal } from './components/modals'
import { Database } from './database'
import {
eventBus,
EventNames,
indexingStep,
IndexingStepType,
type TextExtractorApi,
type AIImageAnalyzerAPI,
} from './globals'
import { notifyOnIndexed, registerAPI } from './tools/api'
import { Database } from './database'
import { SearchEngine } from './search/search-engine'
import { DocumentsRepository } from './repositories/documents-repository'
import { logVerbose } from './tools/utils'
import { NotesIndexer } from './notes-indexer'
import { TextProcessor } from './tools/text-processing'
import { DocumentsRepository } from './repositories/documents-repository'
import { EmbedsRepository } from './repositories/embeds-repository'
import { SearchEngine } from './search/search-engine'
import { SearchHistory } from './search/search-history'
import { getDefaultSettings, loadSettings, SettingsTab } from './settings'
import type { LocatorSettings } from './settings/utils'
import { isCacheEnabled, saveSettings, showExcerpt } from './settings/utils'
import { notifyOnIndexed, registerAPI } from './tools/api'
import { TextProcessor } from './tools/text-processing'
import { logVerbose } from './tools/utils'
export default class LocatorPlugin extends Plugin {
// FIXME: fix the type
@ -70,11 +59,6 @@ export default class LocatorPlugin extends Plugin {
)
}
if (isPluginDisabled(this.app)) {
console.debug('Plugin disabled')
return
}
await cleanOldCacheFiles(this.app)
await this.database.clearOldDatabases()
@ -100,16 +84,6 @@ export default class LocatorPlugin extends Plugin {
},
})
this.addCommand({
id: 'show-modal-infile',
name: 'In-file search',
editorCallback: (_editor, view) => {
if (view.file) {
new LocatorInFileModal(this, view.file).open()
}
},
})
const searchEngine = this.searchEngine
this.app.workspace.onLayoutReady(async () => {
@ -229,14 +203,6 @@ export default class LocatorPlugin extends Plugin {
return (this.app as any).plugins?.plugins?.['text-extractor']?.api
}
/**
* Plugin dependency - Ai Image Analyzer
* @returns
*/
public getAIImageAnalyzer(): AIImageAnalyzerAPI | undefined {
return (this.app as any).plugins?.plugins?.['ai-image-analyzer']?.api
}
private async populateIndex(): Promise<void> {
console.time('Indexing total time')
indexingStep.set(IndexingStepType.ReadingFiles)
@ -291,7 +257,7 @@ export default class LocatorPlugin extends Plugin {
// Disable settings.useCache while writing the cache, in case it freezes
const cacheEnabled = this.settings.useCache
if (cacheEnabled && !this.settings.DANGER_forceSaveCache) {
if (cacheEnabled) {
this.settings.useCache = false
await saveSettings(this)
}

View File

@ -1,7 +1,7 @@
import type { TAbstractFile } from 'obsidian'
import type { IndexedDocument } from './globals'
import type LocatorPlugin from './main'
import { removeAnchors } from './tools/notes'
import type { IndexedDocument } from './globals'
import {
isFileCanvas,
isFileFromDataloom,
@ -44,17 +44,14 @@ export class NotesIndexer {
public isContentIndexable(path: string): boolean {
const settings = this.plugin.settings
const hasTextExtractor = !!this.plugin.getTextExtractor()
const hasAIImageAnalyzer = !!this.plugin.getAIImageAnalyzer()
const canIndexPDF = hasTextExtractor && settings.PDFIndexing
const canIndexImages = hasTextExtractor && settings.imagesIndexing
const canIndexImagesAI = hasAIImageAnalyzer && settings.aiImageIndexing
return (
this.isFilePlaintext(path) ||
isFileCanvas(path) ||
isFileFromDataloom(path) ||
(canIndexPDF && isFilePDF(path)) ||
(canIndexImages && isFileImage(path)) ||
(canIndexImagesAI && isFileImage(path))
(canIndexImages && isFileImage(path))
)
}

View File

@ -1,5 +1,8 @@
import { normalizePath, Notice, TFile } from 'obsidian'
import { normalizePath, TFile } from 'obsidian'
import type { CanvasData } from 'obsidian/canvas'
import type { IndexedDocument } from '../globals'
import type LocatorPlugin from '../main'
import { getNonExistingNotes } from '../tools/notes'
import {
countError,
extractHeadingsFromCache,
@ -14,9 +17,6 @@ import {
removeDiacritics,
stripMarkdownCharacters,
} from '../tools/utils'
import type { CanvasData } from 'obsidian/canvas'
import type LocatorPlugin from '../main'
import { getNonExistingNotes } from '../tools/notes'
export class DocumentsRepository {
/**
@ -96,7 +96,6 @@ export class DocumentsRepository {
let content: string | null = null
const extractor = this.plugin.getTextExtractor()
const aiImageAnalyzer = this.plugin.getAIImageAnalyzer()
// ** Plain text **
// Just read the file content
@ -151,10 +150,8 @@ export class DocumentsRepository {
// ** Image **
else if (
isFileImage(path) &&
((this.plugin.settings.imagesIndexing &&
extractor?.canFileBeExtracted(path)) ||
(this.plugin.settings.aiImageIndexing &&
aiImageAnalyzer?.canBeAnalyzed(file)))
(this.plugin.settings.imagesIndexing &&
extractor?.canFileBeExtracted(path))
) {
if (
this.plugin.settings.imagesIndexing &&
@ -162,13 +159,6 @@ export class DocumentsRepository {
) {
content = await extractor.extractText(file)
}
if (
this.plugin.settings.aiImageIndexing &&
aiImageAnalyzer?.canBeAnalyzed(file)
) {
content = (await aiImageAnalyzer.analyzeImage(file)) + (content ?? '')
}
}
// ** PDF **
else if (

View File

@ -331,9 +331,7 @@ export class SearchEngine {
results.map(async result => {
const doc = await this.plugin.documentsRepository.getDocument(result.id)
if (!doc) {
console.warn(
`Locator - Note "${result.id}" not in the live cache`
)
console.warn(`Locator - Note "${result.id}" not in the live cache`)
countError(true)
}
return doc
@ -349,12 +347,7 @@ export class SearchEngine {
const title = document?.path.toLowerCase() ?? ''
const content = (document?.cleanedContent ?? '').toLowerCase()
return exactTerms.every(
q =>
content.includes(q) ||
removeDiacritics(
title,
this.plugin.settings.ignoreArabicDiacritics
).includes(q)
q => content.includes(q) || removeDiacritics(title).includes(q)
)
})
}
@ -524,11 +517,7 @@ export class SearchEngine {
}
return (doc as any)[fieldName]
},
processTerm: (term: string) =>
(this.plugin.settings.ignoreDiacritics
? removeDiacritics(term, this.plugin.settings.ignoreArabicDiacritics)
: term
).toLowerCase(),
processTerm: (term: string) => removeDiacritics(term).toLowerCase(),
idField: 'path',
fields: [
'basename',

View File

@ -1,7 +1,7 @@
import type { QueryCombination } from 'minisearch'
import { BRACKETS_AND_SPACE, chsRegex, SPACE_OR_PUNCTUATION } from '../globals'
import { logVerbose, splitCamelCase, splitHyphens } from '../tools/utils'
import type LocatorPlugin from '../main'
import { splitCamelCase, splitHyphens } from '../tools/utils'
const markdownLinkExtractor = require('markdown-link-extractor')
@ -17,26 +17,16 @@ export class Tokenizer {
public tokenizeForIndexing(text: string): string[] {
try {
const words = this.tokenizeWords(text)
let urls: string[] = []
if (this.plugin.settings.tokenizeUrls) {
try {
urls = markdownLinkExtractor(text)
} catch (e) {
logVerbose('Error extracting urls', e)
}
}
let tokens = this.tokenizeTokens(text, { skipChs: true })
tokens = [...tokens.flatMap(token => [
token,
...splitHyphens(token),
...splitCamelCase(token),
]), ...words]
// Add urls
if (urls.length) {
tokens = [...tokens, ...urls]
}
tokens = [
...tokens.flatMap(token => [
token,
...splitHyphens(token),
...splitCamelCase(token),
]),
...words,
]
// Remove duplicates
tokens = [...new Set(tokens)]

View File

@ -1,26 +1,15 @@
// noinspection CssUnresolvedCustomProperty
import {
App,
Plugin,
PluginSettingTab,
Setting,
} from 'obsidian'
import { writable } from 'svelte/store'
import { K_DISABLE_OMNISEARCH, RecencyCutoff } from '../globals'
import { App, Plugin, PluginSettingTab, Setting } from 'obsidian'
import { RecencyCutoff } from '../globals'
import type LocatorPlugin from '../main'
import { enableVerboseLogging } from '../tools/utils'
import { injectSettingsIndexing } from './settings-indexing'
import { type LocatorSettings, saveSettings } from './utils'
import { injectSettingsBehavior } from './settings-behavior'
import { injectSettingsDanger } from './settings-danger'
import { injectSettingsHttp } from './settings-http'
import { injectSettingsIndexing } from './settings-indexing'
import { injectSettingsUserInterface } from './settings-ui'
import { injectSettingsWeighting } from './settings-weighting'
import { injectSettingsHttp } from './settings-http'
import { injectSettingsDanger } from './settings-danger'
/**
* A store to reactively toggle the `showExcerpt` setting on the fly
*/
export const showExcerpt = writable(false)
import { type LocatorSettings, saveSettings, showExcerpt } from './utils'
export class SettingsTab extends PluginSettingTab {
plugin: LocatorPlugin
@ -37,15 +26,9 @@ export class SettingsTab extends PluginSettingTab {
display(): void {
const { containerEl } = this
const database = this.plugin.database
containerEl.empty()
if (this.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
containerEl.createEl('h1', { text: 'Locator' })
@ -100,16 +83,12 @@ export function getDefaultSettings(app: App): LocatorSettings {
hideExcluded: false,
recencyBoost: RecencyCutoff.Disabled,
downrankedFoldersFilters: [] as string[],
ignoreDiacritics: true,
ignoreArabicDiacritics: false,
indexedFileTypes: [] as string[],
displayTitle: '',
PDFIndexing: false,
officeIndexing: false,
imagesIndexing: false,
aiImageIndexing: false,
unsupportedFilesIndexing: 'default',
splitCamelCase: false,
openInNewPane: false,
vimLikeNavigationShortcut: app.vault.getConfig('vimMode') as boolean,
@ -118,10 +97,8 @@ export function getDefaultSettings(app: App): LocatorSettings {
maxEmbeds: 5,
renderLineReturnInExcerpts: true,
showCreateButton: false,
highlight: true,
showPreviousQueryResults: true,
simpleSearch: false,
tokenizeUrls: false,
fuzziness: '1',
weightBasename: 10,
@ -134,13 +111,11 @@ export function getDefaultSettings(app: App): LocatorSettings {
httpApiEnabled: false,
httpApiPort: '51361',
httpApiNotice: true,
welcomeMessage: '',
verboseLogging: false,
DANGER_httpHost: null,
DANGER_forceSaveCache: false,
}
}
@ -156,9 +131,7 @@ export let settings: LocatorSettings
// return settings
// }
export async function loadSettings(
plugin: Plugin
): Promise<LocatorSettings> {
export async function loadSettings(plugin: Plugin): Promise<LocatorSettings> {
settings = Object.assign(
{},
getDefaultSettings(plugin.app),

View File

@ -70,22 +70,6 @@ export function injectSettingsBehavior(
})
})
// Split CamelCaseWords
new Setting(containerEl)
.setName('Split CamelCaseWords')
.setDesc(
htmlDescription(`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>
${needsARestart}`)
)
.addToggle(toggle =>
toggle.setValue(settings.splitCamelCase).onChange(async v => {
await database.clearCache()
settings.splitCamelCase = v
await saveSettings(plugin)
})
)
// Simpler search
new Setting(containerEl)
.setName('Simpler search')
@ -100,23 +84,6 @@ export function injectSettingsBehavior(
})
)
// Extract URLs
// Crashes on iOS
if (!Platform.isIosApp) {
new Setting(containerEl)
.setName('Tokenize URLs')
.setDesc(
`Enable this if you want to be able to search for URLs as separate words.
This setting has a strong impact on indexing performance, and can crash Obsidian under certain conditions.`
)
.addToggle(toggle =>
toggle.setValue(settings.tokenizeUrls).onChange(async v => {
settings.tokenizeUrls = v
await saveSettings(plugin)
})
)
}
// Open in new pane
new Setting(containerEl)
.setName('Open in new pane')

View File

@ -1,10 +1,7 @@
import { Notice, Setting } from 'obsidian'
import type { LocatorSettings } from './utils'
import { isCacheEnabled } from './utils'
import { saveSettings } from './utils'
import { htmlDescription, isPluginDisabled, needsARestart } from './utils'
import { Setting } from 'obsidian'
import type LocatorPlugin from 'src/main'
import { K_DISABLE_OMNISEARCH } from 'src/globals'
import type { LocatorSettings } from './utils'
import { htmlDescription, isCacheEnabled, needsARestart } from './utils'
export function injectSettingsDanger(
plugin: LocatorPlugin,
@ -15,69 +12,6 @@ export function injectSettingsDanger(
new Setting(containerEl).setName('Danger Zone').setHeading()
// Ignore diacritics
new Setting(containerEl)
.setName('Ignore diacritics')
.setDesc(
htmlDescription(`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)">Changing this setting will clear the cache.</span><br>
${needsARestart}`)
)
.addToggle(toggle =>
toggle.setValue(settings.ignoreDiacritics).onChange(async v => {
await database.clearCache()
settings.ignoreDiacritics = v
await saveSettings(plugin)
})
)
new Setting(containerEl)
.setName('Ignore Arabic diacritics (beta)')
.addToggle(toggle =>
toggle.setValue(settings.ignoreArabicDiacritics).onChange(async v => {
await database.clearCache()
settings.ignoreArabicDiacritics = v
await saveSettings(plugin)
})
)
// Disable Locator
const disableDesc = new DocumentFragment()
disableDesc.createSpan({}, span => {
span.innerHTML = `Disable Locator on this device only.<br>
${needsARestart}`
})
new Setting(containerEl)
.setName('Disable on this device')
.setDesc(disableDesc)
.addToggle(toggle =>
toggle.setValue(isPluginDisabled(plugin.app)).onChange(async v => {
if (v) {
plugin.app.saveLocalStorage(K_DISABLE_OMNISEARCH, '1')
new Notice('Locator - Disabled. Please restart Obsidian.')
} else {
plugin.app.saveLocalStorage(K_DISABLE_OMNISEARCH) // No value = unset
new Notice('Locator - Enabled. Please restart Obsidian.')
}
})
)
// Force save cache
new Setting(containerEl)
.setName('Force save the cache')
.setDesc(
htmlDescription(`Locator has a security feature that automatically disables cache writing if it cannot fully perform the operation.<br>
Use this option to force the cache to be saved, even if it causes a crash.<br>
<span style="color: var(--text-accent)">Enabling this setting could lead to crash loops</span>`)
)
.addToggle(toggle =>
toggle.setValue(settings.DANGER_forceSaveCache).onChange(async v => {
settings.DANGER_forceSaveCache = v
await saveSettings(plugin)
})
)
// Clear cache data
if (isCacheEnabled()) {
new Setting(containerEl)

View File

@ -50,17 +50,5 @@ export function injectSettingsHttp(
await saveSettings(plugin)
})
})
new Setting(containerEl)
.setName('Show a notification when the server starts')
.setDesc(
'Will display a notification if the server is enabled, at Obsidian startup.'
)
.addToggle(toggle =>
toggle.setValue(settings.httpApiNotice).onChange(async v => {
settings.httpApiNotice = v
await saveSettings(plugin)
})
)
}
}

View File

@ -11,7 +11,6 @@ export function injectSettingsIndexing(
containerEl: HTMLElement
) {
const textExtractor = plugin.getTextExtractor()
const aiImageAnalyzer = plugin.getAIImageAnalyzer()
const database = plugin.database
const clearCacheDebounced = debounce(async () => {
@ -28,11 +27,6 @@ export function injectSettingsIndexing(
? `👍 You have installed <a href="https://github.com/scambier/obsidian-text-extractor">Text Extractor</a>, Locator can use it to index PDFs and images contents.
<br />Text extraction only works on desktop, but the cache can be synchronized with your mobile device.`
: `⚠️ Locator requires <a href="https://github.com/scambier/obsidian-text-extractor">Text Extractor</a> to index PDFs and images.`
}
${
aiImageAnalyzer
? `<br/>👍 You have installed <a href="https://github.com/Swaggeroo/obsidian-ai-image-analyzer">AI Image Analyzer</a>, Locator can use it to index images contents with ai.`
: `<br/>⚠️ Locator requires <a href="https://github.com/Swaggeroo/obsidian-ai-image-analyzer">AI Image Analyzer</a> to index images with ai.`
}`)
)
@ -87,23 +81,6 @@ export function injectSettingsIndexing(
)
.setDisabled(!textExtractor)
// AI Images Indexing
const aiIndexImagesDesc = new DocumentFragment()
aiIndexImagesDesc.createSpan({}, span => {
span.innerHTML = `Locator will use AI Image Analyzer to index the content of your images with ai.`
})
new Setting(containerEl)
.setName(`Images AI indexing ${aiImageAnalyzer ? '' : '⚠️ Disabled'}`)
.setDesc(aiIndexImagesDesc)
.addToggle(toggle =>
toggle.setValue(settings.aiImageIndexing).onChange(async v => {
await database.clearCache()
settings.aiImageIndexing = v
await saveSettings(plugin)
})
)
.setDisabled(!aiImageAnalyzer)
// Index filenames of unsupported files
new Setting(containerEl)
.setName('Index paths of unsupported files')
@ -115,7 +92,7 @@ export function injectSettingsIndexing(
)
.addDropdown(dropdown => {
dropdown
.addOptions({ yes: 'Yes', no: 'No', default: 'Obsidian setting' })
.addOptions({ yes: 'Yes', no: 'No', default: 'Obsidian default' })
.setValue(settings.unsupportedFilesIndexing)
.onChange(async v => {
await clearCacheDebounced()

View File

@ -1,9 +1,7 @@
import { Setting } from 'obsidian'
import type LocatorPlugin from 'src/main'
import { showExcerpt } from '.'
import type { LocatorSettings } from './utils'
import { saveSettings } from './utils'
import { htmlDescription } from './utils'
import { htmlDescription, saveSettings, showExcerpt } from './utils'
export function injectSettingsUserInterface(
plugin: LocatorPlugin,
@ -82,17 +80,4 @@ export function injectSettingsUserInterface(
await saveSettings(plugin)
})
)
// Highlight results
new Setting(containerEl)
.setName('Highlight matching words in results')
.setDesc(
'Will highlight matching results when enabled. See README for more customization options.'
)
.addToggle(toggle =>
toggle.setValue(settings.highlight).onChange(async v => {
settings.highlight = v
await saveSettings(plugin)
})
)
}

View File

@ -1,7 +1,13 @@
import { App, Platform, Plugin } from 'obsidian'
import { K_DISABLE_OMNISEARCH, RecencyCutoff } from 'src/globals'
import { Platform, Plugin } from 'obsidian'
import { RecencyCutoff } from 'src/globals'
import { writable } from 'svelte/store'
import { settings } from '.'
/**
* A store to reactively toggle the `showExcerpt` setting on the fly
*/
export const showExcerpt = writable(false)
export function htmlDescription(innerHTML: string): DocumentFragment {
const desc = new DocumentFragment()
desc.createSpan({}, span => {
@ -20,9 +26,6 @@ export interface WeightingSettings {
weightH3: number
weightUnmarkedTags: number
}
export function isPluginDisabled(app: App): boolean {
return app.loadLocalStorage(K_DISABLE_OMNISEARCH) === '1'
}
export async function saveSettings(plugin: Plugin): Promise<void> {
await plugin.saveData(settings)
@ -41,9 +44,6 @@ export interface LocatorSettings extends WeightingSettings {
recencyBoost: RecencyCutoff
/** downrank files in the given folders */
downrankedFoldersFilters: string[]
/** Ignore diacritics when indexing files */
ignoreDiacritics: boolean
ignoreArabicDiacritics: boolean
/** Extensions of plain text files to index, in addition to .md */
indexedFileTypes: string[]
@ -55,8 +55,6 @@ export interface LocatorSettings extends WeightingSettings {
imagesIndexing: boolean
/** Enable Office documents indexing */
officeIndexing: boolean
/** Enable image ai indexing */
aiImageIndexing: boolean
/** Enable indexing of unknown files */
unsupportedFilesIndexing: 'yes' | 'no' | 'default'
@ -76,17 +74,12 @@ export interface LocatorSettings extends WeightingSettings {
welcomeMessage: string
/** If a query returns 0 result, try again with more relax conditions */
simpleSearch: boolean
tokenizeUrls: boolean
highlight: boolean
splitCamelCase: boolean
openInNewPane: boolean
verboseLogging: boolean
vimLikeNavigationShortcut: boolean
fuzziness: '0' | '1' | '2'
httpApiEnabled: boolean
httpApiPort: string
httpApiNotice: boolean
DANGER_httpHost: string | null
DANGER_forceSaveCache: boolean
}

View File

@ -48,10 +48,15 @@ export function getServer(plugin: LocatorPlugin) {
},
() => {
console.log(`Locator - Started HTTP server on port ${port}`)
if (plugin.settings.DANGER_httpHost && plugin.settings.DANGER_httpHost !== 'localhost') {
new Notice(`Locator - Started non-localhost HTTP server at ${plugin.settings.DANGER_httpHost}:${port}`, 120_000)
}
else if (plugin.settings.httpApiNotice) {
if (
plugin.settings.DANGER_httpHost &&
plugin.settings.DANGER_httpHost !== 'localhost'
) {
new Notice(
`Locator - Started non-localhost HTTP server at ${plugin.settings.DANGER_httpHost}:${port}`,
120_000
)
} else {
new Notice(`Locator - Started HTTP server on port ${port}`)
}
}
@ -67,7 +72,7 @@ export function getServer(plugin: LocatorPlugin) {
close() {
server.close()
console.log(`Locator - Terminated HTTP server`)
if (plugin.settings.httpApiEnabled && plugin.settings.httpApiNotice) {
if (plugin.settings.httpApiEnabled) {
new Notice(`Locator - Terminated HTTP server`)
}
},

View File

@ -87,8 +87,8 @@ export function getApi(plugin: LocatorPlugin) {
return {
async search(q: string): Promise<ResultNoteApi[]> {
const query = new Query(q, {
ignoreDiacritics: plugin.settings.ignoreDiacritics,
ignoreArabicDiacritics: plugin.settings.ignoreArabicDiacritics,
ignoreDiacritics: true,
ignoreArabicDiacritics: true,
})
const raw = await plugin.searchEngine.getSuggestions(query)
return mapResults(plugin, raw)

View File

@ -15,9 +15,7 @@ export class TextProcessor {
* @returns The html string with the matches highlighted
*/
public highlightText(text: string, matches: SearchMatch[]): string {
const highlightClass = `suggestion-highlight locator-highlight ${
this.plugin.settings.highlight ? 'locator-default-highlight' : ''
}`
const highlightClass = 'suggestion-highlight locator-highlight locator-default-highlight'
if (!matches.length) {
return text
@ -68,9 +66,7 @@ export class TextProcessor {
const reg = this.stringsToRegex(words)
const originalText = text
// text = text.toLowerCase().replace(new RegExp(SEPARATORS, 'gu'), ' ')
if (this.plugin.settings.ignoreDiacritics) {
text = removeDiacritics(text, this.plugin.settings.ignoreArabicDiacritics)
}
text = removeDiacritics(text)
const startTime = new Date().getTime()
let match: RegExpExecArray | null = null
let matches: SearchMatch[] = []

View File

@ -115,7 +115,7 @@ const diacriticsRegex = new RegExp(`(?!${regexpExclude})\\p{Diacritic}`, 'gu')
/**
* https://stackoverflow.com/a/37511463
*/
export function removeDiacritics(str: string, arabic = false): string {
export function removeDiacritics(str: string, arabic = true): string {
if (str === null || str === undefined) {
return ''
}