Files
counterplugin/main.ts
Sayuop ff1298c7d3 Fix counter syntax to use empty parentheses
- Change syntax from ~ (number) to ~ ( )
- Counter now starts at 0 when using empty parentheses
- Update README with correct syntax examples

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 10:18:40 +01:00

151 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Plugin, MarkdownPostProcessorContext, MarkdownView } from 'obsidian';
export default class CounterPlugin extends Plugin {
async onload() {
console.log('Loading Counter Plugin');
this.registerMarkdownPostProcessor((element, context) => {
this.processCounters(element, context);
});
this.registerEvent(
this.app.workspace.on('editor-change', () => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (view) {
// Refresh the preview to update counters
view.previewMode.rerender(true);
}
})
);
}
onunload() {
console.log('Unloading Counter Plugin');
}
processCounters(element: HTMLElement, context: MarkdownPostProcessorContext) {
const counterRegex = /^~\s*\(\s*(\d*)\s*\)\s*(.*)$/;
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
const nodesToReplace: Array<{ node: Node; parent: Node }> = [];
let node;
while ((node = walker.nextNode())) {
const text = node.textContent || '';
const lines = text.split('\n');
lines.forEach((line, index) => {
const match = line.match(counterRegex);
if (match) {
nodesToReplace.push({ node, parent: node.parentNode! });
}
});
}
nodesToReplace.forEach(({ node, parent }) => {
const text = node.textContent || '';
const lines = text.split('\n');
const fragment = document.createDocumentFragment();
lines.forEach((line, index) => {
const match = line.match(counterRegex);
if (match) {
const currentValue = match[1] === '' ? 0 : parseInt(match[1], 10);
const label = match[2].trim();
const counterContainer = this.createCounterElement(
currentValue,
label,
node,
context
);
fragment.appendChild(counterContainer);
} else {
if (line) {
fragment.appendChild(document.createTextNode(line));
}
}
if (index < lines.length - 1) {
fragment.appendChild(document.createTextNode('\n'));
}
});
parent.replaceChild(fragment, node);
});
}
createCounterElement(
value: number,
label: string,
sourceNode: Node,
context: MarkdownPostProcessorContext
): HTMLElement {
const container = document.createElement('div');
container.className = 'counter-container';
const minusButton = document.createElement('button');
minusButton.className = 'counter-button counter-minus';
minusButton.textContent = '';
minusButton.setAttribute('aria-label', 'Decrease counter');
const counterDisplay = document.createElement('span');
counterDisplay.className = 'counter-display';
counterDisplay.textContent = value.toString();
const plusButton = document.createElement('button');
plusButton.className = 'counter-button counter-plus';
plusButton.textContent = '+';
plusButton.setAttribute('aria-label', 'Increase counter');
const labelSpan = document.createElement('span');
labelSpan.className = 'counter-label';
labelSpan.textContent = label;
const updateSource = (newValue: number) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view) return;
const editor = view.editor;
const content = editor.getValue();
const counterRegex = /^~\s*\(\s*\d*\s*\)\s*(.*)$/gm;
let matchIndex = 0;
const newContent = content.replace(counterRegex, (match, capturedLabel) => {
const currentLabel = capturedLabel.trim();
if (currentLabel === label) {
matchIndex++;
return `~ (${newValue}) ${label}`;
}
return match;
});
if (content !== newContent) {
editor.setValue(newContent);
}
};
minusButton.addEventListener('click', () => {
const newValue = value - 1;
counterDisplay.textContent = newValue.toString();
updateSource(newValue);
});
plusButton.addEventListener('click', () => {
const newValue = value + 1;
counterDisplay.textContent = newValue.toString();
updateSource(newValue);
});
container.appendChild(minusButton);
container.appendChild(counterDisplay);
container.appendChild(plusButton);
if (label) {
container.appendChild(labelSpan);
}
return container;
}
}