Merge branch 'develop'
This commit is contained in:
@@ -25,7 +25,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.omnisearch-result__title > span {
|
.omnisearch-result__title > span {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.omnisearch-result__folder-path {
|
.omnisearch-result__folder-path {
|
||||||
@@ -39,7 +38,7 @@
|
|||||||
.omnisearch-result__extension {
|
.omnisearch-result__extension {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.omnisearch-result__counter {
|
.omnisearch-result__counter {
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
|
|
||||||
.omnisearch-result__image-container {
|
.omnisearch-result__image-container {
|
||||||
flex-basis: 20%;
|
flex-basis: 20%;
|
||||||
text-align: right
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.omnisearch-highlight {
|
.omnisearch-highlight {
|
||||||
@@ -78,6 +77,7 @@
|
|||||||
|
|
||||||
.omnisearch-input-container {
|
.omnisearch-input-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
@@ -86,6 +86,23 @@
|
|||||||
.omnisearch-input-container {
|
.omnisearch-input-container {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.omnisearch-input-container__buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 1em 0 1em;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
.omnisearch-input-container__buttons > button {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 600px) {
|
||||||
|
.omnisearch-input-container__buttons {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.omnisearch-input-field {
|
.omnisearch-input-field {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "scambier.obsidian-search",
|
"name": "scambier.obsidian-search",
|
||||||
"version": "1.18.1",
|
"version": "1.19.0-beta.1",
|
||||||
"description": "A search engine for Obsidian",
|
"description": "A search engine for Obsidian",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -217,6 +217,9 @@ class CacheManager {
|
|||||||
await database.searchHistory.bulkAdd(history)
|
await database.searchHistory.bulkAdd(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns The search history, in reverse chronological order
|
||||||
|
*/
|
||||||
public async getSearchHistory(): Promise<ReadonlyArray<string>> {
|
public async getSearchHistory(): Promise<ReadonlyArray<string>> {
|
||||||
const data = (await database.searchHistory.toArray())
|
const data = (await database.searchHistory.toArray())
|
||||||
.reverse()
|
.reverse()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
} from 'src/globals'
|
} from 'src/globals'
|
||||||
import { getCtrlKeyLabel, loopIndex } from 'src/tools/utils'
|
import { getCtrlKeyLabel, loopIndex } from 'src/tools/utils'
|
||||||
import { onDestroy, onMount, tick } from 'svelte'
|
import { onDestroy, onMount, tick } from 'svelte'
|
||||||
import { MarkdownView } from 'obsidian'
|
import { MarkdownView, App, Platform } from 'obsidian'
|
||||||
import ModalContainer from './ModalContainer.svelte'
|
import ModalContainer from './ModalContainer.svelte'
|
||||||
import {
|
import {
|
||||||
OmnisearchInFileModal,
|
OmnisearchInFileModal,
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
export let parent: OmnisearchVaultModal | null = null
|
export let parent: OmnisearchVaultModal | null = null
|
||||||
export let singleFilePath = ''
|
export let singleFilePath = ''
|
||||||
export let previousQuery: string | undefined
|
export let previousQuery: string | undefined
|
||||||
|
export let app: App
|
||||||
|
|
||||||
let searchQuery: string
|
let searchQuery: string
|
||||||
let groupedOffsets: number[] = []
|
let groupedOffsets: number[] = []
|
||||||
@@ -150,7 +151,13 @@
|
|||||||
<InputSearch
|
<InputSearch
|
||||||
on:input="{e => (searchQuery = e.detail)}"
|
on:input="{e => (searchQuery = e.detail)}"
|
||||||
placeholder="Omnisearch - File"
|
placeholder="Omnisearch - File"
|
||||||
initialValue="{previousQuery}" />
|
initialValue="{previousQuery}">
|
||||||
|
<div class="omnisearch-input-container__buttons">
|
||||||
|
{#if Platform.isMobile}
|
||||||
|
<button on:click="{switchToVaultModal}">Vault search</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</InputSearch>
|
||||||
|
|
||||||
<ModalContainer>
|
<ModalContainer>
|
||||||
{#if groupedOffsets.length && note}
|
{#if groupedOffsets.length && note}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { MarkdownView, Notice, TFile } from 'obsidian'
|
import { App, MarkdownView, Notice, Platform, TFile } from 'obsidian'
|
||||||
import { onDestroy, onMount, tick } from 'svelte'
|
import { onDestroy, onMount, tick } from 'svelte'
|
||||||
import InputSearch from './InputSearch.svelte'
|
import InputSearch from './InputSearch.svelte'
|
||||||
import ModalContainer from './ModalContainer.svelte'
|
import ModalContainer from './ModalContainer.svelte'
|
||||||
@@ -29,9 +29,12 @@
|
|||||||
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'
|
import { cancelable, CancelablePromise } from 'cancelable-promise'
|
||||||
|
import { debounce } from 'lodash-es'
|
||||||
|
|
||||||
export let modal: OmnisearchVaultModal
|
export let modal: OmnisearchVaultModal
|
||||||
export let previousQuery: string | undefined
|
export let previousQuery: string | undefined
|
||||||
|
export let app: App
|
||||||
|
|
||||||
let selectedIndex = 0
|
let selectedIndex = 0
|
||||||
let historySearchIndex = 0
|
let historySearchIndex = 0
|
||||||
let searchQuery: string | undefined
|
let searchQuery: string | undefined
|
||||||
@@ -59,10 +62,7 @@
|
|||||||
createInCurrentPaneKey = 'shift ↵'
|
createInCurrentPaneKey = 'shift ↵'
|
||||||
}
|
}
|
||||||
$: if (searchQuery) {
|
$: if (searchQuery) {
|
||||||
searching = true
|
updateResultsDebounced()
|
||||||
updateResults().then(() => {
|
|
||||||
searching = false
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
searching = false
|
searching = false
|
||||||
resultNotes = []
|
resultNotes = []
|
||||||
@@ -79,11 +79,11 @@
|
|||||||
indexingStepDesc = 'Indexing files...'
|
indexingStepDesc = 'Indexing files...'
|
||||||
break
|
break
|
||||||
case IndexingStepType.WritingCache:
|
case IndexingStepType.WritingCache:
|
||||||
updateResults()
|
updateResultsDebounced()
|
||||||
indexingStepDesc = 'Updating cache...'
|
indexingStepDesc = 'Updating cache...'
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
updateResults()
|
updateResultsDebounced()
|
||||||
indexingStepDesc = ''
|
indexingStepDesc = ''
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -102,9 +102,7 @@
|
|||||||
eventBus.on('vault', Action.PrevSearchHistory, prevSearchHistory)
|
eventBus.on('vault', Action.PrevSearchHistory, prevSearchHistory)
|
||||||
eventBus.on('vault', Action.NextSearchHistory, nextSearchHistory)
|
eventBus.on('vault', Action.NextSearchHistory, nextSearchHistory)
|
||||||
await NotesIndex.refreshIndex()
|
await NotesIndex.refreshIndex()
|
||||||
if (settings.showPreviousQueryResults) {
|
await updateResultsDebounced()
|
||||||
previousQuery = (await cacheManager.getSearchHistory())[0]
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@@ -132,6 +130,7 @@
|
|||||||
|
|
||||||
let cancelableQuery: CancelablePromise<ResultNote[]> | null = null
|
let cancelableQuery: CancelablePromise<ResultNote[]> | null = null
|
||||||
async function updateResults() {
|
async function updateResults() {
|
||||||
|
searching = true
|
||||||
// If search is already in progress, cancel it and start a new one
|
// If search is already in progress, cancel it and start a new one
|
||||||
if (cancelableQuery) {
|
if (cancelableQuery) {
|
||||||
cancelableQuery.cancel()
|
cancelableQuery.cancel()
|
||||||
@@ -146,8 +145,12 @@
|
|||||||
resultNotes = await cancelableQuery
|
resultNotes = await cancelableQuery
|
||||||
selectedIndex = 0
|
selectedIndex = 0
|
||||||
await scrollIntoView()
|
await scrollIntoView()
|
||||||
|
searching = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debounce this function to avoid multiple calls caused by Svelte reactivity
|
||||||
|
const updateResultsDebounced = debounce(updateResults, 0)
|
||||||
|
|
||||||
function onClick(evt?: MouseEvent | KeyboardEvent) {
|
function onClick(evt?: MouseEvent | KeyboardEvent) {
|
||||||
if (!selectedNote) return
|
if (!selectedNote) return
|
||||||
if (evt?.ctrlKey) {
|
if (evt?.ctrlKey) {
|
||||||
@@ -282,9 +285,14 @@
|
|||||||
initialValue="{searchQuery}"
|
initialValue="{searchQuery}"
|
||||||
on:input="{e => (searchQuery = e.detail)}"
|
on:input="{e => (searchQuery = e.detail)}"
|
||||||
placeholder="Omnisearch - Vault">
|
placeholder="Omnisearch - Vault">
|
||||||
|
<div class="omnisearch-input-container__buttons">
|
||||||
{#if settings.showCreateButton}
|
{#if settings.showCreateButton}
|
||||||
<button on:click="{onClickCreateNote}">Create note</button>
|
<button on:click="{onClickCreateNote}">Create note</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if Platform.isMobile}
|
||||||
|
<button on:click="{switchToInFileModal}">In-File search</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</InputSearch>
|
</InputSearch>
|
||||||
|
|
||||||
{#if indexingStepDesc}
|
{#if indexingStepDesc}
|
||||||
@@ -296,6 +304,7 @@
|
|||||||
<ModalContainer>
|
<ModalContainer>
|
||||||
{#each resultNotes as result, i}
|
{#each resultNotes as result, i}
|
||||||
<ResultItemVault
|
<ResultItemVault
|
||||||
|
app="{app}"
|
||||||
selected="{i === selectedIndex}"
|
selected="{i === selectedIndex}"
|
||||||
note="{result}"
|
note="{result}"
|
||||||
on:mousemove="{_ => (selectedIndex = i)}"
|
on:mousemove="{_ => (selectedIndex = i)}"
|
||||||
|
|||||||
@@ -10,12 +10,13 @@
|
|||||||
removeDiacritics,
|
removeDiacritics,
|
||||||
} from '../tools/utils'
|
} from '../tools/utils'
|
||||||
import ResultItemContainer from './ResultItemContainer.svelte'
|
import ResultItemContainer from './ResultItemContainer.svelte'
|
||||||
import { setIcon } from 'obsidian'
|
import { TFile, setIcon, App } from 'obsidian'
|
||||||
import { cloneDeep } from 'lodash-es'
|
import { cloneDeep } from 'lodash-es'
|
||||||
import { stringsToRegex, getMatches, makeExcerpt, highlightText } from 'src/tools/text-processing'
|
import { stringsToRegex, getMatches, makeExcerpt, highlightText } from 'src/tools/text-processing'
|
||||||
|
|
||||||
export let selected = false
|
export let selected = false
|
||||||
export let note: ResultNote
|
export let note: ResultNote
|
||||||
|
export let app: App
|
||||||
|
|
||||||
let imagePath: string | null = null
|
let imagePath: string | null = null
|
||||||
let title = ''
|
let title = ''
|
||||||
@@ -26,10 +27,8 @@
|
|||||||
$: {
|
$: {
|
||||||
imagePath = null
|
imagePath = null
|
||||||
if (isFileImage(note.path)) {
|
if (isFileImage(note.path)) {
|
||||||
// @ts-ignore
|
const file = app.vault.getAbstractFileByPath(note.path)
|
||||||
const file = app.vault.getFiles().find(f => f.path === note.path)
|
if (file instanceof TFile) {
|
||||||
if (file) {
|
|
||||||
// @ts-ignore
|
|
||||||
imagePath = app.vault.getResourcePath(file)
|
imagePath = app.vault.getResourcePath(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ModalVault from './ModalVault.svelte'
|
|||||||
import ModalInFile from './ModalInFile.svelte'
|
import ModalInFile from './ModalInFile.svelte'
|
||||||
import { Action, eventBus, EventNames, isInputComposition } from '../globals'
|
import { Action, eventBus, EventNames, isInputComposition } from '../globals'
|
||||||
import { settings } from '../settings'
|
import { settings } from '../settings'
|
||||||
|
import { cacheManager } from 'src/cache-manager'
|
||||||
|
|
||||||
abstract class OmnisearchModal extends Modal {
|
abstract class OmnisearchModal extends Modal {
|
||||||
protected constructor(app: App) {
|
protected constructor(app: App) {
|
||||||
@@ -142,25 +143,38 @@ abstract class OmnisearchModal extends Modal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class OmnisearchVaultModal extends OmnisearchModal {
|
export class OmnisearchVaultModal extends OmnisearchModal {
|
||||||
|
/**
|
||||||
|
* Instanciate the Omnisearch vault modal
|
||||||
|
* @param app
|
||||||
|
* @param query The query to pre-fill the search field with
|
||||||
|
*/
|
||||||
constructor(app: App, query?: string) {
|
constructor(app: App, query?: string) {
|
||||||
super(app)
|
super(app)
|
||||||
|
|
||||||
// Get selected text
|
// Selected text in the editor
|
||||||
const selection = app.workspace.getActiveViewOfType(MarkdownView)?.editor.getSelection()
|
const selectedText = app.workspace
|
||||||
|
.getActiveViewOfType(MarkdownView)
|
||||||
|
?.editor.getSelection()
|
||||||
|
|
||||||
|
cacheManager.getSearchHistory().then(history => {
|
||||||
|
// Previously searched query (if enabled in settings)
|
||||||
|
const previous = settings.showPreviousQueryResults ? history[0] : null
|
||||||
|
|
||||||
|
// Instantiate and display the Svelte component
|
||||||
const cmp = new ModalVault({
|
const cmp = new ModalVault({
|
||||||
target: this.modalEl,
|
target: this.modalEl,
|
||||||
props: {
|
props: {
|
||||||
|
app,
|
||||||
modal: this,
|
modal: this,
|
||||||
previousQuery: selection ?? query,
|
previousQuery: query || selectedText || previous || '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
this.onClose = () => {
|
this.onClose = () => {
|
||||||
// Since the component is manually created,
|
// Since the component is manually created,
|
||||||
// we also need to manually destroy it
|
// we also need to manually destroy it
|
||||||
cmp.$destroy()
|
cmp.$destroy()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +190,7 @@ export class OmnisearchInFileModal extends OmnisearchModal {
|
|||||||
const cmp = new ModalInFile({
|
const cmp = new ModalInFile({
|
||||||
target: this.modalEl,
|
target: this.modalEl,
|
||||||
props: {
|
props: {
|
||||||
|
app,
|
||||||
modal: this,
|
modal: this,
|
||||||
singleFilePath: file.path,
|
singleFilePath: file.path,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
|
|||||||
@@ -125,3 +125,4 @@ const separators =
|
|||||||
.slice(1, -1)
|
.slice(1, -1)
|
||||||
export const SPACE_OR_PUNCTUATION_UNIQUE = new RegExp(`${separators}`, 'u')
|
export const SPACE_OR_PUNCTUATION_UNIQUE = new RegExp(`${separators}`, 'u')
|
||||||
export const SPACE_OR_PUNCTUATION = new RegExp(`${separators}+`, 'u')
|
export const SPACE_OR_PUNCTUATION = new RegExp(`${separators}+`, 'u')
|
||||||
|
export const BRACKETS_AND_SPACE = /[|\[\]\(\)<>\{\} \t\n\r]/u
|
||||||
|
|||||||
20
src/main.ts
20
src/main.ts
@@ -1,4 +1,4 @@
|
|||||||
import { Notice, Platform, Plugin } from 'obsidian'
|
import { App, Notice, Platform, Plugin } from 'obsidian'
|
||||||
import {
|
import {
|
||||||
OmnisearchInFileModal,
|
OmnisearchInFileModal,
|
||||||
OmnisearchVaultModal,
|
OmnisearchVaultModal,
|
||||||
@@ -46,7 +46,7 @@ export default class OmnisearchPlugin extends Plugin {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await cleanOldCacheFiles()
|
await cleanOldCacheFiles(this.app)
|
||||||
await OmnisearchCache.clearOldDatabases()
|
await OmnisearchCache.clearOldDatabases()
|
||||||
|
|
||||||
registerAPI(this)
|
registerAPI(this)
|
||||||
@@ -66,7 +66,7 @@ export default class OmnisearchPlugin extends Plugin {
|
|||||||
id: 'show-modal',
|
id: 'show-modal',
|
||||||
name: 'Vault search',
|
name: 'Vault search',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
new OmnisearchVaultModal(app).open()
|
new OmnisearchVaultModal(this.app).open()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -75,12 +75,12 @@ export default class OmnisearchPlugin extends Plugin {
|
|||||||
name: 'In-file search',
|
name: 'In-file search',
|
||||||
editorCallback: (_editor, view) => {
|
editorCallback: (_editor, view) => {
|
||||||
if (view.file) {
|
if (view.file) {
|
||||||
new OmnisearchInFileModal(app, view.file).open()
|
new OmnisearchInFileModal(this.app, view.file).open()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
app.workspace.onLayoutReady(async () => {
|
this.app.workspace.onLayoutReady(async () => {
|
||||||
// Listeners to keep the search index up-to-date
|
// Listeners to keep the search index up-to-date
|
||||||
this.registerEvent(
|
this.registerEvent(
|
||||||
this.app.vault.on('create', file => {
|
this.app.vault.on('create', file => {
|
||||||
@@ -155,7 +155,7 @@ export default class OmnisearchPlugin extends Plugin {
|
|||||||
|
|
||||||
addRibbonButton(): void {
|
addRibbonButton(): void {
|
||||||
this.ribbonButton = this.addRibbonIcon('search', 'Omnisearch', _evt => {
|
this.ribbonButton = this.addRibbonIcon('search', 'Omnisearch', _evt => {
|
||||||
new OmnisearchVaultModal(app).open()
|
new OmnisearchVaultModal(this.app).open()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ export default class OmnisearchPlugin extends Plugin {
|
|||||||
private async populateIndex(): Promise<void> {
|
private async populateIndex(): Promise<void> {
|
||||||
console.time('Omnisearch - Indexing total time')
|
console.time('Omnisearch - Indexing total time')
|
||||||
indexingStep.set(IndexingStepType.ReadingFiles)
|
indexingStep.set(IndexingStepType.ReadingFiles)
|
||||||
const files = app.vault.getFiles().filter(f => isFileIndexable(f.path))
|
const files = this.app.vault.getFiles().filter(f => isFileIndexable(f.path))
|
||||||
console.log(`Omnisearch - ${files.length} files total`)
|
console.log(`Omnisearch - ${files.length} files total`)
|
||||||
console.log(
|
console.log(
|
||||||
`Omnisearch - Cache is ${isCacheEnabled() ? 'enabled' : 'disabled'}`
|
`Omnisearch - Cache is ${isCacheEnabled() ? 'enabled' : 'disabled'}`
|
||||||
@@ -243,7 +243,7 @@ export default class OmnisearchPlugin extends Plugin {
|
|||||||
* Read the files and feed them to Minisearch
|
* Read the files and feed them to Minisearch
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async function cleanOldCacheFiles() {
|
async function cleanOldCacheFiles(app: App) {
|
||||||
const toDelete = [
|
const toDelete = [
|
||||||
`${app.vault.configDir}/plugins/omnisearch/searchIndex.json`,
|
`${app.vault.configDir}/plugins/omnisearch/searchIndex.json`,
|
||||||
`${app.vault.configDir}/plugins/omnisearch/notesCache.json`,
|
`${app.vault.configDir}/plugins/omnisearch/notesCache.json`,
|
||||||
@@ -264,12 +264,12 @@ async function cleanOldCacheFiles() {
|
|||||||
function registerAPI(plugin: OmnisearchPlugin): void {
|
function registerAPI(plugin: OmnisearchPlugin): void {
|
||||||
// Url scheme for obsidian://omnisearch?query=foobar
|
// Url scheme for obsidian://omnisearch?query=foobar
|
||||||
plugin.registerObsidianProtocolHandler('omnisearch', params => {
|
plugin.registerObsidianProtocolHandler('omnisearch', params => {
|
||||||
new OmnisearchVaultModal(app, params.query).open()
|
new OmnisearchVaultModal(plugin.app, params.query).open()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Public api
|
// Public api
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
globalThis['omnisearch'] = api
|
globalThis['omnisearch'] = api
|
||||||
// Deprecated
|
// Deprecated
|
||||||
;(app as any).plugins.plugins.omnisearch.api = api
|
;(plugin.app as any).plugins.plugins.omnisearch.api = api
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import MiniSearch, { type Options, type SearchResult } from 'minisearch'
|
import MiniSearch, { type Options, type SearchResult } from 'minisearch'
|
||||||
import type { DocumentRef, IndexedDocument, ResultNote } from '../globals'
|
import type { DocumentRef, IndexedDocument, ResultNote } from '../globals'
|
||||||
import { chsRegex, getChsSegmenter, SPACE_OR_PUNCTUATION } from '../globals'
|
import {
|
||||||
|
BRACKETS_AND_SPACE,
|
||||||
|
chsRegex,
|
||||||
|
getChsSegmenter,
|
||||||
|
SPACE_OR_PUNCTUATION,
|
||||||
|
} from '../globals'
|
||||||
import { settings } from '../settings'
|
import { settings } from '../settings'
|
||||||
import {
|
import {
|
||||||
chunkArray,
|
chunkArray,
|
||||||
@@ -17,6 +22,8 @@ import { sortBy } from 'lodash-es'
|
|||||||
import { getMatches, stringsToRegex } from 'src/tools/text-processing'
|
import { getMatches, stringsToRegex } from 'src/tools/text-processing'
|
||||||
|
|
||||||
const tokenize = (text: string): string[] => {
|
const tokenize = (text: string): string[] => {
|
||||||
|
const words = text.split(BRACKETS_AND_SPACE)
|
||||||
|
|
||||||
let tokens = text.split(SPACE_OR_PUNCTUATION)
|
let tokens = text.split(SPACE_OR_PUNCTUATION)
|
||||||
|
|
||||||
// Split hyphenated tokens
|
// Split hyphenated tokens
|
||||||
@@ -25,15 +32,22 @@ const tokenize = (text: string): string[] => {
|
|||||||
// Split camelCase tokens into "camel" and "case
|
// Split camelCase tokens into "camel" and "case
|
||||||
tokens = [...tokens, ...tokens.flatMap(splitCamelCase)]
|
tokens = [...tokens, ...tokens.flatMap(splitCamelCase)]
|
||||||
|
|
||||||
|
// Add whole words (aka "not tokens")
|
||||||
|
tokens = [...tokens, ...words]
|
||||||
|
|
||||||
// When enabled, we only use the chsSegmenter,
|
// When enabled, we only use the chsSegmenter,
|
||||||
// and not the other custom tokenizers
|
// and not the other custom tokenizers
|
||||||
const chsSegmenter = getChsSegmenter()
|
const chsSegmenter = getChsSegmenter()
|
||||||
if (chsSegmenter) {
|
if (chsSegmenter) {
|
||||||
tokens = tokens.flatMap(word =>
|
const chs = tokens.flatMap(word =>
|
||||||
chsRegex.test(word) ? chsSegmenter.cut(word) : [word]
|
chsRegex.test(word) ? chsSegmenter.cut(word) : [word]
|
||||||
)
|
)
|
||||||
|
tokens = [...tokens, ...chs]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove duplicates
|
||||||
|
tokens = [...new Set(tokens)]
|
||||||
|
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export interface OmnisearchSettings extends WeightingSettings {
|
|||||||
fuzziness: '0' | '1' | '2'
|
fuzziness: '0' | '1' | '2'
|
||||||
httpApiEnabled: boolean
|
httpApiEnabled: boolean
|
||||||
httpApiPort: string
|
httpApiPort: string
|
||||||
|
httpApiNotice: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,7 +75,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
plugin: OmnisearchPlugin
|
plugin: OmnisearchPlugin
|
||||||
|
|
||||||
constructor(plugin: OmnisearchPlugin) {
|
constructor(plugin: OmnisearchPlugin) {
|
||||||
super(app, plugin)
|
super(plugin.app, plugin)
|
||||||
this.plugin = plugin
|
this.plugin = plugin
|
||||||
|
|
||||||
showExcerpt.subscribe(async v => {
|
showExcerpt.subscribe(async v => {
|
||||||
@@ -87,7 +88,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
const { containerEl } = this
|
const { containerEl } = this
|
||||||
containerEl.empty()
|
containerEl.empty()
|
||||||
|
|
||||||
if (app.loadLocalStorage(K_DISABLE_OMNISEARCH) == '1') {
|
if (this.app.loadLocalStorage(K_DISABLE_OMNISEARCH) == '1') {
|
||||||
const span = containerEl.createEl('span')
|
const span = containerEl.createEl('span')
|
||||||
span.innerHTML = `<strong style="color: var(--text-accent)">⚠️ OMNISEARCH IS DISABLED ⚠️</strong>`
|
span.innerHTML = `<strong style="color: var(--text-accent)">⚠️ OMNISEARCH IS DISABLED ⚠️</strong>`
|
||||||
}
|
}
|
||||||
@@ -220,6 +221,17 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Show previous query results
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Show previous query results')
|
||||||
|
.setDesc('Re-executes the previous query when opening Omnisearch.')
|
||||||
|
.addToggle(toggle =>
|
||||||
|
toggle.setValue(settings.showPreviousQueryResults).onChange(async v => {
|
||||||
|
settings.showPreviousQueryResults = v
|
||||||
|
await saveSettings(this.plugin)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
// Respect excluded files
|
// Respect excluded files
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Respect Obsidian\'s "Excluded Files"')
|
.setName('Respect Obsidian\'s "Excluded Files"')
|
||||||
@@ -367,17 +379,6 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
// Show previous query results
|
|
||||||
new Setting(containerEl)
|
|
||||||
.setName('Show previous query results')
|
|
||||||
.setDesc('Re-executes the previous query when opening Omnisearch.')
|
|
||||||
.addToggle(toggle =>
|
|
||||||
toggle.setValue(settings.showPreviousQueryResults).onChange(async v => {
|
|
||||||
settings.showPreviousQueryResults = v
|
|
||||||
await saveSettings(this.plugin)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// Show "Create note" button
|
// Show "Create note" button
|
||||||
const createBtnDesc = new DocumentFragment()
|
const createBtnDesc = new DocumentFragment()
|
||||||
createBtnDesc.createSpan({}, span => {
|
createBtnDesc.createSpan({}, span => {
|
||||||
@@ -504,6 +505,18 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
await saveSettings(this.plugin)
|
await saveSettings(this.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(this.plugin)
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion HTTP Server
|
//#endregion HTTP Server
|
||||||
@@ -543,9 +556,9 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
.addToggle(toggle =>
|
.addToggle(toggle =>
|
||||||
toggle.setValue(isPluginDisabled()).onChange(async v => {
|
toggle.setValue(isPluginDisabled()).onChange(async v => {
|
||||||
if (v) {
|
if (v) {
|
||||||
app.saveLocalStorage(K_DISABLE_OMNISEARCH, '1')
|
this.app.saveLocalStorage(K_DISABLE_OMNISEARCH, '1')
|
||||||
} else {
|
} else {
|
||||||
app.saveLocalStorage(K_DISABLE_OMNISEARCH) // No value = unset
|
this.app.saveLocalStorage(K_DISABLE_OMNISEARCH) // No value = unset
|
||||||
}
|
}
|
||||||
new Notice('Omnisearch - Disabled. Please restart Obsidian.')
|
new Notice('Omnisearch - Disabled. Please restart Obsidian.')
|
||||||
})
|
})
|
||||||
@@ -613,6 +626,7 @@ export const DEFAULT_SETTINGS: OmnisearchSettings = {
|
|||||||
|
|
||||||
httpApiEnabled: false,
|
httpApiEnabled: false,
|
||||||
httpApiPort: '51361',
|
httpApiPort: '51361',
|
||||||
|
httpApiNotice: true,
|
||||||
|
|
||||||
welcomeMessage: '',
|
welcomeMessage: '',
|
||||||
verboseLogging: false,
|
verboseLogging: false,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as http from 'http'
|
|||||||
import * as url from 'url'
|
import * as url from 'url'
|
||||||
import api from './api'
|
import api from './api'
|
||||||
import { Notice } from 'obsidian'
|
import { Notice } from 'obsidian'
|
||||||
import { saveSettings, settings } from 'src/settings'
|
import { settings } from 'src/settings'
|
||||||
|
|
||||||
export function getServer() {
|
export function getServer() {
|
||||||
const server = http.createServer(async function (req, res) {
|
const server = http.createServer(async function (req, res) {
|
||||||
@@ -47,8 +47,10 @@ export function getServer() {
|
|||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
console.log(`Omnisearch - Started HTTP server on port ${port}`)
|
console.log(`Omnisearch - Started HTTP server on port ${port}`)
|
||||||
|
if (settings.httpApiNotice) {
|
||||||
new Notice(`Omnisearch - Started HTTP server on port ${port}`)
|
new Notice(`Omnisearch - Started HTTP server on port ${port}`)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
server.on('error', e => {
|
server.on('error', e => {
|
||||||
@@ -61,7 +63,9 @@ export function getServer() {
|
|||||||
close() {
|
close() {
|
||||||
server.close()
|
server.close()
|
||||||
console.log(`Omnisearch - Terminated HTTP server`)
|
console.log(`Omnisearch - Terminated HTTP server`)
|
||||||
|
if (settings.httpApiNotice) {
|
||||||
new Notice(`Omnisearch - Terminated HTTP server`)
|
new Notice(`Omnisearch - Terminated HTTP server`)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export async function openNote(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const pos = view.editor.offsetToPos(offset)
|
const pos = view.editor.offsetToPos(offset)
|
||||||
pos.ch = 0
|
// pos.ch = 0
|
||||||
|
|
||||||
view.editor.setCursor(pos)
|
view.editor.setCursor(pos)
|
||||||
view.editor.scrollIntoView({
|
view.editor.scrollIntoView({
|
||||||
|
|||||||
@@ -29,24 +29,46 @@ export function highlighterGroups(_substring: string, ...args: any[]) {
|
|||||||
* @returns The html string with the matches highlighted
|
* @returns The html string with the matches highlighted
|
||||||
*/
|
*/
|
||||||
export function highlightText(text: string, matches: SearchMatch[]): string {
|
export function highlightText(text: string, matches: SearchMatch[]): string {
|
||||||
|
if (!matches.length) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
const chsSegmenter = getChsSegmenter()
|
||||||
try {
|
try {
|
||||||
return text.replace(
|
// Text to highlight
|
||||||
new RegExp(
|
const src = new RegExp(
|
||||||
matches
|
matches
|
||||||
.map(matchInfo => `\\b${escapeRegExp(matchInfo.match)}\\b`)
|
.map(
|
||||||
|
// This regex will match the word (with \b word boundary)
|
||||||
|
// and, if ChsSegmenter is active, the simple string (without word boundary)
|
||||||
|
matchItem =>
|
||||||
|
`\\b${escapeRegExp(matchItem.match)}\\b${
|
||||||
|
chsSegmenter ? `|${escapeRegExp(matchItem.match)}` : ''
|
||||||
|
}`
|
||||||
|
)
|
||||||
.join('|'),
|
.join('|'),
|
||||||
'giu'
|
'giu'
|
||||||
),
|
)
|
||||||
match => {
|
|
||||||
|
// Replacer function that will highlight the matches
|
||||||
|
const replacer = (match: string) => {
|
||||||
const matchInfo = matches.find(info =>
|
const matchInfo = matches.find(info =>
|
||||||
match.match(new RegExp(`\\b${escapeRegExp(info.match)}\\b`, 'giu'))
|
match.match(
|
||||||
|
new RegExp(
|
||||||
|
`\\b${escapeRegExp(info.match)}\\b${
|
||||||
|
chsSegmenter ? `|${escapeRegExp(info.match)}` : ''
|
||||||
|
}`,
|
||||||
|
'giu'
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if (matchInfo) {
|
if (matchInfo) {
|
||||||
return `<span class="${highlightClass}">${match}</span>`
|
return `<span class="${highlightClass}">${match}</span>`
|
||||||
}
|
}
|
||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
// Effectively highlight the text
|
||||||
|
return text.replace(src, replacer)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Omnisearch - Error in highlightText()', e)
|
console.error('Omnisearch - Error in highlightText()', e)
|
||||||
return text
|
return text
|
||||||
@@ -93,7 +115,7 @@ export function stringsToRegex(strings: string[]): RegExp {
|
|||||||
')' +
|
')' +
|
||||||
`(${strings.map(s => escapeRegExp(s)).join('|')})`
|
`(${strings.map(s => escapeRegExp(s)).join('|')})`
|
||||||
|
|
||||||
return new RegExp(`${joined}`, 'gu')
|
return new RegExp(`${joined}`, 'gui')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMatches(
|
export function getMatches(
|
||||||
|
|||||||
Reference in New Issue
Block a user