This commit is contained in:
Simon Cambier
2022-08-26 08:23:58 +02:00
parent d6455eafa7
commit b73c5a9391
23 changed files with 923 additions and 2188 deletions

View File

@@ -1,60 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true,
jest: true,
},
extends: ['standard'],
globals: {
app: 'readonly',
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 13,
sourceType: 'module',
},
plugins: ['svelte3', '@typescript-eslint'],
overrides: [
{
files: ['*.svelte'],
processor: 'svelte3/svelte3',
},
],
settings: {
'svelte3/typescript': true,
},
rules: {
'comma-dangle': ['error', 'always-multiline'],
'arrow-parens': ['error', 'as-needed'],
'brace-style': ['error', 'stroustrup', { allowSingleLine: true }],
'func-call-spacing': 'off',
// unused vars - fix for enums
'no-unused-vars': ['off'],
'@typescript-eslint/no-unused-vars': ['warn'],
// no redeclare - fix for overloading
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': ['error'],
// 'simple-import-sort/imports': 'warn',
// 'simple-import-sort/exports': 'warn',
'@typescript-eslint/func-call-spacing': ['error'],
'@typescript-eslint/explicit-function-return-type': [
'warn',
{
allowExpressions: true,
allowTypedFunctionExpressions: true,
allowHigherOrderFunctions: true,
allowDirectConstAssertionInArrowFunctions: true,
allowConciseArrowFunctionExpressionsStartingWithVoid: false,
},
],
'space-before-function-paren': [
'error',
{
anonymous: 'always',
named: 'never',
asyncArrow: 'always',
},
],
'no-new': ['off'],
},
}

9
.prettierrc.js Normal file
View File

@@ -0,0 +1,9 @@
// prettier.config.js or .prettierrc.js
module.exports = {
trailingComma: 'es5',
tabWidth: 2,
semi: false,
singleQuote: true,
arrowParens: 'avoid',
bracketSameLine: true
}

View File

@@ -8,41 +8,31 @@
"build": "pnpm run check && node esbuild.config.mjs production", "build": "pnpm run check && node esbuild.config.mjs production",
"check": "tsc -noEmit -skipLibCheck", "check": "tsc -noEmit -skipLibCheck",
"version": "node version-bump.mjs && git add manifest.json versions.json package.json", "version": "node version-bump.mjs && git add manifest.json versions.json package.json",
"lint": "eslint . --ext .ts --fix --ignore-path .gitignore",
"test": "jest" "test": "jest"
}, },
"keywords": [], "keywords": [],
"author": "Simon Cambier", "author": "Simon Cambier",
"license": "GPL-3", "license": "GPL-3",
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.18.6", "@babel/preset-env": "^7.18.10",
"@babel/preset-typescript": "^7.18.6", "@babel/preset-typescript": "^7.18.6",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.5",
"@tsconfig/svelte": "^3.0.0", "@tsconfig/svelte": "^3.0.0",
"@types/jest": "^27.5.2", "@types/jest": "^27.5.2",
"@types/node": "^16.11.44", "@types/node": "^16.11.56",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"babel-jest": "^27.5.1", "babel-jest": "^27.5.1",
"builtin-modules": "^3.3.0", "builtin-modules": "^3.3.0",
"esbuild": "0.13.12", "esbuild": "0.13.12",
"esbuild-plugin-copy": "^1.3.0", "esbuild-plugin-copy": "^1.3.0",
"esbuild-svelte": "^0.7.1", "esbuild-svelte": "^0.7.1",
"eslint": "7.12.1",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "5.0.0",
"eslint-plugin-svelte3": "^3.4.1",
"jest": "^27.5.1", "jest": "^27.5.1",
"obsidian": "latest", "obsidian": "latest",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"prettier-eslint": "^13.0.0",
"svelte": "^3.49.0", "svelte": "^3.49.0",
"svelte-jester": "^2.3.2", "svelte-jester": "^2.3.2",
"svelte-preprocess": "^4.10.7", "svelte-preprocess": "^4.10.7",
"tslib": "2.3.1", "tslib": "2.3.1",
"typescript": "^4.7.4" "typescript": "^4.8.2"
}, },
"dependencies": { "dependencies": {
"@vanakat/plugin-api": "^0.1.0", "@vanakat/plugin-api": "^0.1.0",

2252
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,10 +4,10 @@ describe('EventBus', () => {
it('should refuse the registering of invalid ctx/event names', () => { it('should refuse the registering of invalid ctx/event names', () => {
const eventBus = new EventBus() const eventBus = new EventBus()
expect(() => eventBus.on('@', 'event', () => {})).toThrowError( expect(() => eventBus.on('@', 'event', () => {})).toThrowError(
'Invalid context/event name - Cannot contain @', 'Invalid context/event name - Cannot contain @'
) )
expect(() => eventBus.on('context', '@', () => {})).toThrowError( expect(() => eventBus.on('context', '@', () => {})).toThrowError(
'Invalid context/event name - Cannot contain @', 'Invalid context/event name - Cannot contain @'
) )
}) })

View File

@@ -30,13 +30,13 @@ describe('The Query class', () => {
// Assert // Assert
expect(query.segments.filter(s => s.exact)).toHaveLength(2) expect(query.segments.filter(s => s.exact)).toHaveLength(2)
expect( expect(
query.segments.find(o => o.value === 'lorem ipsum')!.exact, query.segments.find(o => o.value === 'lorem ipsum')!.exact
).toBeTruthy() ).toBeTruthy()
expect(query.segments.find(o => o.value === 'sit amet')!.exact).toBeTruthy() expect(query.segments.find(o => o.value === 'sit amet')!.exact).toBeTruthy()
expect(query.exclusions.filter(s => s.exact)).toHaveLength(1) expect(query.exclusions.filter(s => s.exact)).toHaveLength(1)
expect( expect(
query.exclusions.find(o => o.value === 'quoted exclusion')!.exact, query.exclusions.find(o => o.value === 'quoted exclusion')!.exact
).toBeTruthy() ).toBeTruthy()
}) })

View File

@@ -1,2 +1,9 @@
<script lang="ts"></script> <script lang="ts"></script>
<span class="suggestion-flair" aria-label="Not created yet, select to create"><svg viewBox="0 0 100 100" class="add-note-glyph" width="16" height="16"><path fill="currentColor" stroke="currentColor" d="M23.3,6.7c-3.7,0-6.7,3-6.7,6.7v73.3c0,3.7,3,6.7,6.7,6.7h28.4c-3.2-4.8-5.1-10.5-5.1-16.7c0-16.6,13.4-30,30-30 c2.3,0,4.5,0.3,6.7,0.8V31.7c0-0.9-0.3-1.7-1-2.4L60.7,7.6c-0.6-0.6-1.5-1-2.4-1L23.3,6.7z M56.7,13L77,33.3H60 c-1.8,0-3.3-1.5-3.3-3.3L56.7,13z M76.7,53.3c-12.9,0-23.3,10.4-23.3,23.3S63.8,100,76.7,100S100,89.6,100,76.7 S89.6,53.3,76.7,53.3z M76.7,63.3c1.8,0,3.3,1.5,3.3,3.3v6.7h6.7c1.8,0,3.3,1.5,3.3,3.3c0,1.8-1.5,3.3-3.3,3.3H80v6.7 c0,1.8-1.5,3.3-3.3,3.3c-1.8,0-3.3-1.5-3.3-3.3V80h-6.7c-1.8,0-3.3-1.5-3.3-3.3s1.5-3.3,3.3-3.3h6.7v-6.7 C73.3,64.8,74.8,63.3,76.7,63.3L76.7,63.3z"></path></svg></span>
<span class="suggestion-flair" aria-label="Not created yet, select to create"
><svg viewBox="0 0 100 100" class="add-note-glyph" width="16" height="16"
><path
fill="currentColor"
stroke="currentColor"
d="M23.3,6.7c-3.7,0-6.7,3-6.7,6.7v73.3c0,3.7,3,6.7,6.7,6.7h28.4c-3.2-4.8-5.1-10.5-5.1-16.7c0-16.6,13.4-30,30-30 c2.3,0,4.5,0.3,6.7,0.8V31.7c0-0.9-0.3-1.7-1-2.4L60.7,7.6c-0.6-0.6-1.5-1-2.4-1L23.3,6.7z M56.7,13L77,33.3H60 c-1.8,0-3.3-1.5-3.3-3.3L56.7,13z M76.7,53.3c-12.9,0-23.3,10.4-23.3,23.3S63.8,100,76.7,100S100,89.6,100,76.7 S89.6,53.3,76.7,53.3z M76.7,63.3c1.8,0,3.3,1.5,3.3,3.3v6.7h6.7c1.8,0,3.3,1.5,3.3,3.3c0,1.8-1.5,3.3-3.3,3.3H80v6.7 c0,1.8-1.5,3.3-3.3,3.3c-1.8,0-3.3-1.5-3.3-3.3V80h-6.7c-1.8,0-3.3-1.5-3.3-3.3s1.5-3.3,3.3-3.3h6.7v-6.7 C73.3,64.8,74.8,63.3,76.7,63.3L76.7,63.3z" /></svg
></span>

View File

@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import { debounce } from "obsidian" import { debounce } from 'obsidian'
import { toggleInputComposition } from "src/globals" import { toggleInputComposition } from 'src/globals'
import { createEventDispatcher, onMount, tick } from "svelte" import { createEventDispatcher, onMount, tick } from 'svelte'
export let value = "" export let value = ''
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let elInput: HTMLInputElement let elInput: HTMLInputElement
@@ -15,7 +15,7 @@ onMount(async () => {
}) })
const debouncedOnInput = debounce(() => { const debouncedOnInput = debounce(() => {
dispatch("input", value) dispatch('input', value)
}, 100) }, 100)
</script> </script>
@@ -23,10 +23,9 @@ const debouncedOnInput = debounce(() => {
bind:value bind:value
bind:this={elInput} bind:this={elInput}
on:input={debouncedOnInput} on:input={debouncedOnInput}
on:compositionstart={(_) => toggleInputComposition(true)} on:compositionstart={_ => toggleInputComposition(true)}
on:compositionend={(_) => toggleInputComposition(false)} on:compositionend={_ => toggleInputComposition(false)}
type="text" type="text"
class="prompt-input" class="prompt-input"
placeholder="Type to search through your notes" placeholder="Type to search through your notes"
spellcheck="false" spellcheck="false" />
/>

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
</script> </script>
<div class="prompt-results" on:mousedown={(e) => e.preventDefault()}> <div class="prompt-results" on:mousedown={e => e.preventDefault()}>
<slot /> <slot />
</div> </div>

View File

@@ -1,28 +1,28 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
let lastSearch = "" let lastSearch = ''
</script> </script>
<script lang="ts"> <script lang="ts">
import InputSearch from "./InputSearch.svelte" import InputSearch from './InputSearch.svelte'
import { import {
eventBus, eventBus,
excerptAfter, excerptAfter,
type ResultNote, type ResultNote,
type SearchMatch, type SearchMatch,
} from "src/globals" } from 'src/globals'
import { loopIndex } from "src/utils" import { loopIndex } from 'src/utils'
import { onDestroy, onMount, tick } from "svelte" import { onDestroy, onMount, tick } from 'svelte'
import { MarkdownView } from "obsidian" import { MarkdownView } from 'obsidian'
import { getSuggestions } from "src/search" import { getSuggestions } from 'src/search'
import ModalContainer from "./ModalContainer.svelte" import ModalContainer from './ModalContainer.svelte'
import { OmnisearchInFileModal, OmnisearchVaultModal } from "src/modals" import { OmnisearchInFileModal, OmnisearchVaultModal } from 'src/modals'
import ResultItemInFile from "./ResultItemInFile.svelte" import ResultItemInFile from './ResultItemInFile.svelte'
import { Query } from "src/query" import { Query } from 'src/query'
import { openNote } from "src/notes"; import { openNote } from 'src/notes'
export let modal: OmnisearchInFileModal export let modal: OmnisearchInFileModal
export let parent: OmnisearchVaultModal | null = null export let parent: OmnisearchVaultModal | null = null
export let singleFilePath = "" export let singleFilePath = ''
export let searchQuery: string export let searchQuery: string
let groupedOffsets: number[] = [] let groupedOffsets: number[] = []
@@ -34,16 +34,16 @@ onMount(() => {
if (lastSearch && !searchQuery) { if (lastSearch && !searchQuery) {
searchQuery = lastSearch searchQuery = lastSearch
} }
eventBus.enable("infile") eventBus.enable('infile')
eventBus.on("infile", "enter", openSelection) eventBus.on('infile', 'enter', openSelection)
eventBus.on("infile", "arrow-up", () => moveIndex(-1)) eventBus.on('infile', 'arrow-up', () => moveIndex(-1))
eventBus.on("infile", "arrow-down", () => moveIndex(1)) eventBus.on('infile', 'arrow-down', () => moveIndex(1))
eventBus.on("infile", "tab", switchToVaultModal) eventBus.on('infile', 'tab', switchToVaultModal)
}) })
onDestroy(() => { onDestroy(() => {
eventBus.disable("infile") eventBus.disable('infile')
}) })
$: (async () => { $: (async () => {
@@ -59,7 +59,7 @@ $: (async () => {
$: { $: {
if (note) { if (note) {
const groups = getGroups(note.matches) const groups = getGroups(note.matches)
groupedOffsets = groups.map((group) => groupedOffsets = groups.map(group =>
Math.round((group.first()!.offset + group.last()!.offset) / 2) Math.round((group.first()!.offset + group.last()!.offset) / 2)
) )
// console.log(groups) // console.log(groups)
@@ -89,10 +89,10 @@ function getGroupedMatches(
offsetFrom: number, offsetFrom: number,
maxLen: number maxLen: number
): SearchMatch[] { ): SearchMatch[] {
const first = matches.find((m) => m.offset > offsetFrom) const first = matches.find(m => m.offset > offsetFrom)
if (!first) return [] if (!first) return []
return matches.filter( return matches.filter(
(m) => m.offset > offsetFrom && m.offset <= first.offset + maxLen m => m.offset > offsetFrom && m.offset <= first.offset + maxLen
) )
} }
@@ -104,10 +104,12 @@ function moveIndex(dir: 1 | -1): void {
async function scrollIntoView(): Promise<void> { async function scrollIntoView(): Promise<void> {
await tick() await tick()
const elem = document.querySelector(`[data-result-id="${selectedIndex}"]`) const elem = document.querySelector(`[data-result-id="${selectedIndex}"]`)
elem?.scrollIntoView({ behavior: "auto", block: "nearest" }) elem?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
} }
async function openSelection(evt?: MouseEvent | KeyboardEvent): Promise<void> { async function openSelection(
evt?: MouseEvent | KeyboardEvent
): Promise<void> {
if (note) { if (note) {
modal.close() modal.close()
if (parent) parent.close() if (parent) parent.close()
@@ -118,7 +120,7 @@ async function openSelection(evt?: MouseEvent | KeyboardEvent): Promise<void> {
// Move cursor to the match // Move cursor to the match
const view = app.workspace.getActiveViewOfType(MarkdownView) const view = app.workspace.getActiveViewOfType(MarkdownView)
if (!view) { if (!view) {
throw new Error("OmniSearch - No active MarkdownView") throw new Error('OmniSearch - No active MarkdownView')
} }
const offset = groupedOffsets[selectedIndex] ?? 0 const offset = groupedOffsets[selectedIndex] ?? 0
@@ -139,7 +141,7 @@ function switchToVaultModal(): void {
</script> </script>
<div class="modal-title">Omnisearch - File</div> <div class="modal-title">Omnisearch - File</div>
<InputSearch value={searchQuery} on:input={(e) => (searchQuery = e.detail)} /> <InputSearch value={searchQuery} on:input={e => (searchQuery = e.detail)} />
<ModalContainer> <ModalContainer>
{#if groupedOffsets.length && note} {#if groupedOffsets.length && note}
@@ -149,9 +151,8 @@ function switchToVaultModal(): void {
{note} {note}
index={i} index={i}
selected={i === selectedIndex} selected={i === selectedIndex}
on:mousemove={(e) => (selectedIndex = i)} on:mousemove={e => (selectedIndex = i)}
on:click={openSelection} on:click={openSelection} />
/>
{/each} {/each}
{:else} {:else}
<center> We found 0 result for your search here. </center> <center> We found 0 result for your search here. </center>

View File

@@ -1,19 +1,19 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
let lastSearch = "" let lastSearch = ''
</script> </script>
<script lang="ts"> <script lang="ts">
import { MarkdownView, Notice, TFile } from "obsidian" import { MarkdownView, Notice, TFile } from 'obsidian'
import { onMount, onDestroy, tick } from "svelte" import { onMount, onDestroy, tick } from 'svelte'
import InputSearch from "./InputSearch.svelte" import InputSearch from './InputSearch.svelte'
import ModalContainer from "./ModalContainer.svelte" import ModalContainer from './ModalContainer.svelte'
import { eventBus, type ResultNote } from "src/globals" import { eventBus, type ResultNote } from 'src/globals'
import { createNote, openNote } from "src/notes" import { createNote, openNote } from 'src/notes'
import { getSuggestions, reindexNotes } from "src/search" import { getSuggestions, reindexNotes } from 'src/search'
import { getCtrlKeyLabel, loopIndex } from "src/utils" import { getCtrlKeyLabel, loopIndex } from 'src/utils'
import { OmnisearchInFileModal, type OmnisearchVaultModal } from "src/modals" import { OmnisearchInFileModal, type OmnisearchVaultModal } from 'src/modals'
import ResultItemVault from "./ResultItemVault.svelte" import ResultItemVault from './ResultItemVault.svelte'
import { Query } from "src/query" import { Query } from 'src/query'
export let modal: OmnisearchVaultModal export let modal: OmnisearchVaultModal
let selectedIndex = 0 let selectedIndex = 0
@@ -31,23 +31,25 @@ $: if (searchQuery) {
onMount(async () => { onMount(async () => {
await reindexNotes() await reindexNotes()
searchQuery = lastSearch searchQuery = lastSearch
eventBus.enable("vault") eventBus.enable('vault')
eventBus.on("vault", "enter", openNoteAndCloseModal) eventBus.on('vault', 'enter', openNoteAndCloseModal)
eventBus.on("vault", "shift-enter", createNoteAndCloseModal) eventBus.on('vault', 'shift-enter', createNoteAndCloseModal)
eventBus.on("vault", "ctrl-enter", openNoteInNewPane) eventBus.on('vault', 'ctrl-enter', openNoteInNewPane)
eventBus.on("vault", "alt-enter", insertLink) eventBus.on('vault', 'alt-enter', insertLink)
eventBus.on("vault", "tab", switchToInFileModal) eventBus.on('vault', 'tab', switchToInFileModal)
eventBus.on("vault", "arrow-up", () => moveIndex(-1)) eventBus.on('vault', 'arrow-up', () => moveIndex(-1))
eventBus.on("vault", "arrow-down", () => moveIndex(1)) eventBus.on('vault', 'arrow-down', () => moveIndex(1))
}) })
onDestroy(() => { onDestroy(() => {
eventBus.disable("vault") eventBus.disable('vault')
}) })
async function updateResults() { async function updateResults() {
query = new Query(searchQuery) query = new Query(searchQuery)
resultNotes = (await getSuggestions(query)).sort((a, b) => b.score - a.score) resultNotes = (await getSuggestions(query)).sort(
(a, b) => b.score - a.score
)
lastSearch = searchQuery lastSearch = searchQuery
selectedIndex = 0 selectedIndex = 0
scrollIntoView() scrollIntoView()
@@ -85,11 +87,11 @@ function insertLink(): void {
if (!selectedNote) return if (!selectedNote) return
const file = app.vault const file = app.vault
.getMarkdownFiles() .getMarkdownFiles()
.find((f) => f.path === selectedNote.path) .find(f => f.path === selectedNote.path)
const active = app.workspace.getActiveFile() const active = app.workspace.getActiveFile()
const view = app.workspace.getActiveViewOfType(MarkdownView) const view = app.workspace.getActiveViewOfType(MarkdownView)
if (!view?.editor) { if (!view?.editor) {
new Notice("Omnisearch - Error - No active editor", 3000) new Notice('Omnisearch - Error - No active editor', 3000)
return return
} }
@@ -138,22 +140,21 @@ async function scrollIntoView(): Promise<void> {
const elem = document.querySelector( const elem = document.querySelector(
`[data-result-id="${selectedNote.path}"]` `[data-result-id="${selectedNote.path}"]`
) )
elem?.scrollIntoView({ behavior: "auto", block: "nearest" }) elem?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
} }
} }
</script> </script>
<div class="modal-title">Omnisearch - Vault</div> <div class="modal-title">Omnisearch - Vault</div>
<InputSearch value={lastSearch} on:input={(e) => (searchQuery = e.detail)} /> <InputSearch value={lastSearch} on:input={e => (searchQuery = e.detail)} />
<ModalContainer> <ModalContainer>
{#each resultNotes as result, i} {#each resultNotes as result, i}
<ResultItemVault <ResultItemVault
selected={i === selectedIndex} selected={i === selectedIndex}
note={result} note={result}
on:mousemove={(e) => (selectedIndex = i)} on:mousemove={e => (selectedIndex = i)}
on:click={onClick} on:click={onClick} />
/>
{/each} {/each}
{#if !resultNotes.length && searchQuery} {#if !resultNotes.length && searchQuery}
<center> We found 0 result for your search here. </center> <center> We found 0 result for your search here. </center>

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import GlyphAddNote from "./GlyphAddNote.svelte" import GlyphAddNote from './GlyphAddNote.svelte'
export let id: string export let id: string
export let selected = false export let selected = false
@@ -12,8 +12,7 @@ export let glyph = false
class:is-selected={selected} class:is-selected={selected}
on:mousemove on:mousemove
on:click on:click
on:auxclick on:auxclick>
>
{#if glyph} {#if glyph}
<GlyphAddNote /> <GlyphAddNote />
{/if} {/if}

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { ResultNote } from "../globals" import type { ResultNote } from '../globals'
import { highlighter, makeExcerpt, stringsToRegex } from "../utils" import { highlighter, makeExcerpt, stringsToRegex } from '../utils'
import ResultItemContainer from "./ResultItemContainer.svelte" import ResultItemContainer from './ResultItemContainer.svelte'
export let offset: number export let offset: number
export let note: ResultNote export let note: ResultNote
@@ -9,7 +9,7 @@ export let index = 0
export let selected = false export let selected = false
$: reg = stringsToRegex(note.foundWords) $: reg = stringsToRegex(note.foundWords)
$: cleanedContent = makeExcerpt(note?.content ?? "", offset) $: cleanedContent = makeExcerpt(note?.content ?? '', offset)
</script> </script>
<ResultItemContainer id={index.toString()} {selected} on:mousemove on:click> <ResultItemContainer id={index.toString()} {selected} on:mousemove on:click>

View File

@@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import { getNoteFromCache } from "src/notes" import { getNoteFromCache } from 'src/notes'
import { settings } from "src/settings" import { settings } from 'src/settings'
import type { ResultNote } from "../globals" import type { ResultNote } from '../globals'
import { getMatches } from "../search" import { getMatches } from '../search'
import { highlighter, makeExcerpt, stringsToRegex } from "../utils" import { highlighter, makeExcerpt, stringsToRegex } from '../utils'
import ResultItemContainer from "./ResultItemContainer.svelte" import ResultItemContainer from './ResultItemContainer.svelte'
export let selected = false export let selected = false
export let note: ResultNote export let note: ResultNote
@@ -24,7 +24,7 @@ $: title = settings.showShortName ? note.basename : note.path
{#if matches.length > 0} {#if matches.length > 0}
<span class="omnisearch-result__counter"> <span class="omnisearch-result__counter">
{matches.length}&nbsp;{matches.length > 1 ? "matches" : "match"} {matches.length}&nbsp;{matches.length > 1 ? 'matches' : 'match'}
</span> </span>
{/if} {/if}
</div> </div>

View File

@@ -14,8 +14,7 @@ export class EventBus {
public off(ctx: string, event?: string): void { public off(ctx: string, event?: string): void {
if (event) { if (event) {
this.handlers.delete(`${ctx}@${event}`) this.handlers.delete(`${ctx}@${event}`)
} } else {
else {
for (const [key] of this.handlers.entries()) { for (const [key] of this.handlers.entries()) {
if (key.startsWith(`${ctx}@`)) { if (key.startsWith(`${ctx}@`)) {
this.handlers.delete(key) this.handlers.delete(key)
@@ -35,7 +34,7 @@ export class EventBus {
public emit(event: string, ...args: any[]): void { public emit(event: string, ...args: any[]): void {
const entries = [...this.handlers.entries()].filter( const entries = [...this.handlers.entries()].filter(
([k, h]) => !this.disabled.includes(k.split('@')[0]), ([k, h]) => !this.disabled.includes(k.split('@')[0])
) )
for (const [key, handler] of entries) { for (const [key, handler] of entries) {
if (key.endsWith(`@${event}`)) { if (key.endsWith(`@${event}`)) {

View File

@@ -30,7 +30,7 @@ export type IndexedNote = {
content: string content: string
aliases: string aliases: string
tags: string[], tags: string[]
headings1: string headings1: string
headings2: string headings2: string
headings3: string headings3: string

View File

@@ -63,17 +63,17 @@ export default class OmnisearchPlugin extends Plugin {
this.registerEvent( this.registerEvent(
this.app.vault.on('create', file => { this.app.vault.on('create', file => {
addToIndex(file) addToIndex(file)
}), })
) )
this.registerEvent( this.registerEvent(
this.app.vault.on('delete', file => { this.app.vault.on('delete', file => {
removeFromIndex(file.path) removeFromIndex(file.path)
}), })
) )
this.registerEvent( this.registerEvent(
this.app.vault.on('modify', async file => { this.app.vault.on('modify', async file => {
addNoteToReindex(file) addNoteToReindex(file)
}), })
) )
this.registerEvent( this.registerEvent(
this.app.vault.on('rename', async (file, oldPath) => { this.app.vault.on('rename', async (file, oldPath) => {
@@ -81,7 +81,7 @@ export default class OmnisearchPlugin extends Plugin {
removeFromIndex(oldPath) removeFromIndex(oldPath)
await addToIndex(file) await addToIndex(file)
} }
}), })
) )
await initGlobalSearchIndex() await initGlobalSearchIndex()

View File

@@ -114,7 +114,7 @@ export class OmnisearchInFileModal extends OmnisearchModal {
app: App, app: App,
file: TFile, file: TFile,
searchQuery: string = '', searchQuery: string = '',
parent?: OmnisearchModal, parent?: OmnisearchModal
) { ) {
super(app) super(app)

View File

@@ -27,8 +27,7 @@ export async function loadNotesCache(): Promise<void> {
const json = await app.vault.adapter.read(notesCacheFilePath) const json = await app.vault.adapter.read(notesCacheFilePath)
notesCache = JSON.parse(json) notesCache = JSON.parse(json)
console.log('Notes cache loaded from the file') console.log('Notes cache loaded from the file')
} } catch (e) {
catch (e) {
console.trace('Could not load Notes cache from the file') console.trace('Could not load Notes cache from the file')
console.error(e) console.error(e)
} }
@@ -53,7 +52,7 @@ export function removeNoteFromCache(key: string): void {
export async function openNote( export async function openNote(
item: ResultNote, item: ResultNote,
newPane = false, newPane = false
): Promise<void> { ): Promise<void> {
const reg = stringsToRegex(item.foundWords) const reg = stringsToRegex(item.foundWords)
reg.exec(item.content) reg.exec(item.content)
@@ -116,9 +115,9 @@ export async function createNote(name: string): Promise<void> {
} }
const pos = view.editor.offsetToPos(name.length + 5) const pos = view.editor.offsetToPos(name.length + 5)
pos.ch = 0 pos.ch = 0
} } catch (e) {
catch (e) { ;(e as any).message =
(e as any).message = 'OmniSearch - Could not create note: ' + (e as any).message 'OmniSearch - Could not create note: ' + (e as any).message
console.error(e) console.error(e)
throw e throw e
} }
@@ -132,7 +131,7 @@ export async function createNote(name: string): Promise<void> {
*/ */
export function getNonExistingNotes( export function getNonExistingNotes(
file: TFile, file: TFile,
metadata: CachedMetadata, metadata: CachedMetadata
): string[] { ): string[] {
return (metadata.links ?? []) return (metadata.links ?? [])
.map(l => { .map(l => {

View File

@@ -41,10 +41,9 @@ const tokenize = (text: string): string[] => {
if (chsSegmenter) { if (chsSegmenter) {
return tokens.flatMap(word => return tokens.flatMap(word =>
chsRegex.test(word) ? chsSegmenter.cut(word) : [word], chsRegex.test(word) ? chsSegmenter.cut(word) : [word]
) )
} } else return tokens
else return tokens
} }
/** /**
@@ -77,8 +76,7 @@ export async function initGlobalSearchIndex(): Promise<void> {
minisearchInstance = MiniSearch.loadJSON(json, options) minisearchInstance = MiniSearch.loadJSON(json, options)
console.log('MiniSearch index loaded from the file') console.log('MiniSearch index loaded from the file')
await loadNotesCache() await loadNotesCache()
} } catch (e) {
catch (e) {
console.trace('Could not load MiniSearch index from the file') console.trace('Could not load MiniSearch index from the file')
console.error(e) console.error(e)
} }
@@ -99,8 +97,7 @@ export async function initGlobalSearchIndex(): Promise<void> {
if (settings.storeIndexInFile) { if (settings.storeIndexInFile) {
files = allFiles.filter(file => isCacheOutdated(file)) files = allFiles.filter(file => isCacheOutdated(file))
notesSuffix = 'modified notes' notesSuffix = 'modified notes'
} } else {
else {
files = allFiles files = allFiles
notesSuffix = 'notes' notesSuffix = 'notes'
} }
@@ -174,7 +171,7 @@ async function search(query: Query): Promise<SearchResult[]> {
results = results.filter(r => { results = results.filter(r => {
const title = getNoteFromCache(r.id)?.path.toLowerCase() ?? '' const title = getNoteFromCache(r.id)?.path.toLowerCase() ?? ''
const content = stripMarkdownCharacters( const content = stripMarkdownCharacters(
getNoteFromCache(r.id)?.content ?? '', getNoteFromCache(r.id)?.content ?? ''
).toLowerCase() ).toLowerCase()
return exactTerms.every(q => content.includes(q) || title.includes(q)) return exactTerms.every(q => content.includes(q) || title.includes(q))
}) })
@@ -185,7 +182,7 @@ async function search(query: Query): Promise<SearchResult[]> {
if (exclusions.length) { if (exclusions.length) {
results = results.filter(r => { results = results.filter(r => {
const content = stripMarkdownCharacters( const content = stripMarkdownCharacters(
getNoteFromCache(r.id)?.content ?? '', getNoteFromCache(r.id)?.content ?? ''
).toLowerCase() ).toLowerCase()
return exclusions.every(q => !content.includes(q.value)) return exclusions.every(q => !content.includes(q.value))
}) })
@@ -221,7 +218,7 @@ export function getMatches(text: string, reg: RegExp): SearchMatch[] {
*/ */
export async function getSuggestions( export async function getSuggestions(
query: Query, query: Query,
options?: Partial<{ singleFilePath: string | null }>, options?: Partial<{ singleFilePath: string | null }>
): Promise<ResultNote[]> { ): Promise<ResultNote[]> {
// Get the raw results // Get the raw results
let results = await search(query) let results = await search(query)
@@ -238,8 +235,7 @@ export async function getSuggestions(
const result = results.find(r => r.id === options.singleFilePath) const result = results.find(r => r.id === options.singleFilePath)
if (result) results = [result] if (result) results = [result]
else results = [] else results = []
} } else {
else {
results = results.slice(0, 50) results = results.slice(0, 50)
// Put the results with tags on top // Put the results with tags on top
@@ -355,8 +351,7 @@ export async function addToIndex(file: TAbstractFile): Promise<void> {
minisearchInstance.add(note) minisearchInstance.add(note)
isIndexChanged = true isIndexChanged = true
addNoteToCache(note.path, note) addNoteToCache(note.path, note)
} } catch (e) {
catch (e) {
console.trace('Error while indexing ' + file.basename) console.trace('Error while indexing ' + file.basename)
console.error(e) console.error(e)
} }
@@ -410,8 +405,7 @@ export function removeFromIndex(path: string): void {
.forEach(n => { .forEach(n => {
removeFromIndex(n.path) removeFromIndex(n.path)
}) })
} } else {
else {
console.warn(`not not found under path ${path}`) console.warn(`not not found under path ${path}`)
} }
} }

View File

@@ -43,13 +43,13 @@ export class SettingsTab extends PluginSettingTab {
new Setting(containerEl) new Setting(containerEl)
.setName('Respect Obsidian\'s "Excluded Files"') .setName('Respect Obsidian\'s "Excluded Files"')
.setDesc( .setDesc(
'Files that are in Obsidian\'s "Options > Files & Links > Excluded Files" list will be downranked in results.', 'Files that are in Obsidian\'s "Options > Files & Links > Excluded Files" list will be downranked in results.'
) )
.addToggle(toggle => .addToggle(toggle =>
toggle.setValue(settings.respectExcluded).onChange(async v => { toggle.setValue(settings.respectExcluded).onChange(async v => {
settings.respectExcluded = v settings.respectExcluded = v
await saveSettings(this.plugin) await saveSettings(this.plugin)
}), })
) )
// Ignore diacritics // Ignore diacritics
@@ -66,7 +66,7 @@ export class SettingsTab extends PluginSettingTab {
toggle.setValue(settings.ignoreDiacritics).onChange(async v => { toggle.setValue(settings.ignoreDiacritics).onChange(async v => {
settings.ignoreDiacritics = v settings.ignoreDiacritics = v
await saveSettings(this.plugin) await saveSettings(this.plugin)
}), })
) )
const serializedIndexDesc = new DocumentFragment() const serializedIndexDesc = new DocumentFragment()
@@ -85,7 +85,7 @@ export class SettingsTab extends PluginSettingTab {
app.vault.adapter.remove(searchIndexFilePath) app.vault.adapter.remove(searchIndexFilePath)
settings.storeIndexInFile = v settings.storeIndexInFile = v
await saveSettings(this.plugin) await saveSettings(this.plugin)
}), })
) )
// #endregion Behavior // #endregion Behavior
@@ -97,7 +97,9 @@ export class SettingsTab extends PluginSettingTab {
// Show Ribbon Icon // Show Ribbon Icon
new Setting(containerEl) new Setting(containerEl)
.setName('Show ribbon button') .setName('Show ribbon button')
.setDesc('Add a button on the sidebar to open the Vault search modal. Needs a restart to remove the button.') .setDesc(
'Add a button on the sidebar to open the Vault search modal. Needs a restart to remove the button.'
)
.addToggle(toggle => .addToggle(toggle =>
toggle.setValue(settings.ribbonIcon).onChange(async v => { toggle.setValue(settings.ribbonIcon).onChange(async v => {
settings.ribbonIcon = v settings.ribbonIcon = v
@@ -105,7 +107,7 @@ export class SettingsTab extends PluginSettingTab {
if (v) { if (v) {
this.plugin.addRibbonButton() this.plugin.addRibbonButton()
} }
}), })
) )
// Show notices // Show notices
@@ -116,20 +118,20 @@ export class SettingsTab extends PluginSettingTab {
toggle.setValue(settings.showIndexingNotices).onChange(async v => { toggle.setValue(settings.showIndexingNotices).onChange(async v => {
settings.showIndexingNotices = v settings.showIndexingNotices = v
await saveSettings(this.plugin) await saveSettings(this.plugin)
}), })
) )
// Display note names without the full path // Display note names without the full path
new Setting(containerEl) new Setting(containerEl)
.setName('Hide full path in results list') .setName('Hide full path in results list')
.setDesc( .setDesc(
'In the search results, only show the note name, without the full path.', 'In the search results, only show the note name, without the full path.'
) )
.addToggle(toggle => .addToggle(toggle =>
toggle.setValue(settings.showShortName).onChange(async v => { toggle.setValue(settings.showShortName).onChange(async v => {
settings.showShortName = v settings.showShortName = v
await saveSettings(this.plugin) await saveSettings(this.plugin)
}), })
) )
// #endregion User Interface // #endregion User Interface
@@ -140,7 +142,7 @@ export class SettingsTab extends PluginSettingTab {
new Setting(containerEl) new Setting(containerEl)
.setName( .setName(
`File name & declared aliases (default: ${DEFAULT_SETTINGS.weightBasename})`, `File name & declared aliases (default: ${DEFAULT_SETTINGS.weightBasename})`
) )
.addSlider(cb => this.weightSlider(cb, 'weightBasename')) .addSlider(cb => this.weightSlider(cb, 'weightBasename'))
@@ -164,24 +166,24 @@ export class SettingsTab extends PluginSettingTab {
new Setting(containerEl) new Setting(containerEl)
.setName( .setName(
'Use [Ctrl/Cmd]+j/k to navigate up/down in the results, if Vim mode is enabled', 'Use [Ctrl/Cmd]+j/k to navigate up/down in the results, if Vim mode is enabled'
) )
.addToggle(toggle => .addToggle(toggle =>
toggle.setValue(settings.CtrlJK).onChange(async v => { toggle.setValue(settings.CtrlJK).onChange(async v => {
settings.CtrlJK = v settings.CtrlJK = v
await saveSettings(this.plugin) await saveSettings(this.plugin)
}), })
) )
new Setting(containerEl) new Setting(containerEl)
.setName( .setName(
'Use [Ctrl/Cmd]+n/p to navigate up/down in the results, if Vim mode is enabled', 'Use [Ctrl/Cmd]+n/p to navigate up/down in the results, if Vim mode is enabled'
) )
.addToggle(toggle => .addToggle(toggle =>
toggle.setValue(settings.CtrlNP).onChange(async v => { toggle.setValue(settings.CtrlNP).onChange(async v => {
settings.CtrlNP = v settings.CtrlNP = v
await saveSettings(this.plugin) await saveSettings(this.plugin)
}), })
) )
// #endregion Shortcuts // #endregion Shortcuts

View File

@@ -63,7 +63,7 @@ export function stringsToRegex(strings: string[]): RegExp {
export function extractHeadingsFromCache( export function extractHeadingsFromCache(
cache: CachedMetadata, cache: CachedMetadata,
level: number, level: number
): string[] { ): string[] {
return ( return (
cache.headings?.filter(h => h.level === level).map(h => h.heading) ?? [] cache.headings?.filter(h => h.level === level).map(h => h.heading) ?? []
@@ -107,7 +107,7 @@ export function stripSurroundingQuotes(str: string): string {
function mapAsync<T, U>( function mapAsync<T, U>(
array: T[], array: T[],
callbackfn: (value: T, index: number, array: T[]) => Promise<U>, callbackfn: (value: T, index: number, array: T[]) => Promise<U>
): Promise<U[]> { ): Promise<U[]> {
return Promise.all(array.map(callbackfn)) return Promise.all(array.map(callbackfn))
} }
@@ -120,7 +120,7 @@ function mapAsync<T, U>(
*/ */
export async function filterAsync<T>( export async function filterAsync<T>(
array: T[], array: T[],
callbackfn: (value: T, index: number, array: T[]) => Promise<boolean>, callbackfn: (value: T, index: number, array: T[]) => Promise<boolean>
): Promise<T[]> { ): Promise<T[]> {
const filterMap = await mapAsync(array, callbackfn) const filterMap = await mapAsync(array, callbackfn)
return array.filter((value, index) => filterMap[index]) return array.filter((value, index) => filterMap[index])
@@ -136,10 +136,12 @@ export function stripMarkdownCharacters(text: string): string {
} }
export function getAliasesFromMetadata( export function getAliasesFromMetadata(
metadata: CachedMetadata | null, metadata: CachedMetadata | null
): string[] { ): string[] {
const arrOrString = metadata?.frontmatter?.aliases ?? [] const arrOrString = metadata?.frontmatter?.aliases ?? []
return (Array.isArray(arrOrString) ? arrOrString : arrOrString.toString().split(',')) return (
Array.isArray(arrOrString) ? arrOrString : arrOrString.toString().split(',')
)
.map(s => (s ? s.trim() : s)) .map(s => (s ? s.trim() : s))
.filter(s => !!s) .filter(s => !!s)
} }
@@ -154,7 +156,7 @@ export function getTagsFromMetadata(metadata: CachedMetadata | null): string[] {
const fromBody = (metadata?.tags ?? []).map(t => t.tag) const fromBody = (metadata?.tags ?? []).map(t => t.tag)
return [...fromFrontMatter, ...fromBody].map(t => return [...fromFrontMatter, ...fromBody].map(t =>
t[0] !== '#' ? '#' + t : t, t[0] !== '#' ? '#' + t : t
) )
} }

View File

@@ -43,13 +43,12 @@ interface SearchParserResult extends ISearchParserDictionary {
export function parseQuery( export function parseQuery(
string: string, string: string,
options: SearchParserOptions, options: SearchParserOptions
): SearchParserResult { ): SearchParserResult {
// Set a default options object when none is provided // Set a default options object when none is provided
if (!options) { if (!options) {
options = { offsets: true, tokenize: true } options = { offsets: true, tokenize: true }
} } else {
else {
// If options offsets was't passed, set it to true // If options offsets was't passed, set it to true
options.offsets = options.offsets =
typeof options.offsets === 'undefined' ? true : options.offsets typeof options.offsets === 'undefined' ? true : options.offsets
@@ -131,8 +130,7 @@ export function parseQuery(
if (isExcludedTerm) { if (isExcludedTerm) {
exclusion.text.push(term) exclusion.text.push(term)
} } else {
else {
terms.push({ terms.push({
text: term, text: term,
offsetStart: match.index, offsetStart: match.index,
@@ -164,8 +162,7 @@ export function parseQuery(
let isExclusion = false let isExclusion = false
if (!/^-/.test(key)) { if (!/^-/.test(key)) {
isKeyword = !(options.keywords.indexOf(key) === -1) isKeyword = !(options.keywords.indexOf(key) === -1)
} } else if (key[0] === '-') {
else if (key[0] === '-') {
const _key = key.slice(1) const _key = key.slice(1)
isKeyword = !(options.keywords.indexOf(_key) === -1) isKeyword = !(options.keywords.indexOf(_key) === -1)
if (isKeyword) { if (isKeyword) {
@@ -202,8 +199,7 @@ export function parseQuery(
if (values.length > 1) { if (values.length > 1) {
// ... concatenate both arrays. // ... concatenate both arrays.
exclusion[key] = exclusion[key].concat(values) exclusion[key] = exclusion[key].concat(values)
} } else {
else {
// ... append the current single value. // ... append the current single value.
exclusion[key].push(value) exclusion[key].push(value)
} }
@@ -229,15 +225,13 @@ export function parseQuery(
if (options.alwaysArray) { if (options.alwaysArray) {
// ...but we always return an array if option alwaysArray is true // ...but we always return an array if option alwaysArray is true
exclusion[key] = [value] exclusion[key] = [value]
} } else {
else {
// Record its value as a string // Record its value as a string
exclusion[key] = value exclusion[key] = value
} }
} }
} }
} } else {
else {
// If we already have seen that keyword... // If we already have seen that keyword...
if (query[key]) { if (query[key]) {
// ...many times... // ...many times...
@@ -246,8 +240,7 @@ export function parseQuery(
if (values.length > 1) { if (values.length > 1) {
// ... concatenate both arrays. // ... concatenate both arrays.
query[key] = query[key].concat(values) query[key] = query[key].concat(values)
} } else {
else {
// ... append the current single value. // ... append the current single value.
query[key].push(value) query[key].push(value)
} }
@@ -272,8 +265,7 @@ export function parseQuery(
if (options.alwaysArray) { if (options.alwaysArray) {
// ...but we always return an array if option alwaysArray is true // ...but we always return an array if option alwaysArray is true
query[key] = [value] query[key] = [value]
} } else {
else {
// Record its value as a string // Record its value as a string
query[key] = value query[key] = value
} }
@@ -308,8 +300,7 @@ export function parseQuery(
else { else {
query[key].from = value query[key].from = value
} }
} } else {
else {
// We add it as pure text // We add it as pure text
const text = term.keyword + ':' + term.value const text = term.keyword + ':' + term.value
query.text.push(text) query.text.push(text)