Compare commits

...

3 Commits

Author SHA1 Message Date
53348963a4 Remove release files from version control 2025-11-26 18:27:04 +01:00
cb4763982e Remove .claude directory from version control 2025-11-26 18:26:49 +01:00
2aa79b1a39 v1.1.0 - Polish and critical bug fixes
## Bug Fixes
- Fixed critical issue where clicking + on one counter would update multiple counters
- Fixed page scrolling to top when clicking counter buttons
- Fixed counter widgets causing Obsidian to crash or behave erratically
- Fixed counters not working independently in edit mode

## Improvements
- Added support for negative numbers (counters can now go below zero)
- Improved counter targeting using position-based tracking in edit mode
- Improved counter targeting using section info in reading mode
- Better event handling to prevent unwanted side effects
2025-11-26 18:21:24 +01:00
4 changed files with 82 additions and 20 deletions

7
.gitignore vendored
View File

@@ -13,5 +13,12 @@ main.js
.vscode .vscode
.idea .idea
# Claude Code
.claude
# Release
release/
*.zip
# Obsidian # Obsidian
data.json data.json

91
main.ts
View File

@@ -3,18 +3,18 @@ import { Decoration, DecorationSet, EditorView, WidgetType, ViewPlugin, ViewUpda
import { RangeSetBuilder } from '@codemirror/state'; import { RangeSetBuilder } from '@codemirror/state';
class CounterWidget extends WidgetType { class CounterWidget extends WidgetType {
constructor(private value: number, private label: string, private originalText: string, private plugin: CounterPlugin) { constructor(private value: number, private label: string, private originalText: string, private plugin: CounterPlugin, private position: number) {
super(); super();
} }
toDOM(view: EditorView): HTMLElement { toDOM(view: EditorView): HTMLElement {
return this.plugin.createCounterElement(this.value, this.label, this.originalText, {} as MarkdownPostProcessorContext); return this.plugin.createCounterElement(this.value, this.label, this.originalText, null, this.position);
} }
} }
function buildCounterDecorations(view: EditorView, plugin: CounterPlugin): DecorationSet { function buildCounterDecorations(view: EditorView, plugin: CounterPlugin): DecorationSet {
const builder = new RangeSetBuilder<Decoration>(); const builder = new RangeSetBuilder<Decoration>();
const counterRegex = /~\s*\(\s*(\d*)\s*\)\s*(.+)/g; const counterRegex = /~\s*\(\s*(-?\d*)\s*\)\s*(.+)/g;
for (let { from, to } of view.visibleRanges) { for (let { from, to } of view.visibleRanges) {
const text = view.state.doc.sliceString(from, to); const text = view.state.doc.sliceString(from, to);
@@ -29,7 +29,7 @@ function buildCounterDecorations(view: EditorView, plugin: CounterPlugin): Decor
const originalText = match[0]; const originalText = match[0];
const widget = Decoration.replace({ const widget = Decoration.replace({
widget: new CounterWidget(value, label, originalText, plugin), widget: new CounterWidget(value, label, originalText, plugin, startPos),
}); });
builder.add(startPos, endPos, widget); builder.add(startPos, endPos, widget);
@@ -75,7 +75,7 @@ export default class CounterPlugin extends Plugin {
} }
processCounters(element: HTMLElement, context: MarkdownPostProcessorContext) { processCounters(element: HTMLElement, context: MarkdownPostProcessorContext) {
const counterRegex = /~\s*\(\s*(\d*)\s*\)\s*(.+)/g; const counterRegex = /~\s*\(\s*(-?\d*)\s*\)\s*(.+)/g;
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT); const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
const nodesToReplace: Array<{ node: Node; parent: Node; replacements: Array<{type: 'text' | 'counter', content: string, value?: number, label?: string, originalText?: string}> }> = []; const nodesToReplace: Array<{ node: Node; parent: Node; replacements: Array<{type: 'text' | 'counter', content: string, value?: number, label?: string, originalText?: string}> }> = [];
@@ -151,11 +151,15 @@ export default class CounterPlugin extends Plugin {
initialValue: number, initialValue: number,
label: string, label: string,
originalText: string, originalText: string,
context: MarkdownPostProcessorContext context: MarkdownPostProcessorContext | null,
position?: number
): HTMLElement { ): HTMLElement {
const container = document.createElement('div'); const container = document.createElement('div');
container.className = 'counter-container'; container.className = 'counter-container';
// Store the section info to locate this specific counter (only available in reading mode)
const sectionInfo = context?.getSectionInfo?.(container);
let currentValue = initialValue; let currentValue = initialValue;
const minusButton = document.createElement('button'); const minusButton = document.createElement('button');
@@ -176,32 +180,83 @@ export default class CounterPlugin extends Plugin {
labelSpan.className = 'counter-label'; labelSpan.className = 'counter-label';
labelSpan.textContent = label; labelSpan.textContent = label;
const updateSource = (newValue: number) => { const updateSource = (oldValue: number, newValue: number) => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView); const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view) return; if (!view) return;
const editor = view.editor; const editor = view.editor;
const content = editor.getValue(); const counterRegex = /~\s*\(\s*(-?\d*)\s*\)\s*(.+)/;
// Find and replace only the first occurrence of this exact counter text // If we have a position (from edit mode), use it to find the exact counter
const index = content.indexOf(originalText); if (position !== undefined) {
if (index !== -1) { // Convert document position to line number
const newText = `~ (${newValue}) ${label}`; const content = editor.getValue();
const newContent = content.substring(0, index) + newText + content.substring(index + originalText.length); let currentPos = 0;
editor.setValue(newContent); let targetLine = 0;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const lineEndPos = currentPos + lines[i].length;
if (position >= currentPos && position <= lineEndPos) {
targetLine = i;
break;
}
currentPos = lineEndPos + 1; // +1 for the newline character
}
const lineText = editor.getLine(targetLine);
const match = lineText.match(counterRegex);
if (match && match[2].trim() === label) {
const matchedValue = match[1] === '' ? 0 : parseInt(match[1], 10);
// Verify this is our counter by checking the value
if (matchedValue === oldValue) {
const newText = lineText.replace(counterRegex, `~ (${newValue}) ${label}`);
editor.setLine(targetLine, newText);
return;
}
}
}
// Get section info to find the exact line range (reading mode)
if (sectionInfo) {
const lineStart = sectionInfo.lineStart;
const lineEnd = sectionInfo.lineEnd;
// Search only within this section
for (let line = lineStart; line <= lineEnd; line++) {
const lineText = editor.getLine(line);
const match = lineText.match(counterRegex);
if (match && match[2].trim() === label) {
const matchedValue = match[1] === '' ? 0 : parseInt(match[1], 10);
// Only update if this counter has the old value
if (matchedValue === oldValue) {
const newText = lineText.replace(counterRegex, `~ (${newValue}) ${label}`);
editor.setLine(line, newText);
return;
}
}
}
} }
}; };
minusButton.addEventListener('click', () => { minusButton.addEventListener('click', (e) => {
e.stopPropagation();
const oldValue = currentValue;
currentValue = currentValue - 1; currentValue = currentValue - 1;
counterDisplay.textContent = currentValue.toString(); counterDisplay.textContent = currentValue.toString();
updateSource(currentValue); updateSource(oldValue, currentValue);
}); });
plusButton.addEventListener('click', () => { plusButton.addEventListener('click', (e) => {
e.stopPropagation();
const oldValue = currentValue;
currentValue = currentValue + 1; currentValue = currentValue + 1;
counterDisplay.textContent = currentValue.toString(); counterDisplay.textContent = currentValue.toString();
updateSource(currentValue); updateSource(oldValue, currentValue);
}); });
container.appendChild(counterDisplay); container.appendChild(counterDisplay);

View File

@@ -1,7 +1,7 @@
{ {
"id": "counter-plugin", "id": "counter-plugin",
"name": "Counter Plugin", "name": "Counter Plugin",
"version": "1.0.0", "version": "1.1.0",
"minAppVersion": "0.15.0", "minAppVersion": "0.15.0",
"description": "Create interactive counters with +/- buttons using ~ ( ) syntax", "description": "Create interactive counters with +/- buttons using ~ ( ) syntax",
"author": "crib", "author": "crib",

View File

@@ -1,6 +1,6 @@
{ {
"name": "obsidian-counter-plugin", "name": "obsidian-counter-plugin",
"version": "1.0.0", "version": "1.1.0",
"description": "A simple counter plugin for Obsidian that creates interactive number counters with +/- buttons", "description": "A simple counter plugin for Obsidian that creates interactive number counters with +/- buttons",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {