Merge branch 'develop'
This commit is contained in:
30
.github/workflows/release.yml
vendored
30
.github/workflows/release.yml
vendored
@@ -14,17 +14,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: pnpm/action-setup@v2.1.0
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
version: 7.17.0
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9.3.0
|
||||
run_install: true
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18.x"
|
||||
cache: 'pnpm'
|
||||
node-version: "20.x"
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
@@ -37,6 +40,18 @@ jobs:
|
||||
ls
|
||||
echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)"
|
||||
|
||||
- name: Generate a changelog
|
||||
uses: orhun/git-cliff-action@v3
|
||||
id: git-cliff
|
||||
with:
|
||||
config: cliff.toml
|
||||
args: --verbose
|
||||
env:
|
||||
GITHUB_REPO: ${{ github.repository }}
|
||||
|
||||
- name: Print the changelog
|
||||
run: cat "${{ steps.git-cliff.outputs.changelog }}"
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
@@ -46,7 +61,8 @@ jobs:
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: false
|
||||
body: ${{ steps.git-cliff.outputs.changelog }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
|
||||
- name: Upload zip file
|
||||
|
||||
12
cliff.toml
Normal file
12
cliff.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[changelog]
|
||||
header = "Changelog"
|
||||
body = """
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {{ commit.message | upper_first }}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
"""
|
||||
trim = true
|
||||
footer = "<!-- generated by git-cliff -->"
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "scambier.obsidian-search",
|
||||
"version": "1.23.1",
|
||||
"version": "1.24.0-beta.3",
|
||||
"description": "A search engine for Obsidian",
|
||||
"main": "dist/main.js",
|
||||
"scripts": {
|
||||
@@ -52,6 +52,5 @@
|
||||
"overrides": {
|
||||
"moment@>=2.18.0 <2.29.4": ">=2.29.4"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@9.1.0+sha512.67f5879916a9293e5cf059c23853d571beaf4f753c707f40cb22bed5fb1578c6aad3b6c4107ccb3ba0b35be003eb621a16471ac836c87beb53f9d54bb4612724"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ describe('The Query class', () => {
|
||||
|
||||
it('should correctly parse string queries', () => {
|
||||
// Act
|
||||
const query = new Query(stringQuery, { ignoreDiacritics: true })
|
||||
const query = new Query(stringQuery, {
|
||||
ignoreDiacritics: true,
|
||||
ignoreArabicDiacritics: true,
|
||||
})
|
||||
|
||||
// Assert
|
||||
const segments = query.query.text
|
||||
@@ -25,7 +28,10 @@ describe('The Query class', () => {
|
||||
|
||||
it('should not exclude words when there is no space before', () => {
|
||||
// Act
|
||||
const query = new Query('foo bar-baz', { ignoreDiacritics: true })
|
||||
const query = new Query('foo bar-baz', {
|
||||
ignoreDiacritics: true,
|
||||
ignoreArabicDiacritics: true,
|
||||
})
|
||||
|
||||
// Assert
|
||||
expect(query.query.exclude.text).toHaveLength(0)
|
||||
@@ -34,7 +40,10 @@ describe('The Query class', () => {
|
||||
describe('.getExactTerms()', () => {
|
||||
it('should an array of strings containg "exact" values', () => {
|
||||
// Act
|
||||
const query = new Query(stringQuery, { ignoreDiacritics: true })
|
||||
const query = new Query(stringQuery, {
|
||||
ignoreDiacritics: true,
|
||||
ignoreArabicDiacritics: true,
|
||||
})
|
||||
|
||||
// Assert
|
||||
expect(query.getExactTerms()).toEqual(['lorem ipsum', 'sit amet'])
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
getAliasesFromMetadata,
|
||||
getTagsFromMetadata,
|
||||
isFileCanvas,
|
||||
isFileFromDataloomPlugin,
|
||||
isFileFromDataloom,
|
||||
isFileImage,
|
||||
isFileOffice,
|
||||
isFilePDF,
|
||||
@@ -136,7 +136,7 @@ export class CacheManager {
|
||||
}
|
||||
|
||||
// ** Dataloom plugin **
|
||||
else if (isFileFromDataloomPlugin(path)) {
|
||||
else if (isFileFromDataloom(path)) {
|
||||
try {
|
||||
const data = JSON.parse(await app.vault.cachedRead(file))
|
||||
// data is a json object, we recursively iterate the keys
|
||||
@@ -230,10 +230,11 @@ export class CacheManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const displayTitle = metadata?.frontmatter?.[this.plugin.settings.displayTitle] ?? ''
|
||||
const tags = getTagsFromMetadata(metadata)
|
||||
return {
|
||||
basename: file.basename,
|
||||
displayTitle,
|
||||
content,
|
||||
/** Content without diacritics and markdown chars */
|
||||
cleanedContent: stripMarkdownCharacters(removeDiacritics(content)),
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
}
|
||||
query = new Query(searchQuery, {
|
||||
ignoreDiacritics: plugin.settings.ignoreDiacritics,
|
||||
ignoreArabicDiacritics: plugin.settings.ignoreArabicDiacritics,
|
||||
})
|
||||
cancelableQuery = cancelable(
|
||||
new Promise(resolve => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import type { ResultNote } from '../globals'
|
||||
import {
|
||||
getExtension,
|
||||
isFileCanvas,
|
||||
isFileCanvas, isFileExcalidraw,
|
||||
isFileImage,
|
||||
isFilePDF,
|
||||
pathWithoutFilename,
|
||||
@@ -36,7 +36,7 @@
|
||||
$: cleanedContent = plugin.textProcessor.makeExcerpt(note.content, note.matches[0]?.offset ?? -1)
|
||||
$: glyph = false //cacheManager.getLiveDocument(note.path)?.doesNotExist
|
||||
$: {
|
||||
title = note.basename
|
||||
title = note.displayTitle || note.basename
|
||||
notePath = pathWithoutFilename(note.path)
|
||||
|
||||
// Icons
|
||||
@@ -44,11 +44,18 @@
|
||||
setIcon(elFolderPathIcon, 'folder-open')
|
||||
}
|
||||
if (elFilePathIcon) {
|
||||
if (isFileImage(note.path)) setIcon(elFilePathIcon, 'image')
|
||||
else if (isFilePDF(note.path)) setIcon(elFilePathIcon, 'file-text')
|
||||
else if (isFileCanvas(note.path))
|
||||
if (isFileImage(note.path)) {
|
||||
setIcon(elFilePathIcon, 'image')
|
||||
}
|
||||
else if (isFilePDF(note.path)) {
|
||||
setIcon(elFilePathIcon, 'file-text')
|
||||
}
|
||||
else if (isFileCanvas(note.path) || isFileExcalidraw(note.path)) {
|
||||
setIcon(elFilePathIcon, 'layout-dashboard')
|
||||
else setIcon(elFilePathIcon, 'file')
|
||||
}
|
||||
else {
|
||||
setIcon(elFilePathIcon, 'file')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -119,7 +119,7 @@ abstract class OmnisearchModal extends Modal {
|
||||
})
|
||||
|
||||
// Open in background
|
||||
this.scope.register(['Alt'], 'O', e => {
|
||||
this.scope.register(['Ctrl'], 'O', e => {
|
||||
if (!isInputComposition()) {
|
||||
// Check if the user is still typing
|
||||
e.preventDefault()
|
||||
|
||||
@@ -46,6 +46,7 @@ export type DocumentRef = { path: string; mtime: number }
|
||||
export type IndexedDocument = {
|
||||
path: string
|
||||
basename: string
|
||||
displayTitle: string
|
||||
mtime: number
|
||||
|
||||
content: string
|
||||
@@ -76,6 +77,7 @@ export type ResultNote = {
|
||||
score: number
|
||||
path: string
|
||||
basename: string
|
||||
displayTitle: string
|
||||
content: string
|
||||
foundWords: string[]
|
||||
matches: SearchMatch[]
|
||||
|
||||
@@ -138,7 +138,9 @@ export default class OmnisearchPlugin extends Plugin {
|
||||
})
|
||||
)
|
||||
|
||||
this.refreshIndexCallback = this.notesIndexer.refreshIndex.bind(this.notesIndexer)
|
||||
this.refreshIndexCallback = this.notesIndexer.refreshIndex.bind(
|
||||
this.notesIndexer
|
||||
)
|
||||
addEventListener('blur', this.refreshIndexCallback)
|
||||
removeEventListener
|
||||
|
||||
@@ -272,16 +274,21 @@ export default class OmnisearchPlugin extends Plugin {
|
||||
indexingStep.set(IndexingStepType.WritingCache)
|
||||
|
||||
// Disable settings.useCache while writing the cache, in case it freezes
|
||||
const cacheEnabled = this.settings.useCache
|
||||
if (cacheEnabled && !this.settings.DANGER_forceSaveCache) {
|
||||
this.settings.useCache = false
|
||||
await saveSettings(this)
|
||||
}
|
||||
|
||||
// Write the cache
|
||||
await searchEngine.writeToCache()
|
||||
|
||||
// Re-enable settings.caching
|
||||
if (cacheEnabled) {
|
||||
this.settings.useCache = true
|
||||
await saveSettings(this)
|
||||
}
|
||||
}
|
||||
|
||||
console.timeEnd('Omnisearch - Indexing total time')
|
||||
if (diff.toAdd.length >= 1000 && isCacheEnabled()) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { removeAnchors } from './tools/notes'
|
||||
import type { IndexedDocument } from './globals'
|
||||
import {
|
||||
isFileCanvas,
|
||||
isFileFromDataloomPlugin,
|
||||
isFileFromDataloom,
|
||||
isFileImage,
|
||||
isFilePDF,
|
||||
logDebug,
|
||||
@@ -51,7 +51,7 @@ export class NotesIndexer {
|
||||
return (
|
||||
this.isFilePlaintext(path) ||
|
||||
isFileCanvas(path) ||
|
||||
isFileFromDataloomPlugin(path) ||
|
||||
isFileFromDataloom(path) ||
|
||||
(canIndexPDF && isFilePDF(path)) ||
|
||||
(canIndexImages && isFileImage(path)) ||
|
||||
(canIndexImagesAI && isFileImage(path))
|
||||
@@ -63,7 +63,7 @@ export class NotesIndexer {
|
||||
this.canIndexUnsupportedFiles() ||
|
||||
this.isFilePlaintext(path) ||
|
||||
isFileCanvas(path) ||
|
||||
isFileFromDataloomPlugin(path)
|
||||
isFileFromDataloom(path)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ export class NotesIndexer {
|
||||
return {
|
||||
path: filename,
|
||||
basename: name,
|
||||
displayTitle: '',
|
||||
mtime: 0,
|
||||
|
||||
content: '',
|
||||
|
||||
@@ -13,9 +13,9 @@ export class Query {
|
||||
}
|
||||
#inQuotes: string[]
|
||||
|
||||
constructor(text = '', options: { ignoreDiacritics: boolean }) {
|
||||
constructor(text = '', options: { ignoreDiacritics: boolean, ignoreArabicDiacritics: boolean}) {
|
||||
if (options.ignoreDiacritics) {
|
||||
text = removeDiacritics(text)
|
||||
text = removeDiacritics(text, options.ignoreArabicDiacritics)
|
||||
}
|
||||
const parsed = parse(text.toLowerCase(), {
|
||||
tokenize: true,
|
||||
|
||||
@@ -154,8 +154,9 @@ export class SearchEngine {
|
||||
term.length <= 3 ? 0 : term.length <= 5 ? fuzziness / 2 : fuzziness,
|
||||
boost: {
|
||||
basename: settings.weightBasename,
|
||||
directory: settings.weightDirectory,
|
||||
aliases: settings.weightBasename,
|
||||
displayTitle: settings.weightBasename,
|
||||
directory: settings.weightDirectory,
|
||||
headings1: settings.weightH1,
|
||||
headings2: settings.weightH2,
|
||||
headings3: settings.weightH3,
|
||||
@@ -304,7 +305,12 @@ export class SearchEngine {
|
||||
const title = document?.path.toLowerCase() ?? ''
|
||||
const content = (document?.cleanedContent ?? '').toLowerCase()
|
||||
return exactTerms.every(
|
||||
q => content.includes(q) || removeDiacritics(title).includes(q)
|
||||
q =>
|
||||
content.includes(q) ||
|
||||
removeDiacritics(
|
||||
title,
|
||||
this.plugin.settings.ignoreArabicDiacritics
|
||||
).includes(q)
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -434,7 +440,7 @@ export class SearchEngine {
|
||||
},
|
||||
processTerm: (term: string) =>
|
||||
(this.plugin.settings.ignoreDiacritics
|
||||
? removeDiacritics(term)
|
||||
? removeDiacritics(term, this.plugin.settings.ignoreArabicDiacritics)
|
||||
: term
|
||||
).toLowerCase(),
|
||||
idField: 'path',
|
||||
|
||||
@@ -15,6 +15,7 @@ export class Tokenizer {
|
||||
* @returns
|
||||
*/
|
||||
public tokenizeForIndexing(text: string): string[] {
|
||||
try {
|
||||
const words = this.tokenizeWords(text)
|
||||
let urls: string[] = []
|
||||
if (this.plugin.settings.tokenizeUrls) {
|
||||
@@ -45,6 +46,10 @@ export class Tokenizer {
|
||||
tokens = [...new Set(tokens)]
|
||||
|
||||
return tokens
|
||||
} catch (e) {
|
||||
console.error('Error tokenizing text, skipping document', e)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
190
src/settings.ts
190
src/settings.ts
@@ -11,7 +11,8 @@ import {
|
||||
import { writable } from 'svelte/store'
|
||||
import { K_DISABLE_OMNISEARCH } from './globals'
|
||||
import type OmnisearchPlugin from './main'
|
||||
import { enablePrintDebug } from "./tools/utils";
|
||||
import { enablePrintDebug } from './tools/utils'
|
||||
import { debounce } from 'lodash-es'
|
||||
|
||||
interface WeightingSettings {
|
||||
weightBasename: number
|
||||
@@ -32,8 +33,12 @@ export interface OmnisearchSettings extends WeightingSettings {
|
||||
downrankedFoldersFilters: string[]
|
||||
/** Ignore diacritics when indexing files */
|
||||
ignoreDiacritics: boolean
|
||||
ignoreArabicDiacritics: boolean
|
||||
|
||||
/** Extensions of plain text files to index, in addition to .md */
|
||||
indexedFileTypes: string[]
|
||||
/** Custom title field */
|
||||
displayTitle: string
|
||||
/** Enable PDF indexing */
|
||||
PDFIndexing: boolean
|
||||
/** Enable Images indexing */
|
||||
@@ -71,6 +76,7 @@ export interface OmnisearchSettings extends WeightingSettings {
|
||||
httpApiNotice: boolean
|
||||
|
||||
DANGER_httpHost: string | null
|
||||
DANGER_forceSaveCache: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,6 +103,11 @@ export class SettingsTab extends PluginSettingTab {
|
||||
const { containerEl } = this
|
||||
const database = this.plugin.database
|
||||
const textExtractor = this.plugin.getTextExtractor()
|
||||
|
||||
const clearCacheDebounced = debounce(async () => {
|
||||
await database.clearCache()
|
||||
}, 1000)
|
||||
|
||||
const aiImageAnalyzer = this.plugin.getAIImageAnalyzer()
|
||||
containerEl.empty()
|
||||
|
||||
@@ -138,18 +149,24 @@ export class SettingsTab extends PluginSettingTab {
|
||||
new Setting(containerEl)
|
||||
.setName('Indexing')
|
||||
.setHeading()
|
||||
.setDesc(indexingDesc)
|
||||
.setDesc(
|
||||
htmlDescription(`⚠️ <span style="color: var(--text-accent)">Changing indexing settings will clear the cache, and requires a restart of Obsidian.</span><br/><br/>
|
||||
${
|
||||
textExtractor
|
||||
? `👍 You have installed <a href="https://github.com/scambier/obsidian-text-extractor">Text Extractor</a>, Omnisearch can use it to index PDFs and images contents.
|
||||
<br />Text extraction only works on desktop, but the cache can be synchronized with your mobile device.`
|
||||
: `⚠️ Omnisearch requires <a href="https://github.com/scambier/obsidian-text-extractor">Text Extractor</a> to index PDFs and images.`
|
||||
}`)
|
||||
)
|
||||
|
||||
// PDF Indexing
|
||||
const indexPDFsDesc = new DocumentFragment()
|
||||
indexPDFsDesc.createSpan({}, span => {
|
||||
span.innerHTML = `Omnisearch will use Text Extractor to index the content of your PDFs.`
|
||||
})
|
||||
new Setting(containerEl)
|
||||
.setName(
|
||||
`PDFs content indexing ${textExtractor ? '' : '⚠️ Disabled'}`
|
||||
.setName(`PDFs content indexing ${textExtractor ? '' : '⚠️ Disabled'}`)
|
||||
.setDesc(
|
||||
htmlDescription(
|
||||
`Omnisearch will use Text Extractor to index the content of your PDFs.`
|
||||
)
|
||||
)
|
||||
.setDesc(indexPDFsDesc)
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(settings.PDFIndexing).onChange(async v => {
|
||||
await database.clearCache()
|
||||
@@ -160,13 +177,13 @@ export class SettingsTab extends PluginSettingTab {
|
||||
.setDisabled(!textExtractor)
|
||||
|
||||
// Images Indexing
|
||||
const indexImagesDesc = new DocumentFragment()
|
||||
indexImagesDesc.createSpan({}, span => {
|
||||
span.innerHTML = `Omnisearch will use Text Extractor to OCR your images and index their content.`
|
||||
})
|
||||
new Setting(containerEl)
|
||||
.setName(`Images OCR indexing ${textExtractor ? '' : '⚠️ Disabled'}`)
|
||||
.setDesc(indexImagesDesc)
|
||||
.setDesc(
|
||||
htmlDescription(
|
||||
`Omnisearch will use Text Extractor to OCR your images and index their content.`
|
||||
)
|
||||
)
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(settings.imagesIndexing).onChange(async v => {
|
||||
await database.clearCache()
|
||||
@@ -213,38 +230,49 @@ export class SettingsTab extends PluginSettingTab {
|
||||
.setDisabled(!aiImageAnalyzer)
|
||||
|
||||
// Index filenames of unsupported files
|
||||
const indexUnsupportedDesc = new DocumentFragment()
|
||||
indexUnsupportedDesc.createSpan({}, span => {
|
||||
span.innerHTML = `
|
||||
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/>
|
||||
"Obsidian setting" will respect the value of "Files & Links > Detect all file extensions".`
|
||||
})
|
||||
new Setting(containerEl)
|
||||
.setName('Index paths of unsupported files')
|
||||
.setDesc(indexUnsupportedDesc)
|
||||
.setDesc(
|
||||
htmlDescription(`
|
||||
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/>
|
||||
"Obsidian setting" will respect the value of "Files & Links > Detect all file extensions".`)
|
||||
)
|
||||
.addDropdown(dropdown => {
|
||||
dropdown
|
||||
.addOptions({ yes: 'Yes', no: 'No', default: 'Obsidian setting' })
|
||||
.setValue(settings.unsupportedFilesIndexing)
|
||||
.onChange(async v => {
|
||||
await database.clearCache()
|
||||
await clearCacheDebounced()
|
||||
;(settings.unsupportedFilesIndexing as any) = v
|
||||
await saveSettings(this.plugin)
|
||||
})
|
||||
})
|
||||
|
||||
// Additional text files to index
|
||||
const indexedFileTypesDesc = new DocumentFragment()
|
||||
indexedFileTypesDesc.createSpan({}, span => {
|
||||
span.innerHTML = `In addition to standard <code>md</code> files, Omnisearch can also index other <strong style="color: var(--text-accent)">PLAINTEXT</strong> files.<br/>
|
||||
Add extensions separated by a space, without the dot. Example: "<code>txt org csv</code>".<br />
|
||||
⚠️ <span style="color: var(--text-accent)">Using extensions of non-plaintext files (like .pptx) WILL cause crashes,
|
||||
because Omnisearch will try to index their content.</span>`
|
||||
// Custom display title
|
||||
new Setting(containerEl)
|
||||
.setName('Set frontmatter property key as title')
|
||||
.setDesc(
|
||||
htmlDescription(`If you have a custom property in your notes that you want to use as the title in search results.<br>
|
||||
Leave empty to disable.`)
|
||||
)
|
||||
.addText(component => {
|
||||
component.setValue(settings.displayTitle).onChange(async v => {
|
||||
await clearCacheDebounced()
|
||||
settings.displayTitle = v
|
||||
await saveSettings(this.plugin)
|
||||
})
|
||||
})
|
||||
|
||||
// Additional text files to index
|
||||
new Setting(containerEl)
|
||||
.setName('Additional TEXT files to index')
|
||||
.setDesc(indexedFileTypesDesc)
|
||||
.setDesc(
|
||||
htmlDescription(`In addition to standard <code>md</code> files, Omnisearch can also index other <strong style="color: var(--text-accent)">PLAINTEXT</strong> files.<br/>
|
||||
Add extensions separated by a space, without the dot. Example: "<code>txt org csv</code>".<br />
|
||||
⚠️ <span style="color: var(--text-accent)">Using extensions of non-plaintext files (like .pptx) WILL cause crashes,
|
||||
because Omnisearch will try to index their content.</span>`)
|
||||
)
|
||||
.addText(component => {
|
||||
component
|
||||
.setValue(settings.indexedFileTypes.join(' '))
|
||||
@@ -319,16 +347,13 @@ export class SettingsTab extends PluginSettingTab {
|
||||
})
|
||||
|
||||
// Split CamelCaseWords
|
||||
const camelCaseDesc = new DocumentFragment()
|
||||
camelCaseDesc.createSpan({}, span => {
|
||||
span.innerHTML = `Enable this if you want to be able to search for CamelCaseWords as separate words.<br/>
|
||||
⚠️ <span style="color: var(--text-accent)">Changing this setting will clear the cache.</span><br>
|
||||
${needsARestart}
|
||||
`
|
||||
})
|
||||
new Setting(containerEl)
|
||||
.setName('Split CamelCaseWords')
|
||||
.setDesc(camelCaseDesc)
|
||||
.setDesc(
|
||||
htmlDescription(`Enable this if you want to be able to search for CamelCaseWords as separate words.<br/>
|
||||
⚠️ <span style="color: var(--text-accent)">Changing this setting will clear the cache.</span><br>
|
||||
${needsARestart}`)
|
||||
)
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(settings.splitCamelCase).onChange(async v => {
|
||||
await database.clearCache()
|
||||
@@ -469,14 +494,12 @@ export class SettingsTab extends PluginSettingTab {
|
||||
)
|
||||
|
||||
// Show "Create note" button
|
||||
const createBtnDesc = new DocumentFragment()
|
||||
createBtnDesc.createSpan({}, span => {
|
||||
span.innerHTML = `Shows a button next to the search input, to create a note.
|
||||
Acts the same as the <code>shift ↵</code> shortcut, can be useful for mobile device users.`
|
||||
})
|
||||
new Setting(containerEl)
|
||||
.setName('Show "Create note" button')
|
||||
.setDesc(createBtnDesc)
|
||||
.setDesc(
|
||||
htmlDescription(`Shows a button next to the search input, to create a note.
|
||||
Acts the same as the <code>shift ↵</code> shortcut, can be useful for mobile device users.`)
|
||||
)
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(settings.showCreateButton).onChange(async v => {
|
||||
settings.showCreateButton = v
|
||||
@@ -589,14 +612,14 @@ export class SettingsTab extends PluginSettingTab {
|
||||
//#region HTTP Server
|
||||
|
||||
if (!Platform.isMobile) {
|
||||
const httpServerDesc = new DocumentFragment()
|
||||
httpServerDesc.createSpan({}, span => {
|
||||
span.innerHTML = `Omnisearch can be used through a simple HTTP server (<a href="https://publish.obsidian.md/omnisearch/Public+API+%26+URL+Scheme#HTTP+Server">more information</a>).`
|
||||
})
|
||||
new Setting(containerEl)
|
||||
.setName('API Access Through HTTP')
|
||||
.setHeading()
|
||||
.setDesc(httpServerDesc)
|
||||
.setDesc(
|
||||
htmlDescription(
|
||||
`Omnisearch can be used through a simple HTTP server (<a href="https://publish.obsidian.md/omnisearch/Public+API+%26+URL+Scheme#HTTP+Server">more information</a>).`
|
||||
)
|
||||
)
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Enable the HTTP server')
|
||||
@@ -668,17 +691,14 @@ export class SettingsTab extends PluginSettingTab {
|
||||
new Setting(containerEl).setName('Danger Zone').setHeading()
|
||||
|
||||
// Ignore diacritics
|
||||
const diacriticsDesc = new DocumentFragment()
|
||||
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 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>
|
||||
${needsARestart}
|
||||
`
|
||||
})
|
||||
new Setting(containerEl)
|
||||
.setName('Ignore diacritics')
|
||||
.setDesc(diacriticsDesc)
|
||||
.setDesc(
|
||||
htmlDescription(`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 <strong>NOT</strong> disable this.</span><br>
|
||||
⚠️ <span style="color: var(--text-accent)">Changing this setting will clear the cache.</span><br>
|
||||
${needsARestart}`)
|
||||
)
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(settings.ignoreDiacritics).onChange(async v => {
|
||||
await database.clearCache()
|
||||
@@ -687,6 +707,16 @@ export class SettingsTab extends PluginSettingTab {
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Ignore Arabic diacritics (beta)')
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(settings.ignoreArabicDiacritics).onChange(async v => {
|
||||
await database.clearCache()
|
||||
settings.ignoreArabicDiacritics = v
|
||||
await saveSettings(this.plugin)
|
||||
})
|
||||
)
|
||||
|
||||
// Disable Omnisearch
|
||||
const disableDesc = new DocumentFragment()
|
||||
disableDesc.createSpan({}, span => {
|
||||
@@ -700,24 +730,38 @@ export class SettingsTab extends PluginSettingTab {
|
||||
toggle.setValue(isPluginDisabled(this.app)).onChange(async v => {
|
||||
if (v) {
|
||||
this.app.saveLocalStorage(K_DISABLE_OMNISEARCH, '1')
|
||||
new Notice('Omnisearch - Disabled. Please restart Obsidian.')
|
||||
} else {
|
||||
this.app.saveLocalStorage(K_DISABLE_OMNISEARCH) // No value = unset
|
||||
new Notice('Omnisearch - Enabled. Please restart Obsidian.')
|
||||
}
|
||||
new Notice('Omnisearch - Disabled. Please restart Obsidian.')
|
||||
})
|
||||
)
|
||||
|
||||
// Force save cache
|
||||
new Setting(containerEl)
|
||||
.setName('Force save the cache')
|
||||
.setDesc(
|
||||
htmlDescription(`Omnisearch has a security feature that automatically disables cache writing if it cannot fully perform the operation.<br>
|
||||
Use this option to force the cache to be saved, even if it causes a crash.<br>
|
||||
⚠️ <span style="color: var(--text-accent)">Enabling this setting could lead to crash loops</span>`)
|
||||
)
|
||||
.addToggle(toggle =>
|
||||
toggle.setValue(settings.DANGER_forceSaveCache).onChange(async v => {
|
||||
settings.DANGER_forceSaveCache = v
|
||||
await saveSettings(this.plugin)
|
||||
})
|
||||
)
|
||||
|
||||
// Clear cache data
|
||||
if (isCacheEnabled()) {
|
||||
const resetCacheDesc = new DocumentFragment()
|
||||
resetCacheDesc.createSpan({}, span => {
|
||||
span.innerHTML = `Erase all Omnisearch cache data.
|
||||
Use this if Omnisearch results are inconsistent, missing, or appear outdated.<br>
|
||||
${needsARestart}`
|
||||
})
|
||||
new Setting(containerEl)
|
||||
.setName('Clear cache data')
|
||||
.setDesc(resetCacheDesc)
|
||||
.setDesc(
|
||||
htmlDescription(`Erase all Omnisearch cache data.
|
||||
Use this if Omnisearch results are inconsistent, missing, or appear outdated.<br>
|
||||
${needsARestart}`)
|
||||
)
|
||||
.addButton(btn => {
|
||||
btn.setButtonText('Clear cache')
|
||||
btn.onClick(async () => {
|
||||
@@ -725,6 +769,7 @@ export class SettingsTab extends PluginSettingTab {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//#endregion Danger Zone
|
||||
}
|
||||
|
||||
@@ -745,7 +790,9 @@ export function getDefaultSettings(app: App): OmnisearchSettings {
|
||||
hideExcluded: false,
|
||||
downrankedFoldersFilters: [] as string[],
|
||||
ignoreDiacritics: true,
|
||||
ignoreArabicDiacritics: false,
|
||||
indexedFileTypes: [] as string[],
|
||||
displayTitle: '',
|
||||
PDFIndexing: false,
|
||||
officeIndexing: false,
|
||||
imagesIndexing: false,
|
||||
@@ -781,11 +828,20 @@ export function getDefaultSettings(app: App): OmnisearchSettings {
|
||||
verboseLogging: false,
|
||||
|
||||
DANGER_httpHost: null,
|
||||
DANGER_forceSaveCache: false,
|
||||
}
|
||||
}
|
||||
|
||||
let settings: OmnisearchSettings
|
||||
|
||||
function htmlDescription(innerHTML: string): DocumentFragment {
|
||||
const desc = new DocumentFragment()
|
||||
desc.createSpan({}, span => {
|
||||
span.innerHTML = innerHTML
|
||||
})
|
||||
return desc
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @deprecated
|
||||
// */
|
||||
|
||||
@@ -88,6 +88,7 @@ export function getApi(plugin: OmnisearchPlugin) {
|
||||
async search(q: string): Promise<ResultNoteApi[]> {
|
||||
const query = new Query(q, {
|
||||
ignoreDiacritics: plugin.settings.ignoreDiacritics,
|
||||
ignoreArabicDiacritics: plugin.settings.ignoreArabicDiacritics,
|
||||
})
|
||||
const raw = await plugin.searchEngine.getSuggestions(query)
|
||||
return mapResults(plugin, raw)
|
||||
|
||||
@@ -115,7 +115,7 @@ export class TextProcessor {
|
||||
const originalText = text
|
||||
// text = text.toLowerCase().replace(new RegExp(SEPARATORS, 'gu'), ' ')
|
||||
if (this.plugin.settings.ignoreDiacritics) {
|
||||
text = removeDiacritics(text)
|
||||
text = removeDiacritics(text, this.plugin.settings.ignoreArabicDiacritics)
|
||||
}
|
||||
const startTime = new Date().getTime()
|
||||
let match: RegExpExecArray | null = null
|
||||
|
||||
@@ -109,15 +109,30 @@ export function getTagsFromMetadata(metadata: CachedMetadata | null): string[] {
|
||||
/**
|
||||
* https://stackoverflow.com/a/37511463
|
||||
*/
|
||||
export function removeDiacritics(str: string): string {
|
||||
// Japanese diacritics that should be distinguished
|
||||
const excludeDiacritics: string[] = ['\\u30FC', '\\u309A', '\\u3099']
|
||||
const regexpExclude: string = excludeDiacritics.join('|')
|
||||
const regexp: RegExp = new RegExp(`(?!${regexpExclude})\\p{Diacritic}`, 'gu')
|
||||
|
||||
export function removeDiacritics(str: string, arabic = false): string {
|
||||
if (str === null || str === undefined) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// Japanese diacritics that should be distinguished
|
||||
const japaneseDiacritics: string[] = ['\\u30FC', '\\u309A', '\\u3099']
|
||||
const regexpExclude: string = japaneseDiacritics.join('|')
|
||||
const regexp: RegExp = new RegExp(`(?!${regexpExclude})\\p{Diacritic}`, 'gu')
|
||||
|
||||
if (arabic) {
|
||||
// Arabic diacritics
|
||||
// https://stackoverflow.com/a/40959537
|
||||
str = str
|
||||
.replace(/([^\u0621-\u063A\u0641-\u064A\u0660-\u0669a-zA-Z 0-9])/g, '')
|
||||
.replace(/(آ|إ|أ)/g, 'ا')
|
||||
.replace(/(ة)/g, 'ه')
|
||||
.replace(/(ئ|ؤ)/g, 'ء')
|
||||
.replace(/(ى)/g, 'ي')
|
||||
for (let i = 0; i < 10; i++) {
|
||||
str.replace(String.fromCharCode(0x660 + i), String.fromCharCode(48 + i))
|
||||
}
|
||||
}
|
||||
|
||||
// Keep backticks for code blocks, because otherwise they are removed by the .normalize() function
|
||||
// https://stackoverflow.com/a/36100275
|
||||
str = str.replaceAll('`', '[__omnisearch__backtick__]')
|
||||
@@ -152,7 +167,11 @@ export function isFileCanvas(path: string): boolean {
|
||||
return path.endsWith('.canvas')
|
||||
}
|
||||
|
||||
export function isFileFromDataloomPlugin(path: string): boolean {
|
||||
export function isFileExcalidraw(path: string): boolean {
|
||||
return path.endsWith('.excalidraw')
|
||||
}
|
||||
|
||||
export function isFileFromDataloom(path: string): boolean {
|
||||
return path.endsWith('.loom')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user