Merge branch 'develop'
This commit is contained in:
10
README.md
10
README.md
@@ -48,7 +48,7 @@ You can check the [CHANGELOG](./CHANGELOG.md) for more information on the differ
|
|||||||
- Resistance to typos
|
- Resistance to typos
|
||||||
- Switch between Vault and In-file search to quickly skim multiple results in a single note
|
- Switch between Vault and In-file search to quickly skim multiple results in a single note
|
||||||
- Supports `"expressions in quotes"` and `-exclusions`
|
- Supports `"expressions in quotes"` and `-exclusions`
|
||||||
- Filters file types with '.jpg' or '.md'
|
- Filters file types with `.jpg` or `.md`
|
||||||
- Directly Insert a `[[link]]` from the search results
|
- Directly Insert a `[[link]]` from the search results
|
||||||
- Supports Vim navigation keys
|
- Supports Vim navigation keys
|
||||||
|
|
||||||
@@ -56,6 +56,14 @@ You can check the [CHANGELOG](./CHANGELOG.md) for more information on the differ
|
|||||||
on [this additional plugin](https://github.com/aidenlx/cm-chs-patch). Please read its documentation for more
|
on [this additional plugin](https://github.com/aidenlx/cm-chs-patch). Please read its documentation for more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
|
## Projects that use Omnisearch
|
||||||
|
|
||||||
|
_Submit a PR to add your own project!_
|
||||||
|
|
||||||
|
- [Omnisearch Companion](https://github.com/ALegendsTale/omnisearch-companion), an extension for your browser ([Firefox](https://addons.mozilla.org/en-US/firefox/addon/omnisearch-companion/), [Chrome](https://chromewebstore.google.com/detail/omnisearch-companion/kcjcnnlpfbilodfnnkpioijobpjhokkd))
|
||||||
|
- [Actions for Obsidian](https://actions.work/actions-for-obsidian)
|
||||||
|
- [Userscripts](https://publish.obsidian.md/omnisearch/Inject+Omnisearch+results+into+your+search+engine) to inject Omnisearch into your favorite web search engine
|
||||||
|
|
||||||
## LICENSE
|
## LICENSE
|
||||||
|
|
||||||
Omnisearch is licensed under [GPL-3](https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)).
|
Omnisearch is licensed under [GPL-3](https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)).
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "scambier.obsidian-search",
|
"name": "scambier.obsidian-search",
|
||||||
"version": "1.21.1",
|
"version": "1.22.0-beta.3",
|
||||||
"description": "A search engine for Obsidian",
|
"description": "A search engine for Obsidian",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -14,13 +14,13 @@
|
|||||||
"author": "Simon Cambier",
|
"author": "Simon Cambier",
|
||||||
"license": "GPL-3",
|
"license": "GPL-3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.23.8",
|
"@babel/preset-env": "^7.24.3",
|
||||||
"@babel/preset-typescript": "^7.23.3",
|
"@babel/preset-typescript": "^7.24.1",
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@tsconfig/svelte": "^3.0.0",
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^16.18.74",
|
"@types/node": "^16.18.91",
|
||||||
"@types/pako": "^2.0.3",
|
"@types/pako": "^2.0.3",
|
||||||
"babel-jest": "^27.5.1",
|
"babel-jest": "^27.5.1",
|
||||||
"builtin-modules": "^3.3.0",
|
"builtin-modules": "^3.3.0",
|
||||||
@@ -37,12 +37,13 @@
|
|||||||
"svelte-preprocess": "^4.10.7",
|
"svelte-preprocess": "^4.10.7",
|
||||||
"tslib": "2.3.1",
|
"tslib": "2.3.1",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"vite": "^3.2.8"
|
"vite": "^3.2.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cancelable-promise": "^4.3.1",
|
"cancelable-promise": "^4.3.1",
|
||||||
"dexie": "^3.2.4",
|
"dexie": "^3.2.7",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
|
"markdown-link-extractor": "^4.0.2",
|
||||||
"minisearch": "^6.3.0",
|
"minisearch": "^6.3.0",
|
||||||
"pure-md5": "^0.1.14",
|
"pure-md5": "^0.1.14",
|
||||||
"search-query-parser": "^1.6.0"
|
"search-query-parser": "^1.6.0"
|
||||||
|
|||||||
1500
pnpm-lock.yaml
generated
1500
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -47,6 +47,7 @@
|
|||||||
let openInCurrentPaneKey: string
|
let openInCurrentPaneKey: string
|
||||||
let createInNewPaneKey: string
|
let createInNewPaneKey: string
|
||||||
let createInCurrentPaneKey: string
|
let createInCurrentPaneKey: string
|
||||||
|
let openInNewLeafKey: string = getCtrlKeyLabel() + ' alt ↵'
|
||||||
|
|
||||||
$: selectedNote = resultNotes[selectedIndex]
|
$: selectedNote = resultNotes[selectedIndex]
|
||||||
$: searchQuery = searchQuery ?? previousQuery
|
$: searchQuery = searchQuery ?? previousQuery
|
||||||
@@ -101,6 +102,7 @@
|
|||||||
eventBus.on('vault', Action.ArrowDown, () => moveIndex(1))
|
eventBus.on('vault', Action.ArrowDown, () => moveIndex(1))
|
||||||
eventBus.on('vault', Action.PrevSearchHistory, prevSearchHistory)
|
eventBus.on('vault', Action.PrevSearchHistory, prevSearchHistory)
|
||||||
eventBus.on('vault', Action.NextSearchHistory, nextSearchHistory)
|
eventBus.on('vault', Action.NextSearchHistory, nextSearchHistory)
|
||||||
|
eventBus.on('vault', Action.OpenInNewLeaf, openNoteInNewLeaf)
|
||||||
await NotesIndex.refreshIndex()
|
await NotesIndex.refreshIndex()
|
||||||
await updateResultsDebounced()
|
await updateResultsDebounced()
|
||||||
})
|
})
|
||||||
@@ -178,16 +180,26 @@
|
|||||||
modal.close()
|
modal.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openNoteInNewLeaf(): void {
|
||||||
|
if (!selectedNote) return
|
||||||
|
openSearchResult(selectedNote, true, true)
|
||||||
|
modal.close()
|
||||||
|
}
|
||||||
|
|
||||||
function saveCurrentQuery() {
|
function saveCurrentQuery() {
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
cacheManager.addToSearchHistory(searchQuery)
|
cacheManager.addToSearchHistory(searchQuery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSearchResult(note: ResultNote, newPane = false) {
|
function openSearchResult(
|
||||||
|
note: ResultNote,
|
||||||
|
newPane = false,
|
||||||
|
newLeaf = false
|
||||||
|
) {
|
||||||
saveCurrentQuery()
|
saveCurrentQuery()
|
||||||
const offset = note.matches?.[0]?.offset ?? 0
|
const offset = note.matches?.[0]?.offset ?? 0
|
||||||
openNote(note, offset, newPane)
|
openNote(note, offset, newPane, newLeaf)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onClickCreateNote(_e: MouseEvent) {
|
async function onClickCreateNote(_e: MouseEvent) {
|
||||||
@@ -354,6 +366,11 @@
|
|||||||
<span>to open in a new pane</span>
|
<span>to open in a new pane</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="prompt-instruction">
|
||||||
|
<span class="prompt-instruction-command">{openInNewLeafKey}</span>
|
||||||
|
<span>to open in a new split</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="prompt-instruction">
|
<div class="prompt-instruction">
|
||||||
<span class="prompt-instruction-command">alt o</span>
|
<span class="prompt-instruction-command">alt o</span>
|
||||||
<span>to open in the background</span>
|
<span>to open in the background</span>
|
||||||
@@ -373,7 +390,7 @@
|
|||||||
<span>to insert a link</span>
|
<span>to insert a link</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="prompt-instruction">
|
<div class="prompt-instruction">
|
||||||
<span class="prompt-instruction-command">ctrl h</span>
|
<span class="prompt-instruction-command">ctrl g</span>
|
||||||
<span>to toggle excerpts</span>
|
<span>to toggle excerpts</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="prompt-instruction">
|
<div class="prompt-instruction">
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import { makeExcerpt, highlightText } from 'src/tools/text-processing'
|
||||||
makeExcerpt,
|
|
||||||
highlightText,
|
|
||||||
} from 'src/tools/text-processing'
|
|
||||||
import type { ResultNote } from '../globals'
|
import type { ResultNote } from '../globals'
|
||||||
import ResultItemContainer from './ResultItemContainer.svelte'
|
import ResultItemContainer from './ResultItemContainer.svelte'
|
||||||
import { cloneDeep } from 'lodash-es'
|
import { cloneDeep } from 'lodash-es'
|
||||||
@@ -13,10 +10,6 @@
|
|||||||
export let selected = false
|
export let selected = false
|
||||||
|
|
||||||
$: cleanedContent = makeExcerpt(note?.content ?? '', offset)
|
$: cleanedContent = makeExcerpt(note?.content ?? '', offset)
|
||||||
$: matchesExcerpt = cloneDeep(note.matches).map(m => {
|
|
||||||
m.offset = m.offset - cleanedContent.offset
|
|
||||||
return m
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ResultItemContainer
|
<ResultItemContainer
|
||||||
@@ -26,6 +19,6 @@
|
|||||||
on:click
|
on:click
|
||||||
on:auxclick>
|
on:auxclick>
|
||||||
<div class="omnisearch-result__body">
|
<div class="omnisearch-result__body">
|
||||||
{@html highlightText(cleanedContent.content, matchesExcerpt)}
|
{@html highlightText(cleanedContent, note.matches)}
|
||||||
</div>
|
</div>
|
||||||
</ResultItemContainer>
|
</ResultItemContainer>
|
||||||
|
|||||||
@@ -40,10 +40,6 @@
|
|||||||
$: reg = stringsToRegex(note.foundWords)
|
$: reg = stringsToRegex(note.foundWords)
|
||||||
$: matchesTitle = getMatches(title, reg)
|
$: matchesTitle = getMatches(title, reg)
|
||||||
$: matchesNotePath = getMatches(notePath, reg)
|
$: matchesNotePath = getMatches(notePath, reg)
|
||||||
$: matchesExcerpt = cloneDeep(note.matches).map(m => {
|
|
||||||
m.offset = m.offset - cleanedContent.offset
|
|
||||||
return m
|
|
||||||
})
|
|
||||||
$: cleanedContent = makeExcerpt(note.content, note.matches[0]?.offset ?? -1)
|
$: cleanedContent = makeExcerpt(note.content, note.matches[0]?.offset ?? -1)
|
||||||
$: glyph = false //cacheManager.getLiveDocument(note.path)?.doesNotExist
|
$: glyph = false //cacheManager.getLiveDocument(note.path)?.doesNotExist
|
||||||
$: {
|
$: {
|
||||||
@@ -102,7 +98,7 @@
|
|||||||
<div style="display: flex; flex-direction: row;">
|
<div style="display: flex; flex-direction: row;">
|
||||||
{#if $showExcerpt}
|
{#if $showExcerpt}
|
||||||
<div class="omnisearch-result__body">
|
<div class="omnisearch-result__body">
|
||||||
{@html highlightText(cleanedContent.content, matchesExcerpt)}
|
{@html highlightText(cleanedContent, note.matches)}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ abstract class OmnisearchModal extends Modal {
|
|||||||
let openInNewPaneKey: Modifier[]
|
let openInNewPaneKey: Modifier[]
|
||||||
let createInCurrentPaneKey: Modifier[]
|
let createInCurrentPaneKey: Modifier[]
|
||||||
let createInNewPaneKey: Modifier[]
|
let createInNewPaneKey: Modifier[]
|
||||||
|
let openInNewLeafKey: Modifier[] = ['Mod', 'Alt']
|
||||||
if (settings.openInNewPane) {
|
if (settings.openInNewPane) {
|
||||||
openInCurrentPaneKey = ['Mod']
|
openInCurrentPaneKey = ['Mod']
|
||||||
openInNewPaneKey = []
|
openInNewPaneKey = []
|
||||||
@@ -86,6 +87,12 @@ abstract class OmnisearchModal extends Modal {
|
|||||||
eventBus.emit(Action.OpenInNewPane)
|
eventBus.emit(Action.OpenInNewPane)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Open in a new leaf
|
||||||
|
this.scope.register(openInNewLeafKey, 'Enter', e => {
|
||||||
|
e.preventDefault()
|
||||||
|
eventBus.emit(Action.OpenInNewLeaf)
|
||||||
|
})
|
||||||
|
|
||||||
// Insert link
|
// Insert link
|
||||||
this.scope.register(['Alt'], 'Enter', e => {
|
this.scope.register(['Alt'], 'Enter', e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -136,7 +143,7 @@ abstract class OmnisearchModal extends Modal {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
this.scope.register(['Ctrl'], 'H', _e => {
|
this.scope.register(['Ctrl'], 'G', _e => {
|
||||||
eventBus.emit(EventNames.ToggleExcerpts)
|
eventBus.emit(EventNames.ToggleExcerpts)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const enum Action {
|
|||||||
ArrowDown = 'arrow-down',
|
ArrowDown = 'arrow-down',
|
||||||
PrevSearchHistory = 'prev-search-history',
|
PrevSearchHistory = 'prev-search-history',
|
||||||
NextSearchHistory = 'next-search-history',
|
NextSearchHistory = 'next-search-history',
|
||||||
|
OpenInNewLeaf = 'open-in-new-leaf',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DocumentRef = { path: string; mtime: number }
|
export type DocumentRef = { path: string; mtime: number }
|
||||||
|
|||||||
@@ -190,6 +190,8 @@ export class Omnisearch {
|
|||||||
headings3: settings.weightH3,
|
headings3: settings.weightH3,
|
||||||
unmarkedTags: settings.weightUnmarkedTags,
|
unmarkedTags: settings.weightUnmarkedTags,
|
||||||
},
|
},
|
||||||
|
// The query is already tokenized, don't tokenize again
|
||||||
|
tokenize: text => [text],
|
||||||
})
|
})
|
||||||
|
|
||||||
logDebug('Found', results.length, 'results')
|
logDebug('Found', results.length, 'results')
|
||||||
@@ -252,6 +254,40 @@ export class Omnisearch {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logDebug(
|
||||||
|
'searching with downranked folders',
|
||||||
|
settings.downrankedFoldersFilters
|
||||||
|
)
|
||||||
|
// downrank files that are in folders listed in the downrankedFoldersFilters
|
||||||
|
if (settings.downrankedFoldersFilters.length > 0) {
|
||||||
|
results.forEach(result => {
|
||||||
|
const path = result.id
|
||||||
|
let downrankingFolder = false
|
||||||
|
settings.downrankedFoldersFilters.forEach(filter => {
|
||||||
|
if (path.startsWith(filter)) {
|
||||||
|
// we don't want the filter to match the folder sources, e.g.
|
||||||
|
// it needs to match a whole folder name
|
||||||
|
if (path === filter || path.startsWith(filter + '/')) {
|
||||||
|
logDebug('searching with downranked folders in path: ', path)
|
||||||
|
downrankingFolder = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (downrankingFolder) {
|
||||||
|
result.score /= 10
|
||||||
|
}
|
||||||
|
const pathParts = path.split('/')
|
||||||
|
const pathPartsLength = pathParts.length
|
||||||
|
for (let i = 0; i < pathPartsLength; i++) {
|
||||||
|
const pathPart = pathParts[i]
|
||||||
|
if (settings.downrankedFoldersFilters.includes(pathPart)) {
|
||||||
|
result.score /= 10
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Extract tags from the query
|
// Extract tags from the query
|
||||||
const tags = query.getTags()
|
const tags = query.getTags()
|
||||||
|
|
||||||
@@ -370,7 +406,7 @@ export class Omnisearch {
|
|||||||
|
|
||||||
// Tags, starting with #
|
// Tags, starting with #
|
||||||
...query.getTags(),
|
...query.getTags(),
|
||||||
].filter(w => w.length > 1 || /\p{Emoji}/u.test(w))
|
]
|
||||||
logDebug('Matching tokens:', foundWords)
|
logDebug('Matching tokens:', foundWords)
|
||||||
|
|
||||||
logDebug('Getting matches locations...')
|
logDebug('Getting matches locations...')
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
getChsSegmenter,
|
getChsSegmenter,
|
||||||
} from 'src/globals'
|
} from 'src/globals'
|
||||||
import { logDebug, splitCamelCase, splitHyphens } from 'src/tools/utils'
|
import { logDebug, splitCamelCase, splitHyphens } from 'src/tools/utils'
|
||||||
|
const markdownLinkExtractor = require('markdown-link-extractor')
|
||||||
|
|
||||||
function tokenizeWords(text: string): string[] {
|
function tokenizeWords(text: string): string[] {
|
||||||
return text.split(BRACKETS_AND_SPACE)
|
return text.split(BRACKETS_AND_SPACE)
|
||||||
@@ -23,6 +24,7 @@ function tokenizeTokens(text: string): string[] {
|
|||||||
*/
|
*/
|
||||||
export function tokenizeForIndexing(text: string): string[] {
|
export function tokenizeForIndexing(text: string): string[] {
|
||||||
const words = tokenizeWords(text)
|
const words = tokenizeWords(text)
|
||||||
|
const urls: string[] = markdownLinkExtractor(text)
|
||||||
|
|
||||||
let tokens = tokenizeTokens(text)
|
let tokens = tokenizeTokens(text)
|
||||||
|
|
||||||
@@ -35,6 +37,11 @@ export function tokenizeForIndexing(text: string): string[] {
|
|||||||
// Add whole words (aka "not tokens")
|
// Add whole words (aka "not tokens")
|
||||||
tokens = [...tokens, ...words]
|
tokens = [...tokens, ...words]
|
||||||
|
|
||||||
|
// Add urls
|
||||||
|
if (urls.length) {
|
||||||
|
tokens = [...tokens, ...urls]
|
||||||
|
}
|
||||||
|
|
||||||
const chsSegmenter = getChsSegmenter()
|
const chsSegmenter = getChsSegmenter()
|
||||||
if (chsSegmenter) {
|
if (chsSegmenter) {
|
||||||
const chs = tokens.flatMap(word =>
|
const chs = tokens.flatMap(word =>
|
||||||
@@ -56,7 +63,12 @@ export function tokenizeForIndexing(text: string): string[] {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function tokenizeForSearch(text: string): QueryCombination {
|
export function tokenizeForSearch(text: string): QueryCombination {
|
||||||
const tokens = tokenizeTokens(text)
|
|
||||||
|
// Extract urls and remove them from the query
|
||||||
|
const urls: string[] = markdownLinkExtractor(text)
|
||||||
|
text = urls.reduce((acc, url) => acc.replace(url, ''), text)
|
||||||
|
|
||||||
|
const tokens = [...tokenizeTokens(text), ...urls].filter(Boolean)
|
||||||
|
|
||||||
let chs: string[] = []
|
let chs: string[] = []
|
||||||
const chsSegmenter = getChsSegmenter()
|
const chsSegmenter = getChsSegmenter()
|
||||||
@@ -70,7 +82,7 @@ export function tokenizeForSearch(text: string): QueryCombination {
|
|||||||
combineWith: 'OR',
|
combineWith: 'OR',
|
||||||
queries: [
|
queries: [
|
||||||
{ combineWith: 'AND', queries: tokens },
|
{ combineWith: 'AND', queries: tokens },
|
||||||
{ combineWith: 'AND', queries: tokenizeWords(text) },
|
{ combineWith: 'AND', queries: tokenizeWords(text).filter(Boolean) },
|
||||||
{ combineWith: 'AND', queries: tokens.flatMap(splitHyphens) },
|
{ combineWith: 'AND', queries: tokens.flatMap(splitHyphens) },
|
||||||
{ combineWith: 'AND', queries: tokens.flatMap(splitCamelCase) },
|
{ combineWith: 'AND', queries: tokens.flatMap(splitCamelCase) },
|
||||||
{ combineWith: 'AND', queries: chs },
|
{ combineWith: 'AND', queries: chs },
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export interface OmnisearchSettings extends WeightingSettings {
|
|||||||
useCache: boolean
|
useCache: boolean
|
||||||
/** Respect the "excluded files" Obsidian setting by downranking results ignored files */
|
/** Respect the "excluded files" Obsidian setting by downranking results ignored files */
|
||||||
hideExcluded: boolean
|
hideExcluded: boolean
|
||||||
|
/** downrank files in the given folders */
|
||||||
|
downrankedFoldersFilters: string[]
|
||||||
/** Ignore diacritics when indexing files */
|
/** Ignore diacritics when indexing files */
|
||||||
ignoreDiacritics: boolean
|
ignoreDiacritics: boolean
|
||||||
/** Extensions of plain text files to index, in addition to .md */
|
/** Extensions of plain text files to index, in addition to .md */
|
||||||
@@ -268,6 +270,24 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Downranked files
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Folders to downrank in search results')
|
||||||
|
.setDesc(
|
||||||
|
`Folders to downrank in search results. Files in these folders will be downranked in results. They will still be indexed for tags, unlike excluded files. Folders should be comma delimited.`
|
||||||
|
)
|
||||||
|
.addText(component => {
|
||||||
|
component
|
||||||
|
.setValue(settings.downrankedFoldersFilters.join(','))
|
||||||
|
.setPlaceholder('Example: src,p2/dir')
|
||||||
|
.onChange(async v => {
|
||||||
|
let folders = v.split(',')
|
||||||
|
folders = folders.map(f => f.trim())
|
||||||
|
settings.downrankedFoldersFilters = folders
|
||||||
|
await saveSettings(this.plugin)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// Split CamelCaseWords
|
// Split CamelCaseWords
|
||||||
const camelCaseDesc = new DocumentFragment()
|
const camelCaseDesc = new DocumentFragment()
|
||||||
camelCaseDesc.createSpan({}, span => {
|
camelCaseDesc.createSpan({}, span => {
|
||||||
@@ -621,6 +641,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
export const DEFAULT_SETTINGS: OmnisearchSettings = {
|
export const DEFAULT_SETTINGS: OmnisearchSettings = {
|
||||||
useCache: true,
|
useCache: true,
|
||||||
hideExcluded: false,
|
hideExcluded: false,
|
||||||
|
downrankedFoldersFilters: [] as string[],
|
||||||
ignoreDiacritics: true,
|
ignoreDiacritics: true,
|
||||||
indexedFileTypes: [] as string[],
|
indexedFileTypes: [] as string[],
|
||||||
PDFIndexing: false,
|
PDFIndexing: false,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function mapResults(results: ResultNote[]): ResultNoteApi[] {
|
|||||||
offset: match.offset,
|
offset: match.offset,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
excerpt: excerpt.content,
|
excerpt: excerpt,
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { stringsToRegex } from './text-processing'
|
|||||||
export async function openNote(
|
export async function openNote(
|
||||||
item: ResultNote,
|
item: ResultNote,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
newPane = false
|
newPane = false,
|
||||||
|
newLeaf = false
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Check if the note is already open,
|
// Check if the note is already open,
|
||||||
// to avoid opening it twice if the first one is pinned
|
// to avoid opening it twice if the first one is pinned
|
||||||
@@ -25,7 +26,7 @@ export async function openNote(
|
|||||||
|
|
||||||
if (!alreadyOpenAndPinned) {
|
if (!alreadyOpenAndPinned) {
|
||||||
// Open the note normally
|
// Open the note normally
|
||||||
await app.workspace.openLinkText(item.path, '', newPane)
|
await app.workspace.openLinkText(item.path, '', newLeaf ? 'split' : newPane)
|
||||||
}
|
}
|
||||||
|
|
||||||
const view = app.workspace.getActiveViewOfType(MarkdownView)
|
const view = app.workspace.getActiveViewOfType(MarkdownView)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
regexStripQuotes,
|
regexStripQuotes,
|
||||||
excerptAfter,
|
excerptAfter,
|
||||||
excerptBefore,
|
excerptBefore,
|
||||||
SEPARATORS,
|
|
||||||
} from 'src/globals'
|
} from 'src/globals'
|
||||||
import { settings } from 'src/settings'
|
import { settings } from 'src/settings'
|
||||||
import { removeDiacritics, warnDebug } from './utils'
|
import { removeDiacritics, warnDebug } from './utils'
|
||||||
@@ -26,17 +25,17 @@ export function highlightText(text: string, matches: SearchMatch[]): string {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Text to highlight
|
// Text to highlight
|
||||||
const src = new RegExp(
|
const smartMatches = new RegExp(
|
||||||
matches
|
matches
|
||||||
.map(
|
.map(
|
||||||
// This regex will match the word (with \b word boundary)
|
// This regex will match the word (with \b word boundary)
|
||||||
// \b doesn't detect non-alphabetical character's word boundary, so we need to escape it
|
// \b doesn't detect non-alphabetical character's word boundary, so we need to escape it
|
||||||
matchItem =>
|
matchItem => {
|
||||||
`\\b${escapeRegExp(matchItem.match)}\\b${
|
const escaped = escapeRegExp(matchItem.match)
|
||||||
!/[a-zA-Z]/.test(matchItem.match)
|
return `\\b${escaped}\\b${
|
||||||
? `|${escapeRegExp(matchItem.match)}`
|
!/[a-zA-Z]/.test(matchItem.match) ? `|${escaped}` : ''
|
||||||
: ''
|
|
||||||
}`
|
}`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.join('|'),
|
.join('|'),
|
||||||
'giu'
|
'giu'
|
||||||
@@ -61,7 +60,17 @@ export function highlightText(text: string, matches: SearchMatch[]): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Effectively highlight the text
|
// Effectively highlight the text
|
||||||
return text.replace(src, replacer)
|
let newText = text.replace(smartMatches, replacer)
|
||||||
|
|
||||||
|
// If the text didn't change (= nothing to highlight), re-run the regex but just replace the matches without the word boundary
|
||||||
|
if (newText === text) {
|
||||||
|
const dumbMatches = new RegExp(
|
||||||
|
matches.map(matchItem => escapeRegExp(matchItem.match)).join('|'),
|
||||||
|
'giu'
|
||||||
|
)
|
||||||
|
newText = text.replace(dumbMatches, replacer)
|
||||||
|
}
|
||||||
|
return newText
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Omnisearch - Error in highlightText()', e)
|
console.error('Omnisearch - Error in highlightText()', e)
|
||||||
return text
|
return text
|
||||||
@@ -87,7 +96,8 @@ export function removeFrontMatter(text: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to find excerpts in a note body, or select which words to highlight
|
* Converts a list of strings to a list of words, using the \b word boundary.
|
||||||
|
* Used to find excerpts in a note body, or select which words to highlight.
|
||||||
*/
|
*/
|
||||||
export function stringsToRegex(strings: string[]): RegExp {
|
export function stringsToRegex(strings: string[]): RegExp {
|
||||||
if (!strings.length) return /^$/g
|
if (!strings.length) return /^$/g
|
||||||
@@ -95,19 +105,26 @@ export function stringsToRegex(strings: string[]): RegExp {
|
|||||||
// sort strings by decreasing length, so that longer strings are matched first
|
// sort strings by decreasing length, so that longer strings are matched first
|
||||||
strings.sort((a, b) => b.length - a.length)
|
strings.sort((a, b) => b.length - a.length)
|
||||||
|
|
||||||
const joined =`(${strings.map(s => escapeRegExp(s)).join('|')})`
|
const joined = `(${strings
|
||||||
|
.map(s => `\\b${escapeRegExp(s)}\\b|${escapeRegExp(s)}`)
|
||||||
|
.join('|')})`
|
||||||
|
|
||||||
return new RegExp(`${joined}`, 'gui')
|
return new RegExp(`${joined}`, 'gui')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of matches in the text, using the provided regex
|
||||||
|
* @param text
|
||||||
|
* @param reg
|
||||||
|
* @param query
|
||||||
|
*/
|
||||||
export function getMatches(
|
export function getMatches(
|
||||||
text: string,
|
text: string,
|
||||||
reg: RegExp,
|
reg: RegExp,
|
||||||
query?: Query
|
query?: Query
|
||||||
): SearchMatch[] {
|
): SearchMatch[] {
|
||||||
const separatorRegExp = new RegExp(SEPARATORS, 'gu')
|
|
||||||
const originalText = text
|
const originalText = text
|
||||||
text = text.toLowerCase().replace(separatorRegExp, ' ')
|
// text = text.toLowerCase().replace(new RegExp(SEPARATORS, 'gu'), ' ')
|
||||||
if (settings.ignoreDiacritics) {
|
if (settings.ignoreDiacritics) {
|
||||||
text = removeDiacritics(text)
|
text = removeDiacritics(text)
|
||||||
}
|
}
|
||||||
@@ -132,24 +149,22 @@ export function getMatches(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the query is more than 1 token and can be found "as is" in the text, put this match first
|
// If the query is more than 1 token and can be found "as is" in the text, put this match first
|
||||||
if (query && (query.query.text.length > 1 || query.getExactTerms().length > 0)) {
|
if (
|
||||||
|
query &&
|
||||||
|
(query.query.text.length > 1 || query.getExactTerms().length > 0)
|
||||||
|
) {
|
||||||
const best = text.indexOf(query.getBestStringForExcerpt())
|
const best = text.indexOf(query.getBestStringForExcerpt())
|
||||||
if (best > -1 && matches.find(m => m.offset === best)) {
|
if (best > -1 && matches.find(m => m.offset === best)) {
|
||||||
matches = matches.filter(m => m.offset !== best)
|
|
||||||
matches.unshift({
|
matches.unshift({
|
||||||
offset: best,
|
offset: best,
|
||||||
match: query.getBestStringForExcerpt(),
|
match: query.getBestStringForExcerpt(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeExcerpt(
|
export function makeExcerpt(content: string, offset: number): string {
|
||||||
content: string,
|
|
||||||
offset: number
|
|
||||||
): { content: string; offset: number } {
|
|
||||||
try {
|
try {
|
||||||
const pos = offset ?? -1
|
const pos = offset ?? -1
|
||||||
const from = Math.max(0, pos - excerptBefore)
|
const from = Math.max(0, pos - excerptBefore)
|
||||||
@@ -183,14 +198,14 @@ export function makeExcerpt(
|
|||||||
content = content.trim().replaceAll('\n', '<br>')
|
content = content.trim().replaceAll('\n', '<br>')
|
||||||
}
|
}
|
||||||
|
|
||||||
return { content: content, offset: pos }
|
return content
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
new Notice(
|
new Notice(
|
||||||
'Omnisearch - Error while creating excerpt, see developer console'
|
'Omnisearch - Error while creating excerpt, see developer console'
|
||||||
)
|
)
|
||||||
console.error(`Omnisearch - Error while creating excerpt`)
|
console.error(`Omnisearch - Error while creating excerpt`)
|
||||||
console.error(e)
|
console.error(e)
|
||||||
return { content: '', offset: -1 }
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user