mirror of
https://github.com/kunalja/VariantEditor.git
synced 2026-07-22 13:00:32 +00:00
use codemirror edotor extenstions instead of dom manipulation
This commit is contained in:
parent
7a9e5492b8
commit
be09e60841
5 changed files with 213 additions and 48 deletions
|
|
@ -16,6 +16,9 @@
|
|||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"dotenv-expand": "^12.0.2",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,15 @@ importers:
|
|||
|
||||
.:
|
||||
devDependencies:
|
||||
'@codemirror/language':
|
||||
specifier: ^6.0.0
|
||||
version: 6.11.1
|
||||
'@codemirror/state':
|
||||
specifier: ^6.0.0
|
||||
version: 6.5.2
|
||||
'@codemirror/view':
|
||||
specifier: ^6.0.0
|
||||
version: 6.37.1
|
||||
'@types/node':
|
||||
specifier: ^16.11.6
|
||||
version: 16.18.126
|
||||
|
|
@ -41,6 +50,9 @@ importers:
|
|||
|
||||
packages:
|
||||
|
||||
'@codemirror/language@6.11.1':
|
||||
resolution: {integrity: sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==}
|
||||
|
||||
'@codemirror/state@6.5.2':
|
||||
resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
|
||||
|
||||
|
|
@ -210,6 +222,15 @@ packages:
|
|||
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
|
||||
deprecated: Use @eslint/object-schema instead
|
||||
|
||||
'@lezer/common@1.2.3':
|
||||
resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==}
|
||||
|
||||
'@lezer/highlight@1.2.1':
|
||||
resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==}
|
||||
|
||||
'@lezer/lr@1.4.2':
|
||||
resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2':
|
||||
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||
|
||||
|
|
@ -774,6 +795,15 @@ packages:
|
|||
|
||||
snapshots:
|
||||
|
||||
'@codemirror/language@6.11.1':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.37.1
|
||||
'@lezer/common': 1.2.3
|
||||
'@lezer/highlight': 1.2.1
|
||||
'@lezer/lr': 1.4.2
|
||||
style-mod: 4.1.2
|
||||
|
||||
'@codemirror/state@6.5.2':
|
||||
dependencies:
|
||||
'@marijn/find-cluster-break': 1.0.2
|
||||
|
|
@ -886,6 +916,16 @@ snapshots:
|
|||
|
||||
'@humanwhocodes/object-schema@2.0.3': {}
|
||||
|
||||
'@lezer/common@1.2.3': {}
|
||||
|
||||
'@lezer/highlight@1.2.1':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
|
||||
'@lezer/lr@1.4.2':
|
||||
dependencies:
|
||||
'@lezer/common': 1.2.3
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
|
|
|
|||
176
src/main.ts
176
src/main.ts
|
|
@ -1,42 +1,146 @@
|
|||
import { MarkdownView, Plugin, Notice, Editor, EditorPosition } from 'obsidian';
|
||||
import { hackToRerender } from './utils/editorUtils';
|
||||
import { TextInputModal } from './modals/TextInputModal';
|
||||
|
||||
// Import CodeMirror modules directly as per Obsidian documentation
|
||||
import { ViewUpdate, PluginValue, EditorView, ViewPlugin, Decoration, DecorationSet } from '@codemirror/view';
|
||||
import { RangeSetBuilder, StateField, Extension } from '@codemirror/state';
|
||||
|
||||
export default class VariantEditor extends Plugin {
|
||||
// Track active line for dimming
|
||||
private activeLine: number | null = null;
|
||||
// Store the extension to update it later
|
||||
private dimExtension: Extension | null = null;
|
||||
// Track previous cursor line
|
||||
private previousCursorLine: number | null = null;
|
||||
|
||||
async onload() {
|
||||
console.log('VariantEditor: Plugin loading...');
|
||||
|
||||
try {
|
||||
// Bind the method to ensure proper 'this' context
|
||||
this.highlightSelection = this.highlightSelection.bind(this);
|
||||
this.clearHighlight = this.clearHighlight.bind(this);
|
||||
|
||||
// Register the command with a direct function reference
|
||||
this.addCommand({
|
||||
id: 'variant-editor-highlight',
|
||||
name: 'Variant Editor: Highlight Word & Sentence',
|
||||
hotkeys: [{ modifiers: ["Mod"], key: "h" }],
|
||||
callback: () => {
|
||||
console.log('VariantEditor: Command callback executed');
|
||||
this.highlightSelection();
|
||||
}
|
||||
callback: () => this.highlightSelection()
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('VariantEditor: Error during initialization:', error);
|
||||
// Register the clear command
|
||||
this.addCommand({
|
||||
id: 'variant-editor-clear-highlight',
|
||||
name: 'Variant Editor: Clear Highlighting',
|
||||
callback: () => this.clearHighlight()
|
||||
});
|
||||
|
||||
// Register the editor extension for dimming
|
||||
this.dimExtension = this.createDimExtension();
|
||||
this.registerEditorExtension(this.dimExtension);
|
||||
} catch (e) {
|
||||
console.error('Error during initialization:', e);
|
||||
}
|
||||
}
|
||||
|
||||
onunload() {
|
||||
this.clearHighlight();
|
||||
}
|
||||
|
||||
private createDimExtension() {
|
||||
// Get reference to the plugin instance for the view plugin to use
|
||||
const pluginInstance = this;
|
||||
|
||||
// Create a state field to track cursor position changes
|
||||
const cursorTrackingField = StateField.define({
|
||||
create(state) {
|
||||
return null;
|
||||
},
|
||||
update(value, transaction) {
|
||||
if (transaction.selection) {
|
||||
const selection = transaction.newSelection.main;
|
||||
const currentLine = transaction.newDoc.lineAt(selection.head).number;
|
||||
|
||||
// Store the current cursor line
|
||||
if (pluginInstance.previousCursorLine === null) {
|
||||
pluginInstance.previousCursorLine = currentLine;
|
||||
}
|
||||
|
||||
// If we have an active line and cursor moved to a different line, clear dimming
|
||||
if (pluginInstance.activeLine !== null &&
|
||||
currentLine !== pluginInstance.activeLine &&
|
||||
currentLine !== pluginInstance.previousCursorLine) {
|
||||
// Schedule clearing on next tick to avoid update-during-update
|
||||
setTimeout(() => {
|
||||
pluginInstance.clearHighlight();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Update previous cursor line
|
||||
pluginInstance.previousCursorLine = currentLine;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
// Create the view plugin for decorations
|
||||
const dimPlugin = ViewPlugin.fromClass(
|
||||
class implements PluginValue {
|
||||
decorations: DecorationSet;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.decorations = this.buildDecorations(view);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
// Update decorations if needed
|
||||
if (update.docChanged || update.viewportChanged || pluginInstance.activeLine !== null) {
|
||||
this.decorations = this.buildDecorations(update.view);
|
||||
}
|
||||
}
|
||||
|
||||
buildDecorations(view: EditorView): DecorationSet {
|
||||
// If no active line is set, return empty decorations
|
||||
if (pluginInstance.activeLine === null) {
|
||||
return Decoration.none;
|
||||
}
|
||||
|
||||
const builder = new RangeSetBuilder<Decoration>();
|
||||
const activeLine = pluginInstance.activeLine;
|
||||
|
||||
// Add decorations to all lines except the active one
|
||||
for (let i = 1; i <= view.state.doc.lines; i++) {
|
||||
if (i !== activeLine) {
|
||||
try {
|
||||
const line = view.state.doc.line(i);
|
||||
const decoration = Decoration.line({
|
||||
attributes: { class: "fh-dim" }
|
||||
});
|
||||
builder.add(line.from, line.from, decoration);
|
||||
} catch (e) {
|
||||
console.error(`Error adding decoration to line ${i}:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.finish();
|
||||
}
|
||||
|
||||
destroy() {}
|
||||
},
|
||||
{
|
||||
decorations: (instance) => instance.decorations
|
||||
}
|
||||
);
|
||||
|
||||
// Return both extensions
|
||||
return [cursorTrackingField, dimPlugin];
|
||||
}
|
||||
|
||||
private highlightSelection(): void {
|
||||
try {
|
||||
console.log('VariantEditor: highlightSelection called');
|
||||
|
||||
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (!view) {
|
||||
console.log('VariantEditor: No active Markdown view');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -45,28 +149,18 @@ export default class VariantEditor extends Plugin {
|
|||
const selectedWord = selectedText.trim();
|
||||
|
||||
if (!selectedWord) {
|
||||
console.log('VariantEditor: No text selected');
|
||||
return;
|
||||
}
|
||||
|
||||
const editorEl = view.contentEl;
|
||||
if (!editorEl) {
|
||||
console.log('VariantEditor: Could not find editor container');
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = editorEl.querySelectorAll('.cm-line');
|
||||
console.log(`VariantEditor: Found ${lines.length} lines`);
|
||||
|
||||
lines.forEach(line => {
|
||||
if (!line.classList.contains('cm-active')) {
|
||||
(line as HTMLElement).classList.add('fh-dim')
|
||||
}
|
||||
});
|
||||
|
||||
// Save cursor position for later text insertion
|
||||
const cursorPos = editor.getCursor();
|
||||
|
||||
// Set active line for dimming
|
||||
this.activeLine = cursorPos.line + 1;
|
||||
|
||||
// Force editor refresh to apply decorations
|
||||
this.app.workspace.updateOptions();
|
||||
|
||||
// Open the text input modal after highlighting
|
||||
new TextInputModal(this.app, (inputText: string) => {
|
||||
if (inputText && inputText.trim()) {
|
||||
|
|
@ -76,36 +170,24 @@ export default class VariantEditor extends Plugin {
|
|||
}
|
||||
}).open();
|
||||
|
||||
console.log('VariantEditor: highlightSelection completed');
|
||||
|
||||
} catch (e) {
|
||||
console.error('VariantEditor: Error in highlightSelection:', e);
|
||||
console.error('Error in highlightSelection:', e);
|
||||
}
|
||||
}
|
||||
|
||||
private clearHighlight(): void {
|
||||
try {
|
||||
console.log('VariantEditor: clearHighlight called');
|
||||
// Reset active line and previous cursor line
|
||||
this.activeLine = null;
|
||||
this.previousCursorLine = null;
|
||||
|
||||
// Use the hack to force a complete re-render
|
||||
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (!view) {
|
||||
console.log('VariantEditor: No active Markdown view in clearHighlight');
|
||||
return;
|
||||
if (view) {
|
||||
hackToRerender(view);
|
||||
}
|
||||
|
||||
const editorEl = view.contentEl;
|
||||
if (!editorEl) {
|
||||
console.log('VariantEditor: Could not find editor container in clearHighlight');
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove dim and active classes from all lines
|
||||
const lines = editorEl.querySelectorAll('.cm-line');
|
||||
lines.forEach(line => (line as HTMLElement).classList.remove('fh-dim'));
|
||||
console.log('VariantEditor: clearHighlight completed');
|
||||
|
||||
} catch (e) {
|
||||
console.error('VariantEditor: Error in clearHighlight:', e);
|
||||
console.error('Error in clearHighlight:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
src/utils/editorUtils.ts
Normal file
39
src/utils/editorUtils.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { Editor, MarkdownView } from 'obsidian';
|
||||
|
||||
/**
|
||||
* Forces a complete re-render of the editor using a zero-width space hack
|
||||
* This is a workaround for cases where normal refresh methods don't work
|
||||
* @param view The active MarkdownView
|
||||
*/
|
||||
export function hackToRerender(view: MarkdownView): void {
|
||||
if (!view || !view.editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const editor = view.editor;
|
||||
const cursorPos = editor.getCursor();
|
||||
|
||||
// Use setTimeout to avoid update-during-update errors
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// Insert a zero-width space character and then immediately remove it
|
||||
// This forces a complete redraw without visible changes
|
||||
const pos = { line: cursorPos.line, ch: cursorPos.ch };
|
||||
editor.replaceRange("\u200B", pos); // Insert zero-width space
|
||||
editor.replaceRange("", pos, { line: pos.line, ch: pos.ch + 1 }); // Remove it
|
||||
|
||||
// Also try to access the CM editor view directly if possible
|
||||
const editorView = (view.editor as any).cm;
|
||||
if (editorView) {
|
||||
// Force a complete redraw
|
||||
editorView.requestMeasure();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in hackToRerender:', err);
|
||||
}
|
||||
}, 10);
|
||||
} catch (e) {
|
||||
console.error('Error setting up hackToRerender:', e);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
/* Variant Editor Plugin Styles */
|
||||
|
||||
/* Dimmed lines */
|
||||
.markdown-source-view .cm-line.fh-dim {
|
||||
.markdown-source-view .cm-line.fh-dim,
|
||||
.cm-line.fh-dim {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue