Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mentions not working #86

Open
snehlata08 opened this issue Jul 3, 2024 · 0 comments
Open

Mentions not working #86

snehlata08 opened this issue Jul 3, 2024 · 0 comments

Comments

@snehlata08
Copy link

snehlata08 commented Jul 3, 2024

I am trying to use mentions in the editor but somehow it is not working and throwing error.

Uncaught Error: [CKEDITOR.resourceManager.load] Resource name "{class:'mention'" was not found at "https://cdn.ckeditor.com/4.21.0/standard-all/plugins/{class:'mention'/plugin.js?t=N2M9".
    at window.CKEDITOR.window.CKEDITOR.dom.CKEDITOR.resourceManager.<anonymous> (cke

Here is my code

'grapesjs-plugin-ckeditor': {
                    options: {
                        versionCheck: false,
                        extraPlugins: [ MyCustomUploadAdapterPlugin, MentionLinks, 'WordCount'],
            toolbar: [
                    'heading',
                    '|',
                    'bold',
                    'italic',
                    'underline',
                    'link',
                    'bulletedList',
                    'numberedList',
                    '|',
                    'findAndReplace',
                    'outdent',
                    'indent',
                    'alignment',
                    '|',
                    'imageUpload',
                    'imageInsert',
                    'blockQuote',
                    'insertTable',
                    'mediaEmbed',
                    'undo',
                    'redo',
                    '-',
                    'fontFamily',
                    'fontBackgroundColor',
                    'fontColor',
                    'fontSize',
                    'removeFormat',
                    'horizontalLine',
                    '|',
                    'sourceEditing'
                ],
            language: 'en',
            wordCount: {
                showParagraphs: false,
                showWordCount: false,
                displayWords: false,
                showCharCount: true,
                countSpacesAsChars: true,
                countHTML: false,
                maxWordCount: -1,
                maxCharCount: 10,
                onUpdate: stats => {
                    var maxCharacters = 750;
                    var isLimitExceeded = stats.characters > maxCharacters;
                    var charCount = '';
                    var exc
                    if ( isLimitExceeded ) {
                        charCount = `<span class="text-danger"> <b>${maxCharacters}</b> characters allowed. Content exceeded by <b>${ stats.characters - maxCharacters }</b> characters.</span>
                                    <br><span class="text-danger charCountFlag" data-charcount=false></span>`;

                    } else {
                        charCount = `<span class="charCountFlag" data-charcount=true>Characters: <b>${ stats.characters }</b> of <b>${ maxCharacters }</b></span>`;
                        $("#mandatoryError").remove();
                    }
                    $( `${this.jQuerySelector}-word-count` ).html(`${ charCount }`);
                }
            },
            image: {
                toolbar: [
                    'imageTextAlternative',
                    'imageStyle:inline',
                    'imageStyle:block',
                    'imageStyle:full',
                    'imageStyle:alignLeft',
                    'imageStyle:alignRight',
                ],
                resizeUnit: 'px'
            },
            mediaEmbed: {
                extraProviders: [
                    {
                        name: 'xyz',
                        url: /^edms\.xyz\.(\w+)\.com\/documents\/documents\/(\d+)\/download/,
                        html: match => {
                            var rawUrl = match[0];
                            return (`
                                <video class="" controls="" src="https://${ rawUrl }" preload="metadata" style="height: 100%; width: 100%;" title="">
                                </video>
                            `);
                        }
                    }
                ],
                previewsInData: true
            },
            alignment: {
                options: ['left', 'right', 'center', 'justify']
            },
            table: {
                contentToolbar: [
                    'tableColumn',
                    'tableRow',
                    'mergeTableCells',
                    'tableCellProperties',
                    'tableProperties'
                ]
            },
            licenseKey: '',
            mention: {
                dropdownLimit: 500,
                feeds: [
//                    {
//                        marker: '@',
//                        feed: getFeedItems,
//                        minimumCharacters: 1
//                    },

                    {
                        marker: '@',
                        feed: getNodeItems,
                        minimumCharacters: 1,
                        itemRenderer: customItemRenderer,

                    }
                ]
            }
                    },
                    },
          'grapesjs-plugin-dynamic-columns': {}
        },
    });

    // COPIED from https://ckeditor.com/docs/ckeditor5/latest/framework/guides/deep-dive/upload-adapter.html
class MyUploadAdapter {
    constructor( loader ) {
        // The file loader instance to use during the upload.
        this.loader = loader;
    }

    // Starts the upload process.
    upload() {
        return this.loader.file
            .then( file => new Promise( ( resolve, reject ) => {
                this._initRequest();
                this._initListeners( resolve, reject, file );
                this._sendRequest( file );
            } ) );
    }

    // Aborts the upload process.
    abort() {
        if ( this.xhr ) {
            this.xhr.abort();
        }
    }

    // Initializes the XMLHttpRequest object using the URL passed to the constructor.
    _initRequest() {
        const xhr = this.xhr = new XMLHttpRequest();

        // Note that your request may look different. It is up to you and your editor
        // integration to choose the right communication channel. This example uses
        // a POST request with JSON as a data structure but your configuration
        // could be different.
        xhr.open( 'POST', '/file_upload/', true );
        xhr.responseType = 'json';
    }

    // Initializes XMLHttpRequest listeners.
    _initListeners( resolve, reject, file ) {
        const xhr = this.xhr;
        const loader = this.loader;
        const genericErrorText = `Couldn't upload file: ${ file.name }.`;

        xhr.addEventListener( 'error', () => reject( genericErrorText ) );
        xhr.addEventListener( 'abort', () => reject() );
        xhr.addEventListener( 'load', () => {
            const response = xhr.response;

            // This example assumes the XHR server's "response" object will come with
            // an "error" which has its own "message" that can be passed to reject()
            // in the upload promise.
            //
            // Your integration may handle upload errors in a different way so make sure
            // it is done properly. The reject() function must be called when the upload fails.
            if ( !response || response.error ) {
                return reject( response && response.error ? response.error.message : genericErrorText );
            }

            // If the upload is successful, resolve the upload promise with an object containing
            // at least the "default" URL, pointing to the image on the server.
            // This URL will be used to display the image in the content. Learn more in the
            // UploadAdapter#upload documentation.
            resolve( {
                default: response.file_path.replace("/home", "")
            } );
        } );

        // Upload progress when it is supported. The file loader has the #uploadTotal and #uploaded
        // properties which are used e.g. to display the upload progress bar in the editor
        // user interface.
        if ( xhr.upload ) {
            xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken"));
            xhr.upload.addEventListener('progress', evt => {
                if ( evt.lengthComputable ) {
                    loader.uploadTotal = evt.total;
                    loader.uploaded = evt.loaded;
                }
            } );
        }
    }

    // Prepares the data and sends the request.
    _sendRequest( file ) {
        // Prepare the form data.
        const data = new FormData();
        data.append( 'file', file );

        // Important note: This is the right place to implement security mechanisms
        // like authentication and CSRF protection. For instance, you can use
        // XMLHttpRequest.setRequestHeader() to set the request headers containing
        // the CSRF token generated earlier by your application.

        // Send the request.
        this.xhr.send( data );
    }
}

function MyCustomUploadAdapterPlugin( editor ) {
    editor.plugins.get('FileRepository').createUploadAdapter = ( loader ) => {
        // Configure the URL to the upload script in your back-end here!
        return new MyUploadAdapter( loader );
    };
}

// TODO Commented code for future reference
//function getFeedItems( queryText ) {
//    return new Promise( resolve => {
//        setTimeout( () => {
//            $.ajax({
//                url : "/get_user_list/?queryText="+queryText,
//                type : "GET",
//                success : function(data){
//                    var data = JSON.parse(data);
//                    resolve( data );
//                },
//                error: handleAJAXError
//            });
//        }, 100 );
//    } );
//}

function getNodeItems( queryText ){
    return new Promise( resolve =>{
        setTimeout(()=>{
            $.ajax({
                url : "/typeahead/?q="+queryText,
                type : "GET",
                success : function(data){
                    var data = JSON.parse(data);
                    var result = []
                    last_type_added = null
                    for(node of data) {
                        var _type = node['labels'].split("_").join(" ")
                        var _title = node['title']
                        var _display = node['display']?node['display']:""
                        var _hint = node['hint']?node['hint']:null
                        if (last_type_added != _type) {
                            last_type_added = _type;
                        }
                        result.push({id: "@" + _type + "." + _title, type: _type, title: _title})
                    }
                    resolve( result );
                }
            });

        }, 100);
    });
}

/*
 * This plugin customizes the way mentions are handled in the editor model and data.
 * Instead of a classic <span class="mention"></span>,
 */
function MentionLinks( editor ) {
    // The upcast converter will convert a view
    //
    //		<a href="..." class="mention" data-mention="...">...</a>
    //
    // element to the model "mention" text attribute.
    editor.conversion.for( 'upcast' ).elementToAttribute( {
        view: {
            name: 'a',
            key: 'data-mention',
            classes: 'mention',
            attributes: {
                href: true,
                target: true,
            }
        },
        model: {
            key: 'mention',
            value: viewItem => editor.plugins.get( 'Mention' ).toMentionAttribute( viewItem )
        },
        converterPriority: 'high'
    } );

    // Downcast the model "mention" text attribute to a view
    //
    //		<a href="..." class="mention" data-mention="...">...</a>
    //
    // element.
    editor.conversion.for( 'downcast' ).attributeToElement( {
        model: 'mention',
        view: ( modelAttributeValue, { writer } ) => {
            // Do not convert empty attributes (lack of value means no mention).
            if ( !modelAttributeValue ) {
                return;
            }

            const nt_title = modelAttributeValue.id.slice(1).split(/\.(.+)/);
            const nt = encodeURIComponent(nt_title[0]);
            const title = encodeURIComponent(nt_title[1]);

            return writer.createAttributeElement( 'a', {
                class: 'mention',
                'data-mention': modelAttributeValue.id,
                target: "_blank",
                href: `https://${window.location.hostname}/draw_graph/?ntype1=${nt}&title1=${title}&focusWithPropWin=true`
            }, {
                // Make mention attribute to be wrapped by other attribute elements.
                priority: 20,
                // Prevent merging mentions together.
                id: modelAttributeValue.id
            } );
        },
        converterPriority: 'high'
    } );
}

function customItemRenderer( item ) {
    const itemElement = document.createElement('div');
    itemElement.innerHTML = `<span style='font-weight: bold'>@${ item.type }.</span>`;

    const usernameElement = document.createElement('span');
    usernameElement.innerHTML = synthesize_string(item.title);
    itemElement.appendChild(usernameElement);
    $(".ck-balloon-panel").css("z-index", 9999);
    return itemElement;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant