fix(obsidian): warning+id

- Changed plugin ID to md-discord-syntax across manifests, validation, package metadata, and release instructions.
 - Resolved TypeScript unsafe-value warnings through correct typed ESLint configuration and source typing.
 - Replaced flagged document.createElement usage with Obsidian createEl.
 - Addressed text-decoration compatibility warnings.
 - Optimized spoiler detection to inspect only relevant document lines instead of allocating the full document text.
 - Updated tests for strict type safety and promise handling.
This commit is contained in:
Edems 2026-07-21 21:45:33 +02:00
parent a23dbf13b3
commit b3294030f8
15 changed files with 53 additions and 57 deletions

View file

@ -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

View file

@ -7,6 +7,8 @@ export default tseslint.config(
"**/dist/**",
"packages/obsidian/main.js",
"packages/obsidian/styles.css",
"**/*.mjs",
"**/*.js",
],
},
eslint.configs.recommended,

View file

@ -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",

View file

@ -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",

View file

@ -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);

View file

@ -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",

View file

@ -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",

View file

@ -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 ||

View file

@ -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(

View file

@ -1,29 +1,26 @@
function createDetachedElement<K extends keyof HTMLElementTagNameMap>(
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);

View file

@ -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;

View file

@ -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();
}

View file

@ -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",
() => {

View file

@ -12,7 +12,7 @@ interface TestNode {
data?: Record<string, unknown>;
}
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 () => {

View file

@ -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}"`,
);
}