Fix counter to update only clicked instance

- Track each counter by its original text instead of label
- Use indexOf to find and replace specific counter occurrence
- Pass originalText to createCounterElement method
- Update both MarkdownPostProcessor and Live Preview widget
- Prevents all counters with same label from updating together

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-26 10:58:09 +01:00
parent f5a3047417
commit c908cc42b9

33
main.ts
View File

@@ -3,12 +3,12 @@ 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 plugin: CounterPlugin) { constructor(private value: number, private label: string, private originalText: string, private plugin: CounterPlugin) {
super(); super();
} }
toDOM(view: EditorView): HTMLElement { toDOM(view: EditorView): HTMLElement {
return this.plugin.createCounterElement(this.value, this.label, {} as MarkdownPostProcessorContext); return this.plugin.createCounterElement(this.value, this.label, this.originalText, {} as MarkdownPostProcessorContext);
} }
} }
@@ -26,9 +26,10 @@ function buildCounterDecorations(view: EditorView, plugin: CounterPlugin): Decor
const endPos = startPos + match[0].length; const endPos = startPos + match[0].length;
const value = match[1] === '' ? 0 : parseInt(match[1], 10); const value = match[1] === '' ? 0 : parseInt(match[1], 10);
const label = match[2].trim(); const label = match[2].trim();
const originalText = match[0];
const widget = Decoration.replace({ const widget = Decoration.replace({
widget: new CounterWidget(value, label, plugin), widget: new CounterWidget(value, label, originalText, plugin),
}); });
builder.add(startPos, endPos, widget); builder.add(startPos, endPos, widget);
@@ -76,7 +77,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}> }> = []; const nodesToReplace: Array<{ node: Node; parent: Node; replacements: Array<{type: 'text' | 'counter', content: string, value?: number, label?: string, originalText?: string}> }> = [];
let node: Node | null; let node: Node | null;
while ((node = walker.nextNode()) !== null) { while ((node = walker.nextNode()) !== null) {
@@ -84,7 +85,7 @@ export default class CounterPlugin extends Plugin {
if (counterRegex.test(text)) { if (counterRegex.test(text)) {
counterRegex.lastIndex = 0; counterRegex.lastIndex = 0;
const replacements: Array<{type: 'text' | 'counter', content: string, value?: number, label?: string}> = []; const replacements: Array<{type: 'text' | 'counter', content: string, value?: number, label?: string, originalText?: string}> = [];
let lastIndex = 0; let lastIndex = 0;
let match; let match;
@@ -103,7 +104,8 @@ export default class CounterPlugin extends Plugin {
type: 'counter', type: 'counter',
content: match[0], content: match[0],
value: value, value: value,
label: label label: label,
originalText: match[0]
}); });
lastIndex = match.index + match[0].length; lastIndex = match.index + match[0].length;
@@ -132,6 +134,7 @@ export default class CounterPlugin extends Plugin {
const counterContainer = this.createCounterElement( const counterContainer = this.createCounterElement(
replacement.value!, replacement.value!,
replacement.label!, replacement.label!,
replacement.originalText!,
context context
); );
fragment.appendChild(counterContainer); fragment.appendChild(counterContainer);
@@ -147,6 +150,7 @@ export default class CounterPlugin extends Plugin {
createCounterElement( createCounterElement(
initialValue: number, initialValue: number,
label: string, label: string,
originalText: string,
context: MarkdownPostProcessorContext context: MarkdownPostProcessorContext
): HTMLElement { ): HTMLElement {
const container = document.createElement('div'); const container = document.createElement('div');
@@ -178,19 +182,12 @@ export default class CounterPlugin extends Plugin {
const editor = view.editor; const editor = view.editor;
const content = editor.getValue(); const content = editor.getValue();
const counterRegex = /~\s*\(\s*\d*\s*\)\s*(.+)/gm;
let matchIndex = 0; // Find and replace only the first occurrence of this exact counter text
const newContent = content.replace(counterRegex, (match, capturedLabel) => { const index = content.indexOf(originalText);
const currentLabel = capturedLabel.trim(); if (index !== -1) {
if (currentLabel === label) { const newText = `~ (${newValue}) ${label}`;
matchIndex++; const newContent = content.substring(0, index) + newText + content.substring(index + originalText.length);
return `~ (${newValue}) ${label}`;
}
return match;
});
if (content !== newContent) {
editor.setValue(newContent); editor.setValue(newContent);
} }
}; };