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",
"check": "tsc -noEmit -skipLibCheck",
"version": "node version-bump.mjs && git add manifest.json versions.json package.json",
"lint": "eslint . --ext .ts --fix --ignore-path .gitignore",
"test": "jest"
},
"keywords": [],
"author": "Simon Cambier",
"license": "GPL-3",
"devDependencies": {
"@babel/preset-env": "^7.18.6",
"@babel/preset-env": "^7.18.10",
"@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",
"@types/jest": "^27.5.2",
"@types/node": "^16.11.44",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"@types/node": "^16.11.56",
"babel-jest": "^27.5.1",
"builtin-modules": "^3.3.0",
"esbuild": "0.13.12",
"esbuild-plugin-copy": "^1.3.0",
"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",
"obsidian": "latest",
"prettier": "^2.7.1",
"prettier-eslint": "^13.0.0",
"svelte": "^3.49.0",
"svelte-jester": "^2.3.2",
"svelte-preprocess": "^4.10.7",
"tslib": "2.3.1",
"typescript": "^4.7.4"
"typescript": "^4.8.2"
},
"dependencies": {
"@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', () => {
const eventBus = new EventBus()
expect(() => eventBus.on('@', 'event', () => {})).toThrowError(
'Invalid context/event name - Cannot contain @',
'Invalid context/event name - Cannot contain @'
)
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
expect(query.segments.filter(s => s.exact)).toHaveLength(2)
expect(
query.segments.find(o => o.value === 'lorem ipsum')!.exact,
query.segments.find(o => o.value === 'lorem ipsum')!.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.find(o => o.value === 'quoted exclusion')!.exact,
query.exclusions.find(o => o.value === 'quoted exclusion')!.exact
).toBeTruthy()
})

View File

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

View File

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

View File

@@ -1,52 +1,52 @@
<script lang="ts" context="module">
let lastSearch = ""
let lastSearch = ''
</script>
<script lang="ts">
import InputSearch from "./InputSearch.svelte"
import {
import InputSearch from './InputSearch.svelte'
import {
eventBus,
excerptAfter,
type ResultNote,
type SearchMatch,
} from "src/globals"
import { loopIndex } from "src/utils"
import { onDestroy, onMount, tick } from "svelte"
import { MarkdownView } from "obsidian"
import { getSuggestions } from "src/search"
import ModalContainer from "./ModalContainer.svelte"
import { OmnisearchInFileModal, OmnisearchVaultModal } from "src/modals"
import ResultItemInFile from "./ResultItemInFile.svelte"
import { Query } from "src/query"
import { openNote } from "src/notes";
} from 'src/globals'
import { loopIndex } from 'src/utils'
import { onDestroy, onMount, tick } from 'svelte'
import { MarkdownView } from 'obsidian'
import { getSuggestions } from 'src/search'
import ModalContainer from './ModalContainer.svelte'
import { OmnisearchInFileModal, OmnisearchVaultModal } from 'src/modals'
import ResultItemInFile from './ResultItemInFile.svelte'
import { Query } from 'src/query'
import { openNote } from 'src/notes'
export let modal: OmnisearchInFileModal
export let parent: OmnisearchVaultModal | null = null
export let singleFilePath = ""
export let searchQuery: string
export let modal: OmnisearchInFileModal
export let parent: OmnisearchVaultModal | null = null
export let singleFilePath = ''
export let searchQuery: string
let groupedOffsets: number[] = []
let selectedIndex = 0
let note: ResultNote | null = null
let query: Query
let groupedOffsets: number[] = []
let selectedIndex = 0
let note: ResultNote | null = null
let query: Query
onMount(() => {
onMount(() => {
if (lastSearch && !searchQuery) {
searchQuery = lastSearch
}
eventBus.enable("infile")
eventBus.enable('infile')
eventBus.on("infile", "enter", openSelection)
eventBus.on("infile", "arrow-up", () => moveIndex(-1))
eventBus.on("infile", "arrow-down", () => moveIndex(1))
eventBus.on("infile", "tab", switchToVaultModal)
})
eventBus.on('infile', 'enter', openSelection)
eventBus.on('infile', 'arrow-up', () => moveIndex(-1))
eventBus.on('infile', 'arrow-down', () => moveIndex(1))
eventBus.on('infile', 'tab', switchToVaultModal)
})
onDestroy(() => {
eventBus.disable("infile")
})
onDestroy(() => {
eventBus.disable('infile')
})
$: (async () => {
$: (async () => {
if (searchQuery) {
query = new Query(searchQuery)
note = (await getSuggestions(query, { singleFilePath }))[0] ?? null
@@ -54,23 +54,23 @@ $: (async () => {
}
selectedIndex = 0
scrollIntoView()
})()
})()
$: {
$: {
if (note) {
const groups = getGroups(note.matches)
groupedOffsets = groups.map((group) =>
groupedOffsets = groups.map(group =>
Math.round((group.first()!.offset + group.last()!.offset) / 2)
)
// console.log(groups)
// console.log(groupedOffsets)
}
}
}
/**
/**
* Group together close matches to reduce the number of results
*/
function getGroups(matches: SearchMatch[]): SearchMatch[][] {
function getGroups(matches: SearchMatch[]): SearchMatch[][] {
const groups: SearchMatch[][] = []
let lastOffset = -1
let count = 0 // TODO: FIXME: this is a hack to avoid infinite loops
@@ -82,32 +82,34 @@ function getGroups(matches: SearchMatch[]): SearchMatch[][] {
if (++count > 100) break
}
return groups
}
}
function getGroupedMatches(
function getGroupedMatches(
matches: SearchMatch[],
offsetFrom: number,
maxLen: number
): SearchMatch[] {
const first = matches.find((m) => m.offset > offsetFrom)
): SearchMatch[] {
const first = matches.find(m => m.offset > offsetFrom)
if (!first) return []
return matches.filter(
(m) => m.offset > offsetFrom && m.offset <= first.offset + maxLen
m => m.offset > offsetFrom && m.offset <= first.offset + maxLen
)
}
}
function moveIndex(dir: 1 | -1): void {
function moveIndex(dir: 1 | -1): void {
selectedIndex = loopIndex(selectedIndex + dir, groupedOffsets.length)
scrollIntoView()
}
}
async function scrollIntoView(): Promise<void> {
async function scrollIntoView(): Promise<void> {
await tick()
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) {
modal.close()
if (parent) parent.close()
@@ -118,7 +120,7 @@ async function openSelection(evt?: MouseEvent | KeyboardEvent): Promise<void> {
// Move cursor to the match
const view = app.workspace.getActiveViewOfType(MarkdownView)
if (!view) {
throw new Error("OmniSearch - No active MarkdownView")
throw new Error('OmniSearch - No active MarkdownView')
}
const offset = groupedOffsets[selectedIndex] ?? 0
@@ -130,16 +132,16 @@ async function openSelection(evt?: MouseEvent | KeyboardEvent): Promise<void> {
to: { line: pos.line + 10, ch: 0 },
})
}
}
}
function switchToVaultModal(): void {
function switchToVaultModal(): void {
new OmnisearchVaultModal(app).open()
modal.close()
}
}
</script>
<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>
{#if groupedOffsets.length && note}
@@ -149,9 +151,8 @@ function switchToVaultModal(): void {
{note}
index={i}
selected={i === selectedIndex}
on:mousemove={(e) => (selectedIndex = i)}
on:click={openSelection}
/>
on:mousemove={e => (selectedIndex = i)}
on:click={openSelection} />
{/each}
{:else}
<center> We found 0 result for your search here. </center>

View File

@@ -1,77 +1,79 @@
<script lang="ts" context="module">
let lastSearch = ""
let lastSearch = ''
</script>
<script lang="ts">
import { MarkdownView, Notice, TFile } from "obsidian"
import { onMount, onDestroy, tick } from "svelte"
import InputSearch from "./InputSearch.svelte"
import ModalContainer from "./ModalContainer.svelte"
import { eventBus, type ResultNote } from "src/globals"
import { createNote, openNote } from "src/notes"
import { getSuggestions, reindexNotes } from "src/search"
import { getCtrlKeyLabel, loopIndex } from "src/utils"
import { OmnisearchInFileModal, type OmnisearchVaultModal } from "src/modals"
import ResultItemVault from "./ResultItemVault.svelte"
import { Query } from "src/query"
import { MarkdownView, Notice, TFile } from 'obsidian'
import { onMount, onDestroy, tick } from 'svelte'
import InputSearch from './InputSearch.svelte'
import ModalContainer from './ModalContainer.svelte'
import { eventBus, type ResultNote } from 'src/globals'
import { createNote, openNote } from 'src/notes'
import { getSuggestions, reindexNotes } from 'src/search'
import { getCtrlKeyLabel, loopIndex } from 'src/utils'
import { OmnisearchInFileModal, type OmnisearchVaultModal } from 'src/modals'
import ResultItemVault from './ResultItemVault.svelte'
import { Query } from 'src/query'
export let modal: OmnisearchVaultModal
let selectedIndex = 0
let searchQuery: string
let resultNotes: ResultNote[] = []
let query: Query
$: selectedNote = resultNotes[selectedIndex]
export let modal: OmnisearchVaultModal
let selectedIndex = 0
let searchQuery: string
let resultNotes: ResultNote[] = []
let query: Query
$: selectedNote = resultNotes[selectedIndex]
$: if (searchQuery) {
$: if (searchQuery) {
updateResults()
} else {
} else {
resultNotes = []
}
}
onMount(async () => {
onMount(async () => {
await reindexNotes()
searchQuery = lastSearch
eventBus.enable("vault")
eventBus.on("vault", "enter", openNoteAndCloseModal)
eventBus.on("vault", "shift-enter", createNoteAndCloseModal)
eventBus.on("vault", "ctrl-enter", openNoteInNewPane)
eventBus.on("vault", "alt-enter", insertLink)
eventBus.on("vault", "tab", switchToInFileModal)
eventBus.on("vault", "arrow-up", () => moveIndex(-1))
eventBus.on("vault", "arrow-down", () => moveIndex(1))
})
eventBus.enable('vault')
eventBus.on('vault', 'enter', openNoteAndCloseModal)
eventBus.on('vault', 'shift-enter', createNoteAndCloseModal)
eventBus.on('vault', 'ctrl-enter', openNoteInNewPane)
eventBus.on('vault', 'alt-enter', insertLink)
eventBus.on('vault', 'tab', switchToInFileModal)
eventBus.on('vault', 'arrow-up', () => moveIndex(-1))
eventBus.on('vault', 'arrow-down', () => moveIndex(1))
})
onDestroy(() => {
eventBus.disable("vault")
})
onDestroy(() => {
eventBus.disable('vault')
})
async function updateResults() {
async function updateResults() {
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
selectedIndex = 0
scrollIntoView()
}
}
function onClick(evt?: MouseEvent | KeyboardEvent) {
function onClick(evt?: MouseEvent | KeyboardEvent) {
if (!selectedNote) return
openNote(selectedNote, evt?.ctrlKey) // Keep ctrl pressed to open in a new pane
modal.close()
}
}
function openNoteAndCloseModal(): void {
function openNoteAndCloseModal(): void {
if (!selectedNote) return
openNote(selectedNote)
modal.close()
}
}
function openNoteInNewPane(): void {
function openNoteInNewPane(): void {
if (!selectedNote) return
openNote(selectedNote, true)
modal.close()
}
}
async function createNoteAndCloseModal(): Promise<void> {
async function createNoteAndCloseModal(): Promise<void> {
try {
await createNote(searchQuery)
} catch (e) {
@@ -79,17 +81,17 @@ async function createNoteAndCloseModal(): Promise<void> {
return
}
modal.close()
}
}
function insertLink(): void {
function insertLink(): void {
if (!selectedNote) return
const file = app.vault
.getMarkdownFiles()
.find((f) => f.path === selectedNote.path)
.find(f => f.path === selectedNote.path)
const active = app.workspace.getActiveFile()
const view = app.workspace.getActiveViewOfType(MarkdownView)
if (!view?.editor) {
new Notice("Omnisearch - Error - No active editor", 3000)
new Notice('Omnisearch - Error - No active editor', 3000)
return
}
@@ -108,9 +110,9 @@ function insertLink(): void {
view.editor.setCursor(cursor)
modal.close()
}
}
function switchToInFileModal(): void {
function switchToInFileModal(): void {
modal.close()
if (selectedNote) {
// Open in-file modal for selected search result
@@ -125,35 +127,34 @@ function switchToInFileModal(): void {
new OmnisearchInFileModal(app, view.file, searchQuery).open()
}
}
}
}
function moveIndex(dir: 1 | -1): void {
function moveIndex(dir: 1 | -1): void {
selectedIndex = loopIndex(selectedIndex + dir, resultNotes.length)
scrollIntoView()
}
}
async function scrollIntoView(): Promise<void> {
async function scrollIntoView(): Promise<void> {
await tick()
if (selectedNote) {
const elem = document.querySelector(
`[data-result-id="${selectedNote.path}"]`
)
elem?.scrollIntoView({ behavior: "auto", block: "nearest" })
elem?.scrollIntoView({ behavior: 'auto', block: 'nearest' })
}
}
}
</script>
<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>
{#each resultNotes as result, i}
<ResultItemVault
selected={i === selectedIndex}
note={result}
on:mousemove={(e) => (selectedIndex = i)}
on:click={onClick}
/>
on:mousemove={e => (selectedIndex = i)}
on:click={onClick} />
{/each}
{#if !resultNotes.length && searchQuery}
<center> We found 0 result for your search here. </center>

View File

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

View File

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

View File

@@ -1,19 +1,19 @@
<script lang="ts">
import { getNoteFromCache } from "src/notes"
import { settings } from "src/settings"
import type { ResultNote } from "../globals"
import { getMatches } from "../search"
import { highlighter, makeExcerpt, stringsToRegex } from "../utils"
import ResultItemContainer from "./ResultItemContainer.svelte"
import { getNoteFromCache } from 'src/notes'
import { settings } from 'src/settings'
import type { ResultNote } from '../globals'
import { getMatches } from '../search'
import { highlighter, makeExcerpt, stringsToRegex } from '../utils'
import ResultItemContainer from './ResultItemContainer.svelte'
export let selected = false
export let note: ResultNote
export let selected = false
export let note: ResultNote
$: reg = stringsToRegex(note.foundWords)
$: matches = getMatches(note.content, reg)
$: cleanedContent = makeExcerpt(note.content, note.matches[0]?.offset ?? -1)
$: glyph = getNoteFromCache(note.path)?.doesNotExist
$: title = settings.showShortName ? note.basename : note.path
$: reg = stringsToRegex(note.foundWords)
$: matches = getMatches(note.content, reg)
$: cleanedContent = makeExcerpt(note.content, note.matches[0]?.offset ?? -1)
$: glyph = getNoteFromCache(note.path)?.doesNotExist
$: title = settings.showShortName ? note.basename : note.path
</script>
<ResultItemContainer id={note.path} {selected} on:mousemove on:click {glyph}>
@@ -24,7 +24,7 @@ $: title = settings.showShortName ? note.basename : note.path
{#if matches.length > 0}
<span class="omnisearch-result__counter">
{matches.length}&nbsp;{matches.length > 1 ? "matches" : "match"}
{matches.length}&nbsp;{matches.length > 1 ? 'matches' : 'match'}
</span>
{/if}
</div>

View File

@@ -14,8 +14,7 @@ export class EventBus {
public off(ctx: string, event?: string): void {
if (event) {
this.handlers.delete(`${ctx}@${event}`)
}
else {
} else {
for (const [key] of this.handlers.entries()) {
if (key.startsWith(`${ctx}@`)) {
this.handlers.delete(key)
@@ -35,7 +34,7 @@ export class EventBus {
public emit(event: string, ...args: any[]): void {
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) {
if (key.endsWith(`@${event}`)) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -63,7 +63,7 @@ export function stringsToRegex(strings: string[]): RegExp {
export function extractHeadingsFromCache(
cache: CachedMetadata,
level: number,
level: number
): string[] {
return (
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>(
array: T[],
callbackfn: (value: T, index: number, array: T[]) => Promise<U>,
callbackfn: (value: T, index: number, array: T[]) => Promise<U>
): Promise<U[]> {
return Promise.all(array.map(callbackfn))
}
@@ -120,7 +120,7 @@ function mapAsync<T, U>(
*/
export async function filterAsync<T>(
array: T[],
callbackfn: (value: T, index: number, array: T[]) => Promise<boolean>,
callbackfn: (value: T, index: number, array: T[]) => Promise<boolean>
): Promise<T[]> {
const filterMap = await mapAsync(array, callbackfn)
return array.filter((value, index) => filterMap[index])
@@ -136,10 +136,12 @@ export function stripMarkdownCharacters(text: string): string {
}
export function getAliasesFromMetadata(
metadata: CachedMetadata | null,
metadata: CachedMetadata | null
): string[] {
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))
.filter(s => !!s)
}
@@ -154,7 +156,7 @@ export function getTagsFromMetadata(metadata: CachedMetadata | null): string[] {
const fromBody = (metadata?.tags ?? []).map(t => t.tag)
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(
string: string,
options: SearchParserOptions,
options: SearchParserOptions
): SearchParserResult {
// Set a default options object when none is provided
if (!options) {
options = { offsets: true, tokenize: true }
}
else {
} else {
// If options offsets was't passed, set it to true
options.offsets =
typeof options.offsets === 'undefined' ? true : options.offsets
@@ -131,8 +130,7 @@ export function parseQuery(
if (isExcludedTerm) {
exclusion.text.push(term)
}
else {
} else {
terms.push({
text: term,
offsetStart: match.index,
@@ -164,8 +162,7 @@ export function parseQuery(
let isExclusion = false
if (!/^-/.test(key)) {
isKeyword = !(options.keywords.indexOf(key) === -1)
}
else if (key[0] === '-') {
} else if (key[0] === '-') {
const _key = key.slice(1)
isKeyword = !(options.keywords.indexOf(_key) === -1)
if (isKeyword) {
@@ -202,8 +199,7 @@ export function parseQuery(
if (values.length > 1) {
// ... concatenate both arrays.
exclusion[key] = exclusion[key].concat(values)
}
else {
} else {
// ... append the current single value.
exclusion[key].push(value)
}
@@ -229,15 +225,13 @@ export function parseQuery(
if (options.alwaysArray) {
// ...but we always return an array if option alwaysArray is true
exclusion[key] = [value]
}
else {
} else {
// Record its value as a string
exclusion[key] = value
}
}
}
}
else {
} else {
// If we already have seen that keyword...
if (query[key]) {
// ...many times...
@@ -246,8 +240,7 @@ export function parseQuery(
if (values.length > 1) {
// ... concatenate both arrays.
query[key] = query[key].concat(values)
}
else {
} else {
// ... append the current single value.
query[key].push(value)
}
@@ -272,8 +265,7 @@ export function parseQuery(
if (options.alwaysArray) {
// ...but we always return an array if option alwaysArray is true
query[key] = [value]
}
else {
} else {
// Record its value as a string
query[key] = value
}
@@ -308,8 +300,7 @@ export function parseQuery(
else {
query[key].from = value
}
}
else {
} else {
// We add it as pure text
const text = term.keyword + ':' + term.value
query.text.push(text)