Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 153 additions & 37 deletions packages/core/src/handlers/hyperlink.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { blockType } from "../types/pieces";
import { blockType } from '../types/pieces';
import {
saveSelection,
restoreSelection,
getSelectionRange,
} from "../utils/selectionManager";
import EditorView from "../view/editorView";
import TextDocument from "../textDocument";
import UndoRedoManager from "./undoRedoManager";
import { strings } from "../constants/strings";
} from '../utils/selectionManager';
import EditorView from '../view/editorView';
import TextDocument from '../textDocument';
import UndoRedoManager from './undoRedoManager';
import { strings } from '../constants/strings';

class HyperlinkHandler {
savedSelection: { start: number; end: number } | null = null;
Expand Down Expand Up @@ -80,13 +80,26 @@ class HyperlinkHandler {
}

showHyperlinkInput(existingLink: string | null): void {
const hyperlinkContainer = document.getElementById(strings.HYPERLINK_CONTAINER_ID);
const hyperlinkInput = document.getElementById(strings.HYPERLINK_INPUT_ID) as HTMLInputElement;
const hyperlinkContainer = document.getElementById(
strings.HYPERLINK_CONTAINER_ID
);
const hyperlinkInput = document.getElementById(
strings.HYPERLINK_INPUT_ID
) as HTMLInputElement;
const applyButton = document.getElementById(strings.HYPERLINK_APPLY_BTN_ID);
const cancelButton = document.getElementById(strings.HYPERLINK_CANCEL_BTN_ID);
const cancelButton = document.getElementById(
strings.HYPERLINK_CANCEL_BTN_ID
);

if (hyperlinkContainer && hyperlinkInput && applyButton && cancelButton) {
hyperlinkContainer.style.display = "block";
// Hide container first to clear any existing state
hyperlinkContainer.style.display = 'none';

// Force a reflow to ensure the container is hidden
hyperlinkContainer.offsetHeight;

// Now show the container
hyperlinkContainer.style.display = 'block';

const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
Expand All @@ -96,28 +109,65 @@ class HyperlinkHandler {
hyperlinkContainer.style.left = `${rect.left + window.scrollX}px`;
}

hyperlinkInput.value = existingLink || "";
hyperlinkInput.value = existingLink || '';

// Save selection BEFORE highlighting
this.savedSelection = saveSelection(this.editorView.container);
this.highlightSelection();
hyperlinkInput.focus();
console.log('Saved selection:', this.savedSelection);

applyButton.onclick = null;
cancelButton.onclick = null;
this.highlightSelection();

const dataIdsSnapshot = this.document.dataIds;
console.log('Data IDs snapshot:', dataIdsSnapshot);

// Remove any existing event listeners
const newHyperlinkInput = hyperlinkInput.cloneNode(
true
) as HTMLInputElement;
hyperlinkInput.parentNode?.replaceChild(
newHyperlinkInput,
hyperlinkInput
);

applyButton.onclick = () => {
const url = hyperlinkInput.value.trim();
// Focus the input
newHyperlinkInput.focus();

// Create the keydown handler
const handleKeydown = (event: KeyboardEvent) => {
console.log('keydown event triggered:', event.key);
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
console.log('Enter key pressed, triggering apply button click');
handleApplyClick();
}
};

// Create the apply click handler
const handleApplyClick = () => {
console.log('Apply button clicked');
const url = newHyperlinkInput.value.trim();
if (url) {
this.applyHyperlink(url, dataIdsSnapshot);
}
hyperlinkContainer.style.display = "none";
hyperlinkContainer.style.display = 'none';
// Remove the event listener when closing
newHyperlinkInput.removeEventListener('keydown', handleKeydown);
};

cancelButton.onclick = () => {
// Create the cancel click handler
const handleCancelClick = () => {
console.log('Cancel button clicked');
this.removeHyperlink(dataIdsSnapshot);
hyperlinkContainer.style.display = "none";
hyperlinkContainer.style.display = 'none';
// Remove the event listener when closing
newHyperlinkInput.removeEventListener('keydown', handleKeydown);
};

// Add event listeners
newHyperlinkInput.addEventListener('keydown', handleKeydown);
applyButton.addEventListener('click', handleApplyClick);
cancelButton.addEventListener('click', handleCancelClick);
}
}

Expand All @@ -127,7 +177,7 @@ class HyperlinkHandler {
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const span = document.createElement("span");
const span = document.createElement('span');
span.className = strings.TEMPORARY_SELECTION_HIGHLIGHT_CLASS;
span.appendChild(range.extractContents());
range.insertNode(span);
Expand All @@ -142,7 +192,7 @@ class HyperlinkHandler {
const highlights = this.editorContainer?.querySelectorAll(
`span.${strings.TEMPORARY_SELECTION_HIGHLIGHT_CLASS}`
);
highlights?.forEach((span) => {
highlights?.forEach(span => {
const parent = span.parentNode;
if (parent) {
while (span.firstChild) {
Expand All @@ -154,12 +204,56 @@ class HyperlinkHandler {
}

applyHyperlink(url: string, dataIdsSnapshot: any): void {
console.log('applyHyperlink called with:', { url, dataIdsSnapshot });

this.undoRedoManager.saveUndoSnapshot();
this.removeHighlightSelection();
restoreSelection(this.editorView.container, this.savedSelection);

// Restore the saved selection
if (this.savedSelection) {
restoreSelection(this.editorView.container, this.savedSelection);
}

const [start, end] = getSelectionRange(this.editorView);
if (start < end) {
if (dataIdsSnapshot.length > 1) {
console.log('Selection range after restore:', { start, end });

// Set the selectedBlockId if it's not set
if (
!this.document.selectedBlockId &&
dataIdsSnapshot &&
dataIdsSnapshot.length > 0
) {
this.document.selectedBlockId = dataIdsSnapshot[0];
console.log('Set selectedBlockId to:', dataIdsSnapshot[0]);
}

// If no selection range, try to use the saved selection
if (start === end && this.savedSelection) {
console.log('Using saved selection instead');
const savedStart = this.savedSelection.start;
const savedEnd = this.savedSelection.end;

if (savedStart < savedEnd) {
// Use the saved selection range
if (dataIdsSnapshot && dataIdsSnapshot.length > 1) {
this.document.blocks.forEach((block: any) => {
if (dataIdsSnapshot.includes(block.dataId)) {
this.document.selectedBlockId = block.dataId;
let countE = 0;
block.pieces.forEach((obj: any) => {
countE += obj.text.length;
});
let countS = savedStart - countE;
this.document.formatAttribute(countS, countE, 'hyperlink', url);
}
});
} else {
this.document.formatAttribute(savedStart, savedEnd, 'hyperlink', url);
}
}
} else if (start < end) {
// Use the current selection range
if (dataIdsSnapshot && dataIdsSnapshot.length > 1) {
this.document.blocks.forEach((block: any) => {
if (dataIdsSnapshot.includes(block.dataId)) {
this.document.selectedBlockId = block.dataId;
Expand All @@ -168,16 +262,32 @@ class HyperlinkHandler {
countE += obj.text.length;
});
let countS = start - countE;
this.document.formatAttribute(countS, countE, "hyperlink", url);
this.document.formatAttribute(countS, countE, 'hyperlink', url);
}
});
} else {
this.document.formatAttribute(start, end, "hyperlink", url);
// For single block, ensure selectedBlockId is set
if (
!this.document.selectedBlockId &&
dataIdsSnapshot &&
dataIdsSnapshot.length > 0
) {
this.document.selectedBlockId = dataIdsSnapshot[0];
}
this.document.formatAttribute(start, end, 'hyperlink', url);
}
this.editorView.render();
} else {
console.log('No valid selection found');
}

this.editorView.render();

// Restore selection and focus
if (this.savedSelection) {
restoreSelection(this.editorView.container, this.savedSelection);
this.editorView.container.focus();
}
this.editorView.container.focus();

this.savedSelection = null;
}

Expand All @@ -196,11 +306,11 @@ class HyperlinkHandler {
countE += obj.text.length;
});
let countS = start - countE;
this.document.formatAttribute(countS, countE, "hyperlink", false);
this.document.formatAttribute(countS, countE, 'hyperlink', false);
}
});
} else {
this.document.formatAttribute(start, end, "hyperlink", false);
this.document.formatAttribute(start, end, 'hyperlink', false);
}
this.editorView.render();
restoreSelection(this.editorView.container, this.savedSelection);
Expand All @@ -209,12 +319,16 @@ class HyperlinkHandler {
this.savedSelection = null;
}

showHyperlinkViewButton(link: string | ""): void {
const viewHyperlinkContainer = document.getElementById(strings.VIEW_HYPERLINK_CONTAINER_ID) as HTMLDivElement;
const hyperLinkAnchor = document.getElementById(strings.VIEW_HYPERLINK_ANCHOR_ID) as HTMLAnchorElement;
showHyperlinkViewButton(link: string | ''): void {
const viewHyperlinkContainer = document.getElementById(
strings.VIEW_HYPERLINK_CONTAINER_ID
) as HTMLDivElement;
const hyperLinkAnchor = document.getElementById(
strings.VIEW_HYPERLINK_ANCHOR_ID
) as HTMLAnchorElement;

if (viewHyperlinkContainer && hyperLinkAnchor) {
viewHyperlinkContainer.style.display = "block";
viewHyperlinkContainer.style.display = 'block';

const selection = window.getSelection();
if (selection) {
Expand All @@ -232,9 +346,11 @@ class HyperlinkHandler {
}

hideHyperlinkViewButton() {
const hyperlinkContainer = document.getElementById(strings.VIEW_HYPERLINK_CONTAINER_ID);
const hyperlinkContainer = document.getElementById(
strings.VIEW_HYPERLINK_CONTAINER_ID
);
if (hyperlinkContainer) {
hyperlinkContainer.style.display = "none";
hyperlinkContainer.style.display = 'none';
}
}
}
Expand Down
14 changes: 13 additions & 1 deletion packages/core/src/textDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,13 +409,25 @@ class TextDocument extends EventEmitter {
let newPieces: Piece[] = [];
let offset = 0;
let index = -1;
if (this.selectedBlockId !== '' || this.selectedBlockId !== null) {

// Fix the condition - it should be AND, not OR
if (this.selectedBlockId !== '' && this.selectedBlockId !== null) {
index = this.blocks.findIndex(
(block: any) => block.dataId === this.selectedBlockId
);
offset = this.currentOffset;
}

// Add error checking
if (index === -1 || !this.blocks[index]) {
console.error(
'No valid block found for selectedBlockId:',
this.selectedBlockId
);
console.log('Available blocks:', this.blocks);
return;
}

for (let piece of this.blocks[index].pieces) {
const pieceEnd = offset + piece.text.length;

Expand Down
1 change: 0 additions & 1 deletion packages/react/src/components/TextIgniter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom/client";
import '../types/textigniter.d.ts';

// Updated interfaces to support new configuration
Expand Down