mirror of
https://github.com/scotttomaszewski/obsidian-disciples-journal.git
synced 2026-07-22 05:42:13 +00:00
Cleanup
This commit is contained in:
parent
e08f577102
commit
d93b00bc12
26 changed files with 5162 additions and 2425 deletions
|
|
@ -1,3 +0,0 @@
|
|||
node_modules/
|
||||
|
||||
main.js
|
||||
23
.eslintrc
23
.eslintrc
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": { "node": true },
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,14 @@
|
|||
# Changelog
|
||||
|
||||
## 0.13.0
|
||||
|
||||
- Brings the plugin in line with current Obsidian community standards
|
||||
- Modernizes the lint toolchain (flat config, `eslint-plugin-obsidianmd`, type-aware `typescript-eslint`); the plugin now passes `eslint` and `tsc` cleanly
|
||||
- Settings UI text uses sentence case
|
||||
- Corrects the manifest description and raises `minAppVersion` to `1.6.6` to match the APIs actually used
|
||||
- Tightens type safety (removes `any`, narrows YAML/error handling) and moves remaining inline styles to `styles.css`
|
||||
- Known follow-ups (tracked in `FOLLOWUP.md`): the hover-preview event listeners are still attached globally and should move to the registered-listener lifecycle, and file reads/writes should migrate to the `Vault`/`FileManager` APIs
|
||||
|
||||
## 0.12.0
|
||||
|
||||
- Adds "Open Bible" command and ribbon icon to browse books and chapters without a reference
|
||||
106
FOLLOWUP.md
Normal file
106
FOLLOWUP.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Follow-up work
|
||||
|
||||
This file tracks known issues that were **intentionally deferred** during the
|
||||
0.13.0 standards/compliance pass. The codebase currently passes `npx eslint .`
|
||||
and `npm run build` with zero errors/warnings; the items below are silenced with
|
||||
scoped `eslint-disable` comments (each referencing this file) so the deferral is
|
||||
explicit rather than hidden.
|
||||
|
||||
Scope decision at the time: do the "safe subset" of standards fixes and leave the
|
||||
file-API internals and the event-handler internals **functionally unchanged**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Hover-preview event listeners leak (highest priority)
|
||||
|
||||
**Files:** `src/core/BibleEventHandlers.ts`, `src/components/BibleReferenceRenderer.ts`,
|
||||
`src/components/BibleReferenceInlineExtension.ts`
|
||||
|
||||
**What's wrong:**
|
||||
- `BibleReferenceRenderer.processInlineCodeBlocks` and the CodeMirror
|
||||
`eventHandlers` in `BibleReferenceInlineExtension` create a **new
|
||||
`BibleEventHandlers` instance on every `mouseover`/`mouseout`**.
|
||||
- Each `BibleEventHandlers` constructor calls `document.addEventListener('mousemove', …)`
|
||||
and `document.addEventListener('click', …)` on the **global `document`**.
|
||||
- These global listeners are only removed by `cleanup()`, which is **never called** —
|
||||
`DisciplesJournalPlugin.onunload()` is empty. So listeners accumulate for the
|
||||
lifetime of the app session and are never released on plugin unload.
|
||||
- It also uses `window.setInterval` / `clearInterval` and the global `document`
|
||||
rather than `activeDocument`, so hover previews don't behave correctly in
|
||||
pop-out windows.
|
||||
|
||||
**Rules currently disabled for this:** `obsidianmd/prefer-active-doc`,
|
||||
`obsidianmd/prefer-window-timers`, `@typescript-eslint/no-deprecated`
|
||||
(file-level disable at the top of `BibleEventHandlers.ts`).
|
||||
|
||||
**Suggested fix:**
|
||||
- Make `BibleEventHandlers` a single, long-lived instance owned by the plugin
|
||||
(or make it extend `Component`), created once in `onload`.
|
||||
- Register all DOM listeners with `this.registerDomEvent(...)` and the interval
|
||||
with `this.registerInterval(...)` so Obsidian tears them down automatically.
|
||||
- Use `activeDocument` / `activeWindow` instead of the globals for pop-out support.
|
||||
- Stop instantiating `new BibleEventHandlers(...)` inside the hover callbacks.
|
||||
|
||||
---
|
||||
|
||||
## 2. File access via `vault.adapter` instead of the Vault/FileManager APIs
|
||||
|
||||
**Files:** `src/services/ESVApiService.ts`, `src/services/BibleFiles.ts`,
|
||||
`src/core/DisciplesJournalPlugin.ts`
|
||||
|
||||
**What's wrong (Obsidian guideline rules 19–22):**
|
||||
- `vault.adapter.write(...)` is used to create/overwrite notes
|
||||
(`ESVApiService.saveESVApiResponseAsMdNote`, `DisciplesJournalPlugin.updateAllBibleNoteFrontmatter`).
|
||||
Prefer `Vault.create()` for new files and `Vault.process()` for background
|
||||
edits of existing files. For frontmatter specifically, `FileManager.processFrontMatter()`
|
||||
is the intended API and would remove most of the manual YAML string handling
|
||||
in `FrontmatterUtil.ts`.
|
||||
- `vault.adapter.exists` / `vault.adapter.mkdir` (directory creation) should use
|
||||
`Vault.getAbstractFileByPath()` / `Vault.createFolder()`.
|
||||
- `BibleFiles.clearData` uses `vault.adapter.rmdir(...)`; deletion should go
|
||||
through `FileManager.trashFile()` so it respects the user's trash settings.
|
||||
- Paths are built by string concatenation; run user-configurable paths through
|
||||
`normalizePath()`.
|
||||
|
||||
> Note: these are **not** currently flagged by the lint config (the obsidianmd
|
||||
> rules target `Vault.trash/delete`, not `adapter.*`), so there are no disable
|
||||
> comments for them — but they are still non-conformant and worth migrating.
|
||||
|
||||
---
|
||||
|
||||
## 3. Rendering ESV HTML via `innerHTML`
|
||||
|
||||
**File:** `src/components/BibleReferenceRenderer.ts` (4 sites)
|
||||
|
||||
**What's wrong:** passage HTML returned by the ESV API is injected with
|
||||
`element.innerHTML = passage.html`. Flagged by `no-unsanitized/property` and
|
||||
`@microsoft/sdl/no-inner-html`. The content is trusted (comes from the ESV API
|
||||
over HTTPS), so this is currently accepted and disabled inline at each site.
|
||||
|
||||
**Suggested fix:** parse the HTML and rebuild it with Obsidian DOM helpers
|
||||
(`createEl`/`createDiv`), or sanitize before insertion, so no raw HTML string is
|
||||
written to the DOM.
|
||||
|
||||
---
|
||||
|
||||
## 4. `setTimeout` for scroll-to-verse
|
||||
|
||||
**File:** `src/services/BibleChapterFiles.ts`
|
||||
|
||||
The scroll-to-verse uses a fixed `window.setTimeout(..., 300)` to wait for the
|
||||
note to render before querying for the verse element. This was left as-is (only
|
||||
prefixed with `window.` for pop-out compatibility). A more robust approach would
|
||||
key off a render/layout event rather than a magic delay.
|
||||
|
||||
---
|
||||
|
||||
## 5. Smaller cleanups noticed in passing
|
||||
|
||||
- `src/services/BibleContentService.ts` and `src/services/ESVApiService.ts` both
|
||||
contain `// TODO` notes about duplicated ESV-response → `BiblePassage`
|
||||
conversion logic that could be unified.
|
||||
- `src/services/BibleChapterFiles.ts` has a `// TODO - logic in this class needs
|
||||
to move to BibleFiles` note; `BibleFiles.ts` has TODOs for `openChapterNote` /
|
||||
`openPassageNote` helpers.
|
||||
- `openChapterNote` takes a `string` and re-parses it; several call sites already
|
||||
hold a `BibleReference` and could pass it directly (existing TODO).
|
||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Scott Tomaszewski
|
||||
Copyright (c) 2024-2026 Scott Tomaszewski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
import { builtinModules as builtins } from "node:module";
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
|
|
|
|||
54
eslint.config.mjs
Normal file
54
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { defineConfig } from "eslint/config";
|
||||
import obsidianmd from "eslint-plugin-obsidianmd";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
// obsidianmd only wires up JSON parsing for package.json; other JSON
|
||||
// files would be parsed as JS and error. Build artifacts/configs too.
|
||||
ignores: [
|
||||
"main.js",
|
||||
"node_modules/**",
|
||||
"**/*.mjs",
|
||||
"manifest.json",
|
||||
"versions.json",
|
||||
"tsconfig.json",
|
||||
"devbox.json",
|
||||
"devbox.lock",
|
||||
"package-lock.json",
|
||||
],
|
||||
},
|
||||
...obsidianmd.configs.recommended,
|
||||
{
|
||||
files: ["**/*.ts"],
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// Obsidian/CodeMirror callback signatures often include unused args.
|
||||
"@typescript-eslint/no-unused-vars": ["error", { args: "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
// Disabled upstream as "not working as intended" (v0.3.0); it also
|
||||
// lower-cases proper nouns (Bible, ESV, Christ). Sentence case is
|
||||
// applied manually instead.
|
||||
"obsidianmd/ui/sentence-case": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
// obsidianmd's recommended set applies several type-aware rules globally
|
||||
// (no file filter). They require TS parser services and crash on the JSON
|
||||
// files. They are only meaningful for source, so disable them for JSON.
|
||||
files: ["**/*.json"],
|
||||
rules: {
|
||||
"obsidianmd/no-plugin-as-component": "off",
|
||||
"obsidianmd/no-view-references-in-plugin": "off",
|
||||
"obsidianmd/prefer-file-manager-trash-file": "off",
|
||||
"obsidianmd/prefer-instanceof": "off",
|
||||
"obsidianmd/no-unsupported-api": "off",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
"id": "disciples-journal",
|
||||
"name": "Disciples Journal",
|
||||
"version": "0.12.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Embed Bible references and passages in your notes and read the Bible in Obsidian.",
|
||||
"version": "0.13.0",
|
||||
"minAppVersion": "1.6.6",
|
||||
"description": "Embed Bible references and passages in your notes and read the Bible.",
|
||||
"author": "Scott Tomaszewski (Xentis)",
|
||||
"authorUrl": "https://github.com/scottTomaszewski",
|
||||
"isDesktopOnly": false,
|
||||
"fundingUrl": ""
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
7098
package-lock.json
generated
7098
package-lock.json
generated
File diff suppressed because it is too large
Load diff
26
package.json
26
package.json
|
|
@ -1,26 +1,36 @@
|
|||
{
|
||||
"name": "obsidian-disciples-journal",
|
||||
"version": "0.12.0",
|
||||
"description": "Obsidian plugin for Bible verse references and passage display",
|
||||
"version": "0.13.0",
|
||||
"description": "Embed Bible references and passages in your notes and read the Bible.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"lint": "eslint .",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"build-no-check": "node esbuild.config.mjs production"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"keywords": [
|
||||
"obsidian",
|
||||
"bible",
|
||||
"scripture",
|
||||
"esv",
|
||||
"verse"
|
||||
],
|
||||
"author": "Scott Tomaszewski (Xentis)",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"esbuild": "0.17.3",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-obsidianmd": "^0.3.0",
|
||||
"obsidian": "latest",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.20.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,7 +189,8 @@ export class BibleNavigation {
|
|||
await this.bibleBookFiles.openChapterNote(new BibleReference(book, chapter).toString());
|
||||
} catch (error) {
|
||||
console.error('Error navigating to chapter:', error);
|
||||
new Notice(`Error navigating to chapter: ${error.message}`);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
new Notice(`Error navigating to chapter: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ViewUpdate, EditorView, ViewPlugin, Decoration, DecorationSet } from "@codemirror/view";
|
||||
import {RangeSet, RangeSetBuilder} from "@codemirror/state";
|
||||
import {RangeSetBuilder} from "@codemirror/state";
|
||||
import { syntaxTree } from "@codemirror/language";
|
||||
import { BibleReference } from "src/core/BibleReference";
|
||||
import { BibleEventHandlers } from "src/core/BibleEventHandlers";
|
||||
|
|
@ -26,7 +26,7 @@ export function createInlineReferenceExtension(renderer: BibleReferenceRenderer,
|
|||
|
||||
buildDecorations(view: EditorView): DecorationSet {
|
||||
if (!view.state.field(editorLivePreviewField)) {
|
||||
return RangeSet.empty;
|
||||
return Decoration.none;
|
||||
}
|
||||
const builder = new RangeSetBuilder<Decoration>();
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ export function createInlineReferenceExtension(renderer: BibleReferenceRenderer,
|
|||
syntaxTree(view.state).iterate({
|
||||
from,
|
||||
to,
|
||||
enter: (node: any) => {
|
||||
enter: (node) => {
|
||||
if (node.type.name.contains("inline-code")) {
|
||||
const content = view.state.doc.sliceString(node.from, node.to);
|
||||
// Try to parse as a Bible reference
|
||||
|
|
@ -74,12 +74,12 @@ export function createInlineReferenceExtension(renderer: BibleReferenceRenderer,
|
|||
return;
|
||||
}
|
||||
|
||||
contentService.getBibleContent(reference).then(response => {
|
||||
void contentService.getBibleContent(reference).then(response => {
|
||||
if (response.isError()) {
|
||||
new Notice(response.errorMessage, 10000);
|
||||
return;
|
||||
}
|
||||
new BibleEventHandlers(renderer).handleBibleReferenceHover(e, response.passage);
|
||||
void new BibleEventHandlers(renderer).handleBibleReferenceHover(e, response.passage);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export class BibleReferenceRenderer {
|
|||
}
|
||||
|
||||
referenceEl.addEventListener('mouseover', (e) => {
|
||||
new BibleEventHandlers(this).handleBibleReferenceHover(e, response.passage);
|
||||
void new BibleEventHandlers(this).handleBibleReferenceHover(e, response.passage);
|
||||
});
|
||||
referenceEl.addEventListener('mouseout', (e) => {
|
||||
new BibleEventHandlers(this).handleBibleReferenceMouseOut(e);
|
||||
|
|
@ -124,6 +124,7 @@ export class BibleReferenceRenderer {
|
|||
// Add verses
|
||||
const passageEl = el.doc.createElement('div');
|
||||
passageEl.classList.add('bible-passage-text');
|
||||
// eslint-disable-next-line no-unsanitized/property, @microsoft/sdl/no-inner-html -- deferred: ESV API returns trusted formatted HTML; see FOLLOWUP.md
|
||||
passageEl.innerHTML = response.passage.html;
|
||||
|
||||
containerEl.appendChild(passageEl);
|
||||
|
|
@ -152,7 +153,7 @@ export class BibleReferenceRenderer {
|
|||
try {
|
||||
// Call the method to open the chapter note
|
||||
// TODO - openChapterNote should take in a BibleReference instead of string
|
||||
this.plugin.openChapterNote(passage.reference.toString());
|
||||
void this.plugin.openChapterNote(passage.reference.toString());
|
||||
|
||||
// Close the preview - using a method available in BibleEventHandlers
|
||||
// Remove the popup directly instead of trying to access the private property
|
||||
|
|
@ -164,7 +165,7 @@ export class BibleReferenceRenderer {
|
|||
console.error('Error opening chapter note from popup:', error);
|
||||
|
||||
// Show user feedback if there's an error
|
||||
new Notice(`Disciples Journal: Unable to open chapter note for ${passage.reference}`, 10000);
|
||||
new Notice(`Disciples Journal: Unable to open chapter note for ${passage.reference.toString()}`, 10000);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -179,6 +180,7 @@ export class BibleReferenceRenderer {
|
|||
try {
|
||||
// Create a temporary element to parse the HTML
|
||||
const tempEl = element.doc.createElement('div');
|
||||
// eslint-disable-next-line no-unsanitized/property, @microsoft/sdl/no-inner-html -- deferred: ESV API returns trusted formatted HTML; see FOLLOWUP.md
|
||||
tempEl.innerHTML = passage.html;
|
||||
|
||||
// Remove footnote sections before extracting paragraphs
|
||||
|
|
@ -190,7 +192,10 @@ export class BibleReferenceRenderer {
|
|||
const paragraphs = tempEl.querySelectorAll('p:not(.extra_text)');
|
||||
if (paragraphs.length > 0) {
|
||||
for (let i = 0; i < paragraphs.length; i++) {
|
||||
const cloned = paragraphs[i].cloneNode(true) as HTMLElement;
|
||||
const cloned = paragraphs[i].cloneNode(true);
|
||||
if (!cloned.instanceOf(HTMLElement)) {
|
||||
continue;
|
||||
}
|
||||
// Strip inline footnote markers if the setting is enabled
|
||||
if (this.plugin.settings.hideFootnotesInPreview) {
|
||||
cloned.querySelectorAll('.footnote').forEach(fn => fn.remove());
|
||||
|
|
@ -199,10 +204,12 @@ export class BibleReferenceRenderer {
|
|||
}
|
||||
} else {
|
||||
// Fallback if we can't extract the verses properly
|
||||
// eslint-disable-next-line no-unsanitized/property, @microsoft/sdl/no-inner-html -- deferred: ESV API returns trusted formatted HTML; see FOLLOWUP.md
|
||||
contentEl.innerHTML = passage.html;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error extracting verse content from HTML:", error);
|
||||
// eslint-disable-next-line no-unsanitized/property, @microsoft/sdl/no-inner-html -- deferred: ESV API returns trusted formatted HTML; see FOLLOWUP.md
|
||||
contentEl.innerHTML = passage.html;
|
||||
}
|
||||
|
||||
|
|
@ -213,8 +220,11 @@ export class BibleReferenceRenderer {
|
|||
|
||||
// Position the main popup with slight overlap to the reference
|
||||
// This creates an easier hover target when moving from reference to popup
|
||||
versePreviewEl.style.left = `${rect.left}px`;
|
||||
versePreviewEl.style.top = `${rect.bottom - 3}px`;
|
||||
// (dynamic positioning depends on runtime geometry)
|
||||
versePreviewEl.setCssStyles({
|
||||
left: `${rect.left}px`,
|
||||
top: `${rect.bottom - 3}px`,
|
||||
});
|
||||
|
||||
// Add popup to document
|
||||
element.doc.body.appendChild(versePreviewEl);
|
||||
|
|
|
|||
|
|
@ -15,11 +15,10 @@ export class BookSuggest extends AbstractInputSuggest<string> {
|
|||
this.onBookSelected = onBookSelected;
|
||||
|
||||
const wrapper = inputEl.parentElement!;
|
||||
wrapper.style.position = 'relative';
|
||||
wrapper.style.display = 'inline-block'; // shrink‐wrap to input width
|
||||
wrapper.addClass('disciples-journal-book-suggest-wrapper');
|
||||
|
||||
// make room for the clear “×”
|
||||
inputEl.style.paddingRight = '1.5em';
|
||||
inputEl.addClass('disciples-journal-book-suggest-input');
|
||||
const clearBtn = wrapper.createEl('button', {
|
||||
cls: 'clear-input-button',
|
||||
text: '×',
|
||||
|
|
@ -58,22 +57,23 @@ export class BookSuggest extends AbstractInputSuggest<string> {
|
|||
super.open();
|
||||
|
||||
// give Obsidian time to insert & position the popover…
|
||||
requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
// 1) grab the suggestion container
|
||||
// this is the <div> Obsidian just appended after your input
|
||||
const container = this.inputEl.doc
|
||||
?.querySelector('.suggestion-container') as HTMLElement;
|
||||
|
||||
if (!container) return;
|
||||
const container = this.inputEl.doc.querySelector('.suggestion-container');
|
||||
if (!(container instanceof HTMLElement)) return;
|
||||
|
||||
// 2) compute your input's screen-coords
|
||||
const rect = this.inputEl.getBoundingClientRect();
|
||||
|
||||
// 3) force the dropdown to sit flush with your input's left edge
|
||||
container.style.position = 'absolute';
|
||||
container.style.left = `${rect.left}px`;
|
||||
container.style.top = `${rect.bottom}px`; // drop below
|
||||
container.style.minWidth = `${rect.width}px`; // match widths
|
||||
// (dynamic positioning depends on runtime geometry)
|
||||
container.setCssStyles({
|
||||
position: 'absolute',
|
||||
left: `${rect.left}px`,
|
||||
top: `${rect.bottom}px`,
|
||||
minWidth: `${rect.width}px`,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,11 @@ export class OpenBibleModal extends SuggestModal<BibleTarget> {
|
|||
el.setText(item.label);
|
||||
}
|
||||
|
||||
async onChooseSuggestion(item: BibleTarget, _evt: MouseEvent | KeyboardEvent): Promise<void> {
|
||||
onChooseSuggestion(item: BibleTarget, _evt: MouseEvent | KeyboardEvent): void {
|
||||
void this.openTarget(item);
|
||||
}
|
||||
|
||||
private async openTarget(item: BibleTarget): Promise<void> {
|
||||
if (item.chapter === 0) {
|
||||
// Book selected -- reopen modal for chapter selection
|
||||
const chapterCount = BookNames.getChapterCount(item.book);
|
||||
|
|
@ -113,7 +117,11 @@ class OpenBibleChapterModal extends SuggestModal<BibleTarget> {
|
|||
el.setText(item.label);
|
||||
}
|
||||
|
||||
async onChooseSuggestion(item: BibleTarget, _evt: MouseEvent | KeyboardEvent): Promise<void> {
|
||||
onChooseSuggestion(item: BibleTarget, _evt: MouseEvent | KeyboardEvent): void {
|
||||
void this.openChapter(item);
|
||||
}
|
||||
|
||||
private async openChapter(item: BibleTarget): Promise<void> {
|
||||
const loadingNotice = new Notice("Loading chapter...", 0);
|
||||
await this.bibleBookFiles.openChapterNote(`${item.book} ${item.chapter}`);
|
||||
loadingNotice.hide();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
/*
|
||||
* DEFERRED: this class attaches hover-preview listeners to the global `document`
|
||||
* and is created per hover event, leaking listeners. It is slated for a
|
||||
* lifecycle rewrite (registerDomEvent / registerInterval / activeDocument) —
|
||||
* see FOLLOWUP.md. Until then the popout-window, timer, and deprecation rules
|
||||
* tied to that code are disabled here rather than partially reworked.
|
||||
*/
|
||||
/* eslint-disable obsidianmd/prefer-active-doc, obsidianmd/prefer-window-timers, @typescript-eslint/no-deprecated */
|
||||
import { BiblePassage } from 'src/utils/BiblePassage';
|
||||
import { BibleReferenceRenderer } from '../components/BibleReferenceRenderer';
|
||||
|
||||
|
|
@ -134,8 +142,7 @@ export class BibleEventHandlers {
|
|||
|
||||
// If no preview active, nothing to do
|
||||
if (!this.previewPopper) return;
|
||||
|
||||
const target = event.target as HTMLElement;
|
||||
|
||||
const relatedTarget = event.relatedTarget as HTMLElement;
|
||||
|
||||
// If moving directly to the preview element, don't close
|
||||
|
|
@ -158,7 +165,7 @@ export class BibleEventHandlers {
|
|||
try {
|
||||
const hoverGaps = doc.querySelectorAll('.bible-hover-gap');
|
||||
hoverGaps.forEach(gap => gap.remove());
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Ignore any errors during cleanup
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,9 @@ export class BibleMarkupProcessor {
|
|||
await this.bibleReferenceRenderer.processFullBiblePassage(source, el);
|
||||
} catch (error) {
|
||||
console.error('Error processing Bible code block:', error);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
el.createEl('div', {
|
||||
text: `Error processing Bible reference: ${error.message}`,
|
||||
text: `Error processing Bible reference: ${message}`,
|
||||
cls: 'bible-reference-error'
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ export default class DisciplesJournalPlugin extends Plugin {
|
|||
private bibleMarkupProcessor: BibleMarkupProcessor;
|
||||
|
||||
async onload() {
|
||||
console.log('Loading Disciples Journal plugin');
|
||||
|
||||
// Initialize settings
|
||||
await this.loadSettings();
|
||||
|
||||
|
|
@ -61,10 +59,12 @@ export default class DisciplesJournalPlugin extends Plugin {
|
|||
this.bibleMarkupProcessor = new BibleMarkupProcessor(this.bibleReferenceRenderer, this.settings);
|
||||
|
||||
// Register bible reference processor
|
||||
this.registerMarkdownCodeBlockProcessor('bible', this.bibleMarkupProcessor.processBibleCodeBlock.bind(this.bibleMarkupProcessor));
|
||||
this.registerMarkdownCodeBlockProcessor('bible', (source, el, ctx) =>
|
||||
this.bibleMarkupProcessor.processBibleCodeBlock(source, el, ctx));
|
||||
|
||||
// Register markdown post processor for inline references
|
||||
this.registerMarkdownPostProcessor(this.bibleMarkupProcessor.processInlineBibleReferences.bind(this.bibleMarkupProcessor));
|
||||
this.registerMarkdownPostProcessor((el, ctx) =>
|
||||
this.bibleMarkupProcessor.processInlineBibleReferences(el, ctx));
|
||||
|
||||
// Register editor extension for Live Preview
|
||||
if (this.settings.displayInlineVerses) {
|
||||
|
|
@ -93,14 +93,15 @@ export default class DisciplesJournalPlugin extends Plugin {
|
|||
this.addSettingTab(new DisciplesJournalSettingsTab(this.app, this));
|
||||
|
||||
// Register active leaf change to update styles
|
||||
this.registerEvent(this.app.workspace.on('active-leaf-change', this.handleActiveLeafChange.bind(this)));
|
||||
this.registerEvent(this.app.workspace.on('active-leaf-change', () => this.handleActiveLeafChange()));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
const savedData = (await this.loadData()) as Partial<DisciplesJournalSettings> | null;
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, savedData);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
|
|
@ -180,8 +181,8 @@ export default class DisciplesJournalPlugin extends Plugin {
|
|||
continue;
|
||||
}
|
||||
|
||||
const fmData = parseYaml(fmInfo.frontmatter);
|
||||
if (!fmData || !fmData.canonical) {
|
||||
const fmData = parseYaml(fmInfo.frontmatter) as { canonical?: unknown } | null;
|
||||
if (!fmData || typeof fmData.canonical !== 'string') {
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import {TFile} from 'obsidian';
|
||||
import {BibleContentService} from './BibleContentService';
|
||||
import {BibleReference} from '../core/BibleReference';
|
||||
import {BibleCodeblockFormatter} from '../utils/BibleCodeblockFormatter';
|
||||
import DisciplesJournalPlugin from "../core/DisciplesJournalPlugin";
|
||||
import {BibleApiResponse} from "../utils/BibleApiResponse";
|
||||
import {BibleFiles} from "./BibleFiles";
|
||||
|
|
@ -52,7 +51,7 @@ export class BibleChapterFiles {
|
|||
|
||||
// If there's a specific verse, scroll to it
|
||||
if (parsedRef.verse) {
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
// Find the verse element and scroll to it
|
||||
const verseEl = leaf.getContainer().doc.querySelector(`.verse-${parsedRef.verse}`);
|
||||
if (verseEl) {
|
||||
|
|
@ -82,8 +81,8 @@ export class BibleChapterFiles {
|
|||
// Check if passage is null
|
||||
if (response.isError()) {
|
||||
// TODO - handle this better with a dialog box or Notice or something.
|
||||
console.error(`Failed to get Bible content for ${bookChapter}`);
|
||||
throw new Error(`Failed to get Bible content for ${bookChapter}`);
|
||||
console.error(`Failed to get Bible content for ${bookChapter.toString()}`);
|
||||
throw new Error(`Failed to get Bible content for ${bookChapter.toString()}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating chapter note:', error);
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ export class BibleContentService {
|
|||
// Return cached result if available
|
||||
const passage = this.getCachedRef(ref);
|
||||
if (passage) {
|
||||
console.debug(`Returning cached passage(s) (${ref})`)
|
||||
return BibleApiResponse.success(passage);
|
||||
}
|
||||
|
||||
// Load from local file
|
||||
if (await BibleFiles.fileExistsForPassage(ref, this.plugin)) {
|
||||
console.debug(`Pulling passage(s) (${ref}) from local file`)
|
||||
const passageMdFile = BibleFiles.getFileForPassage(ref, this.plugin);
|
||||
const passageMdFile = await BibleFiles.fileExistsForPassage(ref, this.plugin)
|
||||
? BibleFiles.getFileForPassage(ref, this.plugin)
|
||||
: null;
|
||||
if (passageMdFile) {
|
||||
try {
|
||||
const fileContent = await this.plugin.app.vault.read(passageMdFile);
|
||||
const frontmatter = getFrontMatterInfo(fileContent);
|
||||
|
|
@ -42,12 +42,13 @@ export class BibleContentService {
|
|||
if (frontmatter && frontmatter.frontmatter) {
|
||||
return this.convertEsvApiResponseToGeneric(parseYaml(frontmatter.frontmatter), ref);
|
||||
} else {
|
||||
const message = `No frontmatter found in file for reference ${ref}. Loading from API instead.`;
|
||||
const message = `No frontmatter found in file for reference ${ref.toString()}. Loading from API instead.`;
|
||||
console.error(message);
|
||||
// return BibleApiResponse.error(message, ErrorType.BadApiResponse);
|
||||
}
|
||||
} catch (error) {
|
||||
const message = `Error reading file for reference ${ref}: ${error}`;
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
const message = `Error reading file for reference ${ref.toString()}: ${detail}`;
|
||||
console.error(message);
|
||||
return BibleApiResponse.error(message, ErrorType.BadApiResponse);
|
||||
}
|
||||
|
|
@ -55,7 +56,6 @@ export class BibleContentService {
|
|||
|
||||
// Otherwise, grab from the API
|
||||
if (this.plugin.settings.downloadOnDemand) {
|
||||
console.debug(`Pulling passage(s) (${ref}) from ESV API`)
|
||||
const response = await this.esvApiService.downloadFromESVApi(ref);
|
||||
if (response.isError()) {
|
||||
return response
|
||||
|
|
@ -64,20 +64,24 @@ export class BibleContentService {
|
|||
return response;
|
||||
}
|
||||
} else {
|
||||
console.error(`Settings "downloadOnDemand" is false. Skipping request to get passage ${ref}`);
|
||||
return BibleApiResponse.error(`Settings prevent download of passage ${ref}`, ErrorType.RequestsForbidden)
|
||||
console.error(`Settings "downloadOnDemand" is false. Skipping request to get passage ${ref.toString()}`);
|
||||
return BibleApiResponse.error(`Settings prevent download of passage ${ref.toString()}`, ErrorType.RequestsForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - I would like this to go away once I normalize things a bit. This code is duplicated within ESVAPIService
|
||||
private convertEsvApiResponseToGeneric(frontmatter: any, ref: BibleReference) {
|
||||
const canonicalRef = BibleReference.parse(frontmatter.canonical);
|
||||
private convertEsvApiResponseToGeneric(frontmatter: unknown, ref: BibleReference) {
|
||||
const fm = frontmatter as { canonical?: unknown; passages?: unknown } | null;
|
||||
const canonical = fm && typeof fm.canonical === 'string' ? fm.canonical : null;
|
||||
const canonicalRef = canonical ? BibleReference.parse(canonical) : null;
|
||||
if (!canonicalRef) {
|
||||
const message = `Failed to parse canonical reference (${frontmatter.canonical}) from ESV API for ${ref.toString()}`;
|
||||
const message = `Failed to parse canonical reference (${String(canonical)}) from ESV API for ${ref.toString()}`;
|
||||
console.error(message);
|
||||
return BibleApiResponse.error(message, ErrorType.BadApiResponse);
|
||||
}
|
||||
return BibleApiResponse.success(new BiblePassage(canonicalRef, frontmatter.passages[0]));
|
||||
const passages = fm && Array.isArray(fm.passages) ? (fm.passages as unknown[]) : [];
|
||||
const html = typeof passages[0] === 'string' ? passages[0] : '';
|
||||
return BibleApiResponse.success(new BiblePassage(canonicalRef, html));
|
||||
}
|
||||
|
||||
private getCachedRef(ref: BibleReference): BiblePassage | undefined {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {buildFrontmatterString, getCustomFrontmatterForReference} from "../utils
|
|||
/**
|
||||
* Interface for ESV API Response
|
||||
*/
|
||||
export interface ESVApiResponse {
|
||||
export type ESVApiResponse = {
|
||||
query: string;
|
||||
canonical: string;
|
||||
parsed: number[][];
|
||||
|
|
@ -126,6 +126,10 @@ export class ESVApiService {
|
|||
|
||||
// Save the raw API response as a markdown note with frontmatter
|
||||
const passage = BibleReference.parse(data.canonical);
|
||||
if (!passage) {
|
||||
console.error(`Failed to parse canonical reference from ESV API: ${data.canonical}`);
|
||||
return;
|
||||
}
|
||||
const filePath = BibleFiles.pathForPassage(passage, this.plugin);
|
||||
const customYaml = getCustomFrontmatterForReference(passage, this.plugin.settings);
|
||||
const frontmatterBody = buildFrontmatterString(data, customYaml);
|
||||
|
|
|
|||
|
|
@ -56,10 +56,10 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
const {containerEl} = this;
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl).setName('Display Customization').setHeading();
|
||||
new Setting(containerEl).setName('Display customization').setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Display Inline Verses')
|
||||
.setName('Display inline verses')
|
||||
.setDesc('Enable rendering of inline Bible references (in `code blocks`).')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.displayInlineVerses)
|
||||
|
|
@ -70,7 +70,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Display Full Passages')
|
||||
.setName('Display full passages')
|
||||
.setDesc('Enable rendering of full Bible passages (in ```bible code blocks).')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.displayFullPassages)
|
||||
|
|
@ -81,7 +81,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Show Navigation for Verses')
|
||||
.setName('Show navigation for verses')
|
||||
.setDesc('Show chapter navigation when displaying specific verses (by default, navigation only shows for full chapters).')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.showNavigationForVerses)
|
||||
|
|
@ -92,7 +92,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Hide Footnotes')
|
||||
.setName('Hide footnotes')
|
||||
.setDesc('Hide footnotes in the displayed scripture.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.hideFootnotes)
|
||||
|
|
@ -104,7 +104,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Hide Footnotes in Hover Previews')
|
||||
.setName('Hide footnotes in hover previews')
|
||||
.setDesc('Hide footnotes in hover preview popups.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.hideFootnotesInPreview)
|
||||
|
|
@ -116,7 +116,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Style Preset')
|
||||
.setName('Style preset')
|
||||
.setDesc('Choose a predefined style preset for Bible content.')
|
||||
.addDropdown(dropdown => {
|
||||
// Add all theme presets
|
||||
|
|
@ -133,7 +133,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Bible Text Font Size')
|
||||
.setName('Bible text font size')
|
||||
.setDesc('Set the font size for Bible verses and passages.')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('80%', 'Smaller (80%)')
|
||||
|
|
@ -149,10 +149,10 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl).setName('Text Styling').setHeading();
|
||||
new Setting(containerEl).setName('Text styling').setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Words of Christ Color')
|
||||
.setName('Words of Christ color')
|
||||
.setDesc('Set the color for Words of Christ (use `var(--text-normal)` for no special color).')
|
||||
.addText(text => text
|
||||
.setPlaceholder('var(--text-accent)')
|
||||
|
|
@ -164,7 +164,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Verse Number Color')
|
||||
.setName('Verse number color')
|
||||
.setDesc('Set the color for verse numbers.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('var(--text-accent)')
|
||||
|
|
@ -176,7 +176,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Heading Color')
|
||||
.setName('Heading color')
|
||||
.setDesc('Set the color for Bible passage headings.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('var(--text-accent)')
|
||||
|
|
@ -188,7 +188,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Block Indentation')
|
||||
.setName('Block indentation')
|
||||
.setDesc('Set the indentation for block sections.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('2em')
|
||||
|
|
@ -202,7 +202,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
new Setting(containerEl).setName('Bible').setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Preferred Bible Version')
|
||||
.setName('Preferred Bible version')
|
||||
.setDesc('Select your preferred Bible version (only ESV currently supported).')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('ESV', 'English Standard Version (ESV)')
|
||||
|
|
@ -214,7 +214,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Bible Content Vault Path')
|
||||
.setName('Bible content vault path')
|
||||
.setDesc('Root path in your vault where Bible content will be stored. Each translation will be stored in a subdirectory.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Bible')
|
||||
|
|
@ -224,7 +224,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl).setName('Note Frontmatter').setHeading();
|
||||
new Setting(containerEl).setName('Note frontmatter').setHeading();
|
||||
|
||||
const fmInfoDiv = containerEl.createDiv({cls: 'disciples-journal-frontmatter-info'});
|
||||
|
||||
|
|
@ -234,10 +234,10 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
|
||||
fmInfoDiv.createEl('p', {
|
||||
text: 'Template variables -- use these in values to inject reference data:',
|
||||
attr: {style: 'font-weight: bold; margin-bottom: 4px;'}
|
||||
cls: 'disciples-journal-info-label'
|
||||
});
|
||||
|
||||
const varList = fmInfoDiv.createEl('ul', {attr: {style: 'margin-top: 0; padding-left: 1.5em;'}});
|
||||
const varList = fmInfoDiv.createEl('ul', {cls: 'disciples-journal-var-list'});
|
||||
for (const v of TEMPLATE_VARIABLES) {
|
||||
const li = varList.createEl('li');
|
||||
li.createEl('code', {text: v.variable});
|
||||
|
|
@ -245,7 +245,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Chapter Note Frontmatter')
|
||||
.setName('Chapter note frontmatter')
|
||||
.setDesc('Custom YAML frontmatter added to chapter-level notes (e.g., Genesis 1).')
|
||||
.addTextArea(text => text
|
||||
.setPlaceholder('tags:\n - bible/{{book}}\ntype: chapter')
|
||||
|
|
@ -256,7 +256,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Passage Note Frontmatter')
|
||||
.setName('Passage note frontmatter')
|
||||
.setDesc('Custom YAML frontmatter added to passage-level notes (e.g., Genesis 1:5 or Genesis 1:5-10).')
|
||||
.addTextArea(text => text
|
||||
.setPlaceholder('tags:\n - bible/{{book}}\npassage: "{{reference}}"')
|
||||
|
|
@ -276,7 +276,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
|
||||
apiInfoDiv.createEl('p', {
|
||||
text: 'To get a free ESV API token:',
|
||||
attr: {style: 'font-weight: bold;'}
|
||||
cls: 'disciples-journal-info-label'
|
||||
});
|
||||
|
||||
const instructionsList = apiInfoDiv.createEl('ol');
|
||||
|
|
@ -307,7 +307,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('ESV API Token')
|
||||
.setName('ESV API token')
|
||||
.setDesc('Enter your ESV API token from api.esv.org.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your ESV API token')
|
||||
|
|
@ -321,7 +321,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Download on Demand')
|
||||
.setName('Download on demand')
|
||||
.setDesc('Enable automatic downloading of Bible content when requested.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.downloadOnDemand)
|
||||
|
|
@ -333,7 +333,7 @@ export class DisciplesJournalSettingsTab extends PluginSettingTab {
|
|||
new Setting(containerEl)
|
||||
.setDesc("Click this button to clear the cached bible files. Click this after upgrading from 0.7.0 or earlier. Data will be redownloaded on demand")
|
||||
.addButton(btn => btn
|
||||
.setButtonText("Clear Bible Data")
|
||||
.setButtonText("Clear Bible data")
|
||||
.onClick(async evt => {
|
||||
await BibleFiles.clearData(this.plugin);
|
||||
}));
|
||||
|
|
|
|||
2
src/types.d.ts
vendored
2
src/types.d.ts
vendored
|
|
@ -1,5 +1,5 @@
|
|||
declare module '*.json' {
|
||||
const value: any;
|
||||
const value: unknown;
|
||||
export default value;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export function renderCustomFrontmatter(
|
|||
if (!context) {
|
||||
return rawYaml;
|
||||
}
|
||||
return rawYaml.replace(/\{\{(\w+)}}/g, (match, key) => {
|
||||
return rawYaml.replace(/\{\{(\w+)}}/g, (match: string, key: string) => {
|
||||
return key in context ? context[key] : match;
|
||||
});
|
||||
}
|
||||
|
|
@ -56,25 +56,26 @@ export function renderCustomFrontmatter(
|
|||
* Parse user-provided YAML, filtering out API-reserved keys.
|
||||
* Returns null on empty input or parse failure.
|
||||
*/
|
||||
function parseCustomYaml(customYaml: string): Record<string, any> | null {
|
||||
function parseCustomYaml(customYaml: string): Record<string, unknown> | null {
|
||||
const trimmed = customYaml.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const parsed = parseYaml(trimmed);
|
||||
const parsed = parseYaml(trimmed) as unknown;
|
||||
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
||||
console.warn('Disciples Journal: custom frontmatter must be a YAML mapping, ignoring.');
|
||||
return null;
|
||||
}
|
||||
// Filter out API-reserved keys
|
||||
const filtered: Record<string, any> = {};
|
||||
for (const key of Object.keys(parsed)) {
|
||||
const source = parsed as Record<string, unknown>;
|
||||
const filtered: Record<string, unknown> = {};
|
||||
for (const key of Object.keys(source)) {
|
||||
if (API_KEYS.has(key)) {
|
||||
console.warn(`Disciples Journal: custom frontmatter key "${key}" collides with API data, skipping.`);
|
||||
continue;
|
||||
}
|
||||
filtered[key] = parsed[key];
|
||||
filtered[key] = source[key];
|
||||
}
|
||||
return Object.keys(filtered).length > 0 ? filtered : null;
|
||||
} catch (e) {
|
||||
|
|
@ -87,7 +88,7 @@ function parseCustomYaml(customYaml: string): Record<string, any> | null {
|
|||
* Merge cssclasses, ensuring PLUGIN_CSS_CLASS is always present.
|
||||
* Handles string, array, or undefined inputs from both sides.
|
||||
*/
|
||||
function mergeCssClasses(existing: any, custom: any): string[] {
|
||||
function mergeCssClasses(existing: unknown, custom: unknown): string[] {
|
||||
const classes = new Set<string>();
|
||||
classes.add(PLUGIN_CSS_CLASS);
|
||||
|
||||
|
|
@ -107,7 +108,7 @@ function mergeCssClasses(existing: any, custom: any): string[] {
|
|||
* Returns the YAML content without the --- delimiters.
|
||||
*/
|
||||
export function buildFrontmatterString(
|
||||
apiData: Record<string, any>,
|
||||
apiData: Record<string, unknown>,
|
||||
customYaml: string
|
||||
): string {
|
||||
// Separate cssclasses from API data so we can place it last
|
||||
|
|
@ -115,8 +116,8 @@ export function buildFrontmatterString(
|
|||
const customData = parseCustomYaml(customYaml);
|
||||
|
||||
// Extract cssclasses from custom data if present
|
||||
let customCss: any = undefined;
|
||||
const customFields: Record<string, any> = {};
|
||||
let customCss: unknown = undefined;
|
||||
const customFields: Record<string, unknown> = {};
|
||||
if (customData) {
|
||||
for (const [key, value] of Object.entries(customData)) {
|
||||
if (key === 'cssclasses') {
|
||||
|
|
@ -128,7 +129,7 @@ export function buildFrontmatterString(
|
|||
}
|
||||
|
||||
// Build the combined object: API fields, then custom fields, then cssclasses last
|
||||
const combined: Record<string, any> = {...apiFields};
|
||||
const combined: Record<string, unknown> = {...apiFields};
|
||||
for (const [key, value] of Object.entries(customFields)) {
|
||||
combined[key] = value;
|
||||
}
|
||||
|
|
@ -150,21 +151,22 @@ export function mergeCustomFrontmatterIntoExisting(
|
|||
return null;
|
||||
}
|
||||
|
||||
let existing: Record<string, any>;
|
||||
let existing: Record<string, unknown>;
|
||||
try {
|
||||
existing = parseYaml(existingFrontmatter);
|
||||
if (existing === null || typeof existing !== 'object' || Array.isArray(existing)) {
|
||||
const parsedExisting = parseYaml(existingFrontmatter) as unknown;
|
||||
if (parsedExisting === null || typeof parsedExisting !== 'object' || Array.isArray(parsedExisting)) {
|
||||
console.warn('Disciples Journal: existing frontmatter is not a valid YAML mapping, skipping merge.');
|
||||
return null;
|
||||
}
|
||||
existing = parsedExisting as Record<string, unknown>;
|
||||
} catch (e) {
|
||||
console.warn('Disciples Journal: failed to parse existing frontmatter, skipping merge.', e);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract cssclasses from custom data if present
|
||||
let customCss: any = undefined;
|
||||
const customFields: Record<string, any> = {};
|
||||
let customCss: unknown = undefined;
|
||||
const customFields: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(customData)) {
|
||||
if (key === 'cssclasses') {
|
||||
customCss = value;
|
||||
|
|
|
|||
21
styles.css
21
styles.css
|
|
@ -504,3 +504,24 @@ button.clear-input-button {
|
|||
.inline-bible-reference .inline-bible-reference-text { /* Target the text part */
|
||||
font-size: var(--dj-font-size); /* Keep original font size variable for now, or switch to --dj-font-size if desired */
|
||||
}
|
||||
|
||||
/* Settings: frontmatter/API info blocks */
|
||||
.disciples-journal-info-label {
|
||||
font-weight: var(--font-bold);
|
||||
margin-bottom: var(--size-4-1);
|
||||
}
|
||||
|
||||
.disciples-journal-var-list {
|
||||
margin-top: 0;
|
||||
padding-left: var(--size-4-6);
|
||||
}
|
||||
|
||||
/* Book search input (navigation) */
|
||||
.disciples-journal-book-suggest-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.disciples-journal-book-suggest-input {
|
||||
padding-right: 1.5em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"1.0.0": "0.15.0"
|
||||
"0.12.0": "0.15.0",
|
||||
"0.13.0": "1.6.6"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue