fvilers/tiny-editor

Caret Utill

syonfox opened this issue · 2 comments

Tiny Editors need a way of dynamically getting and setting the caret position
I was able to do this in my project using this

// CaretUtil library, based on
// https://stackoverflow.com/questions/6249095/41034697#41034697
var CaretUtil = {};

/**
 * Set the caret position inside a contentEditable container
 */
CaretUtil.setCaretPosition = function (container, position) {
    if (position >= 0) {
        var selection = window.getSelection();
        var range = CaretUtil.createRange(container, {count: position});
        if (range != null) {
            range.collapse(false);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }
};

/**
 * Get the current caret position inside a contentEditable container
 */
CaretUtil.getCaretPosition = function (container) {
    var selection = window.getSelection();
    var charCount = -1;
    var node;
    if (selection.focusNode != null) {
        if (CaretUtil.isDescendantOf(selection.focusNode, container)) {
            node = selection.focusNode;
            charCount = selection.focusOffset;
            while (node != null) {
                if (node == container) {
                    break;
                }
                if (node.previousSibling != null) {
                    node = node.previousSibling;
                    charCount += node.textContent.length;
                } else {
                    node = node.parentNode;
                    if (node == null) {
                        break;
                    }
                }
            }
        }
    }
    return charCount;
};

/**
 * Returns true if the node is a descendant (or equal to) a parent
 */
CaretUtil.isDescendantOf = function (node, parent) {
    while (node != null) {
        if (node == parent) {
            return true;
        }
        node = node.parentNode;
    }
    return false;
};

CaretUtil.createRange = function (node, chars, range) {
    if (range == null) {
        range = window.document.createRange();
        range.selectNode(node);
        range.setStart(node, 0);
    }
    if (chars.count == 0) {
        range.setEnd(node, chars.count);
    } else if (node != null && chars.count > 0) {
        if (node.nodeType == 3) {
            if (node.textContent.length < chars.count) {
                chars.count -= node.textContent.length;
            } else {
                range.setEnd(node, chars.count);
                chars.count = 0;
            }
        } else {
            var _g = 0;
            var _g1 = node.childNodes.length;
            while (_g < _g1) {
                var lp = _g++;
                range = CaretUtil.createRange(node.childNodes[lp], chars, range);
                if (chars.count == 0) {
                    break;
                }
            }
        }
    }
    return range;
};
window.CaretUtil = CaretUtil; // global
// export default CaretUtil; //es6

This is how i used it for example

   setValue: (val, el) => {
            if (el) {
                // let oldpos = getCaretPosition(el);
                let oldpos = CaretUtil.getCaretPosition(el);
                if (val.data == el.innerHTML) {
                    console.log("tis my data, returning!");
                    return;
                }
                el.innerHTML = sanitizeHtmlString(val.data);
                //todo correct position if edit happend before
                // setCaret(el, oldpos);
                CaretUtil.setCaretPosition(el, oldpos)
                let newpos = CaretUtil.getCaretPosition(el);

                console.log('old,new caret: ', oldpos, newpos);

                // https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
                // el.setHTML(val.data);//https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML //too new
            }
        },

Should be a demo at https://bullchat.syon.ca under development
you need to create a login for the tinyEditor Sample to work by registering then logging out and in. but then it should sync when open on two devises. and not reset your curer position

Is this worth including in the library maybe as a separate file?

Hi, thanks for the suggestion. At the moment, the goal of tiny editor is to remain... tiny ! I don't plan to add extra feature like this right now. As you already did, this feature can be implemented outside of this library so I suppose this will be remain as is.

As I suspected just wanted to document :) thanks for the useful piece of code.