diff --git a/.gitignore b/.gitignore index f2e159b..ad1af60 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Cargo.lock .vscode-test .vscode-launch.json .vscode-tasks.json +copy_to_vault.sh \ No newline at end of file diff --git a/docs/dev-docs.md b/docs/dev-docs.md index 5fd5836..9582ebe 100644 --- a/docs/dev-docs.md +++ b/docs/dev-docs.md @@ -15,6 +15,14 @@ The TypeScript part of the project is used create the UI using React. Also, it s Contributions are welcome, but please make sure they are understandable and no bloat +#### Building + +- Build the project by running `npm run build` in the root directory +- To easily copy the build results into your vault, run `npm run build-copy` in the root directory + - NOTE: Don't forget to: + - change the vault path in `package.json` to your vault path! + - make the copy script executable by running `chmod +x ./copy_to_vault.sh` + ### πŸ—ΊοΈ Roadmap ### Near future diff --git a/src/lib.rs b/src/lib.rs index 983317c..3f4e8d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ use crate::rs::note::note_scanned_event::NoteScannedEvent; mod rs; #[wasm_bindgen] -pub fn find(context: JsValue, notes: Array, callback: Function) -> Array { +pub fn find_in_vault(context: JsValue, notes: Array, callback: Function) -> Array { let notes: Vec = notes.iter() .filter_map(|note: JsValue| Note::try_from(note).ok()) .collect(); @@ -36,6 +36,31 @@ pub fn find(context: JsValue, notes: Array, callback: Function) -> Array { array } + +#[wasm_bindgen] +pub fn find_in_note(context: JsValue, active_note: Note, notes: Array, callback: Function) -> Array { + let notes: Vec = notes.iter() + .filter_map(|note: JsValue| Note::try_from(note).ok()) + .collect(); + let mut note = active_note.clone(); + + let mut res: Vec = vec![]; + + let _ = call_callback(&callback, &context, build_args(¬e, 0)); + + let link_finder_result_option = link_finder::find_links(&mut note, ¬es); + if let Some(r) = link_finder_result_option { + res.push(r); + } + + let array: Array = Array::new(); + for r in res { + let js: JsValue = r.into(); + array.push(&js); + } + array +} + fn build_args(note: &Note, index: usize) -> Array { let args = js_sys::Array::new(); let note_scanned_event = NoteScannedEvent::new(note, index).to_json_string(); diff --git a/src/main.ts b/src/main.ts index 4b483a9..e3c395c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,34 +1,58 @@ -import {Plugin} from "obsidian"; +import { Plugin } from "obsidian"; import rustPlugin from "../pkg/obisidian_note_linker_bg.wasm"; import * as wasm from "../pkg"; import MainModal from "./ts/MainModal"; -import {init_panic_hook} from "../pkg/"; +import { init_panic_hook } from "../pkg/"; import * as Comlink from "comlink"; // @ts-ignore -import Wcw from 'web-worker:./ts/webWorkers/WasmWorker.ts'; +import Wcw from "web-worker:./ts/webWorkers/WasmWorker.ts"; +import { MatchingMode } from "./ts/components/containers/MainComponent"; export default class RustPlugin extends Plugin { - async onload() { - // init wasm - const buffer = Uint8Array.from(atob(rustPlugin as unknown as string), c => c.charCodeAt(0)) - await wasm.default(Promise.resolve(buffer)); - init_panic_hook() + async onload() { + // init wasm + const buffer = Uint8Array.from(atob(rustPlugin as unknown as string), (c) => + c.charCodeAt(0) + ); + await wasm.default(Promise.resolve(buffer)); + init_panic_hook(); - this.addRibbonIcon('link', 'Note Linker', async () => { - // init the secondary wasm thread (for searching) - const wcw = new Wcw(); - const WasmComlinkWorker = Comlink.wrap(wcw) - let wasmWorkerInstance: Comlink.Remote; + this.addRibbonIcon("link", "Note Linker", () => this.openModal()); + this.addCommand({ + id: "open-note-linker", + name: "Open", + callback: this.openModal, + }); + this.addCommand({ + id: "open-note-linker-vault", + name: "Scan Vault", + callback: () => this.openModal(MatchingMode.Vault), + }); + this.addCommand({ + id: "open-note-linker-note", + name: "Scan Note", + callback: () => this.openModal(MatchingMode.Note), + }); + } - wasmWorkerInstance = await new WasmComlinkWorker(); - await wasmWorkerInstance.init(); + openModal = async (_matchingModal?: MatchingMode) => { + // init the secondary wasm thread (for searching) + const wcw = new Wcw(); + const WasmComlinkWorker = Comlink.wrap(wcw); + let wasmWorkerInstance: Comlink.Remote; - const linkMatchSelectionModal = new MainModal(app, wasmWorkerInstance, () => { - wcw.terminate(); - }); - linkMatchSelectionModal.open(); - }); - } + wasmWorkerInstance = await new WasmComlinkWorker(); + await wasmWorkerInstance.init(); -} \ No newline at end of file + const linkMatchSelectionModal = new MainModal( + app, + wasmWorkerInstance, + () => { + wcw.terminate(); + }, + _matchingModal + ); + linkMatchSelectionModal.open(); + }; +} diff --git a/src/rs/matching/link_finder.rs b/src/rs/matching/link_finder.rs index b5e6338..1dc10f6 100644 --- a/src/rs/matching/link_finder.rs +++ b/src/rs/matching/link_finder.rs @@ -3,6 +3,7 @@ use std::ops::Add; use fancy_regex::{escape, Regex}; +use crate::rs::util::wasm_util::log; use crate::{LinkFinderResult}; use crate::rs::matching::link_match::LinkMatch; use crate::rs::matching::regex_match::RegexMatch; @@ -19,8 +20,11 @@ struct LinkFinderMatchingResult<'m> { impl<'m> LinkFinderMatchingResult<'m> { fn find_matches(note: &'m mut Note, target_note: &'m Note) -> Self { + // build the regex let regex_matches: Vec = build_link_finder(target_note) + // find all matches .captures_iter(note.get_sanitized_content()) + // map the results to a vector of RegexMatch .filter_map(|capture_result| { match capture_result { Ok(captures) => { @@ -42,6 +46,7 @@ impl<'m> LinkFinderMatchingResult<'m> { } } +/// Creates a vec of LinkMatch from a LinkFinderMatchingResult. impl<'m> Into> for LinkFinderMatchingResult<'m> { fn into(self) -> Vec { let note: &Note = self.note; @@ -60,10 +65,10 @@ impl<'m> Into> for LinkFinderMatchingResult<'m> { fn concat_as_regex_string(strings: &[String]) -> String { strings.iter() .enumerate() - .fold("(".to_string(), |prev, (index, current)| { - return if index == 0 { format!("{}{}", prev, current) } else { format!("{}|{}", prev, current) }; + .fold("".to_string(), |prev, (index, current)| { + return if index == 0 { format!("(\\b{}\\b)", current) } else { format!("{}|(\\b{}\\b)", prev, current) }; }) - .add(")") + .add("") } /// Constructs a LinkFinder for the provided target note. @@ -73,7 +78,9 @@ fn build_link_finder(target_note: &Note) -> LinkFinder { escaped_search_strings.push(escaped_title); let regex_string = concat_as_regex_string(&escaped_search_strings); - Regex::new(&*format!(r"\b{}\b", regex_string)).unwrap() + //log(&format!("Regex string: {}", regex_string)); + + Regex::new(&*format!(r"{}", regex_string)).unwrap() } /// Finds all link candidates in the provided note. diff --git a/src/rs/matching/regex_match.rs b/src/rs/matching/regex_match.rs index aecaafb..a648901 100644 --- a/src/rs/matching/regex_match.rs +++ b/src/rs/matching/regex_match.rs @@ -4,6 +4,7 @@ use fancy_regex::Captures; use crate::rs::Errors::cast_error::CastError; use crate::rs::util::range::Range; +use crate::rs::util::wasm_util::log; /// Utility struct that represents a single match of a regular expression in a note. pub struct RegexMatch { @@ -18,11 +19,16 @@ impl<'c> TryFrom> for RegexMatch { fn try_from(captures: Captures) -> Result { let valid = captures.iter() + // get index of capture group .enumerate() - .find_map(|(i, c)| c.map(|c_| (c_, i))); + // filter out all capture groups that didn't match + .filter_map(|(i, c)| c.map(|c_| (c_, i))) + // pick the last match + .last(); match valid { Some((m, capture_index)) => { + //log(&format!("Found match {} at index {}", m.as_str().to_string(), capture_index )); Ok( RegexMatch { position: Range::new(m.start(), m.end()), diff --git a/src/rs/note/note.rs b/src/rs/note/note.rs index 846eb2f..ac307ae 100644 --- a/src/rs/note/note.rs +++ b/src/rs/note/note.rs @@ -4,20 +4,20 @@ use std::convert::TryFrom; use js_sys::Array; use serde::{Deserialize, Serialize}; -use unicode_segmentation::{Graphemes, UnicodeSegmentation}; -use wasm_bindgen::{JsValue}; use wasm_bindgen::prelude::*; +use wasm_bindgen::JsValue; use crate::rs::text::text_util::create_string_with_n_characters; use crate::rs::util::range::{array_to_range_vec, Range}; -use crate::rs::util::wasm_util::{generic_of_jsval}; +use crate::rs::util::wasm_util::generic_of_jsval; +use crate::rs::util::wasm_util::log; /// A single note. #[wasm_bindgen] #[derive(Clone, Serialize, Deserialize)] pub struct Note { - title: String, // the title of the note - path: String, // the path of the note + title: String, // the title of the note + path: String, // the path of the note content: String, // the content of the note #[serde(skip)] aliases: Array, // possible aliases for the note @@ -36,10 +36,16 @@ pub struct Note { #[wasm_bindgen] impl Note { #[wasm_bindgen(constructor)] - pub fn new(title: String, path: String, content: String, aliases: Array, ignore: Array) -> Note { + pub fn new( + title: String, + path: String, + content: String, + aliases: Array, + ignore: Array, + ) -> Note { let ignore_vec = array_to_range_vec(ignore.clone()); Note { - title, + title: title, path, content: content.clone(), aliases: aliases.clone(), @@ -52,9 +58,13 @@ impl Note { } #[wasm_bindgen(getter)] - pub fn title(&self) -> String { self.title.clone() } + pub fn title(&self) -> String { + self.title.clone() + } #[wasm_bindgen(getter)] - pub fn path(&self) -> String { self.path.clone() } + pub fn path(&self) -> String { + self.path.clone() + } #[wasm_bindgen(getter)] pub fn content(&self) -> String { self.content.clone() @@ -90,26 +100,72 @@ impl Note { /// Cleans up the note content by: /// - removing all parts of the content that are in the ignore list fn sanitize_content(mut content: String, ignore_vec: Vec) -> String { + let mut _offset: usize = 0; + let mut _last_start: usize = 9999999; + // the ignore ranges are sorted for ignore in ignore_vec { + if _last_start == ignore.start() { + // skip this range + continue; + } else { + _last_start = ignore.start(); + } + + let offset = _offset; let range: std::ops::Range = ignore.clone().into(); - let split_content: Graphemes = UnicodeSegmentation::graphemes(content.as_str(), true); + let split_content: Vec = content.encode_utf16().collect(); let mut new_content: String = String::new(); - // necessary to split graphemes, since characters such as emojis - // only increase the character offset by 1 (offset is provided by obsidian api), - // however in rust strings, they have a length of 2-4 - split_content.enumerate().for_each( - |(index, grapheme)| { - if range.start <= index && range.end >= index { new_content.push_str(&*create_string_with_n_characters(&grapheme.len(), '_')) } else { new_content.push_str(grapheme) } - } - ); + + let before_vec_utf_16 = split_content + .iter() + .take(range.start + offset) + .map(|c| c.clone()) + .collect::>(); + + let before_string = String::from_utf16_lossy(before_vec_utf_16.as_slice()); + new_content.push_str(&before_string); + + let replacement_utf_16_vec = split_content + .iter() + .skip(range.start + offset) + .take(range.end - range.start) + .map(|c| c.clone()) + .collect::>(); + + // for the content that should be ignored, we need to push a blank string + // with the *same* length as the utf-8 slice of the string that should be ignored + // if we were to simple push a string of the utf-16 length, + // the regex matching later on would produce false positions + + // utf 16 length + let replacement_len_utf_16 = replacement_utf_16_vec.len(); + // utf 8 length + let replacement_len_utf_8 = + String::from_utf16_lossy(replacement_utf_16_vec.as_slice()).len(); + // now create a string with the same length as the placeholder, but no content + let placeholder_to_push = create_string_with_n_characters(replacement_len_utf_8, '_'); + _offset += replacement_len_utf_8 - replacement_len_utf_16; + new_content.push_str(&placeholder_to_push); + + let after_vec_utf_16 = split_content + .iter() + .skip(range.end + offset) + .map(|c| c.clone()) + .collect::>(); + + let after_string = String::from_utf16_lossy(after_vec_utf_16.as_slice()); + new_content.push_str(&after_string); + content = new_content } + content } pub fn get_sanitized_content(&mut self) -> &String { if self._sanitized_content.is_empty() { - self._sanitized_content = Note::sanitize_content(self.content.clone(), self._ignore.clone()); + self._sanitized_content = + Note::sanitize_content(self.content.clone(), self._ignore.clone()); } &self._sanitized_content } @@ -128,7 +184,8 @@ pub fn note_from_js_value(js: JsValue) -> Option { } pub fn array_to_string_vec(array: Array) -> Vec { - array.iter() + array + .iter() .filter_map(|a: JsValue| a.as_string()) .collect() -} \ No newline at end of file +} diff --git a/src/rs/text/text_util.rs b/src/rs/text/text_util.rs index 9adf505..5836024 100644 --- a/src/rs/text/text_util.rs +++ b/src/rs/text/text_util.rs @@ -1,7 +1,7 @@ /// Creates a string with n times character c. -pub fn create_string_with_n_characters(n: &usize, c: char) -> String { +pub fn create_string_with_n_characters(n: usize, c: char) -> String { let mut s = String::new(); - for _ in 0..*n { + for _ in 0..n { s.push(c); } s @@ -22,4 +22,4 @@ pub fn get_nearest_char_boundary(text: &str, position: usize, do_expand_left: bo } } i -} \ No newline at end of file +} diff --git a/src/rs/util/range.rs b/src/rs/util/range.rs index deedb0d..90a34cc 100644 --- a/src/rs/util/range.rs +++ b/src/rs/util/range.rs @@ -3,8 +3,8 @@ use std::convert::TryFrom; use js_sys::Array; use serde::{Deserialize, Serialize}; use thiserror::Error; -use wasm_bindgen::{JsValue}; use wasm_bindgen::prelude::*; +use wasm_bindgen::JsValue; use crate::rs::util::wasm_util::generic_of_jsval; @@ -21,17 +21,18 @@ pub struct Range { impl Range { #[wasm_bindgen(constructor)] pub fn new(start: usize, end: usize) -> Range { - Range { - start, - end, - } + Range { start, end } } #[wasm_bindgen(getter)] - pub fn start(&self) -> usize { self.start } + pub fn start(&self) -> usize { + self.start + } #[wasm_bindgen(getter)] - pub fn end(&self) -> usize { self.end } + pub fn end(&self) -> usize { + self.end + } #[wasm_bindgen(method)] pub fn is_equal_to(&self, range: &Range) -> bool { @@ -88,8 +89,8 @@ pub fn range_from_js_value(js: JsValue) -> Option { } pub fn array_to_range_vec(range_array: Array) -> Vec { - range_array.iter() - .filter_map(|js_val_range: JsValue| - generic_of_jsval(js_val_range, "Range").ok()) + range_array + .iter() + .filter_map(|js_val_range: JsValue| generic_of_jsval(js_val_range, "Range").ok()) .collect() -} \ No newline at end of file +} diff --git a/src/ts/MainModal.tsx b/src/ts/MainModal.tsx index 3e8d2a9..912808b 100644 --- a/src/ts/MainModal.tsx +++ b/src/ts/MainModal.tsx @@ -1,37 +1,46 @@ -import {App, Modal} from "obsidian"; +import { App, Modal } from "obsidian"; import React from "react"; -import {createRoot, Root} from 'react-dom/client'; -import {AppContext, WasmWorkerInstanceContext} from "./context"; -import {MainComponent} from "./components/containers/MainComponent"; +import { createRoot, Root } from "react-dom/client"; +import { AppContext, WasmWorkerInstanceContext } from "./context"; +import { + MainComponent, + MatchingMode, +} from "./components/containers/MainComponent"; export default class MainModal extends Modal { + private root: Root; + private readonly wasmComlinkWorkerInstance: any; + private readonly _onClose: () => void; + private readonly _matchingMode?: MatchingMode; - private root: Root; - private readonly wasmComlinkWorkerInstance: any; - private readonly _onClose: () => void; + constructor( + app: App, + instance: any, + _onClose: () => void, + _matchingMode?: MatchingMode + ) { + super(app); + this.wasmComlinkWorkerInstance = instance; + this._onClose = _onClose; + this._matchingMode = _matchingMode ?? MatchingMode.None; + } - constructor(app: App, instance: any, _onClose: () => void) { - super(app); - this.wasmComlinkWorkerInstance = instance; - this._onClose = _onClose; - } + onOpen() { + this.root = createRoot(this.contentEl); + // add class to root + this.root.render( + + + + + + ); + } - - onOpen() { - this.root = createRoot(this.contentEl); - // add class to root - this.root.render( - - - - - - ) - } - - onClose() { - super.onClose(); - this._onClose(); - } - -} \ No newline at end of file + onClose() { + super.onClose(); + this._onClose(); + } +} diff --git a/src/ts/components/containers/MainComponent.tsx b/src/ts/components/containers/MainComponent.tsx index 65c369f..443bdb4 100644 --- a/src/ts/components/containers/MainComponent.tsx +++ b/src/ts/components/containers/MainComponent.tsx @@ -1,19 +1,25 @@ -import React, {useState} from "react"; -import {StartComponent} from "./StartComponent"; -import {MatcherComponent} from "./MatcherComponent"; +import React, { useState } from "react"; +import { StartComponent } from "./StartComponent"; +import { MatcherComponent } from "./MatcherComponent"; -enum MainComponentStates { - Start, - Matching +export enum MatchingMode { + None, + Vault, + Note, } -export const MainComponent = () => { - const [mainComponentState, setMainComponentState] = useState(MainComponentStates.Start); +export interface MainComponentProps { + _matchingMode: MatchingMode; +} + +export const MainComponent = ({ _matchingMode }: MainComponentProps) => { + const [matchingMode, setMatchingMode] = useState(_matchingMode); - const onClickScan = () => { - setMainComponentState(MainComponentStates.Matching) - } + const onClickScan = (type: MatchingMode) => { + setMatchingMode(type); + }; - if (mainComponentState == MainComponentStates.Start) return - else return -} \ No newline at end of file + if (matchingMode == MatchingMode.None) + return ; + else return ; +}; diff --git a/src/ts/components/containers/MatcherComponent.tsx b/src/ts/components/containers/MatcherComponent.tsx index e59445b..c84244f 100644 --- a/src/ts/components/containers/MatcherComponent.tsx +++ b/src/ts/components/containers/MatcherComponent.tsx @@ -1,89 +1,161 @@ import * as React from "react"; -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import * as Comlink from "comlink"; -import {NoteChangeOperation, LinkFinderResult, NoteScannedEvent} from "../../../../pkg"; +import { + NoteChangeOperation, + LinkFinderResult, + NoteScannedEvent, +} from "../../../../pkg"; import JsNote from "../../objects/JsNote"; import Progress from "../../objects/Progress"; -import {ProgressComponent} from "../other/ProgressComponent"; -import {MatchSelectionComponent} from "./MatchSelectionComponent"; -import {TFile} from "obsidian"; -import {useApp, useWasmWorkerInstance} from "../../hooks"; -import {LoadingComponent} from "../other/LoadingComponent"; +import { ProgressComponent } from "../other/ProgressComponent"; +import { MatchSelectionComponent } from "./MatchSelectionComponent"; +import { Notice, TFile } from "obsidian"; +import { useApp, useWasmWorkerInstance } from "../../hooks"; +import { MatchingMode } from "./MainComponent"; enum MatchingState { - Scanning, - Selecting, - Replacing, - Finished, - Error + Initializing, + Scanning, + Selecting, + Replacing, + Finished, + Error, } -export const MatcherComponent = () => { - const {vault, metadataCache} = useApp(); - const wasmWorkerInstance = useWasmWorkerInstance(); +export const MatcherComponent = ({ + matchingMode, +}: { + matchingMode: MatchingMode; +}) => { + const { vault, metadataCache } = useApp(); + const wasmWorkerInstance = useWasmWorkerInstance(); - const [matchingState, setMatchingState] = useState(MatchingState.Scanning); - const [numberOfLinkedNotes, setNumberOfLinkedNotes] = useState(0); - const [linkFinderResults, setLinkFinderResults] = useState>([]); - const [linkMatchingProgress, setLinkMatchingProgress] = useState(new Progress(JsNote.getNumberOfNotes(vault))); + const [matchingState, setMatchingState] = useState( + MatchingState.Initializing + ); + const [numberOfLinkedNotes, setNumberOfLinkedNotes] = useState(0); + const [linkFinderResults, setLinkFinderResults] = useState< + Array + >([]); + const [linkMatchingProgress, setLinkMatchingProgress] = useState( + new Progress( + matchingMode == MatchingMode.Vault ? JsNote.getNumberOfNotes(vault) : 1 + ) + ); - const onLinkMatchingProgress = (serializedNoteScannedEvent: string) => { - const noteScannedEvent: NoteScannedEvent = NoteScannedEvent.fromJSON(serializedNoteScannedEvent); - const newLinkMatchingProgress = new Progress(linkMatchingProgress.max, noteScannedEvent); - setLinkMatchingProgress(newLinkMatchingProgress); - } + const onLinkMatchingProgress = (serializedNoteScannedEvent: string) => { + const noteScannedEvent: NoteScannedEvent = NoteScannedEvent.fromJSON( + serializedNoteScannedEvent + ); + const newLinkMatchingProgress = new Progress( + linkMatchingProgress.max, + noteScannedEvent + ); + setLinkMatchingProgress(newLinkMatchingProgress); + }; - const onStartReplacing = () => { - setMatchingState(MatchingState.Replacing) - } + const onStartReplacing = () => { + setMatchingState(MatchingState.Replacing); + }; - const onFinishReplacing = (num: number) => { - setNumberOfLinkedNotes(num); - setMatchingState(MatchingState.Finished) - } + const onFinishReplacing = (num: number) => { + setNumberOfLinkedNotes(num); + setMatchingState(MatchingState.Finished); + }; - const handleReplaceButtonClicked = (noteChangeOperations: Map, noteFiles: Map) => { - onStartReplacing() - const operations: Array> = []; - noteChangeOperations.forEach((op: NoteChangeOperation) => { - op.applyReplacements() - const noteFile = noteFiles.get(op.path); - operations.push(vault.modify(noteFile, op.content)); - }) - Promise.all(operations).then(() => onFinishReplacing(operations.length)) - } + const handleReplaceButtonClicked = ( + noteChangeOperations: Map, + noteFiles: Map + ) => { + onStartReplacing(); + const operations: Array> = []; + let totalNum = 0; + noteChangeOperations.forEach((op: NoteChangeOperation) => { + totalNum += op.replacements.length; + op.applyReplacements(); + const noteFile = noteFiles.get(op.path); + operations.push(vault.modify(noteFile, op.content)); + }); + Promise.all(operations).then(() => onFinishReplacing(totalNum)); + }; - const findLinkFinderResults = (jsNotes: JsNote[]) => { - const noteStrings: Array = jsNotes.map((jsNote: JsNote) => jsNote.toJSON()); - return wasmWorkerInstance.find(noteStrings, Comlink.proxy(onLinkMatchingProgress)); - } - const showMatchSelection = (serializedNoteLinkMatchResults: Array) => { - const linkFinderResults: Array = serializedNoteLinkMatchResults.map((linkFinderResult: string) => LinkFinderResult.fromJSON(linkFinderResult)); - setLinkFinderResults(linkFinderResults); - setMatchingState(MatchingState.Selecting); - } + const getLinkFinderResults = async (jsNotes: JsNote[]) => { + setMatchingState(MatchingState.Scanning); + const noteStrings: Array = jsNotes.map((jsNote: JsNote) => + jsNote.toJSON() + ); + if (matchingMode == MatchingMode.Vault) { + // Search entire vault + return wasmWorkerInstance.findInVault( + noteStrings, + Comlink.proxy(onLinkMatchingProgress) + ); + } else { + // Search only the active note + const activeFile = app.workspace.getActiveFile(); + if (activeFile !== null && activeFile.extension === "md") { + const activeNoteString = ( + await JsNote.fromFile(activeFile, vault, metadataCache) + ).toJSON(); - const showError = (error: Error) => { - console.error(error); - setMatchingState(MatchingState.Error); - } + return wasmWorkerInstance.findInNote( + activeNoteString, + noteStrings, + Comlink.proxy(onLinkMatchingProgress) + ); + } else { + new Notice("No active note found"); + } + } + }; - useEffect(() => { - JsNote.getNotesFromVault(vault, metadataCache) - .then(findLinkFinderResults) - .then(showMatchSelection) - .catch(showError) - }, [wasmWorkerInstance]); + const showMatchSelection = ( + serializedNoteLinkMatchResults: Array + ) => { + const linkFinderResults: Array = + serializedNoteLinkMatchResults.map((linkFinderResult: string) => + LinkFinderResult.fromJSON(linkFinderResult) + ); + setLinkFinderResults(linkFinderResults); + setMatchingState(MatchingState.Selecting); + }; + const showError = (error: Error) => { + console.error(error); + setMatchingState(MatchingState.Error); + }; - if (matchingState == MatchingState.Scanning) return - else if (matchingState == MatchingState.Selecting) return - else if (matchingState == MatchingState.Replacing) return
⏳ Linking Notes...
- else if (matchingState == MatchingState.Finished) return
πŸŽ‰ Successfully - linked {numberOfLinkedNotes} notes!
- else return
πŸ’€ An error occurred while linking notes.
-}; + useEffect(() => { + JsNote.getNotesFromVault(vault, metadataCache) + .then(getLinkFinderResults) + .then(showMatchSelection) + .catch(showError); + }, [wasmWorkerInstance]); + if (matchingState == MatchingState.Initializing) { + return
πŸ—οΈ Retrieving notes...
; + } else if (matchingState == MatchingState.Scanning) + return ; + else if (matchingState == MatchingState.Selecting) + return ( + + ); + else if (matchingState == MatchingState.Replacing) + return
⏳ Linking Notes...
; + else if (matchingState == MatchingState.Finished) + return ( +
+ πŸŽ‰ Successfully created {numberOfLinkedNotes} new links! +
+ ); + else + return ( +
+ πŸ’€ An error occurred while linking notes. +
+ ); +}; diff --git a/src/ts/components/containers/StartComponent.tsx b/src/ts/components/containers/StartComponent.tsx index 088b019..46fb769 100644 --- a/src/ts/components/containers/StartComponent.tsx +++ b/src/ts/components/containers/StartComponent.tsx @@ -1,17 +1,26 @@ import * as React from "react"; +import { MatchingMode } from "./MainComponent"; interface StartComponentProps { - onClickScan: () => void; + onClickScan: (type: MatchingMode) => void; } -export const StartComponent = ({onClickScan}: StartComponentProps) => { - return ( -
-

πŸ”— Obsidian Note Linker

- - Note: Please backup your vault before using this plugin. This plugin is in beta stage and has therefore not been tested sufficiently. - - -
- ) -} \ No newline at end of file +export const StartComponent = ({ onClickScan }: StartComponentProps) => { + return ( +
+

πŸ”— Obsidian Note Linker

+ + Note: Please backup your vault before using this plugin. This plugin is + in beta stage and has therefore not been tested sufficiently. + +
+ + +
+
+ ); +}; diff --git a/src/ts/components/lists/LinkFinderResultsListComponent.tsx b/src/ts/components/lists/LinkFinderResultsListComponent.tsx index 1c85e0e..461f3f9 100644 --- a/src/ts/components/lists/LinkFinderResultsListComponent.tsx +++ b/src/ts/components/lists/LinkFinderResultsListComponent.tsx @@ -1,55 +1,72 @@ import * as React from "react"; -import {LinkFinderResult, Note, NoteChangeOperation} from "../../../../pkg"; -import {LinkMatchesListComponent} from "./LinkMatchesListComponent"; -import {LinkFinderResultContext} from "../../context"; -import {useSelectedNoteChangeOperations} from "../../hooks"; -import {useCallback} from "react"; +import { LinkFinderResult, Note, NoteChangeOperation } from "../../../../pkg"; +import { LinkMatchesListComponent } from "./LinkMatchesListComponent"; +import { LinkFinderResultContext } from "../../context"; +import { useSelectedNoteChangeOperations } from "../../hooks"; +import { useCallback } from "react"; interface LinkFinderResultsListProps { - linkFinderResults: Array, - onClickReplaceButton: () => void + linkFinderResults: Array; + onClickReplaceButton: () => void; } -export const LinkFinderResultsList = ({linkFinderResults, onClickReplaceButton}: LinkFinderResultsListProps) => { - const {noteChangeOperations, setNoteChangeOperations} = useSelectedNoteChangeOperations(); +export const LinkFinderResultsList = ({ + linkFinderResults, + onClickReplaceButton, +}: LinkFinderResultsListProps) => { + const { noteChangeOperations, setNoteChangeOperations } = + useSelectedNoteChangeOperations(); - const findNoteChangeOperation = (note: Note): NoteChangeOperation | undefined => { - return noteChangeOperations.get(note.path); - } + const findNoteChangeOperation = ( + note: Note + ): NoteChangeOperation | undefined => { + return noteChangeOperations.get(note.path); + }; - const findReplacements = (note: Note) => { - return findNoteChangeOperation(note)?.replacements ?? []; - }; + const findReplacements = (note: Note) => { + return findNoteChangeOperation(note)?.replacements ?? []; + }; - const totalReplacements = useCallback(() => { - let total = 0; - noteChangeOperations.forEach((noteChangeOperation: NoteChangeOperation) => { - total += noteChangeOperation.replacements.length; - }) - return total; - }, [noteChangeOperations]); + const totalReplacements = useCallback(() => { + let total = 0; + noteChangeOperations?.forEach( + (noteChangeOperation: NoteChangeOperation) => { + total += noteChangeOperation?.replacements?.length; + } + ); + return total; + }, [noteChangeOperations]); - if (linkFinderResults.length !== 0) return ( -
-

Note Link Matches

-
    - {linkFinderResults.map((linkFinderResult: LinkFinderResult) => { - const selectedReplacements = findReplacements(linkFinderResult.note); - return ( - - ); - } - )} -
- -
- ); - else return ( -
πŸ‘€ No notes to link could be found.
- ) + if (linkFinderResults.length !== 0) + return ( +
+

Note Link Matches

+
    + {linkFinderResults.map((linkFinderResult: LinkFinderResult) => { + const selectedReplacements = findReplacements( + linkFinderResult.note + ); + return ( + + + + ); + })} +
+ +
+ ); + else + return ( +
πŸ‘€ No notes to link could be found.
+ ); }; diff --git a/src/ts/components/lists/LinkMatchesListComponent.tsx b/src/ts/components/lists/LinkMatchesListComponent.tsx index f17518d..f61ad2e 100644 --- a/src/ts/components/lists/LinkMatchesListComponent.tsx +++ b/src/ts/components/lists/LinkMatchesListComponent.tsx @@ -1,47 +1,65 @@ import * as React from "react"; -import {Dispatch, SetStateAction} from "react"; -import {LinkMatch, NoteChangeOperation, Replacement} from "../../../../pkg"; -import {LinkTargetCandidatesListComponent} from "./LinkTargetCandidatesListComponent"; -import {LinkFinderResultTitleComponent} from "../titles/LinkFinderResultTitleComponent"; -import {LinkMatchContext} from "../../context"; -import {useLinkFinderResult} from "../../hooks"; +import { Dispatch, SetStateAction } from "react"; +import { LinkMatch, NoteChangeOperation, Replacement } from "../../../../pkg"; +import { LinkTargetCandidatesListComponent } from "./LinkTargetCandidatesListComponent"; +import { LinkFinderResultTitleComponent } from "../titles/LinkFinderResultTitleComponent"; +import { LinkMatchContext } from "../../context"; +import { useLinkFinderResult } from "../../hooks"; interface LinkMatchesListComponentProps { - selectedReplacements: Array, - noteChangeOperations: Map, - setNoteChangeOperations: Dispatch>> + selectedReplacements: Array; + noteChangeOperations: Map; + setNoteChangeOperations: Dispatch< + SetStateAction> + >; } -export const LinkMatchesListComponent = React.memo(({ - selectedReplacements, - noteChangeOperations, - setNoteChangeOperations - }: LinkMatchesListComponentProps) => { - const linkFinderResult = useLinkFinderResult(); - const parentNote = useLinkFinderResult().note; +export const LinkMatchesListComponent = React.memo( + ({ + selectedReplacements, + noteChangeOperations, + setNoteChangeOperations, + }: LinkMatchesListComponentProps) => { + const linkFinderResult = useLinkFinderResult(); + const parentNote = useLinkFinderResult().note; - return
  • - -
      - {linkFinderResult.linkMatches.map((link_match: LinkMatch) => { - const noteChangeOperation: NoteChangeOperation = noteChangeOperations.get(parentNote.path); - const selectedReplacement = selectedReplacements.find((replacement: Replacement) => { - return replacement.position.is_equal_to(link_match.position); - }); + return ( +
    • + +
        + {linkFinderResult.linkMatches.map((link_match: LinkMatch) => { + // console.log(noteChangeOperations); + const noteChangeOperation: NoteChangeOperation = + noteChangeOperations.get(parentNote.path); + const selectedReplacement = selectedReplacements.find( + (replacement: Replacement) => { + return replacement.position.is_equal_to(link_match.position); + } + ); - return - - ; - } - )} -
      -
    • - ; -}, (prevProps: LinkMatchesListComponentProps, nextProps: LinkMatchesListComponentProps) => prevProps.selectedReplacements == nextProps.selectedReplacements); \ No newline at end of file + return ( + + + + ); + })} +
    +
  • + ); + }, + ( + prevProps: LinkMatchesListComponentProps, + nextProps: LinkMatchesListComponentProps + ) => prevProps.selectedReplacements == nextProps.selectedReplacements +); diff --git a/src/ts/components/lists/LinkTargetCandidatesListComponent.tsx b/src/ts/components/lists/LinkTargetCandidatesListComponent.tsx index 425eb21..900c767 100644 --- a/src/ts/components/lists/LinkTargetCandidatesListComponent.tsx +++ b/src/ts/components/lists/LinkTargetCandidatesListComponent.tsx @@ -1,70 +1,108 @@ import * as React from "react"; -import {Dispatch, SetStateAction} from "react"; -import {LinkTargetCandidate, NoteChangeOperation, Replacement} from "../../../../pkg"; -import {LinkMatchTitleComponent} from "../titles/LinkMatchTitleComponent"; -import {ReplacementsSelectionComponent} from "./ReplacementsSelectionComponent"; -import {LinkTargetCandidateContext} from "../../context"; -import {useLinkFinderResult, useLinkMatch} from "../../hooks"; +import { Dispatch, SetStateAction } from "react"; +import { + LinkTargetCandidate, + NoteChangeOperation, + Replacement, +} from "../../../../pkg"; +import { LinkMatchTitleComponent } from "../titles/LinkMatchTitleComponent"; +import { ReplacementsSelectionComponent } from "./ReplacementsSelectionComponent"; +import { LinkTargetCandidateContext } from "../../context"; +import { useLinkFinderResult, useLinkMatch } from "../../hooks"; interface LinkTargetCandidatesListComponentProps { - _selectedReplacement: Replacement, - noteChangeOperation: NoteChangeOperation, - noteChangeOperations: Map, - setNoteChangeOperations: Dispatch>>, + _selectedReplacement: Replacement; + noteChangeOperation: NoteChangeOperation; + noteChangeOperations: Map; + setNoteChangeOperations: Dispatch< + SetStateAction> + >; } -export const LinkTargetCandidatesListComponent = React.memo(({ - _selectedReplacement, - noteChangeOperation, - noteChangeOperations, - setNoteChangeOperations, - }: LinkTargetCandidatesListComponentProps) => { +export const LinkTargetCandidatesListComponent = React.memo( + ({ + _selectedReplacement, + noteChangeOperation, + noteChangeOperations, + setNoteChangeOperations, + }: LinkTargetCandidatesListComponentProps) => { + const linkMatch = useLinkMatch(); + const parentNote = useLinkFinderResult().note; - const linkMatch = useLinkMatch(); - const parentNote = useLinkFinderResult().note; + const [selectedReplacement, setSelectedReplacement] = + React.useState(_selectedReplacement); + const createNoteChangeOperation = () => { + return new NoteChangeOperation(parentNote.path, parentNote.content, [ + selectedReplacement, + ]); + }; - const [selectedReplacement, setSelectedReplacement] = React.useState(_selectedReplacement); - const createNoteChangeOperation = () => { - return new NoteChangeOperation(parentNote.path, parentNote.content, [selectedReplacement]); - } + const removeReplacement = () => { + if (noteChangeOperation) { + noteChangeOperation.replacements = + noteChangeOperation.replacements?.filter( + (r: Replacement) => !r.position.is_equal_to(linkMatch.position) + ); + saveNoteChangeOperation(noteChangeOperation); + } + }; - const removeReplacement = () => { - noteChangeOperation.replacements = noteChangeOperation.replacements.filter((r: Replacement) => !(r.position.is_equal_to(linkMatch.position))); - saveNoteChangeOperation(noteChangeOperation); - } + const addReplacement = (replacement: Replacement) => { + const _noteChangeOperation = + noteChangeOperation !== undefined + ? noteChangeOperation + : createNoteChangeOperation(); + _noteChangeOperation.replacements = + _noteChangeOperation.replacements?.filter( + (r: Replacement) => !r.position.is_equal_to(linkMatch.position) + ); + _noteChangeOperation.replacements?.push(replacement); + saveNoteChangeOperation(_noteChangeOperation); + }; - const addReplacement = (replacement: Replacement) => { - const _noteChangeOperation = noteChangeOperation !== undefined ? noteChangeOperation : createNoteChangeOperation(); - _noteChangeOperation.replacements = _noteChangeOperation.replacements.filter((r: Replacement) => !r.position.is_equal_to(linkMatch.position)); - _noteChangeOperation.replacements.push(replacement); - saveNoteChangeOperation(_noteChangeOperation); - } + const saveNoteChangeOperation = ( + _noteChangeOperation: NoteChangeOperation + ) => { + const _noteChangeOperations = new Map(noteChangeOperations.entries()); + if (_noteChangeOperation.replacements.length == 0) + _noteChangeOperations.delete(_noteChangeOperation.path); + else _noteChangeOperations.set(parentNote.path, _noteChangeOperation); + setNoteChangeOperations(_noteChangeOperations); + }; - const saveNoteChangeOperation = (_noteChangeOperation: NoteChangeOperation) => { - const _noteChangeOperations = new Map(noteChangeOperations.entries()); - if (_noteChangeOperation.replacements.length == 0) _noteChangeOperations.delete(_noteChangeOperation.path); - else _noteChangeOperations.set(parentNote.path, _noteChangeOperation); - setNoteChangeOperations(_noteChangeOperations); - } + React.useEffect(() => { + if (selectedReplacement === undefined) removeReplacement(); + else addReplacement(selectedReplacement); + }, [selectedReplacement]); - React.useEffect(() => { - if (selectedReplacement === undefined) removeReplacement(); - else addReplacement(selectedReplacement); - }, [selectedReplacement]); - - return
    - -
      - {linkMatch.linkTargetCandidates.map((linkTargetCandidate: LinkTargetCandidate) => - - - - )} -
    -
    - ; -}, (prevProps: LinkTargetCandidatesListComponentProps, nextProps: LinkTargetCandidatesListComponentProps) => { - return prevProps._selectedReplacement == nextProps._selectedReplacement -}); \ No newline at end of file + return ( +
    + +
      + {linkMatch.linkTargetCandidates.map( + (linkTargetCandidate: LinkTargetCandidate) => ( + + + + ) + )} +
    +
    + ); + }, + ( + prevProps: LinkTargetCandidatesListComponentProps, + nextProps: LinkTargetCandidatesListComponentProps + ) => { + return prevProps._selectedReplacement == nextProps._selectedReplacement; + } +); diff --git a/src/ts/components/other/ProgressComponent.tsx b/src/ts/components/other/ProgressComponent.tsx index dd99a48..af54762 100644 --- a/src/ts/components/other/ProgressComponent.tsx +++ b/src/ts/components/other/ProgressComponent.tsx @@ -2,20 +2,19 @@ import * as React from "react"; import Progress from "../../objects/Progress"; interface LinkMatcherProgressComponentProps { - progress: Progress + progress: Progress; } -export const ProgressComponent = ({progress}: LinkMatcherProgressComponentProps) => { - return ( -
    -

    πŸ”Ž Scanning notes for links...

    - - {progress.asAsciiArt()} - - - {progress.asDescriptionText()} - -
    - - ); -}; \ No newline at end of file +export const ProgressComponent = ({ + progress, +}: LinkMatcherProgressComponentProps) => { + return ( +
    +

    πŸ”Ž Scanning notes...

    + {progress.asAsciiArt()} + + {progress.asDescriptionText()} + +
    + ); +}; diff --git a/src/ts/objects/IgnoreRange.ts b/src/ts/objects/IgnoreRange.ts index 474d3e8..e6cf065 100644 --- a/src/ts/objects/IgnoreRange.ts +++ b/src/ts/objects/IgnoreRange.ts @@ -1,29 +1,138 @@ -import {CachedMetadata} from "obsidian"; -import {Range} from "../../../pkg"; +import { + CachedMetadata, + CacheItem, + HeadingCache, + LinkCache, + TFile, +} from "obsidian"; +import { Range } from "../../../pkg"; +class IgnoreRangeBuilder { + private readonly _ignoreRanges: IgnoreRange[] = []; + private readonly _cache: CachedMetadata; + private _content: string; + private _name: string; -export interface IgnoreRangeConfig { - doIgnoreInternalLinks?: boolean; - doIgnoreCodeBlocks?: boolean; + constructor(content: string, cache: CachedMetadata, name: string) { + this._content = content; + this._cache = cache; + this._name = name; + } + + public build(): IgnoreRange[] { + return this._ignoreRanges.sort((a, b) => a.start - b.start); + } + + // Adds an ignore range from the cache for a specific section type + private addCacheSections(type: string): IgnoreRangeBuilder { + (this._cache.sections ? this._cache.sections : []) + .filter((section) => section.type === type) + .forEach((section) => { + const ignoreRange = new IgnoreRange( + section.position.start.offset, + section.position.end.offset + ); + this._ignoreRanges.push(ignoreRange); + + this._content = + this._content.substring(0, ignoreRange.start) + + " ".repeat(ignoreRange.end - ignoreRange.start) + + this._content.substring(ignoreRange.end); + }); + return this; + } + + // adds an ignroe range from the cache for an array of cache items + private addCacheItem(cacheItem: CacheItem[]) { + (cacheItem ? cacheItem : []).forEach((item) => { + const ignoreRange = new IgnoreRange( + item.position.start.offset, + item.position.end.offset + ); + this._ignoreRanges.push(ignoreRange); + this._content = + this._content.substring(0, ignoreRange.start) + + " ".repeat(ignoreRange.end - ignoreRange.start) + + this._content.substring(ignoreRange.end); + }); + return this; + } + + // adds internal links to the ignore ranges + // internal links are of the form [[link text]] or [[#link text]] + public addInternalLinks(): IgnoreRangeBuilder { + return this.addCacheItem(this._cache.links); + } + + // adds all headings to the ignore ranges + // headings are of the form # Heading + public addHeadings(): IgnoreRangeBuilder { + return this.addCacheItem(this._cache.headings); + } + + // adds code blocks to the ignore ranges + // code blocks are of the form ```code``` + public addCodeSections(): IgnoreRangeBuilder { + return this.addCacheSections("code"); + } + + // utility function to add ignore ranges from a regex + private addIgnoreRangesWithRegex(regex: RegExp): IgnoreRangeBuilder { + this._content = this._content.replace(regex, (match, ...args) => { + const start = args[args.length - 2]; + const end = start + match.length; + this._ignoreRanges.push(new IgnoreRange(start, end)); + return " ".repeat(match.length); + }); + return this; + } + + // adds all web links to the ignore ranges + public addWebLinks(): IgnoreRangeBuilder { + // web links are of the form https://www.example.com or http://www.example.com or www.example.com + const regex = /https?:\/\/www\..+|www\..+/g; + return this.addIgnoreRangesWithRegex(regex); + } + + // adds all md links to the ignore ranges + public addMdLinks(): IgnoreRangeBuilder { + // md links are of the form [link text](link) + const regex = /\[([^\[]+)\](\(.*\))/g; + return this.addIgnoreRangesWithRegex(regex); + } + + // adds all html like text sections to the ignore ranges + public addHtml(): IgnoreRangeBuilder { + const regex = /<[^>]+>([^>]+<[^>]+>)?/g; + return this.addIgnoreRangesWithRegex(regex); + } } export default class IgnoreRange extends Range { - constructor(start: number, end: number) { - super(start, end); - } - - static getIgnoreRangesFromCache(cache: CachedMetadata, config: IgnoreRangeConfig): IgnoreRange[] { - const codeBlocks = config.doIgnoreCodeBlocks ? this.findCodeBlocks(cache) : []; - const internalLinks = config.doIgnoreInternalLinks ? this.findInternalLinks(cache) : []; - return [...codeBlocks, ...internalLinks]; - } - - private static findInternalLinks(cache: CachedMetadata): IgnoreRange[] { - return (cache.links ? cache.links : []).map(link => new IgnoreRange(link.position.start.offset, link.position.end.offset)); - } - - private static findCodeBlocks(cache: CachedMetadata): IgnoreRange[] { - return (cache.sections ? cache.sections : []).filter(section => section.type === "code") - .map(section => new IgnoreRange(section.position.start.offset, section.position.end.offset)) - } -} \ No newline at end of file + constructor(start: number, end: number) { + super(start, end); + } + + static getIgnoreRangesFromCache( + content: string, + cache: CachedMetadata, + name: string + ): IgnoreRange[] { + const ignoreRanges: IgnoreRange[] = new IgnoreRangeBuilder( + content, + cache, + name + ) + // from cache + .addInternalLinks() + .addHeadings() + .addCodeSections() + // from regex + .addHtml() + .addMdLinks() + .addWebLinks() + .build(); + + return ignoreRanges; + } +} diff --git a/src/ts/objects/JsNote.ts b/src/ts/objects/JsNote.ts index ab57339..76d65bf 100644 --- a/src/ts/objects/JsNote.ts +++ b/src/ts/objects/JsNote.ts @@ -1,32 +1,50 @@ -import {MetadataCache, parseFrontMatterAliases, Vault} from "obsidian"; +import { MetadataCache, parseFrontMatterAliases, TFile, Vault } from "obsidian"; import IgnoreRange from "./IgnoreRange"; -import {Note} from "../../../pkg"; +import { Note } from "../../../pkg"; +import { getImpliedNodeFormatForFile } from "typescript"; export default class JsNote extends Note { + constructor( + title: string, + path: string, + content: string, + aliases: string[] = [], + ignore: IgnoreRange[] = [] + ) { + super(title, path, content, aliases, ignore); + } + static getNumberOfNotes(vault: Vault): number { + return vault.getMarkdownFiles().length; + } - constructor(title: string, path: string, content: string, aliases: string[] = [], ignore: IgnoreRange[] = []) { - super(title, path, content, aliases, ignore); - } + static async getNotesFromVault( + vault: Vault, + cache: MetadataCache + ): Promise { + const notes = vault.getMarkdownFiles().map(async (file, index) => { + return await JsNote.fromFile(file, vault, cache); + }); + return await Promise.all(notes); + } - static getNumberOfNotes(vault: Vault): number { - return vault.getMarkdownFiles().length; - } - - static async getNotesFromVault(vault: Vault, cache: MetadataCache): Promise { - const notes = vault.getMarkdownFiles().map(async (file, index) => { - const name = file.basename; - const path = file.path; - const content = await vault.cachedRead(file); - const aliases = parseFrontMatterAliases(cache.getFileCache(file).frontmatter) ?? []; - const ignoreRanges = IgnoreRange.getIgnoreRangesFromCache(cache.getFileCache(file), { - doIgnoreInternalLinks: true, - doIgnoreCodeBlocks: true, - }) ?? []; - let jsNote: JsNote = new JsNote(name, path, content, aliases, ignoreRanges); - return jsNote; - }); - return await Promise.all(notes) - } - -} \ No newline at end of file + static async fromFile( + file: TFile, + vault: Vault, + cache: MetadataCache + ): Promise { + const name = file.basename; + const path = file.path; + const content = await vault.cachedRead(file); + const aliases = + parseFrontMatterAliases(cache.getFileCache(file).frontmatter) ?? []; + const ignoreRanges = + IgnoreRange.getIgnoreRangesFromCache( + content, + cache.getFileCache(file), + file.name + ) ?? []; + let jsNote: JsNote = new JsNote(name, path, content, aliases, ignoreRanges); + return jsNote; + } +} diff --git a/src/ts/webWorkers/WasmWorker.ts b/src/ts/webWorkers/WasmWorker.ts index f19bfc8..1145824 100644 --- a/src/ts/webWorkers/WasmWorker.ts +++ b/src/ts/webWorkers/WasmWorker.ts @@ -1,22 +1,58 @@ import * as Comlink from "comlink"; import rustPlugin from "../../../pkg/obisidian_note_linker_bg.wasm"; import * as wasm from "../../../pkg"; -import {find, init_panic_hook, Note, LinkFinderResult} from "../../../pkg"; +import { + init_panic_hook, + Note, + LinkFinderResult, + find_in_vault, + find_in_note, +} from "../../../pkg"; class WasmWorker { - public async init() { - // @ts-ignore - const buffer = Uint8Array.from(atob(rustPlugin), c => c.charCodeAt(0)) - await wasm.default(Promise.resolve(buffer)); - init_panic_hook() - } + public async init() { + // @ts-ignore + const buffer = Uint8Array.from(atob(rustPlugin), (c) => c.charCodeAt(0)); + await wasm.default(Promise.resolve(buffer)); + init_panic_hook(); + } - public find(serializedNotes: Array, callback: Function): Array { - const notes: Array = serializedNotes.map(noteString => Note.fromJSON(noteString)); - const noteMatchingResults: Array = find(this, notes, callback); - return noteMatchingResults.map(noteMatchingResult => noteMatchingResult.toJSON()); - } + public findInVault( + serializedNotes: Array, + callback: Function + ): Array { + const notes: Array = serializedNotes.map((noteString) => + Note.fromJSON(noteString) + ); + const noteMatchingResults: Array = find_in_vault( + this, + notes, + callback + ); + return noteMatchingResults.map((noteMatchingResult) => + noteMatchingResult.toJSON() + ); + } + public findInNote( + serializedNote: string, + searializedNotes: Array, + callback: Function + ): Array { + const note: Note = Note.fromJSON(serializedNote); + const notes: Array = searializedNotes.map((noteString) => + Note.fromJSON(noteString) + ); + const noteMatchingResults: Array = find_in_note( + this, + note, + notes, + callback + ); + return noteMatchingResults.map((noteMatchingResult) => + noteMatchingResult.toJSON() + ); + } } -Comlink.expose(WasmWorker); \ No newline at end of file +Comlink.expose(WasmWorker); diff --git a/styles.css b/styles.css index 61203d4..03380a8 100644 --- a/styles.css +++ b/styles.css @@ -1,212 +1,214 @@ :root { - --super-small-margin: 5px; - --small-margin: 10px; - --default-margin: 15px; - --double-default-margin: 30px; + --super-small-margin: 5px; + --small-margin: 10px; + --default-margin: 15px; + --double-default-margin: 30px; - --default-padding: 10px; - --small-padding: 5px; + --default-padding: 10px; + --small-padding: 5px; - --default-round-borders: 10px; - --small-round-borders: 7px; + --default-round-borders: 10px; + --small-round-borders: 7px; - --small-font-size: 10px; - --enlarged-default-font-size: 15px; + --small-font-size: 10px; + --enlarged-default-font-size: 15px; - --warning-background-color: rgb(255, 165, 0, 0.5); - --success-background-color: rgb(50, 205, 50, 0.5); - --info-background-color: rgb(100, 149, 237, 0.5); + --warning-background-color: rgb(255, 165, 0, 0.5); + --success-background-color: rgb(50, 205, 50, 0.5); + --info-background-color: rgb(100, 149, 237, 0.5); } /* General */ button { - font-size: var(--enlarged-default-font-size); - border-radius: var(--default-round-borders); + font-size: var(--enlarged-default-font-size); + border-radius: var(--default-round-borders); } .loading-component { - display: flex; - flex-direction: column; - align-items: center; + display: flex; + flex-direction: column; + align-items: center; } .multiline { - white-space: pre-wrap; + white-space: pre-wrap; } /* Start component */ .start-component { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .start-component > h1 { - margin-bottom: var(--double-default-margin); + margin-bottom: var(--double-default-margin); } .start-component > span { - margin-bottom: var(--double-default-margin); + margin-bottom: var(--double-default-margin); } - .start-component > button { - align-self: center; + align-self: center; +} + +.start-component > .button-container { + display: flex; + flex-direction: row; + justify-content: center; + margin-top: 0; } /* Progress bar */ .progress-bar-component { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - margin-bottom: var(--double-default-margin); + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + margin-bottom: var(--double-default-margin); } .progress-bar-component > .ascii-art-progress-bar { - border: 1px solid var(--text-normal); - padding: var(--default-padding); - font-size: var(--enlarged-default-font-size); + border: 1px solid var(--text-normal); + padding: var(--default-padding); + font-size: var(--enlarged-default-font-size); } /* Note Matching Results List */ .note-matching-result-list { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .note-matching-result-list > h1 { - border-bottom: 1px solid var(--background-modifier-border); - align-self: center; + border-bottom: 1px solid var(--background-modifier-border); + align-self: center; } .note-matching-result-list > button { - margin-top: var(--default-margin); - align-self: center; - width: 100%; + margin-top: var(--default-margin); + align-self: center; + width: 100%; } - /* Link Matches List Component */ .link-matches-list { - /*add border 2px*/ - border: 1px solid var(--background-modifier-border); - background: var(--background-primary); - border-radius: var(--default-round-borders); - margin-top: var(--default-margin); - padding: var(--small-padding); + /*add border 2px*/ + border: 1px solid var(--background-modifier-border); + background: var(--background-primary); + border-radius: var(--default-round-borders); + margin-top: var(--default-margin); + padding: var(--small-padding); } /* Note Matching Result Title*/ .note-matching-result-title { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin-top: var(--small-margin); - margin-bottom: var(--small-margin); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-top: var(--small-margin); + margin-bottom: var(--small-margin); } .note-matching-result-title > h3 { - margin: 0; + margin: 0; } - /* Link Match Title */ .link-match-title { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; } .link-match-title > h4 { - margin: 0; - padding-right: var(--small-padding); + margin: 0; + padding-right: var(--small-padding); } /* Link Target Candidates List */ .link-target-candidates-list { - padding: var(--default-padding); + padding: var(--default-padding); } .link-target-candidates-list > ul { - padding-left: var(--default-padding); + padding-left: var(--default-padding); } /* Replacements Selection */ .replacements-selection { - margin-top: var(--super-small-margin); + margin-top: var(--super-small-margin); } .replacements-selection > ul { - padding-left: var(--default-padding); + padding-left: var(--default-padding); } /* Replacement Item */ .replacement-item { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; } .replacement-item > .matched-text { - white-space: nowrap; + white-space: nowrap; } .replacement-context { - font-size: var(--enlarged-default-font-size); - color: var(--text-faint); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + font-size: var(--enlarged-default-font-size); + color: var(--text-faint); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .replacement-context > .arrow-icon { - padding-left: var(--small-padding); - padding-right: var(--small-padding); + padding-left: var(--small-padding); + padding-right: var(--small-padding); } #replacement-context > .link-preview { - color: var(--text-muted) !important; - } + color: var(--text-muted) !important; +} /* Other styling */ .hide-list-styling { - list-style: none; - margin: 0; - padding: 0; + list-style: none; + margin: 0; + padding: 0; } .light-description { - font-size: var(--small-font-size); - color: var(--text-muted); - font-style: italic; + font-size: var(--small-font-size); + color: var(--text-muted); + font-style: italic; } - - .warning-toast { - background: var(--warning-background-color); - border-radius: var(--small-round-borders); - padding: var(--small-padding) + background: var(--warning-background-color); + border-radius: var(--small-round-borders); + padding: var(--small-padding); } .success-toast { - background: var(--success-background-color); - border-radius: var(--small-round-borders); - padding: var(--small-padding) + background: var(--success-background-color); + border-radius: var(--small-round-borders); + padding: var(--small-padding); } .info-toast { - background: var(--info-background-color); - border-radius: var(--small-round-borders); - padding: var(--small-padding) -} \ No newline at end of file + background: var(--info-background-color); + border-radius: var(--small-round-borders); + padding: var(--small-padding); +}