diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b6023c2..2b27c53 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,9 +43,9 @@ jobs: body: | Obsidian plugin release for Discord Syntax. - Plugin ID: discord-syntax + Plugin ID: md-discord-syntax - Install by copying main.js, manifest.json, and styles.css into .obsidian/plugins/discord-syntax/. + Install by copying main.js, manifest.json, and styles.css into .obsidian/plugins/md-discord-syntax/. files: | packages/obsidian/main.js packages/obsidian/manifest.json diff --git a/eslint.config.mjs b/eslint.config.mjs index e5eb161..60575a3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -7,6 +7,8 @@ export default tseslint.config( "**/dist/**", "packages/obsidian/main.js", "packages/obsidian/styles.css", + "**/*.mjs", + "**/*.js", ], }, eslint.configs.recommended, diff --git a/manifest.json b/manifest.json index 774bebd..af304ad 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { - "id": "discord-syntax", + "id": "md-discord-syntax", "name": "Discord Syntax", - "version": "1.0.21", + "version": "1.0.22", "minAppVersion": "0.15.0", "description": "Discord-style `||spoiler||` and `-# subtext` markdown formatting extension.", "author": "Edems-DEV", diff --git a/package.json b/package.json index bbf874d..15203e4 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "npm run build --workspaces --if-present", "pretest": "npm run build -w @edems-dev/md-discord-syntax-core", "test": "npm run test --workspaces --if-present", - "lint": "eslint \"packages/**/src/**/*.ts\"", + "lint": "eslint .", "pretypecheck": "npm run build -w @edems-dev/md-discord-syntax-core", "typecheck": "npm run typecheck --workspaces --if-present", "validate": "node scripts/validate.js", diff --git a/packages/core/tests/core.test.ts b/packages/core/tests/core.test.ts index 9311270..cf9d9ad 100644 --- a/packages/core/tests/core.test.ts +++ b/packages/core/tests/core.test.ts @@ -2,14 +2,12 @@ import test from "node:test"; import assert from "node:assert"; import { findSpoilerRanges, - findCodeRanges, - isPosInCode, isSubtextLine, stripSubtextPrefix, hasSubtextMarker, } from "../src/index.js"; -test("Core Spoiler & Subtext Syntax Rules", async (t) => { +void test("Core Spoiler & Subtext Syntax Rules", async (t) => { await t.test("detects basic spoiler range", () => { const text = "Hello ||secret|| world"; const spoilers = findSpoilerRanges(text); diff --git a/packages/obsidian/manifest.json b/packages/obsidian/manifest.json index 774bebd..af304ad 100644 --- a/packages/obsidian/manifest.json +++ b/packages/obsidian/manifest.json @@ -1,7 +1,7 @@ { - "id": "discord-syntax", + "id": "md-discord-syntax", "name": "Discord Syntax", - "version": "1.0.21", + "version": "1.0.22", "minAppVersion": "0.15.0", "description": "Discord-style `||spoiler||` and `-# subtext` markdown formatting extension.", "author": "Edems-DEV", diff --git a/packages/obsidian/package.json b/packages/obsidian/package.json index 61b7e75..d5c8614 100644 --- a/packages/obsidian/package.json +++ b/packages/obsidian/package.json @@ -1,6 +1,6 @@ { - "name": "discord-syntax", - "version": "1.0.21", + "name": "md-discord-syntax", + "version": "1.0.22", "description": "Discord Syntax Obsidian plugin adapter", "private": true, "main": "main.js", diff --git a/packages/obsidian/src/main.ts b/packages/obsidian/src/main.ts index 833fcb0..d0c21c1 100644 --- a/packages/obsidian/src/main.ts +++ b/packages/obsidian/src/main.ts @@ -14,10 +14,6 @@ interface ObsidianEditor { cm?: EditorView; } -interface MarkdownPreviewView { - containerEl?: HTMLElement; -} - export default class DiscordSyntaxPlugin extends Plugin { onload() { // ── Reading View Post Processor ────────────────────────────────────── @@ -42,9 +38,7 @@ export default class DiscordSyntaxPlugin extends Plugin { const mode = activeView.getMode(); if (mode === "preview") { - const preview = - activeView.previewMode as unknown as MarkdownPreviewView; - const container = preview?.containerEl; + const container = activeView.previewMode?.containerEl; if (!container) return; const spoilers = container.querySelectorAll( ".note-flow-spoiler, .discord-syntax-spoiler", @@ -76,7 +70,7 @@ export default class DiscordSyntaxPlugin extends Plugin { } } } else if (mode === "source") { - const editor = activeView.editor as unknown as ObsidianEditor; + const editor = activeView.editor as ObsidianEditor; const cm = editor.cm; if ( !cm || diff --git a/packages/obsidian/src/spoiler-detector.ts b/packages/obsidian/src/spoiler-detector.ts index e4b4f07..b6d05a6 100644 --- a/packages/obsidian/src/spoiler-detector.ts +++ b/packages/obsidian/src/spoiler-detector.ts @@ -82,8 +82,8 @@ export function getSpoilerAtSelection(state: EditorState): SpoilerRange | null { if (!selection.main.empty) return null; const pos = selection.main.head; - const text = state.doc.toString(); - const spoilers = findSpoilerRanges(text); + const line = state.doc.lineAt(pos); + const spoilers = findSpoilerRanges(line.text, line.from); for (const s of spoilers) { if (pos >= s.from && pos <= s.to) { return s; @@ -356,8 +356,8 @@ export const spoilerLivePreviewPlugin = ViewPlugin.fromClass( const pos = view.posAtCoords({ x: event.clientX, y: event.clientY }); if (pos !== null) { - const text = view.state.doc.toString(); - const spoilers = findSpoilerRanges(text); + const line = view.state.doc.lineAt(pos); + const spoilers = findSpoilerRanges(line.text, line.from); const spoiler = spoilers.find((s) => pos >= s.from && pos <= s.to); if (spoiler) { @@ -414,9 +414,9 @@ export const spoilerLivePreviewPlugin = ViewPlugin.fromClass( if (spoilerId) { const fromStr = spoilerId.replace("spoiler-", ""); const from = parseInt(fromStr, 10); - if (!isNaN(from)) { - const text = view.state.doc.toString(); - const spoilers = findSpoilerRanges(text); + if (!isNaN(from) && from >= 0 && from <= view.state.doc.length) { + const line = view.state.doc.lineAt(from); + const spoilers = findSpoilerRanges(line.text, line.from); const spoiler = spoilers.find((s) => s.from === from); if (spoiler) { const currentState = getSpoilerState( diff --git a/packages/obsidian/src/spoiler-post-processor.ts b/packages/obsidian/src/spoiler-post-processor.ts index ff65021..32ac0e9 100644 --- a/packages/obsidian/src/spoiler-post-processor.ts +++ b/packages/obsidian/src/spoiler-post-processor.ts @@ -1,29 +1,26 @@ -function createDetachedElement( - doc: Document, - tag: K, -): HTMLElementTagNameMap[K] { - const fragment = doc.createDocumentFragment(); - if (typeof fragment.createEl === "function") { - return fragment.createEl(tag); - } - return doc.createElement(tag); -} - export function processSpoilers( element: HTMLElement, doc: Document = element.ownerDocument, ): void { function createSpoilerSpan(): HTMLSpanElement { - const spoilerSpan = createDetachedElement(doc, "span"); - spoilerSpan.classList.add("note-flow-spoiler", "discord-syntax-spoiler"); - spoilerSpan.setAttribute("role", "button"); - spoilerSpan.setAttribute("tabindex", "0"); - spoilerSpan.setAttribute("aria-expanded", "false"); - spoilerSpan.setAttribute("aria-label", "Spoiler, click to reveal"); + const spoilerSpan = element.createEl("span", { + cls: "note-flow-spoiler discord-syntax-spoiler", + attr: { + role: "button", + tabindex: "0", + "aria-expanded": "false", + "aria-label": "Spoiler, click to reveal", + }, + }); + if (spoilerSpan.parentNode) { + spoilerSpan.parentNode.removeChild(spoilerSpan); + } - const innerSpan = createDetachedElement(doc, "span"); - innerSpan.setAttribute("aria-hidden", "true"); - spoilerSpan.appendChild(innerSpan); + const innerSpan = spoilerSpan.createEl("span", { + attr: { + "aria-hidden": "true", + }, + }); function toggleSpoiler() { const isRevealed = spoilerSpan.classList.contains("is-revealed"); @@ -66,8 +63,12 @@ export function processSpoilers( if (beforeStr) { parent.insertBefore(doc.createTextNode(beforeStr), node); } - const marker = createDetachedElement(doc, "span"); - marker.setAttribute("data-spoiler-marker", "true"); + const marker = element.createEl("span", { + attr: { "data-spoiler-marker": "true" }, + }); + if (marker.parentNode) { + marker.parentNode.removeChild(marker); + } parent.insertBefore(marker, node); markers.push(marker); diff --git a/packages/obsidian/src/styles.css b/packages/obsidian/src/styles.css index 6db945d..2b571cc 100644 --- a/packages/obsidian/src/styles.css +++ b/packages/obsidian/src/styles.css @@ -78,6 +78,7 @@ * { color: transparent; -webkit-text-fill-color: transparent; + text-decoration: none; text-decoration-line: none; text-decoration-color: transparent; text-shadow: none; diff --git a/packages/obsidian/tests/spoiler.test.ts b/packages/obsidian/tests/spoiler.test.ts index 0cdc522..c7f40a3 100644 --- a/packages/obsidian/tests/spoiler.test.ts +++ b/packages/obsidian/tests/spoiler.test.ts @@ -11,7 +11,7 @@ import { findSpoilerRanges, } from "../src/spoiler-detector.js"; -test("Spoiler Detection & Live Preview", async (t) => { +void test("Spoiler Detection & Live Preview", async (t) => { await t.test("detects basic spoiler range", () => { const text = "Hello ||secret|| world"; const ranges = findSpoilerRanges(text); @@ -102,7 +102,7 @@ test("Spoiler Detection & Live Preview", async (t) => { items.push({ from: iter.from, to: iter.to, - value: iter.value as unknown as DecoItem["value"], + value: iter.value, }); iter.next(); } diff --git a/packages/obsidian/tests/subtext.test.ts b/packages/obsidian/tests/subtext.test.ts index 47d5f68..cd05d62 100644 --- a/packages/obsidian/tests/subtext.test.ts +++ b/packages/obsidian/tests/subtext.test.ts @@ -2,7 +2,7 @@ import test from "node:test"; import assert from "node:assert"; import { processSubtextParagraph } from "../src/subtext-post-processor.js"; -test("Subtext Reading View Post Processor", async (t) => { +void test("Subtext Reading View Post Processor", async (t) => { await t.test( "wraps paragraph starting with -# into discord-subtext span", () => { diff --git a/packages/remark/tests/remark.test.ts b/packages/remark/tests/remark.test.ts index 0e4cd3b..78b4f6a 100644 --- a/packages/remark/tests/remark.test.ts +++ b/packages/remark/tests/remark.test.ts @@ -12,7 +12,7 @@ interface TestNode { data?: Record; } -test("Remark Discord Syntax Plugin", async (t) => { +void test("Remark Discord Syntax Plugin", async (t) => { const processor = remark().use(remarkMdDiscordSyntax); await t.test("transforms discord-style spoilers (plain)", async () => { diff --git a/scripts/validate.js b/scripts/validate.js index d5fb2e7..387c4e8 100644 --- a/scripts/validate.js +++ b/scripts/validate.js @@ -36,9 +36,9 @@ try { } // Ensure Obsidian plugin details -if (manifest.id !== "discord-syntax") { +if (manifest.id !== "md-discord-syntax") { exitWithError( - `manifest.json id must be "discord-syntax", got "${manifest.id}"`, + `manifest.json id must be "md-discord-syntax", got "${manifest.id}"`, ); } if (manifest.name !== "Discord Syntax") { @@ -110,9 +110,9 @@ try { ); } -if (obsidianPkg.name !== "discord-syntax") { +if (obsidianPkg.name !== "md-discord-syntax") { exitWithError( - `packages/obsidian/package.json name must be "discord-syntax", got "${obsidianPkg.name}"`, + `packages/obsidian/package.json name must be "md-discord-syntax", got "${obsidianPkg.name}"`, ); }