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

Not working on Android Chrome #50

Open
jhuet opened this issue Dec 1, 2016 · 5 comments
Open

Not working on Android Chrome #50

jhuet opened this issue Dec 1, 2016 · 5 comments
Labels

Comments

@jhuet
Copy link

jhuet commented Dec 1, 2016

Hello,

The plugin doesn't seem to work on Chrome browser under Android OS. It does work on Chrome under iOS though.

Any thoughts ?

@StevenDevooght
Copy link
Owner

StevenDevooght commented Dec 1, 2016

Hi,

Don't know what the issue might be. A remote debugging session will tell us more...(https://developers.google.com/web/tools/chrome-devtools/remote-debugging/)

@jhuet
Copy link
Author

jhuet commented Jan 24, 2017

Hello @StevenDevooght,

We finally had a bit of time to dig into this problem. It appears that keypress event is now deprecated and not used by Chrome on Android anymore. More explanations can be found here.

As there is no real bug to fix, i guess the only way to make this work would be to code some kind of workaround such as this one ?

@StevenDevooght
Copy link
Owner

StevenDevooght commented Jan 24, 2017

According to this post the beforeinput event can be used.

@YCMitch
Copy link

YCMitch commented Jan 18, 2019

Hey @StevenDevooght

Just checking this still hasn't been fixed, has it? Certainly not complaining, just curious.

@brunoniconeves
Copy link

I solved the problem changing the keypress event by on('input'). This change was not simple, because the preventEventDefault stop to work with input event.

So i have to deal with double delimiter input problem...the code can be improved, but it do the job now. The code of the mention.js that i'm using rigth now is that (i also change some event like dismiss dropdown list on scroll event, not a good UX choice in my opinion):

`/*global tinymce, module, require, define, global, self */
var _to_ascii = {
'188': '44',
'109': '45',
'190': '46',
'191': '47',
'192': '96',
'220': '92',
'222': '39',
'221': '93',
'219': '91',
'173': '45',
'187': '61', //IE Key codes
'186': '59', //IE Key codes
'189': '45' //IE Key codes
}

var shiftUps = {
"96": "~",
"49": "!",
"50": "@",
"51": "#",
"52": "$",
"53": "%",
"54": "^",
"55": "&",
"56": "*",
"57": "(",
"48": ")",
"45": "_",
"61": "+",
"91": "{",
"93": "}",
"92": "|",
"59": ":",
"39": """,
"44": "<",
"46": ">",
"47": "?"
};

function getDelimiter(e) {
var c = e.which;

if (_to_ascii.hasOwnProperty(c)) {
    c = _to_ascii[c];
}

if (!e.shiftKey && (c >= 65 && c <= 90)) {
    c = String.fromCharCode(c + 32);
} else if (e.shiftKey && shiftUps.hasOwnProperty(c)) {
    c = shiftUps[c];
} else {
    c = String.fromCharCode(c);
}

return c;

}

;(function (f) {
'use strict';

// CommonJS
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = f(require('jquery'));

// RequireJS
} else if (typeof define === 'function' && define.amd) {
define(['jquery'], f);

// <script>
} else {
var g;
if (typeof window !== 'undefined') {
g = window;
} else if (typeof global !== 'undefined') {
g = global;
} else if (typeof self !== 'undefined') {
g = self;
} else {
g = this;
}

f(g.jQuery);

}

})(function ($) {
'use strict';

var AutoComplete = function (ed, options) {
    this.editor = ed;

    this.options = $.extend({}, {
        source: [],
        delay: 500,
        queryBy: 'name',
        items: 10
    }, options);

    this.options.insertFrom = this.options.insertFrom || this.options.queryBy;

    this.matcher = this.options.matcher || this.matcher;
    this.sorter = this.options.sorter || this.sorter;
    this.renderDropdown = this.options.renderDropdown || this.renderDropdown;
    this.render = this.options.render || this.render;
    this.insert = this.options.insert || this.insert;
    this.highlighter = this.options.highlighter || this.highlighter;

    this.query = '';
    this.hasFocus = true;

    this.renderInput();

    this.bindEvents();
};

AutoComplete.prototype = {

    constructor: AutoComplete,

    renderInput: function () {
        var rawHtml =  '<span id="autocomplete">' +
                            '<span id="autocomplete-delimiter"></span>' +
                            '<span id="autocomplete-searchtext"><span class="dummy">\uFEFF</span></span>' +
                        '</span>';


        tinyMCE.activeEditor.setContent(tinyMCE.activeEditor.getContent() + rawHtml);
        //this.editor.execCommand('mceInsertContent', false, rawHtml);
        this.editor.selection.select(this.editor.selection.dom.select('span#autocomplete-searchtext span')[0]);
        this.editor.selection.collapse(0);
    },

    bindEvents: function () {
        this.editor.on('keyup', this.editorKeyUpProxy = $.proxy(this.rteKeyUp, this));
        this.editor.on('keydown', this.editorKeyDownProxy = $.proxy(this.rteKeyDown, this), true);
        this.editor.on('click', this.editorClickProxy = $.proxy(this.rteClicked, this));

        $('body').on('click', this.bodyClickProxy = $.proxy(this.rteLostFocus, this));

        //$(this.editor.getWin()).on('scroll', this.rteScroll = $.proxy(function () { this.cleanUp(true); }, this));
    },

    unbindEvents: function () {
        this.editor.off('keyup', this.editorKeyUpProxy);
        this.editor.off('keydown', this.editorKeyDownProxy);
        this.editor.off('click', this.editorClickProxy);

        $('body').off('click', this.bodyClickProxy);

        $(this.editor.getWin()).off('scroll', this.rteScroll);
    },

    rteKeyUp: function (e) {
        switch (e.which || e.keyCode) {
        //DOWN ARROW
        case 40:
        //UP ARROW
        case 38:
        //SHIFT
        case 16:
        //CTRL
        case 17:
        //ALT
        case 18:
            break;

        //BACKSPACE
        case 8:
            if (this.query === '') {
                this.cleanUp(true);
            } else {
                this.lookup();
            }
            break;

        //TAB
        case 9:
        //ENTER
        case 13:
            var item = (this.$dropdown !== undefined) ? this.$dropdown.find('li.active') : [];
            if (item.length) {
                this.select(item.data());
                this.cleanUp(false);
            } else {
                this.cleanUp(true);
            }
            break;

        //ESC
        case 27:
            this.cleanUp(true);
            break;

        default:
            this.lookup();
        }
    },

    rteKeyDown: function (e) {
        switch (e.which || e.keyCode) {
         //TAB
        case 9:
        //ENTER
        case 13:
        //ESC
        case 27:
            e.preventDefault();
            break;

        //UP ARROW
        case 38:
            e.preventDefault();
            if (this.$dropdown !== undefined) {
                this.highlightPreviousResult();
            }
            break;
        //DOWN ARROW
        case 40:
            e.preventDefault();
            if (this.$dropdown !== undefined) {
                this.highlightNextResult();
            }
            break;
        }

        e.stopPropagation();
    },

    rteClicked: function (e) {
        var $target = $(e.target);

        if (this.hasFocus && $target.parent().attr('id') !== 'autocomplete-searchtext') {
            this.cleanUp(true);
        }
    },

    rteLostFocus: function () {
        if (this.hasFocus) {
            this.cleanUp(true);
        }
    },

    lookup: function () {
        this.query = $.trim($(this.editor.getBody()).find('#autocomplete-searchtext').text()).replace('\ufeff', '');

        if (this.$dropdown === undefined) {
            this.show();
        }

        clearTimeout(this.searchTimeout);
        this.searchTimeout = setTimeout($.proxy(function () {
            // Added delimiter parameter as last argument for backwards compatibility.
            var items = $.isFunction(this.options.source) ? this.options.source(this.query, $.proxy(this.process, this), this.options.delimiter) : this.options.source;
            if (items) {
                this.process(items);
            }
        }, this), this.options.delay);
    },

    matcher: function (item) {
        return ~item[this.options.queryBy].toLowerCase().indexOf(this.query.toLowerCase());
    },

    sorter: function (items) {
        var beginswith = [],
            caseSensitive = [],
            caseInsensitive = [],
            item;

        while ((item = items.shift()) !== undefined) {
            if (!item[this.options.queryBy].toLowerCase().indexOf(this.query.toLowerCase())) {
                beginswith.push(item);
            } else if (~item[this.options.queryBy].indexOf(this.query)) {
                caseSensitive.push(item);
            } else {
                caseInsensitive.push(item);
            }
        }

        return beginswith.concat(caseSensitive, caseInsensitive);
    },

    highlighter: function (text) {
        return text.replace(new RegExp('(' + this.query.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1') + ')', 'ig'), function ($1, match) {
            return '<strong>' + match + '</strong>';
        });
    },

    show: function () {
        var offset = this.editor.inline ? this.offsetInline() : this.offset();

        this.$dropdown = $(this.renderDropdown())
                            .css({ 'top': offset.top, 'left': offset.left });

        $('body').append(this.$dropdown);

        this.$dropdown.on('click', $.proxy(this.autoCompleteClick, this));
    },

    process: function (data) {
        if (!this.hasFocus) {
            return;
        }

        var _this = this,
            result = [],
            items = $.grep(data, function (item) {
                return _this.matcher(item);
            });

        items = _this.sorter(items);

        items = items.slice(0, this.options.items);

        $.each(items, function (i, item) {
            var $element = $(_this.render(item, i));

            $element.html($element.html().replace($element.text(), _this.highlighter($element.text())));

            $.each(items[i], function (key, val) {
                $element.attr('data-' + key, val);
            });

            result.push($element[0].outerHTML);
        });

        if (result.length) {
            this.$dropdown.html(result.join('')).show();
        } else {
            this.$dropdown.hide();
            this.$dropdown.find('li').removeClass('active');
        }
    },

    renderDropdown: function () {
        return '<ul class="rte-autocomplete dropdown-menu"><li class="loading"></li></ul>';
    },

    render: function (item, index) {
        return '<li>' +
                    '<a href="javascript:;"><span>' + item[this.options.queryBy] + '</span></a>' +
                '</li>';
    },

    autoCompleteClick: function (e) {
        var item = $(e.target).closest('li').data();
        if (!$.isEmptyObject(item)) {
            this.select(item);
            this.cleanUp(false);
        }
        e.stopPropagation();
        e.preventDefault();
    },

    highlightPreviousResult: function () {
        var currentIndex = this.$dropdown.find('li.active').index(),
            index = (currentIndex === 0) ? this.$dropdown.find('li').length - 1 : --currentIndex;

        this.$dropdown.find('li').removeClass('active').eq(index).addClass('active');
    },

    highlightNextResult: function () {
        var currentIndex = this.$dropdown.find('li.active').index(),
            index = (currentIndex === this.$dropdown.find('li').length - 1) ? 0 : ++currentIndex;

        this.$dropdown.find('li').removeClass('active').eq(index).addClass('active');
    },

    select: function (item) {
        this.editor.focus();
        var selection = this.editor.dom.select('span#autocomplete')[0];
        this.editor.dom.remove(selection);
        this.editor.execCommand('mceInsertContent', false, this.insert(item));
    },

    insert: function (item) {
        return '<span>' + item[this.options.insertFrom] + '</span>&nbsp;';
    },

    cleanUp: function (rollback) {
        this.unbindEvents();
        this.hasFocus = false;

        if (this.$dropdown !== undefined) {
            this.$dropdown.remove();
            delete this.$dropdown;
        }

        if (rollback) {
            var text = this.query,
                $selection = $(this.editor.dom.select('span#autocomplete'));

            if (!$selection.length) {
                return;
            }
                
            var replacement = $('<p>' + text + '</p>')[0].firstChild,
                focus = $(this.editor.selection.getNode()).offset().top === ($selection.offset().top + (($selection.outerHeight() - $selection.height()) / 2));

            this.editor.dom.replace(replacement, $selection[0]);

            if (focus) {
                this.editor.selection.select(replacement);
                this.editor.selection.collapse();
            }
        }
    },

    offset: function () {
        var rtePosition = $(this.editor.getContainer()).offset(),
            contentAreaPosition = $(this.editor.getContentAreaContainer()).position(),
            nodePosition = $(this.editor.dom.select('span#autocomplete')).position();

        return {
            top: rtePosition.top + contentAreaPosition.top + nodePosition.top + $(this.editor.selection.getNode()).innerHeight() - $(this.editor.getDoc()).scrollTop() + 5,
            left: rtePosition.left + contentAreaPosition.left + nodePosition.left
        };
    },

    offsetInline: function () {
        var nodePosition = $(this.editor.dom.select('span#autocomplete')).offset();

        return {
            top: nodePosition.top + $(this.editor.selection.getNode()).innerHeight() + 5,
            left: nodePosition.left
        };
    }


};


tinymce.create('tinymce.plugins.Mention', {

    init: function (ed) {

        var autoComplete,
            autoCompleteData = ed.getParam('mentions');

        // If the delimiter is undefined set default value to ['@'].
        // If the delimiter is a string value convert it to an array. (backwards compatibility)
        autoCompleteData.delimiter = (autoCompleteData.delimiter !== undefined) ? !$.isArray(autoCompleteData.delimiter) ? [autoCompleteData.delimiter] : autoCompleteData.delimiter : ['@'];

        function prevCharIsSpace() {
            var start = ed.selection.getRng(true).startOffset,
                  text = ed.selection.getRng(true).startContainer.data || '',
                charachter = text.substr(start > 0 ? start - 1 : 0, 1);

            //tinyMCE.activeEditor.execCommand('mceSetContent', false, text.substring(0, text.length - 1));
            //ed.selection.setRng(text.length);

            return true;//(!!$.trim(charachter).length) ? false : true;
        }

        ed.on('input', function (e) {
            if (e.data == "@" || e.data == "#") {
                var delimiterIndex = $.inArray(e.data, autoCompleteData.delimiter);
                if (delimiterIndex > -1 && prevCharIsSpace()) {
                    if (autoComplete === undefined || (autoComplete.hasFocus !== undefined && !autoComplete.hasFocus)) {
                        e.preventDefault();
                        // Clone options object and set the used delimiter.
                        autoComplete = new AutoComplete(ed, $.extend({}, autoCompleteData, { delimiter: autoCompleteData.delimiter[delimiterIndex] }));
                    }
                }
            }
        });

    },

    getInfo: function () {
        return {
            longname: 'mention',
            author: 'Steven Devooght',
            version: tinymce.majorVersion + '.' + tinymce.minorVersion
        };
    }
});

tinymce.PluginManager.add('mention', tinymce.plugins.Mention);

});`

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

No branches or pull requests

4 participants