From 9b9649a88aa0f1d28fe65e6d22563551d75dca05 Mon Sep 17 00:00:00 2001 From: jaanonim Date: Sat, 31 Aug 2024 23:51:26 +0200 Subject: [PATCH 1/3] basic quoting --- src/EditorSuggester.ts | 81 +++++++++++++++++++++++++--------- src/LinkPreview.ts | 98 ++++++++++++++++++++++++++---------------- src/SettingTab.ts | 23 ++++++++-- src/SettingsData.ts | 6 ++- src/Verse.ts | 45 +++++++++++++++++++ src/VerseEmbed.ts | 14 ++++++ src/VerseLink.ts | 46 ++------------------ 7 files changed, 208 insertions(+), 105 deletions(-) create mode 100644 src/Verse.ts create mode 100644 src/VerseEmbed.ts diff --git a/src/EditorSuggester.ts b/src/EditorSuggester.ts index 8584906..908904e 100644 --- a/src/EditorSuggester.ts +++ b/src/EditorSuggester.ts @@ -1,6 +1,8 @@ import getBooks from "./Books"; import { bookRegex, linkRegex, separatorRegex } from "./Regex"; import { ObsidianYouversionLinkerSettings } from "./SettingsData"; +import Verse from "./Verse"; +import VerseEmbed from "./VerseEmbed"; import VerseLink from "./VerseLink"; import ObsidianYouversionLinker from "./main"; import { @@ -26,8 +28,19 @@ export class EditorSuggester extends EditorSuggest { file: TFile | null ): EditorSuggestTriggerInfo | null { const currentLine = editor.getLine(cursor.line); - const pos = currentLine.search(new RegExp(this.settings.trigger, "u")); - if (pos < 0) return null; + + const link_pos = currentLine.search( + new RegExp(this.settings.linkTrigger, "u") + ); + const embed_pos = currentLine.search( + new RegExp(this.settings.embedTrigger, "u") + ); + + if (link_pos < 0 && embed_pos < 0) return null; + const isLink = + link_pos >= 0 && + (cursor.ch - link_pos > cursor.ch - embed_pos || embed_pos < 0); + const pos = isLink ? link_pos : embed_pos; const currentContent = currentLine.substring(pos + 1, cursor.ch).trim(); const matches = currentContent.match(linkRegex); @@ -42,26 +55,41 @@ export class EditorSuggester extends EditorSuggest { line: cursor.line, ch: pos, }, - query: match, + query: (isLink ? "@" : ">") + match, }; } return null; }, null); } + getSuggestions( context: EditorSuggestContext ): VerseLink[] | Promise { - return getSuggestionsFromQuery(context.query, this.settings); + const query = context.query; + const isLink = query[0] !== ">"; + + if (query[0] !== "@" && query[0] !== ">") { + console.error(`INTERNAL: query should start with @ or >`); + } + + return getSuggestionsFromQuery( + query.substring(1), + isLink, + this.settings + ); } - renderSuggestion(value: VerseLink, el: HTMLElement): void { + renderSuggestion(value: Verse, el: HTMLElement): void { value.render(el); } - selectSuggestion(value: VerseLink, evt: MouseEvent | KeyboardEvent): void { + async selectSuggestion( + value: Verse, + evt: MouseEvent | KeyboardEvent + ): Promise { if (this.context) { (this.context.editor as Editor).replaceRange( - value.toLink(), + await value.toReplace(), this.context.start, this.context.end ); @@ -71,8 +99,9 @@ export class EditorSuggester extends EditorSuggest { export function getSuggestionsFromQuery( query: string, + isLink: boolean, settings: ObsidianYouversionLinkerSettings -): VerseLink[] { +): Verse[] { console.debug("get suggestion for query ", query.toLowerCase()); const bookName = query.match(bookRegex)?.first(); @@ -95,17 +124,29 @@ export function getSuggestionsFromQuery( const verseEndNumber = numbers.length === 3 ? parseInt(numbers[2]) : undefined; - return booksUrl.flatMap((bookUrl) => - settings.bibleVersions.map( - (version) => - new VerseLink( - version, - bookUrl, - bookName, - chapterNumber, - verseNumber, - verseEndNumber - ) + return booksUrl + .flatMap((bookUrl) => + settings.bibleVersions.map((version) => { + if (isLink) { + return new VerseLink( + version, + bookUrl, + bookName, + chapterNumber, + verseNumber, + verseEndNumber + ); + } else if (verseNumber !== undefined) { + return new VerseEmbed( + version, + bookUrl, + bookName, + chapterNumber, + verseNumber, + verseEndNumber + ); + } + }) ) - ); + .filter((v) => v !== undefined); } diff --git a/src/LinkPreview.ts b/src/LinkPreview.ts index 0ccc029..b1af6d8 100644 --- a/src/LinkPreview.ts +++ b/src/LinkPreview.ts @@ -2,64 +2,90 @@ import { requestUrl } from "obsidian"; import tippy from "tippy.js"; import { htmlCleanupRegex, htmlDataRegex } from "./Regex"; -type cacheType = { [key: string]: any }; +type CacheElement = { + info: { version: string; title: string }; + verses: string; + err: boolean; +}; + +type CacheType = { [key: string]: CacheElement }; export default class LinkPreviewManager { - static cache: cacheType = {}; + static cache: CacheType = {}; static async processLink(link: HTMLAnchorElement) { - if (!this.cache[link.href]) { - const res = await requestUrl(link.href); - let text = await res.text; - - const match = text.match(htmlDataRegex); - if (match) { - const json_text = match[0].replace(htmlCleanupRegex, ""); - - try { - const data = JSON.parse(json_text); - - if (data.props.pageProps.type !== "verse") { - throw 1; - } - - const info = - data.props.pageProps.referenceTitle.title + - " " + - data.props.pageProps.version.local_abbreviation; - const verses = data.props.pageProps.verses - .map((ele: any) => ele.content) - .join(" "); - this.cache[link.href] = { info, verses }; - } catch { - this.cache[link.href] = { err: true }; - } - } else { - this.cache[link.href] = { err: true }; - } - } + const content = await this.processUrl(link.href); const popup = document.createElement("div"); popup.addClass("preview-youversion"); - if (this.cache[link.href].err) { + if (content.err) { popup .createSpan({ cls: "error-youversion" }) .setText("Verse preview is unavailable for this link."); } else { popup .createSpan({ cls: "content-youversion" }) - .setText(this.cache[link.href]?.verses); + .setText(content.verses); popup .createSpan({ cls: "info-youversion" }) - .setText(this.cache[link.href].info); + .setText(content.info.title + " " + content.info.version); } tippy(link, { content: popup, allowHTML: true }); } + static async processUrl(url: string): Promise { + if (!this.cache[url]) { + try { + const res = await requestUrl(url); + let text = await res.text; + + const match = text.match(htmlDataRegex); + if (match) { + const json_text = match[0].replace(htmlCleanupRegex, ""); + + const data = JSON.parse(json_text); + + if (data.props.pageProps.type !== "verse") { + throw 1; + } + + const info = { + title: data.props.pageProps.referenceTitle.title, + version: + data.props.pageProps.version.local_abbreviation, + }; + const verses = data.props.pageProps.verses + .map((ele: any) => ele.content) + .join(" "); + + if (verses.length < 1) { + throw 1; + } + + this.cache[url] = { err: false, info, verses }; + } else { + throw 1; + } + } catch { + this.cache[url] = { + err: true, + info: { title: "", version: "" }, + verses: "", + }; + } + } + return this.cache[url]; + } + static clearCache(notClear: Array) { - let dict: cacheType = {}; + console.info( + `Clearing cache... (${Math.abs( + Object.keys(this.cache).length - notClear.length + )} items)` + ); + let dict: CacheType = {}; notClear.forEach((ele) => { dict[ele] = this.cache[ele]; }); diff --git a/src/SettingTab.ts b/src/SettingTab.ts index 88ae51c..5078dfa 100644 --- a/src/SettingTab.ts +++ b/src/SettingTab.ts @@ -19,12 +19,27 @@ export default class SettingTab extends PluginSettingTab { this.bibleVersionSettings(); new Setting(containerEl) - .setName("Trigger") - .setDesc("Trigger for autocomplete in edit mode. Supports regex.") + .setName("Link trigger") + .setDesc( + "Trigger for autocomplete for linking verse in edit mode. Supports regex." + ) + .addText((text) => { + text.setValue(this.plugin.settings.linkTrigger); + text.onChange(async (value) => { + this.plugin.settings.linkTrigger = value; + await this.plugin.saveSettings(); + }); + }); + + new Setting(containerEl) + .setName("Quote Trigger") + .setDesc( + "Trigger for autocomplete for quoting verse in edit mode. Supports regex." + ) .addText((text) => { - text.setValue(this.plugin.settings.trigger); + text.setValue(this.plugin.settings.embedTrigger); text.onChange(async (value) => { - this.plugin.settings.trigger = value; + this.plugin.settings.embedTrigger = value; await this.plugin.saveSettings(); }); }); diff --git a/src/SettingsData.ts b/src/SettingsData.ts index 187068c..5585c37 100644 --- a/src/SettingsData.ts +++ b/src/SettingsData.ts @@ -7,7 +7,8 @@ export interface ObsidianYouversionLinkerSettings { bibleVersions: BibleVersion[]; linkPreviewRead: boolean; linkPreviewLive: boolean; - trigger: string; + linkTrigger: string; + embedTrigger: string; selectedBooksLanguages: string[]; } @@ -20,6 +21,7 @@ export const DEFAULT_SETTINGS: ObsidianYouversionLinkerSettings = { ], linkPreviewRead: true, linkPreviewLive: true, - trigger: "@", + linkTrigger: "@", + embedTrigger: ">", selectedBooksLanguages: ["English"], }; diff --git a/src/Verse.ts b/src/Verse.ts new file mode 100644 index 0000000..9b3094e --- /dev/null +++ b/src/Verse.ts @@ -0,0 +1,45 @@ +import { BibleVersion } from "./SettingsData"; +import VERSIONS from "../data/versions.json"; + +export default abstract class Verse { + constructor( + private version: BibleVersion, + private bookUrl: string, + private book: string, + private chapter: number, + private verse: number | undefined, + private verseEnd: number | undefined + ) {} + + public render(el: HTMLElement) { + const div = el.createDiv(); + div.createSpan().setText(this.toSimpleText()); + const span = div.createSpan(); + span.addClass("verse-link-info"); + + const versionAbbr = (VERSIONS as any)[this.version.language].data.find( + (v: any) => v.id == this.version.id + ).abbreviation; + span.setText(`${this.bookUrl} - ${versionAbbr}`); + } + + toSimpleText() { + return this.verse + ? this.verseEnd + ? `${this.book} ${this.chapter}:${this.verse}-${this.verseEnd}` + : `${this.book} ${this.chapter}:${this.verse}` + : `${this.book} ${this.chapter}`; + } + + abstract toReplace(): Promise; + + getUrl(): string { + const base = "https://www.bible.com/bible"; + let url = `${base}/${this.version.id}/${this.bookUrl}.${this.chapter}`; + if (this.verse) { + url += `.${this.verse}`; + if (this.verseEnd) url += `-${this.verseEnd}`; + } + return url; + } +} diff --git a/src/VerseEmbed.ts b/src/VerseEmbed.ts new file mode 100644 index 0000000..b923776 --- /dev/null +++ b/src/VerseEmbed.ts @@ -0,0 +1,14 @@ +import LinkPreviewManager from "./LinkPreview"; +import Verse from "./Verse"; + +export default class VerseEmbed extends Verse { + async toReplace(): Promise { + const content = await LinkPreviewManager.processUrl(this.getUrl()); + if (content.err) { + return `>[!Error] Cannot get content of ${this.toSimpleText()}.\n`; + } else { + // prettier-ignore + return `>[!Quote] [${this.toSimpleText()} ${content.info.version}](${this.getUrl()})\n>${content.verses}\n`; + } + } +} diff --git a/src/VerseLink.ts b/src/VerseLink.ts index c9e7700..b798d7f 100644 --- a/src/VerseLink.ts +++ b/src/VerseLink.ts @@ -1,47 +1,7 @@ -import { BibleVersion } from "./SettingsData"; -import VERSIONS from "../data/versions.json"; +import Verse from "./Verse"; -export default class VerseLink { - constructor( - private version: BibleVersion, - private bookUrl: string, - private book: string, - private chapter: number, - private verse: number | undefined, - private verseEnd: number | undefined - ) {} - - public render(el: HTMLElement) { - const div = el.createDiv(); - div.createSpan().setText(this.toSimpleText()); - const span = div.createSpan(); - span.addClass("verse-link-info"); - - const versionAbbr = (VERSIONS as any)[this.version.language].data.find( - (v: any) => v.id == this.version.id - ).abbreviation; - span.setText(`${this.bookUrl} - ${versionAbbr}`); - } - - toSimpleText() { - return this.verse - ? this.verseEnd - ? `${this.book} ${this.chapter}:${this.verse}-${this.verseEnd}` - : `${this.book} ${this.chapter}:${this.verse}` - : `${this.book} ${this.chapter}`; - } - - toLink(): string { +export default class VerseLink extends Verse { + async toReplace(): Promise { return `[${this.toSimpleText()}](${this.getUrl()})`; } - - getUrl(): string { - const base = "https://www.bible.com/bible"; - let url = `${base}/${this.version.id}/${this.bookUrl}.${this.chapter}`; - if (this.verse) { - url += `.${this.verse}`; - if (this.verseEnd) url += `-${this.verseEnd}`; - } - return url; - } } From 2e0bfdfdd1be49fbf6dd0f3f644a8c9e76a972ff Mon Sep 17 00:00:00 2001 From: jaanonim Date: Sun, 1 Sep 2024 15:52:07 +0200 Subject: [PATCH 2/3] v 1.3.0 --- .github/workflows/release.yaml | 2 +- README.md | 7 ++----- manifest.json | 2 +- package.json | 2 +- versions.json | 3 ++- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6e78dca..12a5e73 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,7 +2,7 @@ name: Obsidian Plugin Release # credits: https://github.com/scambier/obsidian-omnisearch/blob/master/.github/workflows/release.yml env: - VERSION: 1.2.5 + VERSION: 1.3.0 on: [workflow_dispatch] diff --git a/README.md b/README.md index 9f598d7..abff26a 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,13 @@ **List of supported languages can be found [here](./Languages.md).** -You need to just type for example: `@ John 1:1-6`. +You need to just type for example: `@ John 1:1-6` to link verse to quote verse use for example: `> John 1:1-6`. -`@` char is a trigger for suggestion and it can be changed in settings. +`@` and `>` char is a trigger for suggestion and it can be changed in settings. In settings you can select witch version of bible you want to use and books names in witch language will be detected. I'm from Poland so plugin supports polish books names (eq. `J 1:1-6`, `Mt 24,1`). If you would like it to support your language books names read the guide in [Languages.md](./Languages.md). -**New future 🎉**
-Preview of verse after hover over the link. - The plugins is heavily inspired by [obsidian-bible-reference](https://github.com/tim-hub/obsidian-bible-reference) (also i have "borrowed" some code from there) so check it out. It's available in Obsidian community plugins as diff --git a/manifest.json b/manifest.json index 0a29dd9..a098f7f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "youversion-linker", "name": "YouVersion Linker", - "version": "1.2.5", + "version": "1.3.0", "minAppVersion": "0.15.0", "description": "Automatically link bible verses in your notes to YouVersion bible.", "author": "jaanonim", diff --git a/package.json b/package.json index 3bc7ca4..cd8e242 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "youversion-linker", - "version": "1.2.5", + "version": "1.3.0", "description": "Obsidian plugin that automatically link bible verses to YouVersion bible.", "main": "main.js", "scripts": { diff --git a/versions.json b/versions.json index 68eb837..8523204 100644 --- a/versions.json +++ b/versions.json @@ -12,5 +12,6 @@ "1.2.2": "0.15.0", "1.2.3": "0.15.0", "1.2.4": "0.15.0", - "1.2.5": "0.15.0" + "1.2.5": "0.15.0", + "1.3.0": "0.15.0" } \ No newline at end of file From d94d90f64b3c5ff6bfb392e5340570a6c2c2530e Mon Sep 17 00:00:00 2001 From: jaanonim Date: Sun, 1 Sep 2024 15:58:34 +0200 Subject: [PATCH 3/3] fix errors --- src/EditorSuggester.ts | 51 +++++++++++++++++++++--------------------- src/GenerateLinks.ts | 10 ++++++--- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/EditorSuggester.ts b/src/EditorSuggester.ts index 908904e..2cecc22 100644 --- a/src/EditorSuggester.ts +++ b/src/EditorSuggester.ts @@ -124,29 +124,30 @@ export function getSuggestionsFromQuery( const verseEndNumber = numbers.length === 3 ? parseInt(numbers[2]) : undefined; - return booksUrl - .flatMap((bookUrl) => - settings.bibleVersions.map((version) => { - if (isLink) { - return new VerseLink( - version, - bookUrl, - bookName, - chapterNumber, - verseNumber, - verseEndNumber - ); - } else if (verseNumber !== undefined) { - return new VerseEmbed( - version, - bookUrl, - bookName, - chapterNumber, - verseNumber, - verseEndNumber - ); - } - }) - ) - .filter((v) => v !== undefined); + return booksUrl.flatMap( + (bookUrl) => + settings.bibleVersions + .map((version) => { + if (isLink) { + return new VerseLink( + version, + bookUrl, + bookName, + chapterNumber, + verseNumber, + verseEndNumber + ); + } else if (verseNumber !== undefined) { + return new VerseEmbed( + version, + bookUrl, + bookName, + chapterNumber, + verseNumber, + verseEndNumber + ); + } + }) + .filter((v) => v !== undefined) as Verse[] + ); } diff --git a/src/GenerateLinks.ts b/src/GenerateLinks.ts index 4e0774a..5495d52 100644 --- a/src/GenerateLinks.ts +++ b/src/GenerateLinks.ts @@ -14,11 +14,15 @@ export default function GenerateLinks( const match = [...line.matchAll(linkRegex)]; match.forEach((match) => { - const suggestions = getSuggestionsFromQuery(match[0], settings); - suggestions.forEach((s) => { + const suggestions = getSuggestionsFromQuery( + match[0], + true, + settings + ); + suggestions.forEach(async (s) => { if (match.index === undefined) return; editor.replaceRange( - s.toLink(), + await s.toReplace(), { line: i, ch: match.index,