Skip to content
Merged
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
13 changes: 8 additions & 5 deletions src/components/ai-edition/RightPanes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1309,8 +1309,14 @@ const TranscriptClipBlock = memo(function TranscriptClipBlock({
// their own typing bubbles here natively — React's `stopPropagation` only ever
// stopped the synthetic tree. Their text is theirs.
if (event.target instanceof HTMLInputElement) return;
// Default-deny: the block is contentEditable only to carry a caret, so no native
// edit may land in it. Not only the two families handled below: Ctrl/Cmd+B, I and U
// reach here as `formatBold`, `formatItalic` and `formatUnderline`, and let through
// they styled the DOM behind `words`' back (#1038). Undo loses nothing: Ctrl/Cmd+Z
// goes to the document undo (keydown, or the Edit menu on macOS), never to the
// browser's text history, which has nothing of this block's edits in it anyway.
event.preventDefault();
if (event.inputType.startsWith("delete")) {
event.preventDefault();
cutNativeSelection(event.inputType === "deleteContentForward" ? "forward" : "backward");
return;
}
Expand All @@ -1319,10 +1325,7 @@ const TranscriptClipBlock = memo(function TranscriptClipBlock({
// instead is a field beside the word the caret was on, whose commit creates a real
// word to hold them. So the gesture is the document one — put the caret somewhere
// and type — without the DOM ever getting ahead of `words`.
if (event.inputType.startsWith("insert")) {
event.preventDefault();
openInsertion(event.data ?? "");
}
if (event.inputType.startsWith("insert")) openInsertion(event.data ?? "");
};
editor.addEventListener("beforeinput", onBeforeInput);
return () => editor.removeEventListener("beforeinput", onBeforeInput);
Expand Down
40 changes: 40 additions & 0 deletions src/components/ai-edition/TranscriptPane.wordInsert.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,43 @@ describe("a word that was inserted", () => {
expect(view.onAddTrimRange).not.toHaveBeenCalled();
});
});

describe("the native edits the block does not handle", () => {
/** Dispatch a native `beforeinput`; true when the listener let the browser go ahead. */
function input(editor: HTMLElement, inputType: string, data?: string) {
return fireEvent(
editor,
new InputEvent("beforeinput", { inputType, data, bubbles: true, cancelable: true }),
);
}

// Ctrl/Cmd+B, I and U: let through, they styled the words in the DOM and `words` never
// knew (#1038). Undo and redo belong to the document, never to the browser's text history.
it.each([
"formatBold",
"formatItalic",
"formatUnderline",
"historyUndo",
"historyRedo",
])("refuses %s", (inputType) => {
const view = renderPane();
caretBeforeWordAt(view.editor, 2);
expect(input(view.editor, inputType)).toBe(false);
expect(view.field()).toBeNull();
expect(view.onAddTrimRange).not.toHaveBeenCalled();
});

it("still cuts on a delete", () => {
const view = renderPane();
caretBeforeWordAt(view.editor, 2);
expect(input(view.editor, "deleteContentBackward")).toBe(false);
expect(view.onAddTrimRange).toHaveBeenCalledTimes(1);
});

it("still opens the field on an insert", () => {
const view = renderPane();
caretBeforeWordAt(view.editor, 2);
expect(input(view.editor, "insertText", "v")).toBe(false);
expect(view.field()).toHaveValue("v");
});
});
Loading