Add Live Preview support and reorder counter elements

- Move buttons to right side of number (number, -, +, label)
- Add CodeMirror ViewPlugin for Live Preview rendering
- Counter now works in edit mode (Live Preview)
- Create CounterWidget for editor decorations
- Use Decoration.replace to render counters in editor

🤖 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:42:35 +01:00
parent 0790105aeb
commit f5a3047417

62
main.ts
View File

@@ -1,4 +1,42 @@
import { Plugin, MarkdownPostProcessorContext, MarkdownView } from 'obsidian';
import { Decoration, DecorationSet, EditorView, WidgetType, ViewPlugin, ViewUpdate } from '@codemirror/view';
import { RangeSetBuilder } from '@codemirror/state';
class CounterWidget extends WidgetType {
constructor(private value: number, private label: string, private plugin: CounterPlugin) {
super();
}
toDOM(view: EditorView): HTMLElement {
return this.plugin.createCounterElement(this.value, this.label, {} as MarkdownPostProcessorContext);
}
}
function buildCounterDecorations(view: EditorView, plugin: CounterPlugin): DecorationSet {
const builder = new RangeSetBuilder<Decoration>();
const counterRegex = /~\s*\(\s*(\d*)\s*\)\s*(.+)/g;
for (let { from, to } of view.visibleRanges) {
const text = view.state.doc.sliceString(from, to);
let match;
counterRegex.lastIndex = 0;
while ((match = counterRegex.exec(text)) !== null) {
const startPos = from + match.index;
const endPos = startPos + match[0].length;
const value = match[1] === '' ? 0 : parseInt(match[1], 10);
const label = match[2].trim();
const widget = Decoration.replace({
widget: new CounterWidget(value, label, plugin),
});
builder.add(startPos, endPos, widget);
}
}
return builder.finish();
}
export default class CounterPlugin extends Plugin {
async onload() {
@@ -7,6 +45,28 @@ export default class CounterPlugin extends Plugin {
this.registerMarkdownPostProcessor((element, context) => {
this.processCounters(element, context);
});
const plugin = this;
this.registerEditorExtension(
ViewPlugin.fromClass(
class {
decorations: DecorationSet;
constructor(view: EditorView) {
this.decorations = buildCounterDecorations(view, plugin);
}
update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged) {
this.decorations = buildCounterDecorations(update.view, plugin);
}
}
},
{
decorations: (v) => v.decorations,
}
)
);
}
onunload() {
@@ -147,8 +207,8 @@ export default class CounterPlugin extends Plugin {
updateSource(currentValue);
});
container.appendChild(minusButton);
container.appendChild(counterDisplay);
container.appendChild(minusButton);
container.appendChild(plusButton);
if (label) {
container.appendChild(labelSpan);