Skip to content

[Bug]: Updating the node and its highlight #5155

Open
@VadimYefim

Description

@VadimYefim

Affected Packages

"@tiptap/extension-bullet-list": "^2.3.0", "@tiptap/extension-color": "^2.3.0", "@tiptap/extension-document": "^2.3.0", "@tiptap/extension-heading": "^2.3.0", "@tiptap/extension-highlight": "^2.3.0", "@tiptap/extension-image": "^2.3.0", "@tiptap/extension-link": "^2.3.0", "@tiptap/extension-list-item": "^2.3.0", "@tiptap/extension-ordered-list": "^2.3.0", "@tiptap/extension-paragraph": "^2.3.0", "@tiptap/extension-placeholder": "^2.3.0", "@tiptap/extension-text": "^2.3.0", "@tiptap/extension-text-align": "^2.3.0", "@tiptap/extension-text-style": "^2.3.0", "@tiptap/extension-underline": "^2.3.0", "@tiptap/pm": "^2.3.0", "@tiptap/react": "^2.3.0", "@tiptap/starter-kit": "^2.3.0",

Version(s)

2.3.0

Bug Description

I have my editor as a component. And to the right of it is a list of words (This is a separate component from the editor, not a child).
And when I click on a word from the list, I want to see it highlighted. When I click on another word, it's another.

That is, every time the selectedKeyword changes, the function that makes the decorations should be triggered.

import React from 'react';
import { useSelector } from 'react-redux';

import { Extension } from '@tiptap/core';
import { Decoration, DecorationSet } from '@tiptap/pm/view';
import { EditorProvider } from '@tiptap/react';
import { debounce } from 'lodash';

import useUpdateEditorContent from './hooks/useUpdateEditorContent';

import TipTapEditorSettings from './TipTapEditorSettings';
import EditorToolbar from './toolbar/EditorToolbar';
import EditLinkContent from './toolbar/buttons/content/EditLinkContent';

import { tiptapEditorExtensions } from './extentions/tiptapEditorExtentions';

import { Plugin } from '@tiptap/pm/state';
import { selectSelectedKeyword } from '../../pages/contentOptimizer/contentPage/store/contentOptimizerContent.selectors';
import './TipTapEditor.scss';

const TipTapEditor = () => {
    const [handleUpdateEditor] = useUpdateEditorContent();

    const handleEditorChange = debounce((editorOnChangeData) => {
        const { editor } = editorOnChangeData;
        const updatedEditorData = editor?.getHTML();
        const updatedEditorText = editor?.getText();

        handleUpdateEditor(updatedEditorData, updatedEditorText);
    }, 1000);

    const selectedKeyword = useSelector(selectSelectedKeyword);

    const getHighlightedKeywords = (doc) => {
        const decorations = [];

        doc.descendants((node, position) => {
            if (!node.text) {
                return;
            }

            if (selectedKeyword) {
                const matchedKeywordKey = selectedKeyword?.regex
                    ? selectedKeyword?.regex
                    : selectedKeyword?.keyword;

                Array.from(
                    node.text.toLowerCase().matchAll(matchedKeywordKey)
                ).forEach((match) => {
                    const word = match[0];
                    const index = match.index || 0;
                    const from = position + index;
                    const to = from + word.length;
                    const decoration = Decoration.inline(from, to, {
                        class: `highlight-keyword selected-keyword`,
                    });

                    decorations.push(decoration);
                });
            }
        });

        return DecorationSet.create(doc, decorations);
    };

    const getHighlightKeywordsExtension = (getHighlightedKeywords) => {
        if (!getHighlightedKeywords) return null;

        return Extension.create({
            name: 'colorHighlighter',

            addProseMirrorPlugins() {
                return [
                    new Plugin({
                        state: {
                            init(_, { doc }) {
                                return getHighlightedKeywords(doc);
                            },
                            apply(transaction, oldState) {
                                return transaction.docChanged
                                    ? getHighlightedKeywords(transaction.doc)
                                    : oldState;
                            },
                        },
                        props: {
                            decorations(state) {
                                return this.getState(state);
                            },
                        },
                    }),
                ];
            },
        });
    };

    return (
        <EditorProvider
            slotBefore={<EditorToolbar />}
            extensions={[
                ...tiptapEditorExtensions,
                getHighlightKeywordsExtension(getHighlightedKeywords),
            ]}
            onUpdate={handleEditorChange}
        >
            <TipTapEditorSettings />
            <EditLinkContent />
        </EditorProvider>
    );
};

export default TipTapEditor;

Browser Used

Chrome

Code Example URL

https://codesandbox.io/p/sandbox/romantic-butterfly-r9v92m?file=%2Fsrc%2FApp.js%3A14%2C26&layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clw7qnqr20006356nkbdmkgf3%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clw7qnqr20002356noy51o85r%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clw7qnqr20003356n57oxv5q1%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clw7qnqr20005356nnto9lfux%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clw7qnqr20002356noy51o85r%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clw7qnqr20001356n2o757nk1%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252Findex.js%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522id%2522%253A%2522clw7rq4y70002356ss5y03162%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A14%252C%2522startColumn%2522%253A26%252C%2522endLineNumber%2522%253A14%252C%2522endColumn%2522%253A26%257D%255D%252C%2522filepath%2522%253A%2522%252Fsrc%252FApp.js%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clw7qnqr20002356noy51o85r%2522%252C%2522activeTabId%2522%253A%2522clw7rq4y70002356ss5y03162%2522%257D%252C%2522clw7qnqr20005356nnto9lfux%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clw7qnqr20004356n2zio9u1e%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A0%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522id%2522%253A%2522clw7qnqr20005356nnto9lfux%2522%252C%2522activeTabId%2522%253A%2522clw7qnqr20004356n2zio9u1e%2522%257D%252C%2522clw7qnqr20003356n57oxv5q1%2522%253A%257B%2522tabs%2522%253A%255B%255D%252C%2522id%2522%253A%2522clw7qnqr20003356n57oxv5q1%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Afalse%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D

Expected Behavior

Actual result:
<p>First Edition</p>

Expected result
<p><span class="highlight-keyword selected-keyword">First Edition</span></p>

Additional Context (Optional)

No response

Dependency Updates

  • Yes, I've updated all my dependencies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Open SourceThe issue or pull reuqest is related to the open source packages of Tiptap.

    Type

    Projects

    Status

    Needs Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions