format doccument

This commit is contained in:
kunalja 2025-06-13 00:12:18 -04:00
parent e53a453567
commit ce82bcaeba
3 changed files with 1029 additions and 1019 deletions

View file

@ -28,36 +28,36 @@ class ClickableVariantWidget extends WidgetType {
span.className = 'variant-active-option clickable-variant';
span.setAttribute('data-full-variant', this.fullVariant);
span.setAttribute('data-variant-index', this.variantIndex);
// Add click handler
span.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
// Select the entire variant in the editor
const state = view.state;
const doc = state.doc;
// Find the positions in the current state
const from = this.from;
const to = this.to;
// Set the selection to the entire variant
view.dispatch({
selection: {anchor: from, head: to}
selection: { anchor: from, head: to }
});
// Call the highlightSelection method
this.plugin.highlightSelection();
});
return span;
}
eq(other: ClickableVariantWidget): boolean {
return this.text === other.text &&
this.fullVariant === other.fullVariant &&
this.variantIndex === other.variantIndex;
return this.text === other.text &&
this.fullVariant === other.fullVariant &&
this.variantIndex === other.variantIndex;
}
}
@ -73,13 +73,13 @@ export default class VariantEditor extends Plugin {
// Store the selection range
private selectionFrom: number | null = null;
private selectionTo: number | null = null;
async onload() {
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',
@ -87,24 +87,24 @@ export default class VariantEditor extends Plugin {
hotkeys: [{ modifiers: ["Mod"], key: "h" }],
callback: () => this.highlightSelection()
});
// Register the clear command
this.addCommand({
id: 'variant-editor-clear-highlight',
name: 'Variant Editor: Clear Highlighting',
callback: () => this.clearHighlight()
});
// Register command to commit all variants in selection or document
this.addCommand({
id: 'variant-editor-commit-all',
name: 'Variant Editor: Commit All Variants in Selection/Document',
editorCallback: (editor) => this.commitAllVariants(editor)
});
// Register the editor extension for dimming
this.dimExtension = this.createDimExtension();
// Register the editor extension for variant indicators
this.registerEditorExtension(this.createVariantIndicatorExtension());
this.registerEditorExtension(this.dimExtension);
@ -116,7 +116,7 @@ export default class VariantEditor extends Plugin {
onunload() {
this.clearHighlight();
}
/**
* Creates an extension that styles variant syntax to show only the active variant
* while keeping the original text editable
@ -124,48 +124,48 @@ export default class VariantEditor extends Plugin {
private createVariantIndicatorExtension(): Extension {
// Store a reference to the plugin instance for the widget to use
const pluginInstance = this;
return ViewPlugin.fromClass(
class {
decorations: DecorationSet;
constructor(view: EditorView) {
this.decorations = this.buildDecorations(view);
}
update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged) {
this.decorations = this.buildDecorations(update.view);
}
}
buildDecorations(view: EditorView): DecorationSet {
const builder = new RangeSetBuilder<Decoration>();
for (let {from, to} of view.visibleRanges) {
for (let { from, to } of view.visibleRanges) {
const text = view.state.doc.sliceString(from, to);
const variantRegex = /\{\{([^{}]+?)\}\}\^(\d+)/g;
let match;
while ((match = variantRegex.exec(text)) !== null) {
const matchStart = from + match.index;
const matchEnd = matchStart + match[0].length;
const fullMatch = match[0];
const variantsText = match[1];
const activeIndex = parseInt(match[2], 10);
// Find the position of the active variant
let pos = 0;
if (activeIndex === 0) {
pos += 2; // Skip past the opening {{
}
}
for (let i = 0; i < activeIndex; i++) {
pos = fullMatch.indexOf('|', pos) + 1;
}
const activeVariantStart = matchStart + pos;
const activeVariantEnd = activeVariantStart + variantsText.split('|')[activeIndex].length;
// Add three decorations for each variant:
// 1. Hide everything before the active variant
builder.add(
@ -179,7 +179,7 @@ export default class VariantEditor extends Plugin {
}
})
);
// 2. Show the active variant as a clickable widget
const activeVariantText = variantsText.split('|')[activeIndex];
builder.add(
@ -196,7 +196,7 @@ export default class VariantEditor extends Plugin {
)
})
);
// 3. Hide everything after the active variant
builder.add(
activeVariantEnd,
@ -211,7 +211,7 @@ export default class VariantEditor extends Plugin {
);
}
}
return builder.finish();
}
},
@ -220,11 +220,11 @@ export default class VariantEditor extends Plugin {
}
);
}
private createDimExtension(): Extension {
// 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) {
@ -234,115 +234,115 @@ export default class VariantEditor extends Plugin {
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) {
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(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;
}
// Collect all decorations first, then sort and add them
const allDecorations = [];
const activeLine = pluginInstance.activeLine;
// First pass: Collect line decorations for 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" }
});
allDecorations.push({
from: line.from,
to: line.from,
decoration: decoration
});
} catch (e) {
console.error(`Error creating decoration for line ${i}:`, e);
// If no active line is set, return empty decorations
if (pluginInstance.activeLine === null) {
return Decoration.none;
}
// Collect all decorations first, then sort and add them
const allDecorations = [];
const activeLine = pluginInstance.activeLine;
// First pass: Collect line decorations for 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" }
});
allDecorations.push({
from: line.from,
to: line.from,
decoration: decoration
});
} catch (e) {
console.error(`Error creating decoration for line ${i}:`, e);
}
}
}
// Second pass: Collect highlight decoration only for the currently selected text
if (pluginInstance.selectedText && activeLine <= view.state.doc.lines && pluginInstance.selectionFrom !== null && pluginInstance.selectionTo !== null) {
try {
// Use the exact selection positions instead of searching for all instances
const start = pluginInstance.selectionFrom;
const end = pluginInstance.selectionTo;
allDecorations.push({
from: start,
to: end,
decoration: Decoration.mark({
attributes: { class: "fh-highlight" }
})
});
} catch (e) {
console.error(`Error creating highlight decoration:`, e);
}
}
// Sort all decorations by from position
allDecorations.sort((a, b) => a.from - b.from);
// Now add them to the builder in sorted order
const builder = new RangeSetBuilder<Decoration>();
for (const { from, to, decoration } of allDecorations) {
try {
builder.add(from, to, decoration);
} catch (e) {
console.error(`Error adding decoration at position ${from}:`, e);
}
}
return builder.finish();
}
}
}
// Second pass: Collect highlight decoration only for the currently selected text
if (pluginInstance.selectedText && activeLine <= view.state.doc.lines && pluginInstance.selectionFrom !== null && pluginInstance.selectionTo !== null) {
try {
// Use the exact selection positions instead of searching for all instances
const start = pluginInstance.selectionFrom;
const end = pluginInstance.selectionTo;
allDecorations.push({
from: start,
to: end,
decoration: Decoration.mark({
attributes: { class: "fh-highlight" }
})
});
} catch (e) {
console.error(`Error creating highlight decoration:`, e);
}
}
// Sort all decorations by from position
allDecorations.sort((a, b) => a.from - b.from);
// Now add them to the builder in sorted order
const builder = new RangeSetBuilder<Decoration>();
for (const { from, to, decoration } of allDecorations) {
try {
builder.add(from, to, decoration);
} catch (e) {
console.error(`Error adding decoration at position ${from}:`, e);
}
}
return builder.finish();
}
destroy() {}
destroy() { }
},
{
decorations: (instance) => instance.decorations
}
);
// Return both extensions
return [cursorTrackingField, dimPlugin];
}
@ -355,38 +355,38 @@ export default class VariantEditor extends Plugin {
const editor = view.editor;
const selection = editor.listSelections()[0];
// Variants only work within single lines
if (selection.anchor.line !== selection.head.line) {
new Notice('Selection must be within a single line');
return;
}
// Get the selected text and range
const from = {
line: Math.min(selection.anchor.line, selection.head.line),
ch: Math.min(selection.anchor.ch, selection.head.ch)
};
const to = {
line: Math.max(selection.anchor.line, selection.head.line),
ch: Math.max(selection.anchor.ch, selection.head.ch)
};
let selectedText = editor.getRange(from, to);
if (!selectedText) return;
this.selectedText = selectedText;
// Setup for variant editing
let initialText = selectedText;
let initialActiveIndex = 0;
let isExistingVariant = false;
// Check if the selection is already a variant
const variantRegex = /^\{\{([^{}]+?)\}\}\^(\d+)$/;
const variantMatch = selectedText.match(variantRegex);
if (variantMatch) {
// Direct selection of a variant
initialText = variantMatch[1];
@ -398,27 +398,27 @@ export default class VariantEditor extends Plugin {
if (line) {
const fullLineRegex = /\{\{([^{}]+?)\}\}\^(\d+)/g;
let match;
while ((match = fullLineRegex.exec(line)) !== null) {
const matchStart = match.index;
const matchEnd = matchStart + match[0].length;
// Check if selection overlaps with the variant at all
// This handles partial selections that include any part of the variant
if ((from.ch >= matchStart && from.ch < matchEnd) || // Selection starts inside variant
(to.ch > matchStart && to.ch <= matchEnd) || // Selection ends inside variant
(from.ch <= matchStart && to.ch >= matchEnd)) { // Selection contains variant
(to.ch > matchStart && to.ch <= matchEnd) || // Selection ends inside variant
(from.ch <= matchStart && to.ch >= matchEnd)) { // Selection contains variant
// Selection overlaps with a variant - capture the entire variant
initialText = match[1];
initialActiveIndex = parseInt(match[2], 10);
isExistingVariant = true;
// Expand selection to cover the entire variant
from.ch = matchStart;
to.ch = matchEnd;
editor.setSelection(from, to);
// Update selectedText to match the expanded selection
selectedText = editor.getRange(from, to);
break;
@ -426,17 +426,17 @@ export default class VariantEditor extends Plugin {
}
}
}
if (isExistingVariant) {
new Notice('Editing existing variant');
}
// Set active line for dimming
this.activeLine = from.line + 1;
// Store the selected text for highlighting - make sure we use the potentially updated selection
this.selectedText = selectedText;
// Convert EditorPosition to absolute character positions for highlighting
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (activeView) {
@ -447,7 +447,7 @@ export default class VariantEditor extends Plugin {
// Convert line/ch positions to absolute character positions
const fromPos = editorView.state.doc.line(from.line + 1).from + from.ch;
const toPos = editorView.state.doc.line(to.line + 1).from + to.ch;
// Store the exact selection range for highlighting
this.selectionFrom = fromPos;
this.selectionTo = toPos;
@ -456,10 +456,10 @@ export default class VariantEditor extends Plugin {
}
}
}
// Force editor refresh to apply decorations
this.app.workspace.updateOptions();
// Open the variant editor modal
new TextInputModal(
this.app,
@ -468,7 +468,7 @@ export default class VariantEditor extends Plugin {
// Use the updated cursor positions if provided, otherwise use the original positions
const updateFrom = currentFrom || from;
const updateTo = currentTo || to;
if (modalClosed) {
// Modal was closed without committing (via ESC key or clicking outside)
// Clear the highlights
@ -484,18 +484,18 @@ export default class VariantEditor extends Plugin {
} else {
// Create or update the variant syntax (normal variant creation/update)
const variants = variantText.split('|').filter(v => v);
if (variants.length > 0) {
const activeIdx = typeof activeIndex === 'number' ? activeIndex : 0;
const variantSyntax = `{{${variants.join('|')}}}^${activeIdx}`;
// Use the updated cursor positions for the replacement
editor.replaceRange(variantSyntax, updateFrom, updateTo);
// Update the original positions for future updates
from.ch = updateFrom.ch;
to.ch = updateFrom.ch + variantSyntax.length;
// Update selection positions for highlighting
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (activeView) {
@ -505,11 +505,11 @@ export default class VariantEditor extends Plugin {
// Convert updated line/ch positions to absolute character positions
const fromPos = editorView.state.doc.line(updateFrom.line + 1).from + updateFrom.ch;
const toPos = editorView.state.doc.line(updateFrom.line + 1).from + updateFrom.ch + variantSyntax.length;
// Update the exact selection range for highlighting
this.selectionFrom = fromPos;
this.selectionTo = toPos;
// Force editor refresh to update decorations
this.app.workspace.updateOptions();
} catch (e) {
@ -517,7 +517,7 @@ export default class VariantEditor extends Plugin {
}
}
}
const action = isExistingVariant ? 'Updated' : 'Created';
// Only show notice on explicit user action, not on every update
if (commitVariant) {
@ -548,7 +548,7 @@ export default class VariantEditor extends Plugin {
let text: string;
let from: EditorPosition;
let to: EditorPosition;
const selection = editor.listSelections()[0];
if (selection && (selection.anchor.line !== selection.head.line || selection.anchor.ch !== selection.head.ch)) {
// Use the selection
@ -556,12 +556,12 @@ export default class VariantEditor extends Plugin {
line: Math.min(selection.anchor.line, selection.head.line),
ch: Math.min(selection.anchor.ch, selection.head.ch)
};
to = {
line: Math.max(selection.anchor.line, selection.head.line),
ch: Math.max(selection.anchor.ch, selection.head.ch)
};
text = editor.getRange(from, to);
} else {
// Use the entire document
@ -569,23 +569,23 @@ export default class VariantEditor extends Plugin {
to = { line: editor.lineCount() - 1, ch: editor.getLine(editor.lineCount() - 1).length };
text = editor.getValue();
}
// Find all variants in the text
const variantRegex = /\{\{([^{}]+?)\}\}\^(\d+)/g;
let match;
let variantsFound = 0;
let lastIndex = 0;
let result = '';
// Process each variant
while ((match = variantRegex.exec(text)) !== null) {
// Add text before this variant
result += text.substring(lastIndex, match.index);
// Extract variant information
const variants = match[1].split('|');
const activeIndex = parseInt(match[2], 10);
// Add the active variant text
if (activeIndex >= 0 && activeIndex < variants.length) {
result += variants[activeIndex];
@ -594,17 +594,17 @@ export default class VariantEditor extends Plugin {
// If active index is invalid, keep the original text
result += match[0];
}
// Update lastIndex for next iteration
lastIndex = match.index + match[0].length;
}
// Add any remaining text
result += text.substring(lastIndex);
// Replace the text in the editor
editor.replaceRange(result, from, to);
// Show a notice with the results
if (variantsFound > 0) {
new Notice(`Committed ${variantsFound} variant${variantsFound === 1 ? '' : 's'}`);
@ -616,7 +616,7 @@ export default class VariantEditor extends Plugin {
new Notice('Error committing variants');
}
}
private clearHighlight(): void {
try {
// Reset state
@ -625,7 +625,7 @@ export default class VariantEditor extends Plugin {
this.selectedText = null;
this.selectionFrom = null;
this.selectionTo = null;
// Force editor refresh
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (view) hackToRerender(view);

File diff suppressed because it is too large Load diff

View file

@ -36,10 +36,11 @@
}
/* Make highlighted text stand out even when selected */
.cm-line .cm-selectionBackground ~ .fh-highlight,
.cm-line .cm-selectionBackground + .fh-highlight,
.cm-line .cm-selectionBackground~.fh-highlight,
.cm-line .cm-selectionBackground+.fh-highlight,
.cm-line .fh-highlight.cm-selectionBackground {
background-color: rgba(255, 207, 64, 0.6) !important; /* Lighter yellow that shows through selection */
background-color: rgba(255, 207, 64, 0.6) !important;
/* Lighter yellow that shows through selection */
mix-blend-mode: normal;
opacity: 1;
}
@ -160,7 +161,8 @@
inset: -2px;
padding: 2px;
background: linear-gradient(135deg, #8b5cf6, #ec4899, #f97316);
border-radius: 10px; /* Match the row border-radius + 2px for the inset */
border-radius: 10px;
/* Match the row border-radius + 2px for the inset */
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask-composite: exclude;
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
@ -305,7 +307,8 @@
/* Special handling for active rows with drag indicators */
.variant-editor-row-active.drag-over-top::after,
.variant-editor-row-active.drag-over-bottom::after {
z-index: 20; /* Higher z-index to appear above the rainbow border */
z-index: 20;
/* Higher z-index to appear above the rainbow border */
}
@ -353,12 +356,16 @@
.variant-editor-button {
min-width: 144px;
max-width: 216px;
padding: 10px 24px !important; /* Reduced vertical padding to make buttons thinner */
border-radius: 8px !important; /* Match variant row border-radius */
padding: 10px 24px !important;
/* Reduced vertical padding to make buttons thinner */
border-radius: 8px !important;
/* Match variant row border-radius */
font-size: 16px !important;
font-weight: 600 !important;
transition: all 0.25s cubic-bezier(0.25, 0.1, 0.25, 1) !important; /* Improved animation curve */
border: 1px solid #333 !important; /* Match variant row border */
transition: all 0.25s cubic-bezier(0.25, 0.1, 0.25, 1) !important;
/* Improved animation curve */
border: 1px solid #333 !important;
/* Match variant row border */
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) !important;
text-align: center !important;
color: white !important;
@ -367,13 +374,15 @@
align-items: center !important;
justify-content: center !important;
gap: 10px !important;
height: 42px !important; /* Reduced height by 10px */
height: 42px !important;
/* Reduced height by 10px */
}
.variant-editor-commit-button {
background-color: #7E6BDC !important; /* Obsidian purple */
background-color: #7E6BDC !important;
/* Obsidian purple */
color: white !important;
}
@ -404,7 +413,8 @@
/* Style the variant editor modal */
.variant-editor-modal {
max-width: 90vw !important;
width: 460px !important; /* Reduced from 500px to 460px (40px less) */
width: 460px !important;
/* Reduced from 500px to 460px (40px less) */
max-height: 80vh !important;
padding: 0;
overflow-y: auto !important;
@ -461,7 +471,7 @@
}
/* Remove background dimming for variant editor modal */
.variant-editor-no-dim ~ .modal-bg {
.variant-editor-no-dim~.modal-bg {
background-color: transparent !important;
backdrop-filter: none !important;
}
@ -470,4 +480,4 @@
outline: none;
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2);
}
}