Merge branch 'master' into develop
This commit is contained in:
11
README.md
11
README.md
@@ -6,8 +6,13 @@
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
> **Omnisearch** is a search engine that "_just works_". It always instantly shows you the most relevant results, thanks
|
> 🏆 Winner of the _[2023 Gems of the Year](https://obsidian.md/blog/2023-goty-winners/)_ in the "Existing plugin" category 🏆
|
||||||
> to its smart weighting algorithm.
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Omnisearch** is a search engine that "_just works_".
|
||||||
|
It always instantly shows you the most relevant results, thanks to its smart weighting algorithm.
|
||||||
|
|
||||||
Under the hood, it uses the excellent [MiniSearch](https://github.com/lucaong/minisearch) library.
|
Under the hood, it uses the excellent [MiniSearch](https://github.com/lucaong/minisearch) library.
|
||||||
|
|
||||||
@@ -47,7 +52,7 @@ You can check the [CHANGELOG](./CHANGELOG.md) for more information on the differ
|
|||||||
- Directly Insert a `[[link]]` from the search results
|
- Directly Insert a `[[link]]` from the search results
|
||||||
- Supports Vim navigation keys
|
- Supports Vim navigation keys
|
||||||
|
|
||||||
**Note:** support of Chinese, Japanese, Korean, etc. depends
|
**Note:** support of Chinese depends
|
||||||
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.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "omnisearch",
|
"id": "omnisearch",
|
||||||
"name": "Omnisearch",
|
"name": "Omnisearch",
|
||||||
"version": "1.21.1",
|
"version": "1.22.2",
|
||||||
"minAppVersion": "1.3.0",
|
"minAppVersion": "1.3.0",
|
||||||
"description": "A search engine that just works",
|
"description": "A search engine that just works",
|
||||||
"author": "Simon Cambier",
|
"author": "Simon Cambier",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "omnisearch",
|
"id": "omnisearch",
|
||||||
"name": "Omnisearch",
|
"name": "Omnisearch",
|
||||||
"version": "1.21.1",
|
"version": "1.22.2",
|
||||||
"minAppVersion": "1.3.0",
|
"minAppVersion": "1.3.0",
|
||||||
"description": "A search engine that just works",
|
"description": "A search engine that just works",
|
||||||
"author": "Simon Cambier",
|
"author": "Simon Cambier",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "scambier.obsidian-search",
|
"name": "scambier.obsidian-search",
|
||||||
"version": "1.22.0-beta.3",
|
"version": "1.22.2",
|
||||||
"description": "A search engine for Obsidian",
|
"description": "A search engine for Obsidian",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -52,5 +52,6 @@
|
|||||||
"overrides": {
|
"overrides": {
|
||||||
"moment@>=2.18.0 <2.29.4": ">=2.29.4"
|
"moment@>=2.18.0 <2.29.4": ">=2.29.4"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"packageManager": "pnpm@9.1.0+sha512.67f5879916a9293e5cf059c23853d571beaf4f753c707f40cb22bed5fb1578c6aad3b6c4107ccb3ba0b35be003eb621a16471ac836c87beb53f9d54bb4612724"
|
||||||
}
|
}
|
||||||
|
|||||||
5823
pnpm-lock.yaml
generated
5823
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,9 @@ import type { CanvasData } from 'obsidian/canvas'
|
|||||||
import type { AsPlainObject } from 'minisearch'
|
import type { AsPlainObject } from 'minisearch'
|
||||||
import type MiniSearch from 'minisearch'
|
import type MiniSearch from 'minisearch'
|
||||||
import { settings } from './settings'
|
import { settings } from './settings'
|
||||||
|
import { getObsidianApp } from './stores/obsidian-app'
|
||||||
|
|
||||||
|
const app = getObsidianApp()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is responsible for extracting the text from a file and
|
* This function is responsible for extracting the text from a file and
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import Dexie from 'dexie'
|
|||||||
import type { AsPlainObject } from 'minisearch'
|
import type { AsPlainObject } from 'minisearch'
|
||||||
import type { DocumentRef } from './globals'
|
import type { DocumentRef } from './globals'
|
||||||
import { Notice } from 'obsidian'
|
import { Notice } from 'obsidian'
|
||||||
|
import { getObsidianApp } from './stores/obsidian-app'
|
||||||
|
|
||||||
export class OmnisearchCache extends Dexie {
|
export class OmnisearchCache extends Dexie {
|
||||||
public static readonly dbVersion = 8
|
public static readonly dbVersion = 8
|
||||||
public static readonly dbName = 'omnisearch/cache/' + app.appId
|
public static readonly dbName = 'omnisearch/cache/' + getObsidianApp().appId
|
||||||
|
|
||||||
private static instance: OmnisearchCache
|
private static instance: OmnisearchCache
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { writable } from 'svelte/store'
|
|||||||
import { settings } from './settings'
|
import { settings } from './settings'
|
||||||
import type { TFile } from 'obsidian'
|
import type { TFile } from 'obsidian'
|
||||||
import { Platform } from 'obsidian'
|
import { Platform } from 'obsidian'
|
||||||
|
import { getObsidianApp } from './stores/obsidian-app'
|
||||||
|
|
||||||
export const regexLineSplit = /\r?\n|\r|((\.|\?|!)( |\r?\n|\r))/g
|
export const regexLineSplit = /\r?\n|\r|((\.|\?|!)( |\r?\n|\r))/g
|
||||||
export const regexYaml = /^---\s*\n(.*?)\n?^---\s?/ms
|
export const regexYaml = /^---\s*\n(.*?)\n?^---\s?/ms
|
||||||
@@ -101,7 +102,7 @@ export function isInputComposition(): boolean {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function getChsSegmenter(): any | undefined {
|
export function getChsSegmenter(): any | undefined {
|
||||||
return (app as any).plugins.plugins['cm-chs-patch']
|
return (getObsidianApp() as any).plugins.plugins['cm-chs-patch']
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TextExtractorApi = {
|
export type TextExtractorApi = {
|
||||||
@@ -114,7 +115,7 @@ export type TextExtractorApi = {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function getTextExtractor(): TextExtractorApi | undefined {
|
export function getTextExtractor(): TextExtractorApi | undefined {
|
||||||
return (app as any).plugins?.plugins?.['text-extractor']?.api
|
return (getObsidianApp() as any).plugins?.plugins?.['text-extractor']?.api
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCacheEnabled(): boolean {
|
export function isCacheEnabled(): boolean {
|
||||||
|
|||||||
19
src/main.ts
19
src/main.ts
@@ -25,14 +25,15 @@ import { database, OmnisearchCache } from './database'
|
|||||||
import * as NotesIndex from './notes-index'
|
import * as NotesIndex from './notes-index'
|
||||||
import { searchEngine } from './search/omnisearch'
|
import { searchEngine } from './search/omnisearch'
|
||||||
import { cacheManager } from './cache-manager'
|
import { cacheManager } from './cache-manager'
|
||||||
|
import { setObsidianApp } from './stores/obsidian-app'
|
||||||
|
|
||||||
export default class OmnisearchPlugin extends Plugin {
|
export default class OmnisearchPlugin extends Plugin {
|
||||||
private ribbonButton?: HTMLElement
|
|
||||||
|
|
||||||
// FIXME: fix the type
|
// FIXME: fix the type
|
||||||
public apiHttpServer: null | any = null
|
public apiHttpServer: null | any = null
|
||||||
|
private ribbonButton?: HTMLElement
|
||||||
|
|
||||||
async onload(): Promise<void> {
|
async onload(): Promise<void> {
|
||||||
|
setObsidianApp(this.app)
|
||||||
await loadSettings(this)
|
await loadSettings(this)
|
||||||
this.addSettingTab(new SettingsTab(this))
|
this.addSettingTab(new SettingsTab(this))
|
||||||
|
|
||||||
@@ -131,13 +132,13 @@ export default class OmnisearchPlugin extends Plugin {
|
|||||||
|
|
||||||
async executeFirstLaunchTasks(): Promise<void> {
|
async executeFirstLaunchTasks(): Promise<void> {
|
||||||
const code = '1.21.0'
|
const code = '1.21.0'
|
||||||
if (settings.welcomeMessage !== code && getTextExtractor()) {
|
// if (settings.welcomeMessage !== code && getTextExtractor()) {
|
||||||
const welcome = new DocumentFragment()
|
// const welcome = new DocumentFragment()
|
||||||
welcome.createSpan({}, span => {
|
// welcome.createSpan({}, span => {
|
||||||
span.innerHTML = `🔎 Omnisearch can now index .docx and .xlsx documents. Don't forget to update Text Extractor and enable the toggle in Omnisearch settings.`
|
// span.innerHTML = `🔎 Omnisearch can now index .docx and .xlsx documents. Don't forget to update Text Extractor and enable the toggle in Omnisearch settings.`
|
||||||
})
|
// })
|
||||||
new Notice(welcome, 20_000)
|
// new Notice(welcome, 20_000)
|
||||||
}
|
// }
|
||||||
settings.welcomeMessage = code
|
settings.welcomeMessage = code
|
||||||
await this.saveData(settings)
|
await this.saveData(settings)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ import { cacheManager } from '../cache-manager'
|
|||||||
import { sortBy } from 'lodash-es'
|
import { sortBy } from 'lodash-es'
|
||||||
import { getMatches, stringsToRegex } from 'src/tools/text-processing'
|
import { getMatches, stringsToRegex } from 'src/tools/text-processing'
|
||||||
import { tokenizeForIndexing, tokenizeForSearch } from './tokenizer'
|
import { tokenizeForIndexing, tokenizeForSearch } from './tokenizer'
|
||||||
|
import { getObsidianApp } from '../stores/obsidian-app'
|
||||||
|
|
||||||
export class Omnisearch {
|
export class Omnisearch {
|
||||||
|
|
||||||
|
app = getObsidianApp()
|
||||||
|
|
||||||
public static readonly options: Options<IndexedDocument> = {
|
public static readonly options: Options<IndexedDocument> = {
|
||||||
tokenize: tokenizeForIndexing,
|
tokenize: tokenizeForIndexing,
|
||||||
extractField: (doc, fieldName) => {
|
extractField: (doc, fieldName) => {
|
||||||
@@ -188,6 +192,7 @@ export class Omnisearch {
|
|||||||
headings1: settings.weightH1,
|
headings1: settings.weightH1,
|
||||||
headings2: settings.weightH2,
|
headings2: settings.weightH2,
|
||||||
headings3: settings.weightH3,
|
headings3: settings.weightH3,
|
||||||
|
tags: settings.weightUnmarkedTags,
|
||||||
unmarkedTags: settings.weightUnmarkedTags,
|
unmarkedTags: settings.weightUnmarkedTags,
|
||||||
},
|
},
|
||||||
// The query is already tokenized, don't tokenize again
|
// The query is already tokenized, don't tokenize again
|
||||||
@@ -232,36 +237,40 @@ export class Omnisearch {
|
|||||||
return results.filter(r => r.id === options.singleFilePath)
|
return results.filter(r => r.id === options.singleFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logDebug(
|
||||||
|
'searching with downranked folders',
|
||||||
|
settings.downrankedFoldersFilters
|
||||||
|
)
|
||||||
|
|
||||||
// Hide or downrank files that are in Obsidian's excluded list
|
// Hide or downrank files that are in Obsidian's excluded list
|
||||||
if (settings.hideExcluded) {
|
if (settings.hideExcluded) {
|
||||||
// Filter the files out
|
// Filter the files out
|
||||||
results = results.filter(
|
results = results.filter(
|
||||||
result =>
|
result =>
|
||||||
!(
|
!(
|
||||||
app.metadataCache.isUserIgnored &&
|
this.app.metadataCache.isUserIgnored &&
|
||||||
app.metadataCache.isUserIgnored(result.id)
|
this.app.metadataCache.isUserIgnored(result.id)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Just downrank them
|
// Just downrank them
|
||||||
results.forEach(result => {
|
results.forEach(result => {
|
||||||
if (
|
if (
|
||||||
app.metadataCache.isUserIgnored &&
|
this.app.metadataCache.isUserIgnored &&
|
||||||
app.metadataCache.isUserIgnored(result.id)
|
this.app.metadataCache.isUserIgnored(result.id)
|
||||||
) {
|
) {
|
||||||
result.score /= 10
|
result.score /= 10
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
logDebug(
|
// Extract tags from the query
|
||||||
'searching with downranked folders',
|
const tags = query.getTags()
|
||||||
settings.downrankedFoldersFilters
|
|
||||||
)
|
for (const result of results) {
|
||||||
// downrank files that are in folders listed in the downrankedFoldersFilters
|
const path = result.id
|
||||||
if (settings.downrankedFoldersFilters.length > 0) {
|
if (settings.downrankedFoldersFilters.length > 0) {
|
||||||
results.forEach(result => {
|
// downrank files that are in folders listed in the downrankedFoldersFilters
|
||||||
const path = result.id
|
|
||||||
let downrankingFolder = false
|
let downrankingFolder = false
|
||||||
settings.downrankedFoldersFilters.forEach(filter => {
|
settings.downrankedFoldersFilters.forEach(filter => {
|
||||||
if (path.startsWith(filter)) {
|
if (path.startsWith(filter)) {
|
||||||
@@ -285,21 +294,27 @@ export class Omnisearch {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Extract tags from the query
|
// Boost custom properties
|
||||||
const tags = query.getTags()
|
const metadata = this.app.metadataCache.getCache(path)
|
||||||
|
if (metadata) {
|
||||||
|
for (const { name, weight } of settings.weightCustomProperties) {
|
||||||
|
const values = metadata?.frontmatter?.[name]
|
||||||
|
if (values && result.terms.some(t => values.includes(t))) {
|
||||||
|
logDebug(`Boosting field "${name}" x${weight} for ${path}`)
|
||||||
|
result.score *= weight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Put the results with tags on top
|
// Put the results with tags on top
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
for (const result of results) {
|
|
||||||
if ((result.tags ?? []).includes(tag)) {
|
if ((result.tags ?? []).includes(tag)) {
|
||||||
result.score *= 100
|
result.score *= 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logDebug('Sorting and limiting results')
|
logDebug('Sorting and limiting results')
|
||||||
|
|
||||||
// Sort results and keep the 50 best
|
// Sort results and keep the 50 best
|
||||||
|
|||||||
@@ -5,15 +5,28 @@ import {
|
|||||||
chsRegex,
|
chsRegex,
|
||||||
getChsSegmenter,
|
getChsSegmenter,
|
||||||
} from 'src/globals'
|
} from 'src/globals'
|
||||||
|
import { settings } from 'src/settings'
|
||||||
import { logDebug, splitCamelCase, splitHyphens } from 'src/tools/utils'
|
import { logDebug, splitCamelCase, splitHyphens } from 'src/tools/utils'
|
||||||
const markdownLinkExtractor = require('markdown-link-extractor')
|
const markdownLinkExtractor = require('markdown-link-extractor')
|
||||||
|
|
||||||
function tokenizeWords(text: string): string[] {
|
function tokenizeWords(text: string, { skipChs = false } = {}): string[] {
|
||||||
return text.split(BRACKETS_AND_SPACE)
|
const tokens = text.split(BRACKETS_AND_SPACE)
|
||||||
|
if (skipChs) return tokens
|
||||||
|
return tokenizeChsWord(tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
function tokenizeTokens(text: string): string[] {
|
function tokenizeTokens(text: string, { skipChs = false } = {}): string[] {
|
||||||
return text.split(SPACE_OR_PUNCTUATION)
|
const tokens = text.split(SPACE_OR_PUNCTUATION)
|
||||||
|
if (skipChs) return tokens
|
||||||
|
return tokenizeChsWord(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenizeChsWord(tokens: string[]): string[] {
|
||||||
|
const segmenter = getChsSegmenter()
|
||||||
|
if (!segmenter) return tokens
|
||||||
|
return tokens.flatMap(word =>
|
||||||
|
chsRegex.test(word) ? segmenter.cut(word, { search: true }) : [word]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,9 +37,16 @@ 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 urls: string[] = []
|
||||||
|
if (settings.tokenizeUrls) {
|
||||||
|
try {
|
||||||
|
urls = markdownLinkExtractor(text)
|
||||||
|
} catch (e) {
|
||||||
|
logDebug('Error extracting urls', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let tokens = tokenizeTokens(text)
|
let tokens = tokenizeTokens(text, { skipChs: true })
|
||||||
|
|
||||||
// Split hyphenated tokens
|
// Split hyphenated tokens
|
||||||
tokens = [...tokens, ...tokens.flatMap(splitHyphens)]
|
tokens = [...tokens, ...tokens.flatMap(splitHyphens)]
|
||||||
@@ -42,14 +62,6 @@ export function tokenizeForIndexing(text: string): string[] {
|
|||||||
tokens = [...tokens, ...urls]
|
tokens = [...tokens, ...urls]
|
||||||
}
|
}
|
||||||
|
|
||||||
const chsSegmenter = getChsSegmenter()
|
|
||||||
if (chsSegmenter) {
|
|
||||||
const chs = tokens.flatMap(word =>
|
|
||||||
chsRegex.test(word) ? chsSegmenter.cut(word) : [word]
|
|
||||||
)
|
|
||||||
tokens = [...tokens, ...chs]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove duplicates
|
// Remove duplicates
|
||||||
tokens = [...new Set(tokens)]
|
tokens = [...new Set(tokens)]
|
||||||
|
|
||||||
@@ -63,21 +75,12 @@ export function tokenizeForIndexing(text: string): string[] {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function tokenizeForSearch(text: string): QueryCombination {
|
export function tokenizeForSearch(text: string): QueryCombination {
|
||||||
|
|
||||||
// Extract urls and remove them from the query
|
// Extract urls and remove them from the query
|
||||||
const urls: string[] = markdownLinkExtractor(text)
|
const urls: string[] = markdownLinkExtractor(text)
|
||||||
text = urls.reduce((acc, url) => acc.replace(url, ''), text)
|
text = urls.reduce((acc, url) => acc.replace(url, ''), text)
|
||||||
|
|
||||||
const tokens = [...tokenizeTokens(text), ...urls].filter(Boolean)
|
const tokens = [...tokenizeTokens(text), ...urls].filter(Boolean)
|
||||||
|
|
||||||
let chs: string[] = []
|
|
||||||
const chsSegmenter = getChsSegmenter()
|
|
||||||
if (chsSegmenter) {
|
|
||||||
chs = tokens.flatMap(word =>
|
|
||||||
chsRegex.test(word) ? chsSegmenter.cut(word) : [word]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
combineWith: 'OR',
|
combineWith: 'OR',
|
||||||
queries: [
|
queries: [
|
||||||
@@ -85,7 +88,6 @@ export function tokenizeForSearch(text: string): QueryCombination {
|
|||||||
{ combineWith: 'AND', queries: tokenizeWords(text).filter(Boolean) },
|
{ 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 },
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
src/settings.ts
102
src/settings.ts
@@ -1,3 +1,4 @@
|
|||||||
|
// noinspection CssUnresolvedCustomProperty
|
||||||
import {
|
import {
|
||||||
Notice,
|
Notice,
|
||||||
Platform,
|
Platform,
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
isCacheEnabled,
|
isCacheEnabled,
|
||||||
} from './globals'
|
} from './globals'
|
||||||
import type OmnisearchPlugin from './main'
|
import type OmnisearchPlugin from './main'
|
||||||
|
import { getObsidianApp } from './stores/obsidian-app'
|
||||||
|
|
||||||
interface WeightingSettings {
|
interface WeightingSettings {
|
||||||
weightBasename: number
|
weightBasename: number
|
||||||
@@ -25,6 +27,7 @@ interface WeightingSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface OmnisearchSettings extends WeightingSettings {
|
export interface OmnisearchSettings extends WeightingSettings {
|
||||||
|
weightCustomProperties: { name: string; weight: number }[]
|
||||||
/** Enables caching to speed up indexing */
|
/** Enables caching to speed up indexing */
|
||||||
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 */
|
||||||
@@ -58,6 +61,7 @@ export interface OmnisearchSettings extends WeightingSettings {
|
|||||||
welcomeMessage: string
|
welcomeMessage: string
|
||||||
/** If a query returns 0 result, try again with more relax conditions */
|
/** If a query returns 0 result, try again with more relax conditions */
|
||||||
simpleSearch: boolean
|
simpleSearch: boolean
|
||||||
|
tokenizeUrls: boolean
|
||||||
highlight: boolean
|
highlight: boolean
|
||||||
splitCamelCase: boolean
|
splitCamelCase: boolean
|
||||||
openInNewPane: boolean
|
openInNewPane: boolean
|
||||||
@@ -99,7 +103,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Settings main title
|
// Settings main title
|
||||||
containerEl.createEl('h2', { text: 'Omnisearch' })
|
containerEl.createEl('h1', { text: 'Omnisearch' })
|
||||||
|
|
||||||
// Sponsor link - Thank you!
|
// Sponsor link - Thank you!
|
||||||
const divSponsor = containerEl.createDiv()
|
const divSponsor = containerEl.createDiv()
|
||||||
@@ -130,7 +134,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
// PDF Indexing
|
// PDF Indexing
|
||||||
const indexPDFsDesc = new DocumentFragment()
|
const indexPDFsDesc = new DocumentFragment()
|
||||||
indexPDFsDesc.createSpan({}, span => {
|
indexPDFsDesc.createSpan({}, span => {
|
||||||
span.innerHTML = `Omnisearch will use Text Extractor to index the content of your PDFs`
|
span.innerHTML = `Omnisearch will use Text Extractor to index the content of your PDFs.`
|
||||||
})
|
})
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName(
|
.setName(
|
||||||
@@ -149,7 +153,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
// Images Indexing
|
// Images Indexing
|
||||||
const indexImagesDesc = new DocumentFragment()
|
const indexImagesDesc = new DocumentFragment()
|
||||||
indexImagesDesc.createSpan({}, span => {
|
indexImagesDesc.createSpan({}, span => {
|
||||||
span.innerHTML = `Omnisearch will use Text Extractor to OCR your images and index their content`
|
span.innerHTML = `Omnisearch will use Text Extractor to OCR your images and index their content.`
|
||||||
})
|
})
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName(`Images OCR indexing ${getTextExtractor() ? '' : '⚠️ Disabled'}`)
|
.setName(`Images OCR indexing ${getTextExtractor() ? '' : '⚠️ Disabled'}`)
|
||||||
@@ -166,7 +170,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
// Office Documents Indexing
|
// Office Documents Indexing
|
||||||
const indexOfficesDesc = new DocumentFragment()
|
const indexOfficesDesc = new DocumentFragment()
|
||||||
indexOfficesDesc.createSpan({}, span => {
|
indexOfficesDesc.createSpan({}, span => {
|
||||||
span.innerHTML = `Omnisearch will use Text Extractor to index the content of your office documents (currently <pre style="display:inline">.docx</pre> and <pre style="display:inline">.xlsx</pre>)`
|
span.innerHTML = `Omnisearch will use Text Extractor to index the content of your office documents (currently <pre style="display:inline">.docx</pre> and <pre style="display:inline">.xlsx</pre>).`
|
||||||
})
|
})
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName(
|
.setName(
|
||||||
@@ -188,7 +192,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
span.innerHTML = `
|
span.innerHTML = `
|
||||||
Omnisearch can index file<strong>names</strong> of "unsupported" files, such as e.g. <pre style="display:inline">.mp4</pre>
|
Omnisearch can index file<strong>names</strong> of "unsupported" files, such as e.g. <pre style="display:inline">.mp4</pre>
|
||||||
or non-extracted PDFs & images.<br/>
|
or non-extracted PDFs & images.<br/>
|
||||||
"Obsidian setting" will respect the value of "Files & Links > Detect all file extensions"`
|
"Obsidian setting" will respect the value of "Files & Links > Detect all file extensions".`
|
||||||
})
|
})
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Index paths of unsupported files')
|
.setName('Index paths of unsupported files')
|
||||||
@@ -199,7 +203,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
.setValue(settings.unsupportedFilesIndexing)
|
.setValue(settings.unsupportedFilesIndexing)
|
||||||
.onChange(async v => {
|
.onChange(async v => {
|
||||||
await database.clearCache()
|
await database.clearCache()
|
||||||
; (settings.unsupportedFilesIndexing as any) = v
|
;(settings.unsupportedFilesIndexing as any) = v
|
||||||
await saveSettings(this.plugin)
|
await saveSettings(this.plugin)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -261,7 +265,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
.setName('Respect Obsidian\'s "Excluded Files"')
|
.setName('Respect Obsidian\'s "Excluded Files"')
|
||||||
.setDesc(
|
.setDesc(
|
||||||
`By default, files that are in Obsidian\'s "Options > Files & Links > Excluded Files" list are downranked in results.
|
`By default, files that are in Obsidian\'s "Options > Files & Links > Excluded Files" list are downranked in results.
|
||||||
Enable this option to completely hide them`
|
Enable this option to completely hide them.`
|
||||||
)
|
)
|
||||||
.addToggle(toggle =>
|
.addToggle(toggle =>
|
||||||
toggle.setValue(settings.hideExcluded).onChange(async v => {
|
toggle.setValue(settings.hideExcluded).onChange(async v => {
|
||||||
@@ -321,6 +325,23 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Extract URLs
|
||||||
|
// Crashes on iOS
|
||||||
|
if (!Platform.isIosApp) {
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Tokenize URLs')
|
||||||
|
.setDesc(
|
||||||
|
`Enable this if you want to be able to search for URLs as separate words.
|
||||||
|
This setting has a strong impact on indexing performance, and can crash Obsidian under certain conditions.`
|
||||||
|
)
|
||||||
|
.addToggle(toggle =>
|
||||||
|
toggle.setValue(settings.tokenizeUrls).onChange(async v => {
|
||||||
|
settings.tokenizeUrls = v
|
||||||
|
await saveSettings(this.plugin)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Open in new pane
|
// Open in new pane
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Open in new pane')
|
.setName('Open in new pane')
|
||||||
@@ -480,10 +501,63 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName(
|
.setName(
|
||||||
`Tags without the # (default: ${DEFAULT_SETTINGS.weightUnmarkedTags})`
|
`Tags (default: ${DEFAULT_SETTINGS.weightUnmarkedTags})`
|
||||||
)
|
)
|
||||||
.addSlider(cb => this.weightSlider(cb, 'weightUnmarkedTags'))
|
.addSlider(cb => this.weightSlider(cb, 'weightUnmarkedTags'))
|
||||||
|
|
||||||
|
//#region Specific tags
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Header properties fields')
|
||||||
|
.setDesc('You can set custom weights for values of header properties (e.g. "keywords").')
|
||||||
|
|
||||||
|
|
||||||
|
for (let i = 0; i < settings.weightCustomProperties.length; i++) {
|
||||||
|
const item = settings.weightCustomProperties[i]
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName((i + 1).toString() + '.')
|
||||||
|
// TODO: add autocompletion from app.metadataCache.getAllPropertyInfos()
|
||||||
|
.addText(text => {
|
||||||
|
text
|
||||||
|
.setPlaceholder('Property name')
|
||||||
|
.setValue(item.name)
|
||||||
|
.onChange(async v => {
|
||||||
|
item.name = v
|
||||||
|
await saveSettings(this.plugin)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.addSlider(cb => {
|
||||||
|
cb.setLimits(1, 5, 0.1)
|
||||||
|
.setValue(item.weight)
|
||||||
|
.setDynamicTooltip()
|
||||||
|
.onChange(async v => {
|
||||||
|
item.weight = v
|
||||||
|
await saveSettings(this.plugin)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Remove the tag
|
||||||
|
.addButton(btn => {
|
||||||
|
btn.setButtonText('Remove')
|
||||||
|
btn.onClick(async () => {
|
||||||
|
settings.weightCustomProperties.splice(i, 1)
|
||||||
|
await saveSettings(this.plugin)
|
||||||
|
this.display()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new custom tag
|
||||||
|
new Setting(containerEl)
|
||||||
|
.addButton(btn => {
|
||||||
|
btn.setButtonText('Add a new property')
|
||||||
|
btn.onClick(cb => {
|
||||||
|
settings.weightCustomProperties.push({ name: '', weight: 1 })
|
||||||
|
this.display()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
//#endregion Specific tags
|
||||||
|
|
||||||
//#endregion Results Weighting
|
//#endregion Results Weighting
|
||||||
|
|
||||||
//#region HTTP Server
|
//#region HTTP Server
|
||||||
@@ -617,9 +691,9 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Clear cache data')
|
.setName('Clear cache data')
|
||||||
.setDesc(resetCacheDesc)
|
.setDesc(resetCacheDesc)
|
||||||
.addButton(cb => {
|
.addButton(btn => {
|
||||||
cb.setButtonText('Clear cache')
|
btn.setButtonText('Clear cache')
|
||||||
cb.onClick(async () => {
|
btn.onClick(async () => {
|
||||||
await database.clearCache()
|
await database.clearCache()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -638,6 +712,8 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const app = getObsidianApp()
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: OmnisearchSettings = {
|
export const DEFAULT_SETTINGS: OmnisearchSettings = {
|
||||||
useCache: true,
|
useCache: true,
|
||||||
hideExcluded: false,
|
hideExcluded: false,
|
||||||
@@ -647,7 +723,7 @@ export const DEFAULT_SETTINGS: OmnisearchSettings = {
|
|||||||
PDFIndexing: false,
|
PDFIndexing: false,
|
||||||
officeIndexing: false,
|
officeIndexing: false,
|
||||||
imagesIndexing: false,
|
imagesIndexing: false,
|
||||||
unsupportedFilesIndexing: 'no',
|
unsupportedFilesIndexing: 'default',
|
||||||
splitCamelCase: false,
|
splitCamelCase: false,
|
||||||
openInNewPane: false,
|
openInNewPane: false,
|
||||||
vimLikeNavigationShortcut: app.vault.getConfig('vimMode') as boolean,
|
vimLikeNavigationShortcut: app.vault.getConfig('vimMode') as boolean,
|
||||||
@@ -659,6 +735,7 @@ export const DEFAULT_SETTINGS: OmnisearchSettings = {
|
|||||||
highlight: true,
|
highlight: true,
|
||||||
showPreviousQueryResults: true,
|
showPreviousQueryResults: true,
|
||||||
simpleSearch: false,
|
simpleSearch: false,
|
||||||
|
tokenizeUrls: false,
|
||||||
fuzziness: '1',
|
fuzziness: '1',
|
||||||
|
|
||||||
weightBasename: 3,
|
weightBasename: 3,
|
||||||
@@ -667,6 +744,7 @@ export const DEFAULT_SETTINGS: OmnisearchSettings = {
|
|||||||
weightH2: 1.3,
|
weightH2: 1.3,
|
||||||
weightH3: 1.1,
|
weightH3: 1.1,
|
||||||
weightUnmarkedTags: 1.1,
|
weightUnmarkedTags: 1.1,
|
||||||
|
weightCustomProperties: [] as { name: string; weight: number }[],
|
||||||
|
|
||||||
httpApiEnabled: false,
|
httpApiEnabled: false,
|
||||||
httpApiPort: '51361',
|
httpApiPort: '51361',
|
||||||
|
|||||||
17
src/stores/obsidian-app.ts
Normal file
17
src/stores/obsidian-app.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { App } from 'obsidian'
|
||||||
|
|
||||||
|
let obsidianApp: App | null = null
|
||||||
|
|
||||||
|
export function setObsidianApp(app: App) {
|
||||||
|
obsidianApp = app
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to get the Obsidian app instance.
|
||||||
|
*/
|
||||||
|
export function getObsidianApp() {
|
||||||
|
if (!obsidianApp) {
|
||||||
|
throw new Error('Obsidian app not set')
|
||||||
|
}
|
||||||
|
return obsidianApp as App
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import type { ResultNote } from 'src/globals'
|
|
||||||
import { writable } from 'svelte/store'
|
|
||||||
|
|
||||||
function createSearchResultsStore() {
|
|
||||||
const { subscribe, set, update } = writable<ResultNote[]>([])
|
|
||||||
|
|
||||||
return {
|
|
||||||
subscribe,
|
|
||||||
add: (item: ResultNote) =>
|
|
||||||
update(arr => {
|
|
||||||
arr.push(item)
|
|
||||||
return arr
|
|
||||||
}),
|
|
||||||
set: (items: ResultNote[]) => set(items),
|
|
||||||
reset: () => set([]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const searchResultsStore = createSearchResultsStore()
|
|
||||||
@@ -3,6 +3,7 @@ import { Query } from '../search/query'
|
|||||||
import { searchEngine } from '../search/omnisearch'
|
import { searchEngine } from '../search/omnisearch'
|
||||||
import { makeExcerpt } from './text-processing'
|
import { makeExcerpt } from './text-processing'
|
||||||
import { refreshIndex } from '../notes-index'
|
import { refreshIndex } from '../notes-index'
|
||||||
|
import { getObsidianApp } from '../stores/obsidian-app'
|
||||||
|
|
||||||
type ResultNoteApi = {
|
type ResultNoteApi = {
|
||||||
score: number
|
score: number
|
||||||
@@ -19,6 +20,8 @@ export type SearchMatchApi = {
|
|||||||
offset: number
|
offset: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const app = getObsidianApp()
|
||||||
|
|
||||||
let notified = false
|
let notified = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { type CachedMetadata, MarkdownView, TFile } from 'obsidian'
|
import { type CachedMetadata, MarkdownView, TFile } from 'obsidian'
|
||||||
import type { ResultNote } from '../globals'
|
import type { ResultNote } from '../globals'
|
||||||
import { stringsToRegex } from './text-processing'
|
import { getObsidianApp } from '../stores/obsidian-app'
|
||||||
|
|
||||||
|
const app = getObsidianApp()
|
||||||
|
|
||||||
export async function openNote(
|
export async function openNote(
|
||||||
item: ResultNote,
|
item: ResultNote,
|
||||||
|
|||||||
@@ -129,5 +129,11 @@
|
|||||||
"1.20.3": "1.3.0",
|
"1.20.3": "1.3.0",
|
||||||
"1.20.4": "1.3.0",
|
"1.20.4": "1.3.0",
|
||||||
"1.21.0": "1.3.0",
|
"1.21.0": "1.3.0",
|
||||||
"1.21.1": "1.3.0"
|
"1.21.1": "1.3.0",
|
||||||
|
"1.22.0-beta.1": "1.3.0",
|
||||||
|
"1.22.0-beta.2": "1.3.0",
|
||||||
|
"1.22.0-beta.3": "1.3.0",
|
||||||
|
"1.22.0": "1.3.0",
|
||||||
|
"1.22.1": "1.3.0",
|
||||||
|
"1.22.2": "1.3.0"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user