Merge branch 'develop'
This commit is contained in:
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
23
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,17 +1,12 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: "[BUG]"
|
title: "[BUG] "
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
|
||||||
Please, if possible and before filing an issue,
|
|
||||||
make sure that you have the latest available version of Omnisearch.
|
|
||||||
-->
|
|
||||||
|
|
||||||
**Problem description:**
|
**Problem description:**
|
||||||
|
|
||||||
<!-- Describe your problem in details. -->
|
<!-- Describe your problem in details. -->
|
||||||
@@ -20,8 +15,16 @@ make sure that you have the latest available version of Omnisearch.
|
|||||||
|
|
||||||
**Your environment:**
|
**Your environment:**
|
||||||
|
|
||||||
- Omnisearch version:
|
<!-- Please, if possible and before filing an issue, -->
|
||||||
- Obsidian version:
|
<!-- make sure that you have the latest available version of Omnisearch. -->
|
||||||
|
|
||||||
|
- Omnisearch version:
|
||||||
|
- Obsidian version:
|
||||||
- Operating system:
|
- Operating system:
|
||||||
- Number of notes in your vault (approx.):
|
- Number of indexed documents in your vault (approx.):
|
||||||
- Other plugins that may be related to the issue:
|
|
||||||
|
**Things to try:**
|
||||||
|
|
||||||
|
- Does the problem occur when Obsidian is the only active community plugin:
|
||||||
|
- Does the problem occur when you _don't_ index PDFs, images, or other non-notes files:
|
||||||
|
- Does the problem occur after a cache reset:
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -18,13 +18,13 @@ jobs:
|
|||||||
|
|
||||||
- uses: pnpm/action-setup@v2.1.0
|
- uses: pnpm/action-setup@v2.1.0
|
||||||
with:
|
with:
|
||||||
version: 6.32.4
|
version: 7.17.0
|
||||||
run_install: true
|
run_install: true
|
||||||
|
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: "14.x"
|
node-version: "18.x"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
id: build
|
id: build
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -119,16 +119,6 @@ There are several CSS classes you can use to customize the appearance of Omnisea
|
|||||||
.omnisearch-input-field
|
.omnisearch-input-field
|
||||||
```
|
```
|
||||||
|
|
||||||
For example, if you'd like the usual yellow highlight on search matches, you can add this code inside a CSS snippet
|
|
||||||
file:
|
|
||||||
|
|
||||||
```css
|
|
||||||
.omnisearch-highlight {
|
|
||||||
color: var(--text-normal);
|
|
||||||
background-color: var(--text-highlight-bg);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
See [styles.css](./assets/styles.css) for more information.
|
See [styles.css](./assets/styles.css) for more information.
|
||||||
|
|
||||||
## Issues & Solutions
|
## Issues & Solutions
|
||||||
|
|||||||
@@ -6,5 +6,9 @@
|
|||||||
"description": "A search engine that just works",
|
"description": "A search engine that just works",
|
||||||
"author": "Simon Cambier",
|
"author": "Simon Cambier",
|
||||||
"authorUrl": "https://github.com/scambier/obsidian-omnisearch",
|
"authorUrl": "https://github.com/scambier/obsidian-omnisearch",
|
||||||
"isDesktopOnly": false
|
"isDesktopOnly": false,
|
||||||
|
"fundingUrl": {
|
||||||
|
"Github": "https://github.com/sponsors/scambier",
|
||||||
|
"Ko-fi": "https://ko-fi.com/scambier"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "scambier.obsidian-search",
|
"name": "scambier.obsidian-search",
|
||||||
"version": "1.9.1",
|
"version": "1.10.0-beta.4",
|
||||||
"description": "A search engine for Obsidian",
|
"description": "A search engine for Obsidian",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node esbuild.config.mjs",
|
"dev": "node esbuild.config.mjs",
|
||||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
"version": "node version-bump.mjs && git add manifest.json versions.json package.json",
|
"version": "node version-bump.mjs",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"dexie": "^3.2.2",
|
"dexie": "^3.2.2",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"minisearch": "6.0.0-beta.1",
|
"minisearch": "6.0.0-beta.1",
|
||||||
"obsidian-text-extract": "1.0.4",
|
"obsidian-text-extract": "^1.0.4",
|
||||||
"pure-md5": "^0.1.14"
|
"pure-md5": "^0.1.14"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -22,7 +22,7 @@ specifiers:
|
|||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
minisearch: 6.0.0-beta.1
|
minisearch: 6.0.0-beta.1
|
||||||
obsidian: latest
|
obsidian: latest
|
||||||
obsidian-text-extract: 1.0.4
|
obsidian-text-extract: ^1.0.4
|
||||||
prettier: ^2.8.1
|
prettier: ^2.8.1
|
||||||
prettier-plugin-svelte: ^2.8.1
|
prettier-plugin-svelte: ^2.8.1
|
||||||
pure-md5: ^0.1.14
|
pure-md5: ^0.1.14
|
||||||
@@ -56,7 +56,7 @@ devDependencies:
|
|||||||
esbuild-plugin-copy: 1.3.0_esbuild@0.14.0
|
esbuild-plugin-copy: 1.3.0_esbuild@0.14.0
|
||||||
esbuild-svelte: 0.7.1_wvi5wuag3veo5vm52k3h7pgaae
|
esbuild-svelte: 0.7.1_wvi5wuag3veo5vm52k3h7pgaae
|
||||||
jest: 27.5.1
|
jest: 27.5.1
|
||||||
obsidian: 0.16.3
|
obsidian: 1.1.1
|
||||||
prettier: 2.8.1
|
prettier: 2.8.1
|
||||||
prettier-plugin-svelte: 2.8.1_sro2v6ld777payjtkjtiuogcxi
|
prettier-plugin-svelte: 2.8.1_sro2v6ld777payjtkjtiuogcxi
|
||||||
svelte: 3.54.0
|
svelte: 3.54.0
|
||||||
@@ -4138,6 +4138,7 @@ packages:
|
|||||||
|
|
||||||
/obsidian-text-extract/1.0.4:
|
/obsidian-text-extract/1.0.4:
|
||||||
resolution: {integrity: sha512-lJ7HaEPGUoGVYr6iCSEU8oPrnA8xRBQgSU9KryWteq3wO+Yiw2Zue70G0rZf9Yj+DkgR92PtuR8aQZQxTy/uLA==}
|
resolution: {integrity: sha512-lJ7HaEPGUoGVYr6iCSEU8oPrnA8xRBQgSU9KryWteq3wO+Yiw2Zue70G0rZf9Yj+DkgR92PtuR8aQZQxTy/uLA==}
|
||||||
|
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||||
dependencies:
|
dependencies:
|
||||||
dexie: 3.2.2
|
dexie: 3.2.2
|
||||||
p-limit: 4.0.0
|
p-limit: 4.0.0
|
||||||
@@ -4149,8 +4150,8 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/obsidian/0.16.3:
|
/obsidian/1.1.1:
|
||||||
resolution: {integrity: sha512-hal9qk1A0GMhHSeLr2/+o3OpLmImiP+Y+sx2ewP13ds76KXsziG96n+IPFT0mSkup1zSwhEu+DeRhmbcyCCXWw==}
|
resolution: {integrity: sha512-GcxhsHNkPEkwHEjeyitfYNBcQuYGeAHFs1pEpZIv0CnzSfui8p8bPLm2YKLgcg20B764770B1sYGtxCvk9ptxg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@codemirror/state': ^6.0.0
|
'@codemirror/state': ^6.0.0
|
||||||
'@codemirror/view': ^6.0.0
|
'@codemirror/view': ^6.0.0
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { Notice } from 'obsidian'
|
import { Notice } from 'obsidian'
|
||||||
import type { DocumentRef, IndexedDocument } from './globals'
|
import {
|
||||||
|
type DocumentRef,
|
||||||
|
getTextExtractor,
|
||||||
|
type IndexedDocument,
|
||||||
|
} from './globals'
|
||||||
import { database } from './database'
|
import { database } from './database'
|
||||||
import type { AsPlainObject } from 'minisearch'
|
|
||||||
import type MiniSearch from 'minisearch'
|
|
||||||
import {
|
import {
|
||||||
extractHeadingsFromCache,
|
extractHeadingsFromCache,
|
||||||
getAliasesFromMetadata,
|
getAliasesFromMetadata,
|
||||||
getTagsFromMetadata,
|
getTagsFromMetadata,
|
||||||
|
isFileCanvas,
|
||||||
isFileImage,
|
isFileImage,
|
||||||
isFilePDF,
|
isFilePDF,
|
||||||
isFilePlaintext,
|
isFilePlaintext,
|
||||||
@@ -14,19 +17,71 @@ import {
|
|||||||
removeDiacritics,
|
removeDiacritics,
|
||||||
} from './tools/utils'
|
} from './tools/utils'
|
||||||
import { getImageText, getPdfText } from 'obsidian-text-extract'
|
import { getImageText, getPdfText } from 'obsidian-text-extract'
|
||||||
|
import type { CanvasData } from 'obsidian/canvas'
|
||||||
|
import type { AsPlainObject } from 'minisearch'
|
||||||
|
import type MiniSearch from 'minisearch'
|
||||||
|
|
||||||
async function getIndexedDocument(path: string): Promise<IndexedDocument> {
|
/**
|
||||||
|
* This function is responsible for extracting the text from a file and
|
||||||
|
* returning it as an `IndexedDocument` object.
|
||||||
|
* @param path
|
||||||
|
*/
|
||||||
|
async function getAndMapIndexedDocument(
|
||||||
|
path: string
|
||||||
|
): Promise<IndexedDocument> {
|
||||||
const file = app.vault.getFiles().find(f => f.path === path)
|
const file = app.vault.getFiles().find(f => f.path === path)
|
||||||
if (!file) throw new Error(`Invalid file path: "${path}"`)
|
if (!file) throw new Error(`Invalid file path: "${path}"`)
|
||||||
let content: string
|
let content: string | null = null
|
||||||
|
|
||||||
|
const extractor = getTextExtractor()
|
||||||
|
|
||||||
|
// ** Plain text **
|
||||||
|
// Just read the file content
|
||||||
if (isFilePlaintext(path)) {
|
if (isFilePlaintext(path)) {
|
||||||
content = await app.vault.cachedRead(file)
|
content = await app.vault.cachedRead(file)
|
||||||
} else if (isFilePDF(path)) {
|
}
|
||||||
content = await getPdfText(file)
|
|
||||||
} else if (isFileImage(file.path)) {
|
// ** Canvas **
|
||||||
content = await getImageText(file)
|
// Extract the text fields from the json
|
||||||
} else {
|
else if (isFileCanvas(path)) {
|
||||||
throw new Error('Invalid file format: ' + file.path)
|
const canvas = JSON.parse(await app.vault.cachedRead(file)) as CanvasData
|
||||||
|
let texts: string[] = []
|
||||||
|
// Concatenate text from the canvas fields
|
||||||
|
for (const node of canvas.nodes) {
|
||||||
|
if (node.type === 'text') {
|
||||||
|
texts.push(node.text)
|
||||||
|
} else if (node.type === 'file') {
|
||||||
|
texts.push(node.file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const edge of canvas.edges.filter(e => !!e.label)) {
|
||||||
|
texts.push(edge.label!)
|
||||||
|
}
|
||||||
|
content = texts.join('\r\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// a) ** Image or PDF ** with Text Extractor
|
||||||
|
else if (extractor) {
|
||||||
|
if (extractor.canFileBeExtracted(path)) {
|
||||||
|
content = await extractor.extractText(file)
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid file format: ' + file.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// b) ** Image or PDF ** without the text-extractor plugin
|
||||||
|
else {
|
||||||
|
if (isFilePDF(path)) {
|
||||||
|
content = await getPdfText(file)
|
||||||
|
} else if (isFileImage(file.path)) {
|
||||||
|
content = await getImageText(file)
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid file format: ' + file.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (content === null || content === undefined) {
|
||||||
|
// This shouldn't happen
|
||||||
|
console.warn(`Omnisearch: ${content} content for file`, file.path)
|
||||||
|
content = ''
|
||||||
}
|
}
|
||||||
content = removeDiacritics(content)
|
content = removeDiacritics(content)
|
||||||
const metadata = app.metadataCache.getFileCache(file)
|
const metadata = app.metadataCache.getFileCache(file)
|
||||||
@@ -81,8 +136,12 @@ class CacheManager {
|
|||||||
private documents: Map<string, IndexedDocument> = new Map()
|
private documents: Map<string, IndexedDocument> = new Map()
|
||||||
|
|
||||||
public async addToLiveCache(path: string): Promise<void> {
|
public async addToLiveCache(path: string): Promise<void> {
|
||||||
const doc = await getIndexedDocument(path)
|
try {
|
||||||
this.documents.set(path, doc)
|
const doc = await getAndMapIndexedDocument(path)
|
||||||
|
this.documents.set(path, doc)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Omnisearch: Error while adding to live cache', e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeFromLiveCache(path: string): void {
|
public removeFromLiveCache(path: string): void {
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
let elInput: HTMLInputElement
|
let elInput: HTMLInputElement
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export function setInputValue(v:string): void {
|
||||||
|
value = v
|
||||||
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (initialValue && !initialSet && !value) {
|
if (initialValue && !initialSet && !value) {
|
||||||
initialSet = true
|
initialSet = true
|
||||||
@@ -19,12 +23,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function selectInput() {
|
function selectInput(_?: HTMLElement): void {
|
||||||
await tick()
|
tick()
|
||||||
elInput.focus()
|
.then(() => {
|
||||||
await tick()
|
elInput.focus()
|
||||||
elInput.select()
|
return tick()
|
||||||
await tick()
|
})
|
||||||
|
.then(() => {
|
||||||
|
elInput.select()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const debouncedOnInput = debounce(() => {
|
const debouncedOnInput = debounce(() => {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
let query: Query
|
let query: Query
|
||||||
let indexingStepDesc = ''
|
let indexingStepDesc = ''
|
||||||
let searching = true
|
let searching = true
|
||||||
|
let refInput: InputSearch|undefined
|
||||||
|
|
||||||
$: selectedNote = resultNotes[selectedIndex]
|
$: selectedNote = resultNotes[selectedIndex]
|
||||||
$: searchQuery = searchQuery ?? previousQuery
|
$: searchQuery = searchQuery ?? previousQuery
|
||||||
@@ -31,6 +32,8 @@
|
|||||||
searching = true
|
searching = true
|
||||||
updateResults().then(() => {
|
updateResults().then(() => {
|
||||||
searching = false
|
searching = false
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
searching = false
|
searching = false
|
||||||
@@ -85,7 +88,8 @@
|
|||||||
if (++historySearchIndex >= history.length) {
|
if (++historySearchIndex >= history.length) {
|
||||||
historySearchIndex = 0
|
historySearchIndex = 0
|
||||||
}
|
}
|
||||||
previousQuery = history[historySearchIndex]
|
searchQuery = history[historySearchIndex]
|
||||||
|
refInput?.setInputValue(searchQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function nextSearchHistory() {
|
async function nextSearchHistory() {
|
||||||
@@ -93,7 +97,8 @@
|
|||||||
if (--historySearchIndex < 0) {
|
if (--historySearchIndex < 0) {
|
||||||
historySearchIndex = history.length ? history.length - 1 : 0
|
historySearchIndex = history.length ? history.length - 1 : 0
|
||||||
}
|
}
|
||||||
previousQuery = history[historySearchIndex]
|
searchQuery = history[historySearchIndex]
|
||||||
|
refInput?.setInputValue(searchQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateResults() {
|
async function updateResults() {
|
||||||
@@ -228,6 +233,7 @@
|
|||||||
|
|
||||||
<InputSearch
|
<InputSearch
|
||||||
initialValue="{searchQuery}"
|
initialValue="{searchQuery}"
|
||||||
|
bind:this={refInput}
|
||||||
on:input="{e => (searchQuery = e.detail)}"
|
on:input="{e => (searchQuery = e.detail)}"
|
||||||
placeholder="Omnisearch - Vault">
|
placeholder="Omnisearch - Vault">
|
||||||
{#if settings.showCreateButton}
|
{#if settings.showCreateButton}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { EventBus } from './tools/event-bus'
|
import { EventBus } from './tools/event-bus'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import { settings } from './settings'
|
import { settings } from './settings'
|
||||||
|
import type { TFile } from 'obsidian'
|
||||||
|
|
||||||
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
|
||||||
@@ -76,9 +77,27 @@ export function isInputComposition(): boolean {
|
|||||||
return inComposition
|
return inComposition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin dependency - Chs Patch for Chinese word segmentation
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export function getChsSegmenter(): any | undefined {
|
export function getChsSegmenter(): any | undefined {
|
||||||
return (app as any).plugins.plugins['cm-chs-patch']
|
return (app as any).plugins.plugins['cm-chs-patch']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TextExtractorApi = {
|
||||||
|
extractText: (file: TFile) => Promise<string>
|
||||||
|
canFileBeExtracted: (filePath: string) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin dependency - Text Extractor
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getTextExtractor(): TextExtractorApi | undefined {
|
||||||
|
return (app as any).plugins?.plugins?.['text-extractor']?.api
|
||||||
|
}
|
||||||
|
|
||||||
export const SPACE_OR_PUNCTUATION =
|
export const SPACE_OR_PUNCTUATION =
|
||||||
/[|\n\r -#%-*,-/:;?@[-\]_{}\u00A0\u00A1\u00A7\u00AB\u00B6\u00B7\u00BB\u00BF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C77\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166E\u1680\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2000-\u200A\u2010-\u2029\u202F-\u2043\u2045-\u2051\u2053-\u205F\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4F\u3000-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]+/u
|
/[|\n\r -#%-*,-/:;?@[-\]_{}\u00A0\u00A1\u00A7\u00AB\u00B6\u00B7\u00BB\u00BF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C77\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166E\u1680\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2000-\u200A\u2010-\u2029\u202F-\u2043\u2045-\u2051\u2053-\u205F\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4F\u3000-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]+/u
|
||||||
|
|||||||
12
src/main.ts
12
src/main.ts
@@ -46,7 +46,9 @@ export default class OmnisearchPlugin extends Plugin {
|
|||||||
id: 'show-modal-infile',
|
id: 'show-modal-infile',
|
||||||
name: 'In-file search',
|
name: 'In-file search',
|
||||||
editorCallback: (_editor, view) => {
|
editorCallback: (_editor, view) => {
|
||||||
new OmnisearchInFileModal(app, view.file).open()
|
if (view.file) {
|
||||||
|
new OmnisearchInFileModal(app, view.file).open()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -190,15 +192,13 @@ async function cleanOldCacheFiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function executeFirstLaunchTasks(plugin: Plugin) {
|
function executeFirstLaunchTasks(plugin: Plugin) {
|
||||||
const code = '1.8.0-beta.3'
|
const code = '1.10.0-beta.1'
|
||||||
if (settings.welcomeMessage !== code) {
|
if (settings.welcomeMessage !== code) {
|
||||||
const welcome = new DocumentFragment()
|
const welcome = new DocumentFragment()
|
||||||
welcome.createSpan({}, span => {
|
welcome.createSpan({}, span => {
|
||||||
span.innerHTML = `<strong>Omnisearch has been updated</strong>
|
span.innerHTML = `🔎 Omnisearch will soon require the <strong>Text Extractor</strong> plugin to index PDF and images. See Omnisearch settings for more information.`
|
||||||
You can now enable "Images Indexing" to use Optical Character Recognition on your scanned documents
|
|
||||||
🔎🖼`
|
|
||||||
})
|
})
|
||||||
new Notice(welcome, 30000)
|
new Notice(welcome, 20_000)
|
||||||
}
|
}
|
||||||
settings.welcomeMessage = code
|
settings.welcomeMessage = code
|
||||||
|
|
||||||
|
|||||||
@@ -104,8 +104,7 @@ export class Omnisearch {
|
|||||||
documents.filter(d => this.indexedDocuments.has(d.path)).map(d => d.path)
|
documents.filter(d => this.indexedDocuments.has(d.path)).map(d => d.path)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Split the documents in smaller chunks to regularly save the cache.
|
// Split the documents in smaller chunks to add them to minisearch
|
||||||
// If the user shuts off Obsidian mid-indexing, we at least saved some
|
|
||||||
const chunkedDocs = chunkArray(documents, 500)
|
const chunkedDocs = chunkArray(documents, 500)
|
||||||
for (const docs of chunkedDocs) {
|
for (const docs of chunkedDocs) {
|
||||||
// Update the list of indexed docs
|
// Update the list of indexed docs
|
||||||
@@ -146,7 +145,10 @@ export class Omnisearch {
|
|||||||
|
|
||||||
let results = this.minisearch.search(query.segmentsToStr(), {
|
let results = this.minisearch.search(query.segmentsToStr(), {
|
||||||
prefix: term => term.length >= options.prefixLength,
|
prefix: term => term.length >= options.prefixLength,
|
||||||
fuzzy: 0.2,
|
// length <= 3: no fuzziness
|
||||||
|
// length <= 5: fuzziness of 10%
|
||||||
|
// length > 5: fuzziness of 20%
|
||||||
|
fuzzy: term => (term.length <= 3 ? 0 : term.length <= 5 ? 0.1 : 0.2),
|
||||||
combineWith: 'AND',
|
combineWith: 'AND',
|
||||||
boost: {
|
boost: {
|
||||||
basename: settings.weightBasename,
|
basename: settings.weightBasename,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from 'obsidian'
|
} from 'obsidian'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import { database } from './database'
|
import { database } from './database'
|
||||||
|
import { getTextExtractor } from './globals'
|
||||||
import type OmnisearchPlugin from './main'
|
import type OmnisearchPlugin from './main'
|
||||||
|
|
||||||
interface WeightingSettings {
|
interface WeightingSettings {
|
||||||
@@ -83,16 +84,30 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
|
|
||||||
new Setting(containerEl).setName('Indexing').setHeading()
|
new Setting(containerEl).setName('Indexing').setHeading()
|
||||||
|
|
||||||
|
if (getTextExtractor()) {
|
||||||
|
const desc = new DocumentFragment()
|
||||||
|
desc.createSpan({}, span => {
|
||||||
|
span.innerHTML = `👍 You have installed <a href="https://github.com/scambier/obsidian-text-extractor">Text Extractor</a>, Omnisearch will use it to index PDFs and images.
|
||||||
|
<br />Text extraction only works on desktop, but the cache can be synchronized with your mobile device.`
|
||||||
|
})
|
||||||
|
new Setting(containerEl).setDesc(desc)
|
||||||
|
} else {
|
||||||
|
const label = new DocumentFragment()
|
||||||
|
label.createSpan({}, span => {
|
||||||
|
span.innerHTML = `⚠️ Omnisearch will soon require <a href="https://github.com/scambier/obsidian-text-extractor">Text Extractor</a> to index PDFs and images.
|
||||||
|
You can already install it to get a head start.`
|
||||||
|
})
|
||||||
|
new Setting(containerEl).setDesc(label)
|
||||||
|
}
|
||||||
|
|
||||||
// PDF Indexing
|
// PDF Indexing
|
||||||
if (!Platform.isMobileApp) {
|
if (!Platform.isMobileApp || getTextExtractor()) {
|
||||||
const indexPDFsDesc = new DocumentFragment()
|
const indexPDFsDesc = new DocumentFragment()
|
||||||
indexPDFsDesc.createSpan({}, span => {
|
indexPDFsDesc.createSpan({}, span => {
|
||||||
span.innerHTML = `Omnisearch will include PDFs in search results.<br>
|
span.innerHTML = `Include PDFs in search results - Will soon depend on Text Extractor.`
|
||||||
⚠️ PDFs first need to be processed. This can take anywhere from a few seconds to 2 minutes, then the resulting text is cached.</li>
|
|
||||||
<strong style="color: var(--text-accent)">Needs a restart to fully take effect.</strong>`
|
|
||||||
})
|
})
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('PDF Indexing')
|
.setName(`PDFs Indexing`)
|
||||||
.setDesc(indexPDFsDesc)
|
.setDesc(indexPDFsDesc)
|
||||||
.addToggle(toggle =>
|
.addToggle(toggle =>
|
||||||
toggle.setValue(settings.PDFIndexing).onChange(async v => {
|
toggle.setValue(settings.PDFIndexing).onChange(async v => {
|
||||||
@@ -100,21 +115,14 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
await saveSettings(this.plugin)
|
await saveSettings(this.plugin)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// Images Indexing
|
// Images Indexing
|
||||||
if (!Platform.isMobileApp) {
|
|
||||||
const indexImagesDesc = new DocumentFragment()
|
const indexImagesDesc = new DocumentFragment()
|
||||||
indexImagesDesc.createSpan({}, span => {
|
indexImagesDesc.createSpan({}, span => {
|
||||||
span.innerHTML = `Omnisearch will use <a href="https://en.wikipedia.org/wiki/Tesseract_(software)">Tesseract</a> to index images from their text.
|
span.innerHTML = `Include images in search results - Will soon depend on Text Extractor.`
|
||||||
<ul>
|
|
||||||
<li>Only English is supported at the moment.</li>
|
|
||||||
<li>Not all images can be correctly read by the OCR, this feature works best with scanned documents.</li>
|
|
||||||
</ul>
|
|
||||||
<strong style="color: var(--text-accent)">Needs a restart to fully take effect.</strong>`
|
|
||||||
})
|
})
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('BETA - Images Indexing')
|
.setName(`Images Indexing`)
|
||||||
.setDesc(indexImagesDesc)
|
.setDesc(indexImagesDesc)
|
||||||
.addToggle(toggle =>
|
.addToggle(toggle =>
|
||||||
toggle.setValue(settings.imagesIndexing).onChange(async v => {
|
toggle.setValue(settings.imagesIndexing).onChange(async v => {
|
||||||
@@ -123,6 +131,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional files to index
|
// Additional files to index
|
||||||
const indexedFileTypesDesc = new DocumentFragment()
|
const indexedFileTypesDesc = new DocumentFragment()
|
||||||
indexedFileTypesDesc.createSpan({}, span => {
|
indexedFileTypesDesc.createSpan({}, span => {
|
||||||
@@ -169,7 +178,8 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
const diacriticsDesc = new DocumentFragment()
|
const diacriticsDesc = new DocumentFragment()
|
||||||
diacriticsDesc.createSpan({}, span => {
|
diacriticsDesc.createSpan({}, span => {
|
||||||
span.innerHTML = `Normalize diacritics in search terms. Words like "brûlée" or "žluťoučký" will be indexed as "brulee" and "zlutoucky".<br/>
|
span.innerHTML = `Normalize diacritics in search terms. Words like "brûlée" or "žluťoučký" will be indexed as "brulee" and "zlutoucky".<br/>
|
||||||
⚠️<span style="color: var(--text-accent)">You probably should NOT disable this.</span><br>
|
⚠️ <span style="color: var(--text-accent)">You probably should <strong>NOT</strong> disable this.</span><br>
|
||||||
|
⚠️ <span style="color: var(--text-accent)">Changing this setting will clear the cache.</span><br>
|
||||||
<strong style="color: var(--text-accent)">Needs a restart to fully take effect.</strong>
|
<strong style="color: var(--text-accent)">Needs a restart to fully take effect.</strong>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
@@ -178,6 +188,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
.setDesc(diacriticsDesc)
|
.setDesc(diacriticsDesc)
|
||||||
.addToggle(toggle =>
|
.addToggle(toggle =>
|
||||||
toggle.setValue(settings.ignoreDiacritics).onChange(async v => {
|
toggle.setValue(settings.ignoreDiacritics).onChange(async v => {
|
||||||
|
await database.clearCache()
|
||||||
settings.ignoreDiacritics = v
|
settings.ignoreDiacritics = v
|
||||||
await saveSettings(this.plugin)
|
await saveSettings(this.plugin)
|
||||||
})
|
})
|
||||||
@@ -349,7 +360,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
weightSlider(cb: SliderComponent, key: keyof WeightingSettings): void {
|
weightSlider(cb: SliderComponent, key: keyof WeightingSettings): void {
|
||||||
cb.setLimits(1, 3, 0.1)
|
cb.setLimits(1, 5, 0.1)
|
||||||
.setValue(settings[key])
|
.setValue(settings[key])
|
||||||
.setDynamicTooltip()
|
.setDynamicTooltip()
|
||||||
.onChange(v => {
|
.onChange(v => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ResultNote } from '../globals'
|
import type { ResultNote } from '../globals'
|
||||||
import { Query } from '../search/query'
|
import { Query } from '../search/query'
|
||||||
import { searchEngine } from '../search/omnisearch'
|
import { searchEngine } from '../search/omnisearch'
|
||||||
|
import { makeExcerpt } from './utils'
|
||||||
|
|
||||||
type ResultNoteApi = {
|
type ResultNoteApi = {
|
||||||
score: number
|
score: number
|
||||||
@@ -8,6 +9,7 @@ type ResultNoteApi = {
|
|||||||
basename: string
|
basename: string
|
||||||
foundWords: string[]
|
foundWords: string[]
|
||||||
matches: SearchMatchApi[]
|
matches: SearchMatchApi[]
|
||||||
|
excerpt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SearchMatchApi = {
|
export type SearchMatchApi = {
|
||||||
@@ -17,7 +19,10 @@ export type SearchMatchApi = {
|
|||||||
|
|
||||||
function mapResults(results: ResultNote[]): ResultNoteApi[] {
|
function mapResults(results: ResultNote[]): ResultNoteApi[] {
|
||||||
return results.map(result => {
|
return results.map(result => {
|
||||||
const { score, path, basename, foundWords, matches } = result
|
const { score, path, basename, foundWords, matches, content } = result
|
||||||
|
|
||||||
|
const excerpt = makeExcerpt(content, matches[0]?.offset ?? -1)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
score,
|
score,
|
||||||
path,
|
path,
|
||||||
@@ -29,14 +34,18 @@ function mapResults(results: ResultNote[]): ResultNoteApi[] {
|
|||||||
offset: match.offset,
|
offset: match.offset,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
excerpt,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function search(q: string): Promise<ResultNoteApi[]> {
|
async function search(
|
||||||
|
q: string,
|
||||||
|
options: Partial<{ excerpt: boolean }> = {}
|
||||||
|
): Promise<ResultNoteApi[]> {
|
||||||
const query = new Query(q)
|
const query = new Query(q)
|
||||||
const raw = await searchEngine.getSuggestions(query)
|
const raw = await searchEngine.getSuggestions(query)
|
||||||
return mapResults(raw)
|
return mapResults(raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {search}
|
export default { search }
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import {
|
|||||||
parseFrontMatterAliases,
|
parseFrontMatterAliases,
|
||||||
Platform,
|
Platform,
|
||||||
} from 'obsidian'
|
} from 'obsidian'
|
||||||
import type { SearchMatch } from '../globals'
|
import { getTextExtractor, type SearchMatch } from '../globals'
|
||||||
import {
|
import {
|
||||||
getChsSegmenter,
|
|
||||||
excerptAfter,
|
excerptAfter,
|
||||||
excerptBefore,
|
excerptBefore,
|
||||||
|
getChsSegmenter,
|
||||||
highlightClass,
|
highlightClass,
|
||||||
isSearchMatch,
|
isSearchMatch,
|
||||||
regexLineSplit,
|
regexLineSplit,
|
||||||
@@ -195,14 +195,40 @@ export function getAliasesFromMetadata(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTagsFromMetadata(metadata: CachedMetadata | null): string[] {
|
export function getTagsFromMetadata(metadata: CachedMetadata | null): string[] {
|
||||||
return metadata ? getAllTags(metadata) ?? [] : []
|
let tags = metadata ? getAllTags(metadata) ?? [] : []
|
||||||
|
// This will "un-nest" tags that are in the form of "#tag/subtag"
|
||||||
|
// A tag like "#tag/subtag" will be split into 3 tags: '#tag/subtag", "#tag" and "#subtag"
|
||||||
|
// https://github.com/scambier/obsidian-omnisearch/issues/146
|
||||||
|
tags = [
|
||||||
|
...new Set(
|
||||||
|
tags.reduce((acc, tag) => {
|
||||||
|
return [
|
||||||
|
...acc,
|
||||||
|
...tag
|
||||||
|
.split('/')
|
||||||
|
.filter(t => t)
|
||||||
|
.map(t => (t.startsWith('#') ? t : `#${t}`)),
|
||||||
|
tag,
|
||||||
|
]
|
||||||
|
}, [] as string[])
|
||||||
|
),
|
||||||
|
]
|
||||||
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://stackoverflow.com/a/37511463
|
* https://stackoverflow.com/a/37511463
|
||||||
*/
|
*/
|
||||||
export function removeDiacritics(str: string): string {
|
export function removeDiacritics(str: string): string {
|
||||||
return str.normalize('NFD').replace(/\p{Diacritic}/gu, '')
|
if (str === null || str === undefined) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
// Keep backticks for code blocks, because otherwise they are removed by the .normalize() function
|
||||||
|
// https://stackoverflow.com/a/36100275
|
||||||
|
str = str.replaceAll('`', '[__omnisearch__backtick__]')
|
||||||
|
str = str.normalize('NFD').replace(/\p{Diacritic}/gu, '')
|
||||||
|
str = str.replaceAll('[__omnisearch__backtick__]', '`')
|
||||||
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCtrlKeyLabel(): 'ctrl' | '⌘' {
|
export function getCtrlKeyLabel(): 'ctrl' | '⌘' {
|
||||||
@@ -210,10 +236,13 @@ export function getCtrlKeyLabel(): 'ctrl' | '⌘' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isFileIndexable(path: string): boolean {
|
export function isFileIndexable(path: string): boolean {
|
||||||
|
const canIndexPDF = (!Platform.isMobileApp || !!getTextExtractor()) && settings.PDFIndexing
|
||||||
|
const canIndexImages = (!Platform.isMobileApp || !!getTextExtractor()) && settings.imagesIndexing
|
||||||
return (
|
return (
|
||||||
isFilePlaintext(path) ||
|
isFilePlaintext(path) ||
|
||||||
(!Platform.isMobileApp && settings.PDFIndexing && isFilePDF(path)) ||
|
isFileCanvas(path) ||
|
||||||
(!Platform.isMobileApp && settings.imagesIndexing && isFileImage(path))
|
(canIndexPDF && isFilePDF(path)) ||
|
||||||
|
(canIndexImages && isFileImage(path))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,11 +257,11 @@ export function isFilePDF(path: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isFilePlaintext(path: string): boolean {
|
export function isFilePlaintext(path: string): boolean {
|
||||||
return getPlaintextExtensions().some(t => path.endsWith(`.${t}`))
|
return [...settings.indexedFileTypes, 'md'].some(t => path.endsWith(`.${t}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPlaintextExtensions(): string[] {
|
export function isFileCanvas(path: string): boolean {
|
||||||
return [...settings.indexedFileTypes, 'md']
|
return path.endsWith('.canvas')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExtension(path: string): string {
|
export function getExtension(path: string): string {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { readFileSync, writeFileSync } from 'fs'
|
import { readFileSync, writeFileSync } from 'fs'
|
||||||
|
|
||||||
const targetVersion = process.env.npm_package_version
|
const targetVersion = process.env.npm_package_version
|
||||||
|
console.log(`Bumping version to ${targetVersion}`)
|
||||||
|
|
||||||
// read minAppVersion from manifest.json and bump version to target version
|
// read minAppVersion from manifest.json and bump version to target version
|
||||||
const manifest = JSON.parse(readFileSync('manifest-beta.json', 'utf8'))
|
const manifest = JSON.parse(readFileSync('manifest-beta.json', 'utf8'))
|
||||||
|
|||||||
Reference in New Issue
Block a user