From 19864160e9d4e60a156551d2d058c19c98e364b0 Mon Sep 17 00:00:00 2001 From: kunalja Date: Sat, 7 Jun 2025 15:17:12 -0400 Subject: [PATCH] relative position the modal attempt 1 --- src/main.ts | 52 ++++++++++--------- src/modals/TextInputModal.ts | 97 ++++++++++++++++++++++++++++++------ styles.css | 13 ++++- 3 files changed, 122 insertions(+), 40 deletions(-) diff --git a/src/main.ts b/src/main.ts index 0e31cce..df2e3a0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -268,32 +268,38 @@ export default class VariantEditor extends Plugin { this.app.workspace.updateOptions(); // Open the text input modal after highlighting with the original text - new TextInputModal(this.app, selectedWord, (variantText: string, activeIndex?: number, commitVariant?: boolean) => { - if (variantText && variantText.trim()) { - if (commitVariant) { - // If committing, just replace with the selected variant text directly - editor.replaceRange(variantText, from, to); - new Notice(`Committed variant: "${variantText}"`); - } else { - // Create the variant syntax: {{original | variant1 | variant2}}^n - const variants = variantText.split('|').map(v => v.trim()).filter(v => v); - - // Ensure we have at least one variant (the original text) - if (variants.length > 0) { - // Use the provided activeIndex or default to 0 - const activeIdx = typeof activeIndex === 'number' ? activeIndex : 0; - const variantSyntax = `{{${variants.join(' | ')}}}^${activeIdx}`; + new TextInputModal( + this.app, + selectedWord, + (variantText: string, activeIndex?: number, commitVariant?: boolean) => { + if (variantText && variantText.trim()) { + if (commitVariant) { + // If committing, just replace with the selected variant text directly + editor.replaceRange(variantText, from, to); + new Notice(`Committed variant: "${variantText}"`); + } else { + // Create the variant syntax: {{original | variant1 | variant2}}^n + const variants = variantText.split('|').map(v => v.trim()).filter(v => v); - // Replace the selected text with the variant syntax - editor.replaceRange(variantSyntax, from, to); - new Notice(`Created variant with ${variants.length} options (${variants[activeIdx]} active)`); + // Ensure we have at least one variant (the original text) + if (variants.length > 0) { + // Use the provided activeIndex or default to 0 + const activeIdx = typeof activeIndex === 'number' ? activeIndex : 0; + const variantSyntax = `{{${variants.join(' | ')}}}^${activeIdx}`; + + // Replace the selected text with the variant syntax + editor.replaceRange(variantSyntax, from, to); + new Notice(`Created variant with ${variants.length} options (${variants[activeIdx]} active)`); + } } + + // Clear highlighting after creating variant + this.clearHighlight(); } - - // Clear highlighting after creating variant - this.clearHighlight(); - } - }).open(); + }, + // Pass the cursor position to position the modal relative to the highlighted text + from + ).open(); } catch (e) { console.error('Error in highlightSelection:', e); diff --git a/src/modals/TextInputModal.ts b/src/modals/TextInputModal.ts index 864034a..cccafcc 100644 --- a/src/modals/TextInputModal.ts +++ b/src/modals/TextInputModal.ts @@ -1,4 +1,4 @@ -import { App, Modal, Setting, ButtonComponent } from 'obsidian'; +import { App, Modal, Setting, ButtonComponent, EditorPosition } from 'obsidian'; /** * Modal for text input that appears after highlighting @@ -10,17 +10,34 @@ export class TextInputModal extends Modal { private onSubmit: (result: string, activeIndex?: number, commitVariant?: boolean) => void; private originalText: string; private variantContainer: HTMLElement; + private cursorPosition: EditorPosition | null; - constructor(app: App, originalText: string, onSubmit: (result: string, activeIndex?: number, commitVariant?: boolean) => void) { + constructor( + app: App, + originalText: string, + onSubmit: (result: string, activeIndex?: number, commitVariant?: boolean) => void, + cursorPosition: EditorPosition | null = null + ) { super(app); this.originalText = originalText; this.onSubmit = onSubmit; - // Initialize with the original text as the first variant this.variants = [originalText]; + this.cursorPosition = cursorPosition; } onOpen() { - const {contentEl} = this; + const {contentEl, modalEl} = this; + + // Add a class for styling + modalEl.addClass('variant-editor-modal'); + + // Position the modal relative to the cursor if we have cursor position + if (this.cursorPosition) { + // We need to position the modal after it's rendered + setTimeout(() => { + this.positionModalRelativeToCursor(modalEl); + }, 0); + } contentEl.createEl('h2', {text: 'Create Variants'}); @@ -100,25 +117,73 @@ export class TextInputModal extends Modal { .map((v, i) => ({ text: v.trim(), originalIndex: i })) .filter(v => v.text.length > 0); - if (nonEmptyVariantsWithIndices.length > 0) { - // Find the new index of the active variant after filtering - let newActiveIndex = 0; - for (let i = 0; i < nonEmptyVariantsWithIndices.length; i++) { - if (nonEmptyVariantsWithIndices[i].originalIndex === this.activeVariantIndex) { - newActiveIndex = i; - break; + if (nonEmptyVariantsWithIndices.length >= 2) { + // Find the new index of the active variant after filtering + let newActiveIndex = 0; + for (let i = 0; i < nonEmptyVariantsWithIndices.length; i++) { + if (nonEmptyVariantsWithIndices[i].originalIndex === this.activeVariantIndex) { + newActiveIndex = i; + break; + } } + + // Join variants with pipe character for the expected format and pass the corrected active index + const nonEmptyVariants = nonEmptyVariantsWithIndices.map(v => v.text); + this.onSubmit(nonEmptyVariants.join('|'), newActiveIndex); } - this.close(); - // Join variants with pipe character for the expected format and pass the corrected active index - const nonEmptyVariants = nonEmptyVariantsWithIndices.map(v => v.text); - this.onSubmit(nonEmptyVariants.join('|'), newActiveIndex); - } }) .buttonEl.addClass('variant-editor-button'); } + /** + * Positions the modal relative to the cursor position + */ + private positionModalRelativeToCursor(modalEl: HTMLElement) { + if (!this.cursorPosition) return; + + // Get the active editor view + const activeLeaf = this.app.workspace.activeLeaf; + if (!activeLeaf || !activeLeaf.view) return; + + // Get the editor element + const editorEl = activeLeaf.view.containerEl.querySelector('.cm-editor'); + if (!editorEl) return; + + // Find the line element for the cursor position + const lineElements = editorEl.querySelectorAll('.cm-line'); + if (!lineElements || lineElements.length <= this.cursorPosition.line) return; + + const lineEl = lineElements[this.cursorPosition.line]; + if (!lineEl) return; + + // Get the position of the line element + const lineRect = lineEl.getBoundingClientRect(); + + // Get the modal dimensions + const modalRect = modalEl.getBoundingClientRect(); + + // Position the modal below the line with some padding + const padding = 10; + let top = lineRect.bottom + padding; + + // Make sure the modal doesn't go off the bottom of the screen + const viewportHeight = window.innerHeight; + if (top + modalRect.height > viewportHeight) { + // Position above the line instead + top = lineRect.top - modalRect.height - padding; + } + + // Center horizontally relative to the line + const left = lineRect.left + (lineRect.width / 2) - (modalRect.width / 2); + + // Apply the position + modalEl.style.position = 'fixed'; + modalEl.style.top = `${Math.max(0, top)}px`; + modalEl.style.left = `${Math.max(0, left)}px`; + modalEl.style.transform = 'none'; + } + private renderVariantInputs() { // Clear existing inputs this.variantContainer.empty(); diff --git a/styles.css b/styles.css index c455b50..903f8e8 100644 --- a/styles.css +++ b/styles.css @@ -163,11 +163,22 @@ } .variant-editor-hint { - margin-bottom: 15px; + margin-top: 10px; + margin-bottom: 10px; color: var(--text-muted); font-size: 0.9em; } +/* Style the variant editor modal */ +.variant-editor-modal { + width: 400px !important; + max-width: 90vw !important; + max-height: 80vh !important; + overflow-y: auto !important; + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15) !important; + border: 1px solid var(--background-modifier-border) !important; +} + .variant-editor-textarea:focus { outline: none; border-color: var(--interactive-accent);