<commit_before>define(['underscore', 'require', 'rangy', 'jquery', './select-actors-list', './select-statuses-list', 'ngDialog', 'authentication', 'filters/moment'], function(_, require, rangy, $, actorList, statusesList) { 'use strict';

    var roman = [ undefined, 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV', 'XVI', 'XVII', 'XVIII', 'XIX', 'XX', 'XXI', 'XXII', 'XXII', 'XXIV', 'XXV', 'XXVI', 'XXVII', 'XXVIII', 'XXIX', 'XXX' ];

    return ['$scope', '$http', '$route', '$location', '$filter', '$q', 'ngDialog', function($scope, $http, $route, $location, $filter, $q, ngDialog) {

        var treaty = "CBD";
        var body   = "COP";
        var session = roman.indexOf($route.current.params.meeting.toUpperCase());
        var decision = parseInt($route.current.params.number);
        var selectedElement = null;

        var data = { title: 'agenda', content: 'loading...' };

        $scope.symbol = roman[session] + '/' + decision;
        $scope.save   = save;
        $scope.upload = upload;
        $scope.selectActors = selectActors;
        $scope.deleteActor  = deleteActor;
        $scope.selectStatuses = selectStatuses;
        $scope.deleteStatus   = deleteStatus;
        $scope.selectDecision = selectDecision;
        $scope.deleteDecision = deleteDecision;
        $scope.selectNotification = selectNotification;
        $scope.deleteNotification = deleteNotification;
        $scope.lookupNotification = lookupNotification;
        $scope.actionEdit  = edit;
        $scope.isEditable  = isEditable;
        $scope.tag         = tag;
        $scope.toggle      = toggle;
        $scope.actionBox   = function(tag) { surroundSelection(tag); };
        $scope.actionUnbox = function()    { unsurroundSelection(); };
        $scope.actionClean = function()    { removeSelectionFormatting(); };
        $scope.element     = {};
        $scope.actorsMap   = _(actorList   ).reduce(function(r,v){ r[v.code] = v; return r; }, {});
        $scope.statusesMap = _(statusesList).reduce(function(r,v){ r[v.code] = v; return r; }, {});

        load();

        return this;

        //************************************************************
        //************************************************************
        //************************************************************
        //************************************************************

        //===========================
        //
        //===========================
        function load() {

            //var data = { title: 'agenda', content: $('#content').html() };

            $.ajax({method: 'GET', url: 'https://api.cbd.int/api/v2015/tests?q={"decision":"'+$scope.symbol+'"}&fo=1', contentType: 'application/json' }).done(function( msg ) {

                msg = msg || { decision: $scope.symbol, content: 'paste here' };

                data = msg;

                $('#content').html(msg.content);

                clean($('#content')[0]);
                clean($('#content')[0]);
                clean($('#content')[0]);

                $('#content,element').mousedown(function (event) {

                    event.stopPropagation();

                    var node = this;

                    $scope.$applyAsync(function(){
                        selectNode(node);
                    });
                });
            });
        }

        //===========================
        //
        //===========================
        function save() {

            selectNode(null);

            data.title = "agenda";
            data.content = $('#content').html();

            if(!data._id) {
                $.ajax({method: "POST", url: "https://api.cbd.int/api/v2015/tests", data: JSON.stringify(data), contentType: "application/json" }).done(function( msg ) {
                    data._id = msg.id;
                    alert( "Your document has been successfully created." );
                });
            } else {
                $.ajax({method: "PUT", url: "https://api.cbd.int/api/v2015/tests/"+data._id, data: JSON.stringify(data), contentType: "application/json" }).done(function() {
                    alert( "Your document has been successfully updated." );
                });
            }
        }

        //===========================
        //
        //===========================
        function surroundSelection(tag) {

            var range = window.getSelection().getRangeAt(0);

            var commonAncestor = $(range.commonAncestorContainer);

            if(!commonAncestor.is("#content") && !commonAncestor.parents('#content').size()) {
                alert('Please select text from the decision');
                return;
            }

            var span = document.createElement(tag);
            var content = range.extractContents();

            span.appendChild(content);
            range.insertNode(span);

            clean($('#content')[0]);
            clean($('#content')[0]);
            clean($('#content')[0]);

            $('element').mousedown(function () { selectNode(this); });

            selectNode(span);
        }

        //===========================
        //
        //===========================
        function unsurroundSelection() {

            if(!selectedElement) return alert('no selectedElement');

            $(selectedElement).contents().unwrap();

            selectedElement = null;
        }

        //===========================
        //
        //===========================
        function edit(editable) {

            var $div = $('#content');

            editable = editable===undefined ? !isEditable() : !!editable;

            $div.prop('contenteditable', editable);

            if(editable)
                $div.focus();
        }

        //===========================
        //
        //===========================
        function isEditable() {
            return $('#content').prop('contenteditable') == 'true';
        }

        //===========================
        //
        //===========================
        function selectNode(node) {

            delete $scope.formattingLocked;

            $(selectedElement).attr('data-info', JSON.stringify($scope.element));

            $(selectedElement).removeClass('selected');

            selectedElement = node;

            $(selectedElement).addClass('selected');

            $scope.element = $(selectedElement).data('info');

            if($scope.element && $scope.element.data && $scope.element.data.type == 'information')
                $scope.element.data.type = 'informational';

            $scope.formattingLocked = !!($scope.element && $scope.element.data);
        }

        //===========================
        //
        //===========================
        function tag() {

            if(!selectedElement) return;
            if(!$scope.element)  return;

            var element = $scope.element;

            element.section   = element.section  ||undefined;
            element.paragraph = element.paragraph||undefined;
            element.item      = element.item     ||undefined;
            element.subitem   = element.subitem  ||undefined;

            element.data = element.data || {
                treaty: treaty,
                body: body,
                session : session,
                decision : decision
            };

            var data = element.data;

            data.section   = element.section   && element.section.toUpperCase();
            data.paragraph = element.paragraph && parseInt(element.paragraph);
            data.item      = element.item      && element.item.charCodeAt(0) - 'a'.charCodeAt(0) + 1;
            data.subitem   = element.subitem   && roman.indexOf(element.subitem.toUpperCase());

            data.code  = data.treaty + '/' + data.body + '/' + pad(data.session) + '/' + pad(data.decision);
            data.code += '.'+(data.section||'') + pad(element.paragraph);

            if(data.item)                 data.code += '.'+pad(data.item);
            if(data.item && data.subitem) data.code += '.'+pad(data.subitem);

            // tag

            var tag = element.type || '';

            if(element.section)   tag += ' ' + element.section;
            if(element.paragraph) tag += ' ' + element.paragraph;
            if(element.item)      tag += ''  + element.item;
            if(element.subitem)   tag += ' (' + element.subitem + ')';

            $(selectedElement).attr('data-type', tag);

        }

        //==============================
        //
        //==============================
        function pad(input) {

            var output = (input || '').toString();

            while(output.length<2)
                output = '0' + output;

            return output;
        }

        //==============================
        //
        //==============================
        function toggle(array, value) {

            array = array || [];

            if(~array.indexOf(value)) {
                while(~array.indexOf(value))
                    array.splice(array.indexOf(value), 1);
            }
            else {
                array.push(value);
            }

            if(!array.length)
                array = undefined;

            return array;
        }

        //===========================
        //
        //===========================
        function clean(node) {

            for(var n = 0; n < node.childNodes.length; n ++) {

                var child = node.childNodes[n];

                if(child.nodeType === 8 || (child.nodeType === 3 && !/\S/.test(child.nodeValue))) {

                    node.removeChild(child);
                    n --;

                } else if(child.nodeType === 1) {

                    clean(child);

                    if(!child.innerHTML)
                        node.removeChild(child);
                }
            }
        }

        //===========================
        //
        //===========================
        function upload() {

            var fileUpload = $("#fileUpload");

            fileUpload.bind("change", upload_fileSelected);
            fileUpload.click();
        }

        //===========================
        //
        //===========================
        function upload_fileSelected() {

            var fileUpload = $("#fileUpload");

            fileUpload.unbind("change", upload_fileSelected);

            var file = fileUpload[0].files[0];

            if(!file)
                return;

            $scope.element.files = $scope.element.files || [];
            $scope.element.files.push({ filename : file.name.toLowerCase(), size : file.size });
            $scope.$digest();
        }

        //************************************************************

        //===========================
        //
        //===========================
        function selectActors() {

            openDialog('./select-actors-dialog', { showClose: false, resolve : { actors: function() { return $scope.element.data.actors; } } }).then(function(dialog){

                dialog.closePromise.then(function(res){

                    if(!res.value)
                        return;

                    var actors = res.value.actors;

                    if(actors && !actors.length)
                        actors = undefined;

                    $scope.element.data.actors = actors;
                });
            });
        }

        //===========================
        //
        //===========================
        function deleteActor(item) {

            if(!$scope.data && !$scope.element.data)
                return;

            var items = $scope.element.data.actors || [];
            var index = items.indexOf(item);

            if(index>=0) {
                items.splice(index, 1);
            }

            $scope.element.data.actors = items.length ? items : undefined;
        }

            //===========================
            //
            //===========================
            function selectStatuses() {

                openDialog('./select-statuses-dialog', { showClose: false, resolve : { statuses: function() { return $scope.element.data.statuses; } } }).then(function(dialog){

                    dialog.closePromise.then(function(res){

                        if(!res.value)
                            return;

                        var statuses = res.value.statuses;

                        if(statuses && !statuses.length)
                            statuses = undefined;

                        $scope.element.data.statuses = statuses;
                    });
                });
            }

            //===========================
            //
            //===========================
            function deleteStatus(item) {

                if(!$scope.data && !$scope.element.data)
                    return;

                var items = $scope.element.data.statuses || [];
                var index = items.indexOf(item);

                if(index>=0) {
                    items.splice(index, 1);
                }

                $scope.element.data.statuses = items.length ? items : undefined;
            }

        //===========================
        //
        //===========================
        function selectDecision() {

            openDialog('./select-decision-dialog', { showClose: false }).then(function(dialog){

                dialog.closePromise.then(function(res){

                    if(!res.value)
                        return;

                    $scope.element.decisions = $scope.element.decisions || [];
                    $scope.element.decisions.push(res.value);
                });
            });
        }

        //===========================
        //
        //===========================
        function deleteDecision(item) {

            var items = $scope.element.decisions || [];
            var index = items.indexOf(item);

            if(index>=0) {
                items.splice(index, 1);
            }
        }

        //===========================
        //
        //===========================
        function selectNotification() {

            openDialog('./select-notification-dialog', { showClose: false }).then(function(dialog){

                dialog.closePromise.then(function(res){

                    if(!res.value)
                        return;

                    $scope.element.data.notifications = $scope.element.data.notifications || [];
                    $scope.element.data.notifications.push(res.value.symbol);
                });
            });
        }

        //===========================
        //
        //===========================
        function deleteNotification(item) {

            var items = $scope.element.notifications || [];
            var index = items.indexOf(item);

            if(index>=0)
                items.splice(index, 1);
        }

        //===========================
        //
        //===========================
        var __notifications;
        function lookupNotification(code) {

            __notifications = __notifications||{};

            if(__notifications[code]===undefined) {

                __notifications[code] = code;

                var options = {
                    cache : true,
                    params : {
                        q : "schema_s:notification AND symbol_s:"+code,
                        fl : "symbol_?,reference_?,title_?,date_*,url_*",
                        rows: 1
                    }
                 };

                $http.get("/api/v2013/index", options).then(function(res){

                    var results = res.data.response;
                    __notifications[code] = results.numFound ? results.docs[0] : null;

                    return __notifications[code];
                });
            }

            return __notifications[code];
        }

        //===========================
        //
        //===========================
        function openDialog(dialog, options) {

            options = options || {};

            return $q(function(resolve, reject) {

                require(['text!'+dialog+'.html', dialog], function(template, controller) {

                    options.plain = true;
                    options.template = template;
                    options.controller = controller;

                    var dialog = ngDialog.open(options);

                    dialog.closePromise.then(function(res){
                        console.log(res.value);

                        if(res.value=="$escape")      delete res.value;
                        if(res.value=="$document")    delete res.value;
                        if(res.value=="$closeButton") delete res.value;

                        return res;
                    });

                    resolve(dialog);

                }, reject);
            });
        }
    }];

    var getComputedDisplay = (typeof window.getComputedStyle != "undefined") ?
    function(el) {
        return window.getComputedStyle(el, null).display;
    } :
    function(el) {
        return el.currentStyle.display;
    };

    function replaceWithOwnChildren(el) {
        var parent = el.parentNode;
        while (el.hasChildNodes()) {
            parent.insertBefore(el.firstChild, el);
        }
        parent.removeChild(el);
    }

    function removeSelectionFormatting() {
        var range, sel = rangy.getSelection();

        if(sel.isCollapsed)
            return;

        for (var r = 0; r < sel.rangeCount; ++r) {

            var commonAncestor = $(sel.getRangeAt(r).commonAncestorContainer);

            if(!commonAncestor.is("#content") && !commonAncestor.parents('#content').size()) {
                alert('Please select text only from the decision');
                return;
            }
        }

        for (var i = 0; i < sel.rangeCount; ++i) {
            range = sel.getRangeAt(i);

            // Split partially selected nodes
            range.splitBoundaries();

            // Get formatting elements. For this example, we'll count any
            // element with display: inline, except <br>s.
            var formattingEls = range.getNodes([1], function(el) {
                return el.tagName != "BR";// && getComputedDisplay(el) == "inline";
            });

            // Remove the formatting elements
            for (var j = 0; j<formattingEls.length; j++) {
                replaceWithOwnChildren(formattingEls[j]);
            }
        }
    }
});

(function(){

    var cache = [0],
        expando = 'data' +new Date();

    function data(elem) {

        var cacheIndex = elem[expando],
            nextCacheIndex = cache.length;

        if(!cacheIndex) {
            cacheIndex = elem[expando] = nextCacheIndex;
            cache[cacheIndex] = {};
        }

        return {
            get : function(key) {
                return cache[cacheIndex][key];
            },
            set : function(key, val) {
                cache[cacheIndex][key] = val;
                return val;
            }
        }

    }

    window.data = data;

})();
<commit_msg>Use angular/$http instead of jquery/$.ajax<commit_after>define(['underscore', 'require', 'rangy', 'jquery', './select-actors-list', './select-statuses-list', 'ngDialog', 'authentication', 'filters/moment'], function(_, require, rangy, $, actorList, statusesList) { 'use strict';

    var roman = [ undefined, 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV', 'XVI', 'XVII', 'XVIII', 'XIX', 'XX', 'XXI', 'XXII', 'XXII', 'XXIV', 'XXV', 'XXVI', 'XXVII', 'XXVIII', 'XXIX', 'XXX' ];

    return ['$scope', '$http', '$route', '$location', '$filter', '$q', 'ngDialog', function($scope, $http, $route, $location, $filter, $q, ngDialog) {

        var treaty = "CBD";
        var body   = "COP";
        var session = roman.indexOf($route.current.params.meeting.toUpperCase());
        var decision = parseInt($route.current.params.number);
        var selectedElement = null;

        var data = { title: 'agenda', content: 'loading...' };

        $scope.symbol = roman[session] + '/' + decision;
        $scope.save   = save;
        $scope.upload = upload;
        $scope.selectActors = selectActors;
        $scope.deleteActor  = deleteActor;
        $scope.selectStatuses = selectStatuses;
        $scope.deleteStatus   = deleteStatus;
        $scope.selectDecision = selectDecision;
        $scope.deleteDecision = deleteDecision;
        $scope.selectNotification = selectNotification;
        $scope.deleteNotification = deleteNotification;
        $scope.lookupNotification = lookupNotification;
        $scope.actionEdit  = edit;
        $scope.isEditable  = isEditable;
        $scope.tag         = tag;
        $scope.toggle      = toggle;
        $scope.actionBox   = function(tag) { surroundSelection(tag); };
        $scope.actionUnbox = function()    { unsurroundSelection(); };
        $scope.actionClean = function()    { removeSelectionFormatting(); };
        $scope.element     = {};
        $scope.actorsMap   = _(actorList   ).reduce(function(r,v){ r[v.code] = v; return r; }, {});
        $scope.statusesMap = _(statusesList).reduce(function(r,v){ r[v.code] = v; return r; }, {});

        load();

        return this;

        //************************************************************
        //************************************************************
        //************************************************************
        //************************************************************

        //===========================
        //
        //===========================
        function load() {

            $http.get('/api/v2015/tests', { params : { q : { decision: $scope.symbol }, fo: 1 }}).then(function(res){

                data = res.data || { decision: $scope.symbol, content: 'paste here' };

                $('#content').html(data.content);

                clean($('#content')[0]);
                clean($('#content')[0]);
                clean($('#content')[0]);

                $('#content,element').mousedown(function (event) {

                    var node = this;

                    event.stopPropagation();

                    $scope.$applyAsync(function(){
                        selectNode(node);
                    });
                });

            }).catch(function(err){

                err = (err||{}).data || err;

                console.error(err);

                alert(err.message||err);
            });
        }

        //===========================
        //
        //===========================
        function save() {

            selectNode(null);

            data.title = "agenda";
            data.content = $('#content').html();

            var req = {
                method : data._id ? 'PUT' : 'POST',
                url    : '/api/v2015/tests' + (data._id ? '/'+data._id : ''),
                data   : data
            };

            throw "DISBALED";

            $http(req).then(function(res){

                data._id = data._id || res.data.id;
                alert( "Your document has been successfully save." );

            }).catch(function(err){

                err = (err||{}).data || err;

                console.error(err);

                alert(err.message||err);
            });
        }

        //===========================
        //
        //===========================
        function surroundSelection(tag) {

            var range = window.getSelection().getRangeAt(0);

            var commonAncestor = $(range.commonAncestorContainer);

            if(!commonAncestor.is("#content") && !commonAncestor.parents('#content').size()) {
                alert('Please select text from the decision');
                return;
            }

            var span = document.createElement(tag);
            var content = range.extractContents();

            span.appendChild(content);
            range.insertNode(span);

            clean($('#content')[0]);
            clean($('#content')[0]);
            clean($('#content')[0]);

            $('element').mousedown(function () { selectNode(this); });

            selectNode(span);
        }

        //===========================
        //
        //===========================
        function unsurroundSelection() {

            if(!selectedElement) return alert('no selectedElement');

            $(selectedElement).contents().unwrap();

            selectedElement = null;
        }

        //===========================
        //
        //===========================
        function edit(editable) {

            var $div = $('#content');

            editable = editable===undefined ? !isEditable() : !!editable;

            $div.prop('contenteditable', editable);

            if(editable)
                $div.focus();
        }

        //===========================
        //
        //===========================
        function isEditable() {
            return $('#content').prop('contenteditable') == 'true';
        }

        //===========================
        //
        //===========================
        function selectNode(node) {

            delete $scope.formattingLocked;

            $(selectedElement).attr('data-info', JSON.stringify($scope.element));

            $(selectedElement).removeClass('selected');

            selectedElement = node;

            $(selectedElement).addClass('selected');

            $scope.element = $(selectedElement).data('info');

            if($scope.element && $scope.element.data && $scope.element.data.type == 'information')
                $scope.element.data.type = 'informational';

            $scope.formattingLocked = !!($scope.element && $scope.element.data);
        }

        //===========================
        //
        //===========================
        function tag() {

            if(!selectedElement) return;
            if(!$scope.element)  return;

            var element = $scope.element;

            element.section   = element.section  ||undefined;
            element.paragraph = element.paragraph||undefined;
            element.item      = element.item     ||undefined;
            element.subitem   = element.subitem  ||undefined;

            element.data = element.data || {
                treaty: treaty,
                body: body,
                session : session,
                decision : decision
            };

            var data = element.data;

            data.section   = element.section   && element.section.toUpperCase();
            data.paragraph = element.paragraph && parseInt(element.paragraph);
            data.item      = element.item      && element.item.charCodeAt(0) - 'a'.charCodeAt(0) + 1;
            data.subitem   = element.subitem   && roman.indexOf(element.subitem.toUpperCase());

            data.code  = data.treaty + '/' + data.body + '/' + pad(data.session) + '/' + pad(data.decision);
            data.code += '.'+(data.section||'') + pad(element.paragraph);

            if(data.item)                 data.code += '.'+pad(data.item);
            if(data.item && data.subitem) data.code += '.'+pad(data.subitem);

            // tag

            var tag = element.type || '';

            if(element.section)   tag += ' ' + element.section;
            if(element.paragraph) tag += ' ' + element.paragraph;
            if(element.item)      tag += ''  + element.item;
            if(element.subitem)   tag += ' (' + element.subitem + ')';

            $(selectedElement).attr('data-type', tag);

        }

        //==============================
        //
        //==============================
        function pad(input) {

            var output = (input || '').toString();

            while(output.length<2)
                output = '0' + output;

            return output;
        }

        //==============================
        //
        //==============================
        function toggle(array, value) {

            array = array || [];

            if(~array.indexOf(value)) {
                while(~array.indexOf(value))
                    array.splice(array.indexOf(value), 1);
            }
            else {
                array.push(value);
            }

            if(!array.length)
                array = undefined;

            return array;
        }

        //===========================
        //
        //===========================
        function clean(node) {

            for(var n = 0; n < node.childNodes.length; n ++) {

                var child = node.childNodes[n];

                if(child.nodeType === 8 || (child.nodeType === 3 && !/\S/.test(child.nodeValue))) {

                    node.removeChild(child);
                    n --;

                } else if(child.nodeType === 1) {

                    clean(child);

                    if(!child.innerHTML)
                        node.removeChild(child);
                }
            }
        }

        //===========================
        //
        //===========================
        function upload() {

            var fileUpload = $("#fileUpload");

            fileUpload.bind("change", upload_fileSelected);
            fileUpload.click();
        }

        //===========================
        //
        //===========================
        function upload_fileSelected() {

            var fileUpload = $("#fileUpload");

            fileUpload.unbind("change", upload_fileSelected);

            var file = fileUpload[0].files[0];

            if(!file)
                return;

            $scope.element.files = $scope.element.files || [];
            $scope.element.files.push({ filename : file.name.toLowerCase(), size : file.size });
            $scope.$digest();
        }

        //************************************************************

        //===========================
        //
        //===========================
        function selectActors() {

            openDialog('./select-actors-dialog', { showClose: false, resolve : { actors: function() { return $scope.element.data.actors; } } }).then(function(dialog){

                dialog.closePromise.then(function(res){

                    if(!res.value)
                        return;

                    var actors = res.value.actors;

                    if(actors && !actors.length)
                        actors = undefined;

                    $scope.element.data.actors = actors;
                });
            });
        }

        //===========================
        //
        //===========================
        function deleteActor(item) {

            if(!$scope.data && !$scope.element.data)
                return;

            var items = $scope.element.data.actors || [];
            var index = items.indexOf(item);

            if(index>=0) {
                items.splice(index, 1);
            }

            $scope.element.data.actors = items.length ? items : undefined;
        }

            //===========================
            //
            //===========================
            function selectStatuses() {

                openDialog('./select-statuses-dialog', { showClose: false, resolve : { statuses: function() { return $scope.element.data.statuses; } } }).then(function(dialog){

                    dialog.closePromise.then(function(res){

                        if(!res.value)
                            return;

                        var statuses = res.value.statuses;

                        if(statuses && !statuses.length)
                            statuses = undefined;

                        $scope.element.data.statuses = statuses;
                    });
                });
            }

            //===========================
            //
            //===========================
            function deleteStatus(item) {

                if(!$scope.data && !$scope.element.data)
                    return;

                var items = $scope.element.data.statuses || [];
                var index = items.indexOf(item);

                if(index>=0) {
                    items.splice(index, 1);
                }

                $scope.element.data.statuses = items.length ? items : undefined;
            }

        //===========================
        //
        //===========================
        function selectDecision() {

            openDialog('./select-decision-dialog', { showClose: false }).then(function(dialog){

                dialog.closePromise.then(function(res){

                    if(!res.value)
                        return;

                    $scope.element.decisions = $scope.element.decisions || [];
                    $scope.element.decisions.push(res.value);
                });
            });
        }

        //===========================
        //
        //===========================
        function deleteDecision(item) {

            var items = $scope.element.decisions || [];
            var index = items.indexOf(item);

            if(index>=0) {
                items.splice(index, 1);
            }
        }

        //===========================
        //
        //===========================
        function selectNotification() {

            openDialog('./select-notification-dialog', { showClose: false }).then(function(dialog){

                dialog.closePromise.then(function(res){

                    if(!res.value)
                        return;

                    $scope.element.data.notifications = $scope.element.data.notifications || [];
                    $scope.element.data.notifications.push(res.value.symbol);
                });
            });
        }

        //===========================
        //
        //===========================
        function deleteNotification(item) {

            var items = $scope.element.notifications || [];
            var index = items.indexOf(item);

            if(index>=0)
                items.splice(index, 1);
        }

        //===========================
        //
        //===========================
        var __notifications;
        function lookupNotification(code) {

            __notifications = __notifications||{};

            if(__notifications[code]===undefined) {

                __notifications[code] = code;

                var options = {
                    cache : true,
                    params : {
                        q : "schema_s:notification AND symbol_s:"+code,
                        fl : "symbol_?,reference_?,title_?,date_*,url_*",
                        rows: 1
                    }
                 };

                $http.get("/api/v2013/index", options).then(function(res){

                    var results = res.data.response;
                    __notifications[code] = results.numFound ? results.docs[0] : null;

                    return __notifications[code];
                });
            }

            return __notifications[code];
        }

        //===========================
        //
        //===========================
        function openDialog(dialog, options) {

            options = options || {};

            return $q(function(resolve, reject) {

                require(['text!'+dialog+'.html', dialog], function(template, controller) {

                    options.plain = true;
                    options.template = template;
                    options.controller = controller;

                    var dialog = ngDialog.open(options);

                    dialog.closePromise.then(function(res){
                        console.log(res.value);

                        if(res.value=="$escape")      delete res.value;
                        if(res.value=="$document")    delete res.value;
                        if(res.value=="$closeButton") delete res.value;

                        return res;
                    });

                    resolve(dialog);

                }, reject);
            });
        }
    }];

    var getComputedDisplay = (typeof window.getComputedStyle != "undefined") ?
    function(el) {
        return window.getComputedStyle(el, null).display;
    } :
    function(el) {
        return el.currentStyle.display;
    };

    function replaceWithOwnChildren(el) {
        var parent = el.parentNode;
        while (el.hasChildNodes()) {
            parent.insertBefore(el.firstChild, el);
        }
        parent.removeChild(el);
    }

    function removeSelectionFormatting() {
        var range, sel = rangy.getSelection();

        if(sel.isCollapsed)
            return;

        for (var r = 0; r < sel.rangeCount; ++r) {

            var commonAncestor = $(sel.getRangeAt(r).commonAncestorContainer);

            if(!commonAncestor.is("#content") && !commonAncestor.parents('#content').size()) {
                alert('Please select text only from the decision');
                return;
            }
        }

        for (var i = 0; i < sel.rangeCount; ++i) {
            range = sel.getRangeAt(i);

            // Split partially selected nodes
            range.splitBoundaries();

            // Get formatting elements. For this example, we'll count any
            // element with display: inline, except <br>s.
            var formattingEls = range.getNodes([1], function(el) {
                return el.tagName != "BR";// && getComputedDisplay(el) == "inline";
            });

            // Remove the formatting elements
            for (var j = 0; j<formattingEls.length; j++) {
                replaceWithOwnChildren(formattingEls[j]);
            }
        }
    }
});

(function(){

    var cache = [0],
        expando = 'data' +new Date();

    function data(elem) {

        var cacheIndex = elem[expando],
            nextCacheIndex = cache.length;

        if(!cacheIndex) {
            cacheIndex = elem[expando] = nextCacheIndex;
            cache[cacheIndex] = {};
        }

        return {
            get : function(key) {
                return cache[cacheIndex][key];
            },
            set : function(key, val) {
                cache[cacheIndex][key] = val;
                return val;
            }
        }

    }

    window.data = data;

})();
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Service.extend({
  store: Ember.inject.service(),

  currency () {
    return this.get('store').findRecord('setting', 'currency-symbol')
      .then(response => response.get('value'))
      .catch(err => {
        if (typeof err === 'undefined') {
          return '$';
        }
      });
  },

  currencyNames () {
    return ['AUD', 'CAD', 'CHF', 'EUR', 'GBP', 'JPY', 'NZD', 'RUB', 'USD'];
  }
});
<commit_msg>feat: Add user-setting method that returns current currency name<commit_after>import Ember from 'ember';

export default Ember.Service.extend({
  store: Ember.inject.service(),
  currencies: ['AUD', 'CAD', 'CHF', 'EUR', 'GBP', 'JPY', 'NZD', 'RUB', 'USD'],

  currencySymbol () {
    return this.get('store').findRecord('setting', 'st-setting')
      .then(response => response.get('currencySymbol'))
      .catch(err => {
        if (typeof err === 'undefined') {
          return '$';
        }
      });
  },

  currentCurrencyName () {
    return this.get('store').findRecord('setting', 'st-setting')
      .then(response => response.get('currencyName'))
      .catch(err => {
        if (typeof err === 'undefined') {
          return 'USD';
        }
      });
  },
  }
});
<|endoftext|>


<commit_before>import {Fragment} from "./fragment"
import {Mark} from "./mark"
import {Slice, replace} from "./replace"
import {ResolvedPos} from "./resolvedpos"
import {compareDeep} from "./comparedeep"

const emptyAttrs = Object.create(null)

// ::- This class represents a node in the tree that makes up a
// ProseMirror document. So a document is an instance of `Node`, with
// children that are also instances of `Node`.
//
// Nodes are persistent data structures. Instead of changing them, you
// create new ones with the content you want. Old ones keep pointing
// at the old document shape. This is made cheaper by sharing
// structure between the old and new data as much as possible, which a
// tree shape like this (without back pointers) makes easy.
//
// **Do not** directly mutate the properties of a `Node` object. See
// [the guide](/docs/guide/#doc) for more information.
export class Node {
  constructor(type, attrs, content, marks) {
    // :: NodeType
    // The type of node that this is.
    this.type = type

    // :: Object
    // An object mapping attribute names to values. The kind of
    // attributes allowed and required are
    // [determined](#model.NodeSpec.attrs) by the node type.
    this.attrs = attrs

    // :: Fragment
    // A container holding the node's children.
    this.content = content || Fragment.empty

    // :: [Mark]
    // The marks (things like whether it is emphasized or part of a
    // link) applied to this node.
    this.marks = marks || Mark.none
  }

  // text:: ?string
  // For text nodes, this contains the node's text content.

  // :: number
  // The size of this node, as defined by the integer-based [indexing
  // scheme](/docs/guide/#doc.indexing). For text nodes, this is the
  // amount of characters. For other leaf nodes, it is one. For
  // non-leaf nodes, it is the size of the content plus two (the start
  // and end token).
  get nodeSize() { return this.isLeaf ? 1 : 2 + this.content.size }

  // :: number
  // The number of children that the node has.
  get childCount() { return this.content.childCount }

  // :: (number) → Node
  // Get the child node at the given index. Raises an error when the
  // index is out of range.
  child(index) { return this.content.child(index) }

  // :: (number) → ?Node
  // Get the child node at the given index, if it exists.
  maybeChild(index) { return this.content.maybeChild(index) }

  // :: ((node: Node, offset: number, index: number))
  // Call `f` for every child node, passing the node, its offset
  // into this parent node, and its index.
  forEach(f) { this.content.forEach(f) }

  // :: (number, number, (node: Node, pos: number, parent: Node, index: number) → ?bool, ?number)
  // Invoke a callback for all descendant nodes recursively between
  // the given two positions that are relative to start of this node's
  // content. The callback is invoked with the node, its
  // parent-relative position, its parent node, and its child index.
  // When the callback returns false for a given node, that node's
  // children will not be recursed over. The last parameter can be
  // used to specify a starting position to count from.
  nodesBetween(from, to, f, startPos = 0) {
    this.content.nodesBetween(from, to, f, startPos, this)
  }

  // :: ((node: Node, pos: number, parent: Node) → ?bool)
  // Call the given callback for every descendant node. Doesn't
  // descend into a node when the callback returns `false`.
  descendants(f) {
    this.nodesBetween(0, this.content.size, f)
  }

  // :: string
  // Concatenates all the text nodes found in this fragment and its
  // children.
  get textContent() { return this.textBetween(0, this.content.size, "") }

  // :: (number, number, ?string, ?string) → string
  // Get all text between positions `from` and `to`. When
  // `blockSeparator` is given, it will be inserted whenever a new
  // block node is started. When `leafText` is given, it'll be
  // inserted for every non-text leaf node encountered.
  textBetween(from, to, blockSeparator, leafText) {
    return this.content.textBetween(from, to, blockSeparator, leafText)
  }

  // :: ?Node
  // Returns this node's first child, or `null` if there are no
  // children.
  get firstChild() { return this.content.firstChild }

  // :: ?Node
  // Returns this node's last child, or `null` if there are no
  // children.
  get lastChild() { return this.content.lastChild }

  // :: (Node) → bool
  // Test whether two nodes represent the same piece of document.
  eq(other) {
    return this == other || (this.sameMarkup(other) && this.content.eq(other.content))
  }

  // :: (Node) → bool
  // Compare the markup (type, attributes, and marks) of this node to
  // those of another. Returns `true` if both have the same markup.
  sameMarkup(other) {
    return this.hasMarkup(other.type, other.attrs, other.marks)
  }

  // :: (NodeType, ?Object, ?[Mark]) → bool
  // Check whether this node's markup correspond to the given type,
  // attributes, and marks.
  hasMarkup(type, attrs, marks) {
    return this.type == type &&
      compareDeep(this.attrs, attrs || type.defaultAttrs || emptyAttrs) &&
      Mark.sameSet(this.marks, marks || Mark.none)
  }

  // :: (?Fragment) → Node
  // Create a new node with the same markup as this node, containing
  // the given content (or empty, if no content is given).
  copy(content = null) {
    if (content == this.content) return this
    return new this.constructor(this.type, this.attrs, content, this.marks)
  }

  // :: ([Mark]) → Node
  // Create a copy of this node, with the given set of marks instead
  // of the node's own marks.
  mark(marks) {
    return marks == this.marks ? this : new this.constructor(this.type, this.attrs, this.content, marks)
  }

  // :: (number, ?number) → Node
  // Create a copy of this node with only the content between the
  // given positions. If `to` is not given, it defaults to the end of
  // the node.
  cut(from, to) {
    if (from == 0 && to == this.content.size) return this
    return this.copy(this.content.cut(from, to))
  }

  // :: (number, ?number) → Slice
  // Cut out the part of the document between the given positions, and
  // return it as a `Slice` object.
  slice(from, to = this.content.size, includeParents = false) {
    if (from == to) return Slice.empty

    let $from = this.resolve(from), $to = this.resolve(to)
    let depth = includeParents ? 0 : $from.sharedDepth(to)
    let start = $from.start(depth), node = $from.node(depth)
    let content = node.content.cut($from.pos - start, $to.pos - start)
    return new Slice(content, $from.depth - depth, $to.depth - depth)
  }

  // :: (number, number, Slice) → Node
  // Replace the part of the document between the given positions with
  // the given slice. The slice must 'fit', meaning its open sides
  // must be able to connect to the surrounding content, and its
  // content nodes must be valid children for the node they are placed
  // into. If any of this is violated, an error of type
  // [`ReplaceError`](#model.ReplaceError) is thrown.
  replace(from, to, slice) {
    return replace(this.resolve(from), this.resolve(to), slice)
  }

  // :: (number) → ?Node
  // Find the node starting at the given position.
  nodeAt(pos) {
    for (let node = this;;) {
      let {index, offset} = node.content.findIndex(pos)
      node = node.maybeChild(index)
      if (!node) return null
      if (offset == pos || node.isText) return node
      pos -= offset + 1
    }
  }

  // :: (number) → {node: ?Node, index: number, offset: number}
  // Find the (direct) child node after the given offset, if any,
  // and return it along with its index and offset relative to this
  // node.
  childAfter(pos) {
    let {index, offset} = this.content.findIndex(pos)
    return {node: this.content.maybeChild(index), index, offset}
  }

  // :: (number) → {node: ?Node, index: number, offset: number}
  // Find the (direct) child node before the given offset, if any,
  // and return it along with its index and offset relative to this
  // node.
  childBefore(pos) {
    if (pos == 0) return {node: null, index: 0, offset: 0}
    let {index, offset} = this.content.findIndex(pos)
    if (offset < pos) return {node: this.content.child(index), index, offset}
    let node = this.content.child(index - 1)
    return {node, index: index - 1, offset: offset - node.nodeSize}
  }

  // :: (number) → ResolvedPos
  // Resolve the given position in the document, returning an
  // [object](#model.ResolvedPos) with information about its context.
  resolve(pos) { return ResolvedPos.resolveCached(this, pos) }

  resolveNoCache(pos) { return ResolvedPos.resolve(this, pos) }

  // :: (number, number, MarkType) → bool
  // Test whether a mark of the given type occurs in this document
  // between the two given positions.
  rangeHasMark(from, to, type) {
    let found = false
    if (to > from) this.nodesBetween(from, to, node => {
      if (type.isInSet(node.marks)) found = true
      return !found
    })
    return found
  }

  // :: bool
  // True when this is a block (non-inline node)
  get isBlock() { return this.type.isBlock }

  // :: bool
  // True when this is a textblock node, a block node with inline
  // content.
  get isTextblock() { return this.type.isTextblock }

  // :: bool
  // True when this node has inline content.
  get inlineContent() { return this.type.inlineContent }

  // :: bool
  // True when this is an inline node (a text node or a node that can
  // appear among text).
  get isInline() { return this.type.isInline }

  // :: bool
  // True when this is a text node.
  get isText() { return this.type.isText }

  // :: bool
  // True when this is a leaf node.
  get isLeaf() { return this.type.isLeaf }

  // :: bool
  // True when this is an atom, i.e. when it does not have directly
  // editable content. This is usually the same as `isLeaf`, but can
  // be configured with the [`atom` property](#model.NodeSpec.atom) on
  // a node's spec (typically used when the node is displayed as an
  // uneditable [node view](#view.NodeView)).
  get isAtom() { return this.type.isAtom }

  // :: () → string
  // Return a string representation of this node for debugging
  // purposes.
  toString() {
    let name = this.type.name
    if (this.content.size)
      name += "(" + this.content.toStringInner() + ")"
    return wrapMarks(this.marks, name)
  }

  // :: (number) → ContentMatch
  // Get the content match in this node at the given index.
  contentMatchAt(index) {
    return this.type.contentMatch.matchFragment(this.content, 0, index)
  }

  // :: (number, number, ?Fragment, ?number, ?number) → bool
  // Test whether replacing the range between `from` and `to` (by
  // child index) with the given replacement fragment (which defaults
  // to the empty fragment) would leave the node's content valid. You
  // can optionally pass `start` and `end` indices into the
  // replacement fragment.
  canReplace(from, to, replacement = Fragment.empty, start = 0, end = replacement.childCount) {
    let one = this.contentMatchAt(from).matchFragment(replacement, start, end)
    let two = one && one.matchFragment(this.content, to)
    if (!two || !two.validEnd) return false
    for (let i = start; i < end; i++) if (!this.type.allowsMarks(replacement.child(i).marks)) return false
    return true
  }

  // :: (number, number, NodeType, ?[Mark]) → bool
  // Test whether replacing the range `from` to `to` (by index) with a
  // node of the given type would leave the node's content valid.
  canReplaceWith(from, to, type, marks) {
    if (marks && !this.type.allowsMarks(marks)) return false
    let start = this.contentMatchAt(from).matchType(type)
    let end = start && start.matchFragment(this.content, to)
    return end ? end.validEnd : false
  }

  // :: (Node) → bool
  // Test whether the given node's content could be appended to this
  // node. If that node is empty, this will only return true if there
  // is at least one node type that can appear in both nodes (to avoid
  // merging completely incompatible nodes).
  canAppend(other) {
    if (other.content.size) return this.canReplace(this.childCount, this.childCount, other.content)
    else return this.type.compatibleContent(other.type)
  }

  // Unused. Left for backwards compatibility.
  defaultContentType(at) {
    return this.contentMatchAt(at).defaultType
  }

  // :: ()
  // Check whether this node and its descendants conform to the
  // schema, and raise error when they do not.
  check() {
    if (!this.type.validContent(this.content))
      throw new RangeError(`Invalid content for node ${this.type.name}: ${this.content.toString().slice(0, 50)}`)
    this.content.forEach(node => node.check())
  }

  // :: () → Object
  // Return a JSON-serializeable representation of this node.
  toJSON() {
    let obj = {type: this.type.name}
    for (let _ in this.attrs) {
      obj.attrs = this.attrs
      break
    }
    if (this.content.size)
      obj.content = this.content.toJSON()
    if (this.marks.length)
      obj.marks = this.marks.map(n => n.toJSON())
    return obj
  }

  // :: (Schema, Object) → Node
  // Deserialize a node from its JSON representation.
  static fromJSON(schema, json) {
    if (!json) throw new RangeError("Invalid input for Node.fromJSON")
    let marks = null
    if (json.marks) {
      if (!Array.isArray(json.marks)) throw new RangeError("Invalid mark data for Node.fromJSON")
      marks = json.marks.map(schema.markFromJSON)
    }
    if (json.type == "text") {
      if (typeof json.text != "string") throw new RangeError("Invalid text node in JSON")
      return schema.text(json.text, marks)
    }
    let content = Fragment.fromJSON(schema, json.content)
    return schema.nodeType(json.type).create(json.attrs, content, marks)
  }
}

export class TextNode extends Node {
  constructor(type, attrs, content, marks) {
    super(type, attrs, null, marks)

    if (!content) throw new RangeError("Empty text nodes are not allowed")

    this.text = content
  }

  toString() { return wrapMarks(this.marks, JSON.stringify(this.text)) }

  get textContent() { return this.text }

  textBetween(from, to) { return this.text.slice(from, to) }

  get nodeSize() { return this.text.length }

  mark(marks) {
    return marks == this.marks ? this : new TextNode(this.type, this.attrs, this.text, marks)
  }

  withText(text) {
    if (text == this.text) return this
    return new TextNode(this.type, this.attrs, text, this.marks)
  }

  cut(from = 0, to = this.text.length) {
    if (from == 0 && to == this.text.length) return this
    return this.withText(this.text.slice(from, to))
  }

  eq(other) {
    return this.sameMarkup(other) && this.text == other.text
  }

  toJSON() {
    let base = super.toJSON()
    base.text = this.text
    return base
  }
}

function wrapMarks(marks, str) {
  for (let i = marks.length - 1; i >= 0; i--)
    str = marks[i].type.name + "(" + str + ")"
  return str
}
<commit_msg>Clarify doc comment<commit_after>import {Fragment} from "./fragment"
import {Mark} from "./mark"
import {Slice, replace} from "./replace"
import {ResolvedPos} from "./resolvedpos"
import {compareDeep} from "./comparedeep"

const emptyAttrs = Object.create(null)

// ::- This class represents a node in the tree that makes up a
// ProseMirror document. So a document is an instance of `Node`, with
// children that are also instances of `Node`.
//
// Nodes are persistent data structures. Instead of changing them, you
// create new ones with the content you want. Old ones keep pointing
// at the old document shape. This is made cheaper by sharing
// structure between the old and new data as much as possible, which a
// tree shape like this (without back pointers) makes easy.
//
// **Do not** directly mutate the properties of a `Node` object. See
// [the guide](/docs/guide/#doc) for more information.
export class Node {
  constructor(type, attrs, content, marks) {
    // :: NodeType
    // The type of node that this is.
    this.type = type

    // :: Object
    // An object mapping attribute names to values. The kind of
    // attributes allowed and required are
    // [determined](#model.NodeSpec.attrs) by the node type.
    this.attrs = attrs

    // :: Fragment
    // A container holding the node's children.
    this.content = content || Fragment.empty

    // :: [Mark]
    // The marks (things like whether it is emphasized or part of a
    // link) applied to this node.
    this.marks = marks || Mark.none
  }

  // text:: ?string
  // For text nodes, this contains the node's text content.

  // :: number
  // The size of this node, as defined by the integer-based [indexing
  // scheme](/docs/guide/#doc.indexing). For text nodes, this is the
  // amount of characters. For other leaf nodes, it is one. For
  // non-leaf nodes, it is the size of the content plus two (the start
  // and end token).
  get nodeSize() { return this.isLeaf ? 1 : 2 + this.content.size }

  // :: number
  // The number of children that the node has.
  get childCount() { return this.content.childCount }

  // :: (number) → Node
  // Get the child node at the given index. Raises an error when the
  // index is out of range.
  child(index) { return this.content.child(index) }

  // :: (number) → ?Node
  // Get the child node at the given index, if it exists.
  maybeChild(index) { return this.content.maybeChild(index) }

  // :: ((node: Node, offset: number, index: number))
  // Call `f` for every child node, passing the node, its offset
  // into this parent node, and its index.
  forEach(f) { this.content.forEach(f) }

  // :: (number, number, (node: Node, pos: number, parent: Node, index: number) → ?bool, ?number)
  // Invoke a callback for all descendant nodes recursively between
  // the given two positions that are relative to start of this node's
  // content. The callback is invoked with the node, its
  // parent-relative position, its parent node, and its child index.
  // When the callback returns false for a given node, that node's
  // children will not be recursed over. The last parameter can be
  // used to specify a starting position to count from.
  nodesBetween(from, to, f, startPos = 0) {
    this.content.nodesBetween(from, to, f, startPos, this)
  }

  // :: ((node: Node, pos: number, parent: Node) → ?bool)
  // Call the given callback for every descendant node. Doesn't
  // descend into a node when the callback returns `false`.
  descendants(f) {
    this.nodesBetween(0, this.content.size, f)
  }

  // :: string
  // Concatenates all the text nodes found in this fragment and its
  // children.
  get textContent() { return this.textBetween(0, this.content.size, "") }

  // :: (number, number, ?string, ?string) → string
  // Get all text between positions `from` and `to`. When
  // `blockSeparator` is given, it will be inserted whenever a new
  // block node is started. When `leafText` is given, it'll be
  // inserted for every non-text leaf node encountered.
  textBetween(from, to, blockSeparator, leafText) {
    return this.content.textBetween(from, to, blockSeparator, leafText)
  }

  // :: ?Node
  // Returns this node's first child, or `null` if there are no
  // children.
  get firstChild() { return this.content.firstChild }

  // :: ?Node
  // Returns this node's last child, or `null` if there are no
  // children.
  get lastChild() { return this.content.lastChild }

  // :: (Node) → bool
  // Test whether two nodes represent the same piece of document.
  eq(other) {
    return this == other || (this.sameMarkup(other) && this.content.eq(other.content))
  }

  // :: (Node) → bool
  // Compare the markup (type, attributes, and marks) of this node to
  // those of another. Returns `true` if both have the same markup.
  sameMarkup(other) {
    return this.hasMarkup(other.type, other.attrs, other.marks)
  }

  // :: (NodeType, ?Object, ?[Mark]) → bool
  // Check whether this node's markup correspond to the given type,
  // attributes, and marks.
  hasMarkup(type, attrs, marks) {
    return this.type == type &&
      compareDeep(this.attrs, attrs || type.defaultAttrs || emptyAttrs) &&
      Mark.sameSet(this.marks, marks || Mark.none)
  }

  // :: (?Fragment) → Node
  // Create a new node with the same markup as this node, containing
  // the given content (or empty, if no content is given).
  copy(content = null) {
    if (content == this.content) return this
    return new this.constructor(this.type, this.attrs, content, this.marks)
  }

  // :: ([Mark]) → Node
  // Create a copy of this node, with the given set of marks instead
  // of the node's own marks.
  mark(marks) {
    return marks == this.marks ? this : new this.constructor(this.type, this.attrs, this.content, marks)
  }

  // :: (number, ?number) → Node
  // Create a copy of this node with only the content between the
  // given positions. If `to` is not given, it defaults to the end of
  // the node.
  cut(from, to) {
    if (from == 0 && to == this.content.size) return this
    return this.copy(this.content.cut(from, to))
  }

  // :: (number, ?number) → Slice
  // Cut out the part of the document between the given positions, and
  // return it as a `Slice` object.
  slice(from, to = this.content.size, includeParents = false) {
    if (from == to) return Slice.empty

    let $from = this.resolve(from), $to = this.resolve(to)
    let depth = includeParents ? 0 : $from.sharedDepth(to)
    let start = $from.start(depth), node = $from.node(depth)
    let content = node.content.cut($from.pos - start, $to.pos - start)
    return new Slice(content, $from.depth - depth, $to.depth - depth)
  }

  // :: (number, number, Slice) → Node
  // Replace the part of the document between the given positions with
  // the given slice. The slice must 'fit', meaning its open sides
  // must be able to connect to the surrounding content, and its
  // content nodes must be valid children for the node they are placed
  // into. If any of this is violated, an error of type
  // [`ReplaceError`](#model.ReplaceError) is thrown.
  replace(from, to, slice) {
    return replace(this.resolve(from), this.resolve(to), slice)
  }

  // :: (number) → ?Node
  // Find the node directly after the given position.
  nodeAt(pos) {
    for (let node = this;;) {
      let {index, offset} = node.content.findIndex(pos)
      node = node.maybeChild(index)
      if (!node) return null
      if (offset == pos || node.isText) return node
      pos -= offset + 1
    }
  }

  // :: (number) → {node: ?Node, index: number, offset: number}
  // Find the (direct) child node after the given offset, if any,
  // and return it along with its index and offset relative to this
  // node.
  childAfter(pos) {
    let {index, offset} = this.content.findIndex(pos)
    return {node: this.content.maybeChild(index), index, offset}
  }

  // :: (number) → {node: ?Node, index: number, offset: number}
  // Find the (direct) child node before the given offset, if any,
  // and return it along with its index and offset relative to this
  // node.
  childBefore(pos) {
    if (pos == 0) return {node: null, index: 0, offset: 0}
    let {index, offset} = this.content.findIndex(pos)
    if (offset < pos) return {node: this.content.child(index), index, offset}
    let node = this.content.child(index - 1)
    return {node, index: index - 1, offset: offset - node.nodeSize}
  }

  // :: (number) → ResolvedPos
  // Resolve the given position in the document, returning an
  // [object](#model.ResolvedPos) with information about its context.
  resolve(pos) { return ResolvedPos.resolveCached(this, pos) }

  resolveNoCache(pos) { return ResolvedPos.resolve(this, pos) }

  // :: (number, number, MarkType) → bool
  // Test whether a mark of the given type occurs in this document
  // between the two given positions.
  rangeHasMark(from, to, type) {
    let found = false
    if (to > from) this.nodesBetween(from, to, node => {
      if (type.isInSet(node.marks)) found = true
      return !found
    })
    return found
  }

  // :: bool
  // True when this is a block (non-inline node)
  get isBlock() { return this.type.isBlock }

  // :: bool
  // True when this is a textblock node, a block node with inline
  // content.
  get isTextblock() { return this.type.isTextblock }

  // :: bool
  // True when this node has inline content.
  get inlineContent() { return this.type.inlineContent }

  // :: bool
  // True when this is an inline node (a text node or a node that can
  // appear among text).
  get isInline() { return this.type.isInline }

  // :: bool
  // True when this is a text node.
  get isText() { return this.type.isText }

  // :: bool
  // True when this is a leaf node.
  get isLeaf() { return this.type.isLeaf }

  // :: bool
  // True when this is an atom, i.e. when it does not have directly
  // editable content. This is usually the same as `isLeaf`, but can
  // be configured with the [`atom` property](#model.NodeSpec.atom) on
  // a node's spec (typically used when the node is displayed as an
  // uneditable [node view](#view.NodeView)).
  get isAtom() { return this.type.isAtom }

  // :: () → string
  // Return a string representation of this node for debugging
  // purposes.
  toString() {
    let name = this.type.name
    if (this.content.size)
      name += "(" + this.content.toStringInner() + ")"
    return wrapMarks(this.marks, name)
  }

  // :: (number) → ContentMatch
  // Get the content match in this node at the given index.
  contentMatchAt(index) {
    return this.type.contentMatch.matchFragment(this.content, 0, index)
  }

  // :: (number, number, ?Fragment, ?number, ?number) → bool
  // Test whether replacing the range between `from` and `to` (by
  // child index) with the given replacement fragment (which defaults
  // to the empty fragment) would leave the node's content valid. You
  // can optionally pass `start` and `end` indices into the
  // replacement fragment.
  canReplace(from, to, replacement = Fragment.empty, start = 0, end = replacement.childCount) {
    let one = this.contentMatchAt(from).matchFragment(replacement, start, end)
    let two = one && one.matchFragment(this.content, to)
    if (!two || !two.validEnd) return false
    for (let i = start; i < end; i++) if (!this.type.allowsMarks(replacement.child(i).marks)) return false
    return true
  }

  // :: (number, number, NodeType, ?[Mark]) → bool
  // Test whether replacing the range `from` to `to` (by index) with a
  // node of the given type would leave the node's content valid.
  canReplaceWith(from, to, type, marks) {
    if (marks && !this.type.allowsMarks(marks)) return false
    let start = this.contentMatchAt(from).matchType(type)
    let end = start && start.matchFragment(this.content, to)
    return end ? end.validEnd : false
  }

  // :: (Node) → bool
  // Test whether the given node's content could be appended to this
  // node. If that node is empty, this will only return true if there
  // is at least one node type that can appear in both nodes (to avoid
  // merging completely incompatible nodes).
  canAppend(other) {
    if (other.content.size) return this.canReplace(this.childCount, this.childCount, other.content)
    else return this.type.compatibleContent(other.type)
  }

  // Unused. Left for backwards compatibility.
  defaultContentType(at) {
    return this.contentMatchAt(at).defaultType
  }

  // :: ()
  // Check whether this node and its descendants conform to the
  // schema, and raise error when they do not.
  check() {
    if (!this.type.validContent(this.content))
      throw new RangeError(`Invalid content for node ${this.type.name}: ${this.content.toString().slice(0, 50)}`)
    this.content.forEach(node => node.check())
  }

  // :: () → Object
  // Return a JSON-serializeable representation of this node.
  toJSON() {
    let obj = {type: this.type.name}
    for (let _ in this.attrs) {
      obj.attrs = this.attrs
      break
    }
    if (this.content.size)
      obj.content = this.content.toJSON()
    if (this.marks.length)
      obj.marks = this.marks.map(n => n.toJSON())
    return obj
  }

  // :: (Schema, Object) → Node
  // Deserialize a node from its JSON representation.
  static fromJSON(schema, json) {
    if (!json) throw new RangeError("Invalid input for Node.fromJSON")
    let marks = null
    if (json.marks) {
      if (!Array.isArray(json.marks)) throw new RangeError("Invalid mark data for Node.fromJSON")
      marks = json.marks.map(schema.markFromJSON)
    }
    if (json.type == "text") {
      if (typeof json.text != "string") throw new RangeError("Invalid text node in JSON")
      return schema.text(json.text, marks)
    }
    let content = Fragment.fromJSON(schema, json.content)
    return schema.nodeType(json.type).create(json.attrs, content, marks)
  }
}

export class TextNode extends Node {
  constructor(type, attrs, content, marks) {
    super(type, attrs, null, marks)

    if (!content) throw new RangeError("Empty text nodes are not allowed")

    this.text = content
  }

  toString() { return wrapMarks(this.marks, JSON.stringify(this.text)) }

  get textContent() { return this.text }

  textBetween(from, to) { return this.text.slice(from, to) }

  get nodeSize() { return this.text.length }

  mark(marks) {
    return marks == this.marks ? this : new TextNode(this.type, this.attrs, this.text, marks)
  }

  withText(text) {
    if (text == this.text) return this
    return new TextNode(this.type, this.attrs, text, this.marks)
  }

  cut(from = 0, to = this.text.length) {
    if (from == 0 && to == this.text.length) return this
    return this.withText(this.text.slice(from, to))
  }

  eq(other) {
    return this.sameMarkup(other) && this.text == other.text
  }

  toJSON() {
    let base = super.toJSON()
    base.text = this.text
    return base
  }
}

function wrapMarks(marks, str) {
  for (let i = marks.length - 1; i >= 0; i--)
    str = marks[i].type.name + "(" + str + ")"
  return str
}
<|endoftext|>


<commit_before>'use strict'

var Validate = function (_fields) {
  this._fields = _fields;
  this._valid = true;
  this._messages = [];

  return this;
}

Validate.prototype._addMessage = function (_message) {
  this._messages.push(_message);
}

Validate.prototype._handle = function (_field_name, _rules) {
  if (typeof this[_rules.type] == 'function') {
    this[_rules.type](_field_name, _rules);
  }
}

Validate.prototype._doValidate = function (_field_name, _rules) {
  if (_rules.presence && ! _rules.value) {
    this._addMessage("The field '"+_field_name+"' is required.");
    this._valid = false;
  }

  if (_rules.value) {
    if (_rules.type) {
      this._handle(_field_name, _rules);
    }

    if (_rules.regex && ! _rules.regex.test(_rules.value)) {
      this._addMessage("The field '"+_field_name+"' do not match with regex.");
      this._valid = false;
    }
  }
}

Validate.prototype._validate = function () {
  for (var key in this._fields) {
    var rules = this._fields[key];

    if (! rules.value) {
      rules.value = document.querySelector("input[data-validate="+key+"]");
    }

    var field_name = key;

    if (rules.name) {
      field_name = rules.name;
    }

    this._doValidate(key, rules);
  }
}

Validate.prototype._validateNumberLength = function (_field_name, _value, _rules) {
  var validation_status = true;

  if (_rules.gt && _rules.gt > _value) {
    this._addMessage("The field '"+_field_name+"' must be greater than "+_rules.gt+".");
    validation_status = false;
  }

  if (_rules.gte && _rules.gte >= _value) {
    this._addMessage("The field '"+_field_name+"' must be greater or equal than "+_rules.gte+".");
    validation_status = false;
  }

  if (_rules.lt && _rules.lt < _value) {
    this._addMessage("The field '"+_field_name+"' must be less than "+_rules.lt+".");
    validation_status = false;
  }

  if (_rules.lte && _rules.lte <= _value) {
    this._addMessage("The field '"+_field_name+"' must be less or equal than "+_rules.lte+".");
    validation_status = false;
  }

  return validation_status;
}

Validate.prototype._validateStringLength = function (_field_name, _rules) {
  var value = _rules.value.length
  var validation_status = true

  if (_rules.min && value < _rules.min) {
    this._addMessage("The field '"+_field_name+"' must have at least "+_rules.min+" characters.");
    validation_status = false;
  }

  if (_rules.max && value > _rules.max) {
    this._addMessage("The field '"+_field_name+"' must have up to "+_rules.min+" characters.");
    validation_status = false;
  }

  if (_rules.exact && value == _rules.exact) {
    this._addMessage("The field '"+_field_name+"' must have "+_rules.exact+" characters.");
    validation_status = false;
  }

  return validation_status;
}

Validate.prototype.integer = function (_field_name, _rules) {
  var pattern = /^[0-9]*$/;

  if (! pattern.test(_rules.value)) {
    this._addMessage("The field '"+_field_name+"' must have only numbers.");
    this._valid = false;
    return false;
  }

  var value = parseFloat(_rules.value.replace('.', '').replace(',', '.'))
  return this._validateNumberLength(_field_name, value, _rules);
}

Validate.prototype.money = function (_field_name, _rules) {
  var pattern = /^\$?\d+(.\d{3})*(\,\d*)?$/

  if (! pattern.test(_rules.value)) {
    this._addMessage("The field '"+_field_name+"' is not monetary value.");
    this._valid = false;
    return false;
  }

  var value = parseInt(_rules.value);
  return this._validateNumberLength(_field_name, value, _rules);
}

Validate.prototype.onlyLetters = function (_field_name, _rules) {
  var pattern = /^[0-9]*$/i;

  if (! pattern.test(_rules.value)) {
    this._addMessage("The field '"+_field_name+"' must have only letters.");
    this._valid = false;
    return false;
  }

  return this._validateStringLength(_field_name, _rules);
}

Validate.prototype.noSpecialCharacters = function (_field_name, _rules) {
  var pattern = /^[0-9a-zA-Z]*$/i;

  if (! pattern.test(_rules.value)) {
    this._addMessage("The field '"+_field_name+"' must have only letters and numbers.");
    this._valid = false;
    return false;
  }

  return this._validateStringLength(_field_name, _rules);
}

Validate.prototype.personName = function (_field_name, _rules) {
  var pattern = /^[a-zA-Z\u00C0-\u00FF\s\.\-\']+/i;

  if (! pattern.test(_rules.value)) {
    this._addMessage("The field '"+_field_name+"' is not a person name.");
    this._valid = false;
    return false;
  }

  return this._validateStringLength(_field_name, _rules);
}

Validate.prototype.isValid = function () {
  this._validate();
  return this._valid;
}

Validate.prototype.getMessages = function () {
  return this._messages;
}
<commit_msg>Fix query selector value<commit_after>'use strict'

var Validate = function (_fields) {
  this._fields = _fields;
  this._valid = true;
  this._messages = [];

  return this;
}

Validate.prototype._addMessage = function (_message) {
  this._messages.push(_message);
}

Validate.prototype._handle = function (_field_name, _rules) {
  if (typeof this[_rules.type] == 'function') {
    this[_rules.type](_field_name, _rules);
  }
}

Validate.prototype._doValidate = function (_field_name, _rules) {
  if (_rules.presence && ! _rules.value) {
    this._addMessage("The field '"+_field_name+"' is required.");
    this._valid = false;
  }

  if (_rules.value) {
    if (_rules.type) {
      this._handle(_field_name, _rules);
    }

    if (_rules.regex && ! _rules.regex.test(_rules.value)) {
      this._addMessage("The field '"+_field_name+"' do not match with regex.");
      this._valid = false;
    }
  }
}

Validate.prototype._validate = function () {
  for (var key in this._fields) {
    var rules = this._fields[key];

    if (! rules.value) {
      rules.value = document.querySelector("input[data-validate="+key+"]").value;
    }

    var field_name = key;

    if (rules.name) {
      field_name = rules.name;
    }

    this._doValidate(key, rules);
  }
}

Validate.prototype._validateNumberLength = function (_field_name, _value, _rules) {
  var validation_status = true;

  if (_rules.gt && _rules.gt > _value) {
    this._addMessage("The field '"+_field_name+"' must be greater than "+_rules.gt+".");
    validation_status = false;
  }

  if (_rules.gte && _rules.gte >= _value) {
    this._addMessage("The field '"+_field_name+"' must be greater or equal than "+_rules.gte+".");
    validation_status = false;
  }

  if (_rules.lt && _rules.lt < _value) {
    this._addMessage("The field '"+_field_name+"' must be less than "+_rules.lt+".");
    validation_status = false;
  }

  if (_rules.lte && _rules.lte <= _value) {
    this._addMessage("The field '"+_field_name+"' must be less or equal than "+_rules.lte+".");
    validation_status = false;
  }

  return validation_status;
}

Validate.prototype._validateStringLength = function (_field_name, _rules) {
  var value = _rules.value.length
  var validation_status = true

  if (_rules.min && value < _rules.min) {
    this._addMessage("The field '"+_field_name+"' must have at least "+_rules.min+" characters.");
    validation_status = false;
  }

  if (_rules.max && value > _rules.max) {
    this._addMessage("The field '"+_field_name+"' must have up to "+_rules.min+" characters.");
    validation_status = false;
  }

  if (_rules.exact && value == _rules.exact) {
    this._addMessage("The field '"+_field_name+"' must have "+_rules.exact+" characters.");
    validation_status = false;
  }

  return validation_status;
}

Validate.prototype.integer = function (_field_name, _rules) {
  var pattern = /^[0-9]*$/;

  if (! pattern.test(_rules.value)) {
    this._addMessage("The field '"+_field_name+"' must have only numbers.");
    this._valid = false;
    return false;
  }

  var value = parseFloat(_rules.value.replace('.', '').replace(',', '.'))
  return this._validateNumberLength(_field_name, value, _rules);
}

Validate.prototype.money = function (_field_name, _rules) {
  var pattern = /^\$?\d+(.\d{3})*(\,\d*)?$/

  if (! pattern.test(_rules.value)) {
    this._addMessage("The field '"+_field_name+"' is not monetary value.");
    this._valid = false;
    return false;
  }

  var value = parseInt(_rules.value);
  return this._validateNumberLength(_field_name, value, _rules);
}

Validate.prototype.onlyLetters = function (_field_name, _rules) {
  var pattern = /^[a-zA-Z]*$/;

  if (! pattern.test(_rules.value)) {
    this._addMessage("The field '"+_field_name+"' must have only letters.");
    this._valid = false;
    return false;
  }

  return this._validateStringLength(_field_name, _rules);
}

Validate.prototype.noSpecialCharacters = function (_field_name, _rules) {
  var pattern = /^[0-9a-zA-Z]*$/;

  if (! pattern.test(_rules.value)) {
    this._addMessage("The field '"+_field_name+"' must have only letters and numbers.");
    this._valid = false;
    return false;
  }

  return this._validateStringLength(_field_name, _rules);
}

Validate.prototype.personName = function (_field_name, _rules) {
  var pattern = /^[a-zA-Z\u00C0-\u00FF\s\.\-\']+/i;

  if (! pattern.test(_rules.value)) {
    this._addMessage("The field '"+_field_name+"' is not a person name.");
    this._valid = false;
    return false;
  }

  return this._validateStringLength(_field_name, _rules);
}

Validate.prototype.isValid = function () {
  this._validate();
  return this._valid;
}

Validate.prototype.getMessages = function () {
  return this._messages;
}
<|endoftext|>