Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8aabc058a | |||
| 74ba1c46d8 | |||
| 53348963a4 | |||
| cb4763982e | |||
| 2aa79b1a39 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
# Node
|
# Node
|
||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
package-release.mjs
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
main.js
|
main.js
|
||||||
@@ -13,5 +14,12 @@ main.js
|
|||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# Claude Code
|
||||||
|
.claude
|
||||||
|
|
||||||
|
# Release
|
||||||
|
release/
|
||||||
|
*.zip
|
||||||
|
|
||||||
# Obsidian
|
# Obsidian
|
||||||
data.json
|
data.json
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# Counter Plugin for Obsidian
|
# Counter Plugin for Obsidian
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
A simple Obsidian plugin that creates interactive number counters with +/- buttons.
|
A simple Obsidian plugin that creates interactive number counters with +/- buttons.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|||||||
91
main.ts
91
main.ts
@@ -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);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"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": {
|
||||||
"dev": "node esbuild.config.mjs",
|
"dev": "node esbuild.config.mjs",
|
||||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||||
|
"package": "npm run build && node package-release.mjs"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"obsidian",
|
"obsidian",
|
||||||
|
|||||||
Reference in New Issue
Block a user