Graphite/frontend/src/io-managers/clipboard.ts

98 lines
2.8 KiB
TypeScript

import { type Editor } from "@graphite/editor";
import { TriggerClipboardWrite, TriggerSelectionRead, TriggerSelectionWrite } from "@graphite/messages";
export function createClipboardManager(editor: Editor) {
// Subscribe to process backend event
editor.subscriptions.subscribeJsMessage(TriggerClipboardWrite, (triggerTextCopy) => {
// If the Clipboard API is supported in the browser, copy text to the clipboard
navigator.clipboard?.writeText?.(triggerTextCopy.content);
});
editor.subscriptions.subscribeJsMessage(TriggerSelectionRead, async (data) => {
editor.handle.readSelection(readAtCaret(data.cut), data.cut);
});
editor.subscriptions.subscribeJsMessage(TriggerSelectionWrite, async (data) => {
insertAtCaret(data.content);
});
}
function readAtCaret(cut: boolean): string | undefined {
const element = window.document.activeElement;
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
const start = element.selectionStart;
const end = element.selectionEnd;
if ((!start && start !== 0) || (!end && end !== 0) || start === end) {
return undefined;
}
const value = element.value;
const selectedText = value.slice(start, end);
if (cut) {
element.value = value.slice(0, start) + value.slice(end);
element.selectionStart = element.selectionEnd = start;
element.dispatchEvent(new Event("input", { bubbles: true }));
}
return selectedText;
}
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) {
return undefined;
}
const selectedText = selection.toString();
if (!selectedText) return undefined;
if (cut) {
const range = selection.getRangeAt(0);
range.deleteContents();
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
return selectedText;
}
function insertAtCaret(text: string) {
const element = window.document.activeElement;
if (!element) return;
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
const start = element.selectionStart;
const end = element.selectionEnd;
if ((!start && start !== 0) || (!end && end !== 0)) return;
const value = element.value;
element.value = value.slice(0, start) + text + value.slice(end);
const newPos = start + text.length;
element.selectionStart = element.selectionEnd = newPos;
} else if (element instanceof HTMLElement && element.isContentEditable) {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) return;
const range = selection.getRangeAt(0);
range.deleteContents();
const textNode = window.document.createTextNode(text);
range.insertNode(textNode);
range.setStartAfter(textNode);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
element.dispatchEvent(new Event("input", { bubbles: true }));
}