All Downloads are FREE. Search and download functionalities are using the official Maven repository.

a.hyperscala-contenteditor_2.11.0.10.3.source-code.hyperscala-contenteditor.js Maven / Gradle / Ivy

// Support capitalizing text
String.prototype.capitalize = function () {
    return this.replace(/[a-zA-Z0-9]+/g, function (txt) {
        return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
    });
};

var ContentEditor = {
    instances: {},
    listeners: {},
    current: null,
    focused: function() {
        if (ContentEditor.current != null && $(ContentEditor.current).is("[contenteditable='true']")) {
            return ContentEditor.current;
        } else {
            return null;
        }
    },
    byId: function(id) {
        var instance = this.instances[id];
        if (instance == null) {
            instance = new ContentEditorInstance(id);
            this.instances[id] = instance;
        }
        return instance;
    },
    exists: function(id) {
        return this.instances[id] != null;
    },
    on: function(key, f) {
        if (this.listeners[key] == null) {
            this.listeners[key] = [];
        }
        this.listeners[key].push(f);
    },
    fire: function(key, evt) {
        if (this.listeners[key]) {
            for (var i = 0; i < this.listeners[key].length; i++) {
                this.listeners[key][i](evt);
            }
        }
    },
    lastStyle: null,
    check: function() {
        var focused = ContentEditor.focused();
        var activeId = null;
        if (focused != null) {
            activeId = $(focused).attr('id');
        }
        if (this.instances[activeId]) {       // Only fire change if editable is focused
            var instance = this.instances[activeId];

            // Check style changes
            var currentStyle = this.style2JSON(contentEditor.selectionInfo().scopedStyle);
            if (JSON.stringify(currentStyle) != JSON.stringify(this.lastStyle)) {
                this.fire('styleChanged', {
                    instance: instance,
                    style: currentStyle,
                    previous: this.lastStyle
                });
                this.lastStyle = currentStyle;
            }

            // Check content changes
            var currentContent = instance.$element.html();
            if (currentContent != instance.content) {
                if (instance.content != null) {         // Don't send changes when content is first set
                    this.fire('contentChanged', {
                        instance: instance,
                        content: currentContent,
                        previous: instance.content
                    });
                }
                instance.content = currentContent;
            }
        }
    },
    bindInput: function(id, key, doc, valueCleaner, tagName) {
        if (doc == null) doc = document;
        var element = doc.jQuery('#' + id);
        if (element.size() == 0) {
            realtime.error('ContentEditorInstance.bind: Unable to find element by id ' + id + ' (style: ' + key + ')');
        } else {
            element.change(function() {
                var focused = ContentEditor.focused();
                var activeId = null;
                if (focused != null) {
                    activeId = $(focused).attr('id');
                }
                if (ContentEditor.exists(activeId)) {
                    var value = element.val();
                    if (valueCleaner != null) {
                        value = valueCleaner(value);
                        element.val(value);
                    }
                    ContentEditor.byId(activeId).set(key, value, tagName);
                }
            });
            ContentEditor.on('styleChanged', function(evt) {
                element.val(evt.style[key]);
            });
        }
    },
    /**
     * Custom binding to allow more advanced style bindings.
     *
     * @param id the id of the field with the value
     * @param doc the document (uses document if null)
     * @param fromString function(value: String, instance: ContentEditorInstance) to set the style from the value changed in the field.
     * @param toString function(styleChangeEvent: {instance, style, previous}): String to get the style String from the current style.
     */
    bindCustom: function(id, doc, fromString, toString) {
        if (doc == null) doc = document;
        var element = doc.jQuery('#' + id);
        if (element.size() == 0) {
            realtime.error('ContentEditorInstance.bind: Unable to find element by id ' + id + ' (style: ' + key + ')');
        } else {
            element.change(function() {
                var focused = ContentEditor.focused();
                var activeId = null;
                if (focused != null) {
                    activeId = $(focused).attr('id');
                }
                if (ContentEditor.exists(activeId)) {
                    fromString(element.val(), ContentEditor.byId(activeId));
                }
            });
            ContentEditor.on('styleChanged', function(evt) {
                element.val(toString(evt));
            });
        }
    },
    bindFontStyle: function(id, doc) {
        var weight2Internal = {'Thin': '100', 'Light': '300', 'Medium': '500', 'Extra-Bold': '800', 'Ultra-Bold': '900'};
        var weight2Visual = {
            '100': 'Thin',
            '300': 'Light',
            '400': 'Normal',
            '500': 'Medium',
            '700': 'Bold',
            '800': 'Extra-Bold',
            '900': 'Ultra-Bold'
        };
        var fromString = function(value, instance) {
            var index = value.lastIndexOf(' ');
            var parser = [value];
            if (index > 0) {
                parser = [value.substring(0, index), value.substring(index + 1)];
            }
            for (var i = 0; i < parser.length; i++) {
                parser[i] = parser[i].capitalize();
            }
            var w = 'normal';
            var s = 'normal';
            if (parser.length == 1) {
                if (parser[0].toLowerCase() == 'italic' || parser[0].toLowerCase() == 'oblique') {
                    s = parser[0];
                } else {
                    w = parser[0];
                    if (w in weight2Internal) {
                        w = weight2Internal[w];
                    }
                }
            } else {
                w = parser[0];
                if (w in weight2Internal) {
                    w = weight2Internal[w];
                }
                s = parser[1];
            }
            w = w.toLowerCase();
            s = s.toLowerCase();
            instance.set('font-weight', w);
            instance.set('font-style', s);
        };
        var toString = function(styleChangeEvent) {
            var weight = styleChangeEvent.style['font-weight'];
            if (weight in weight2Visual) {
                weight = weight2Visual[weight];
            }
            var style = styleChangeEvent.style['font-style'];
            if (style == null) {
                style = 'normal';
            }
            style = style.capitalize();

            var s = '';
            if (weight != null && (weight.toLowerCase() != 'normal' || style == null || style.toLowerCase() == 'normal')) {
                s = weight;
            }
            if (style != null && style.toLowerCase() != 'normal') {
                if (s != '') {
                    s += ' ';
                }
                s += style;
            }
            return s.capitalize();
        };
        this.bindCustom(id, doc, fromString, toString);
    },
    bindChanges: function() {
        ContentEditor.on('contentChanged', function(evt) {
            realtime.sendLimited('contentChanged.' + evt.instance.id, {
                type: 'contentChanged',
                id: evt.instance.id,
                oldValue: evt.previous,
                newValue: evt.content
            }, 500);        // TODO: configure per-instance maximumRate
        });
    },
    style2JSON: function(style) {
        var json = {};
        for (var i = 0; i < style.length; i++) {
            json[style[i]] = style[style[i]];
        }
        return json;
    }
};

$(document).on('selectionchange', function() {
    ContentEditor.check();
});
$(document).keyup(function() {
    ContentEditor.check();
});
$(document).on('focus', "[contenteditable='true']", function() {
    ContentEditor.current = this;
});

ContentEditor.bindChanges();        // TODO: allow optionally disabling this or making it limited to specific instances.

var ContentEditorInstance = function(id) {
    this.id = id;
    this.element = document.getElementById(id);
    this.$element = $(this.element);
    this.content = null;
    if (this.element == null) {
        throw 'Element not found by id: ' + id;
    }
    this.formatters = {};
};

ContentEditorInstance.prototype.focused = function() {
    $(this.element).focus();
    return true;
};

ContentEditorInstance.prototype.stylize = function(key, value, tagName) {
    if (this.focused()) {
        var formatter = this.formatters[key];
        if (formatter == null) {
            formatter = contentEditor.createClassWrapper('stylized-' + key, {
                tagName: tagName
            });
            this.formatters[key] = formatter;
        }
        formatter.undo();
        formatter.style[key] = value;
        formatter.apply();
        ContentEditor.check();
    }
};

ContentEditorInstance.prototype.style = function(key) {
    return contentEditor.selectionInfo().scopedStyle[key];
};

ContentEditorInstance.prototype.computedStyle = function(key) {
    return contentEditor.selectionInfo().computedStyle[key];
};

ContentEditorInstance.prototype.hasStyle = function(key, value) {
    return this.style(key) == value;
};

ContentEditorInstance.prototype.toggle = function(key, value, reverse, tagName) {
    if (reverse == null) {
        reverse = '';
    }
    if (this.hasStyle(key, value)) {
        this.stylize(key, reverse, tagName);
    } else {
        this.stylize(key, value, tagName);
    }
};

ContentEditorInstance.prototype.set = function(key, value, tagName) {
    this.stylize(key, value, tagName);
};

ContentEditorInstance.prototype.insert = function(tagName, details) {
    if (this.focused()) {
        var formatter = this.formatters[tagName];
        if (formatter == null) {
            formatter = contentEditor.createHtmlWrapper(tagName);
            this.formatters[tagName] = formatter;
        }
        formatter.insert(details);
        ContentEditor.check();
    }
};

ContentEditorInstance.prototype.insertHTML = function(html) {
    if (this.focused()) {
        var template = document.createElement('template');
        template.innerHTML = html;
        var formatter = contentEditor.createHtmlWrapper(template.content);
        formatter.insert();
        ContentEditor.check();
    }
};

ContentEditorInstance.prototype.wrap = function(tagName, details) {
    if (this.focused()) {
        var formatter = this.formatters[tagName];
        if (formatter == null) {
            formatter = contentEditor.createHtmlWrapper(tagName);
            this.formatters[tagName] = formatter;
        }
        formatter.wrap(details);
        ContentEditor.check();
    }
};

ContentEditorInstance.prototype.wrapHTML = function(html) {
    if (this.focused()) {
        var template = document.createElement('template');
        template.innerHTML = html;
        var formatter = contentEditor.createHtmlWrapper(template.content);
        formatter.wrap();
        ContentEditor.check();
    }
};

ContentEditorInstance.prototype.clearFormatting = function() {
    if (this.focused()) {
        contentEditor.clearFormating();
    }
};

ContentEditorInstance.prototype.adjustStyle = function(styleName, adjuster, tagName) {
    if (this.focused()) {
        var current = this.computedStyle(styleName);
        var updated = adjuster(current);
        this.set(styleName, updated, tagName);
    }
};




© 2015 - 2025 Weber Informatics LLC | Privacy Policy