-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Open
Labels
Open SourceThe issue or pull reuqest is related to the open source packages of Tiptap.The issue or pull reuqest is related to the open source packages of Tiptap.
Description
Affected Packages
Packages used are given in additional
Version(s)
3.13.0
Bug Description
When I double click on a word, it does not select the word and highlights it.
Browser Used
Chrome
Code Example URL
No response
Expected Behavior
Double click should select the word. Although, triple click seems to work and it selects the whole line.
Additional Context (Optional)
Here is my code to define the Editor.
const defaultExtensions: Extensions = [
// Extend Document to use single newline separator between blocks instead of double
// This makes Shift+Enter produce \n instead of \n\n in markdown output
Document.extend({
renderMarkdown: (node: JSONContent, h: MarkdownRendererHelpers) => {
if (!node.content) {
return '';
}
return h.renderChildren(node.content, '\n');
},
}),
Text,
Paragraph,
UndoRedo,
Placeholder.configure({
placeholder: ({ node }) => {
// Don't show a placeholder for ordered or unordered lists
if (
node.type.name === 'orderedList' ||
node.type.name === 'bulletList'
) {
return '';
}
return placeholder;
},
}),
Markdown.configure({
indentation: {
style: 'space',
size: 2,
},
markedOptions: { breaks: true, gfm: true },
}),
ListKit.configure({
taskItem: false,
taskList: false,
bulletList: {
HTMLAttributes: {
class: 'list-disc ml-2',
},
},
orderedList: {
HTMLAttributes: {
class: 'list-decimal ml-4',
},
},
listItem: {
HTMLAttributes: {
class: 'ml-4',
},
},
}),
ModifyTiptapEnterExtension,
ModifyTiptapBackspaceExtension,
DoubleClickWordSelectExtension,
PreserveNewlinesPasteExtension,
...additionalExtensions,
];
const editor = useEditor({
extensions: defaultExtensions,
content: initialContent,
autofocus: autoFocus,
immediatelyRender,
contentType: 'markdown',
});// DoubleClickWordSelectExtension.ts
import { Extension } from '@tiptap/core';
import { Plugin, PluginKey, TextSelection } from '@tiptap/pm/state';
/**
* Extension that enables double-click to select a word.
* This fixes cases where the default browser behavior is being intercepted.
*/
export const DoubleClickWordSelectExtension = Extension.create({
name: 'doubleClickWordSelect',
addProseMirrorPlugins: () => {
return [
new Plugin({
key: new PluginKey('doubleClickWordSelect'),
props: {
handleDoubleClick(view, pos) {
const { state } = view;
const { doc } = state;
// Get the resolved position
const $pos = doc.resolve(pos);
// Get the text content of the parent node
const parent = $pos.parent;
if (!parent.isTextblock) return false;
const text = parent.textContent;
const offset = $pos.parentOffset;
// Find word boundaries
let start = offset;
let end = offset;
// Word character pattern (letters, numbers, underscores)
const isWordChar = (char: string) => /[\w\u00C0-\u024F]/.test(char);
// Find start of word
while (start > 0 && isWordChar(text[start - 1])) {
start--;
}
// Find end of word
while (end < text.length && isWordChar(text[end])) {
end++;
}
// If we found a word (start !== end)
if (start !== end) {
const from = $pos.start() + start;
const to = $pos.start() + end;
const tr = state.tr.setSelection(
TextSelection.create(doc, from, to)
);
view.dispatch(tr);
return true;
}
return false;
},
},
}),
];
},
});// PreserveNewlinesPasteExtension.ts
import { Extension } from '@tiptap/core';
import { Fragment, Slice } from '@tiptap/pm/model';
import { Plugin, PluginKey } from '@tiptap/pm/state';
/**
* Extension that preserves multiple consecutive newlines when pasting plain text.
* By default, TipTap/ProseMirror collapses multiple newlines into single paragraph breaks
* because it uses /(?:\r\n?|\n)+/ regex which matches "one or more" newlines.
* This extension manually handles paste to preserve empty lines between paragraphs.
* ref: https://unpkg.com/[email protected]/src/clipboard.ts, search for /(?:\r\n?|\n)+/
*/
export const PreserveNewlinesPasteExtension = Extension.create({
name: 'preserveNewlinesPaste',
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey('preserveNewlinesPaste'),
props: {
handlePaste: (view, event) => {
const text = event.clipboardData?.getData('text/plain');
const html = event.clipboardData?.getData('text/html');
// If there's HTML content (e.g., from a rich text source), let default handling occur
// unless we also have plain text to preserve
if (!text) {
return false;
}
// If HTML is present but no special newlines in text, let default handle it
if (html && !text.includes('\n')) {
return false;
}
const { state, dispatch } = view;
const { schema, tr } = state;
// Normalize CRLF to LF to handle Windows line endings consistently
const normalizedText = text
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n');
// Split on each individual newline (NOT on "one or more" newlines)
// This preserves empty lines as empty strings in the array
const lines = normalizedText.split('\n');
// Multiple lines - create paragraph nodes
const paragraphType = schema.nodes.paragraph;
if (!paragraphType) {
// Fallback if no paragraph type in schema
return false;
}
const paragraphs = lines.map((line) => {
if (line === '') {
// Empty line becomes an empty paragraph
return paragraphType.create();
}
// Non-empty line becomes a paragraph with text content
return paragraphType.create(null, schema.text(line));
});
// Create a slice with openStart=1 and openEnd=1
// This allows the first/last paragraphs to merge with surrounding content
// when pasting in the middle of existing text
const fragment = Fragment.fromArray(paragraphs);
const slice = new Slice(fragment, 1, 1);
// Replace the current selection with the pasted content
dispatch(tr.replaceSelection(slice).scrollIntoView());
return true;
},
},
}),
];
},
});I've also attached 2 more extensions which should not be required but here we are.
Dependency Updates
- Yes, I've updated all my dependencies.
Metadata
Metadata
Assignees
Labels
Open SourceThe issue or pull reuqest is related to the open source packages of Tiptap.The issue or pull reuqest is related to the open source packages of Tiptap.