Explorar o código

v2 json format tools

zxlie %!s(int64=7) %!d(string=hai) anos
pai
achega
a0b2426f17

+ 903 - 0
v2.0/apps/json-format/format-lib.js

@@ -0,0 +1,903 @@
+/**
+ * FeHelper Json Format Lib
+ */
+
+var JsonFormatEntrance = (function () {
+
+    "use strict";
+
+    var jfContent,
+        pre,
+        jfStyleEl,
+        formattingMsg,
+        slowAnalysisTimeout,
+        isJsonTime,
+        exitedNotJsonTime,
+        displayedFormattedJsonTime
+    ;
+
+    // Add listener to receive response from BG when ready
+    var postMessage = function (msg) {
+        // console.log('Port msg received', msg[0], (""+msg[1]).substring(0,30)) ;
+
+        switch (msg[0]) {
+            case 'NOT JSON' :
+                pre.style.display = "";
+                // console.log('Unhidden the PRE') ;
+                jfContent.innerHTML = '<span class="x-json-tips">JSON不合法,请检查:</span>';
+                exitedNotJsonTime = +(new Date());
+                break;
+
+            case 'FORMATTING' :
+                isJsonTime = +(new Date());
+
+                // It is JSON, and it's now being formatted in the background worker.
+
+                // Clear the slowAnalysisTimeout (if the BG worker had taken longer than 1s to respond with an answer to whether or not this is JSON, then it would have fired, unhiding the PRE... But now that we know it's JSON, we can clear this timeout, ensuring the PRE stays hidden.)
+                clearTimeout(slowAnalysisTimeout);
+
+                // Create option bar
+                var optionBar = document.getElementById('optionBar');
+                if (optionBar) {
+                    optionBar.parentNode.removeChild(optionBar);
+                }
+                optionBar = document.createElement('div');
+                optionBar.id = 'optionBar';
+
+                // Create toggleFormat button
+                var buttonFormatted = document.createElement('button'),
+                    buttonCollapseAll = document.createElement('button');
+                buttonFormatted.id = 'buttonFormatted';
+                buttonFormatted.innerText = '格式化';
+                buttonFormatted.classList.add('selected');
+                buttonCollapseAll.id = 'buttonCollapseAll';
+                buttonCollapseAll.innerText = '折叠所有';
+
+                var plainOn = false;
+                buttonFormatted.addEventListener('click', function () {
+                    // When formatted button clicked...
+                    if (plainOn) {
+                        plainOn = false;
+                        pre.style.display = "none";
+                        jfContent.style.display = "block";
+                        $(this).text('元数据');
+                    } else {
+                        plainOn = true;
+                        pre.style.display = "block";
+                        jfContent.style.display = "none";
+                        $(this).text('格式化');
+                    }
+
+                    $(this).parent().find('button').removeClass('selected');
+                    $(this).addClass('selected');
+                }, false);
+
+                buttonCollapseAll.addEventListener('click', function () {
+                    // 如果内容还没有格式化过,需要再格式化一下
+                    if (plainOn) {
+                        buttonFormatted.click();
+                    }
+                    // When collapaseAll button clicked...
+                    if (!plainOn) {
+                        if (buttonCollapseAll.innerText === '折叠所有') {
+                            buttonCollapseAll.innerText = '展开所有';
+                            collapse(document.getElementsByClassName('objProp'));
+                        } else {
+                            buttonCollapseAll.innerText = '折叠所有';
+                            expand(document.getElementsByClassName('objProp'));
+                        }
+
+                        $(this).parent().find('button').removeClass('selected');
+                        $(this).addClass('selected');
+                    }
+                }, false);
+
+                // Put it in optionBar
+                optionBar.appendChild(buttonFormatted);
+                optionBar.appendChild(buttonCollapseAll);
+
+                // Attach event handlers
+                document.addEventListener('click', generalClick, false);
+
+
+                // Put option bar in DOM
+                jfContent.parentNode.appendChild(optionBar);
+                break;
+
+            case 'FORMATTED' :
+                // Insert HTML content
+                formattingMsg.style.display = "";
+                jfContent.innerHTML = msg[1];
+
+                displayedFormattedJsonTime = +(new Date());
+
+                // console.markTimeline('JSON formatted and displayed') ;
+                break;
+
+            default :
+                throw new Error('Message not understood: ' + msg[0]);
+        }
+    };
+
+    // console.timeEnd('established port') ;
+
+    var lastKvovIdGiven = 0;
+
+    function collapse(elements) {
+        var el, i, blockInner, count;
+
+        for (i = elements.length - 1; i >= 0; i--) {
+            el = elements[i];
+            el.classList.add('collapsed');
+
+            // (CSS hides the contents and shows an ellipsis.)
+
+            // Add a count of the number of child properties/items (if not already done for this item)
+            if (!el.id) {
+                el.id = 'kvov' + (++lastKvovIdGiven);
+
+                // Find the blockInner
+                blockInner = el.firstElementChild;
+                while (blockInner && !blockInner.classList.contains('blockInner')) {
+                    blockInner = blockInner.nextElementSibling;
+                }
+                if (!blockInner)
+                    continue;
+
+                // See how many children in the blockInner
+                count = blockInner.children.length;
+
+                // Generate comment text eg "4 items"
+                var comment = count + (count === 1 ? ' item' : ' items');
+                // Add CSS that targets it
+                jfStyleEl.insertAdjacentHTML(
+                    'beforeend',
+                    '\n#kvov' + lastKvovIdGiven + '.collapsed:after{color: #aaa; content:" // ' + comment + '"}'
+                );
+            }
+        }
+    }
+
+    function expand(elements) {
+        for (var i = elements.length - 1; i >= 0; i--)
+            elements[i].classList.remove('collapsed');
+    }
+
+    var mac = navigator.platform.indexOf('Mac') !== -1,
+        modKey;
+    if (mac)
+        modKey = function (ev) {
+            return ev.metaKey;
+        };
+    else
+        modKey = function (ev) {
+            return ev.ctrlKey;
+        };
+
+    function generalClick(ev) {
+        // console.log('click', ev) ;
+
+        if (ev.which === 1) {
+            var elem = ev.target;
+
+            if (elem.className === 'e') {
+                // It's a click on an expander.
+
+                ev.preventDefault();
+
+                var parent = elem.parentNode,
+                    div = jfContent,
+                    prevBodyHeight = document.body.offsetHeight,
+                    scrollTop = document.body.scrollTop,
+                    parentSiblings
+                ;
+
+                // Expand or collapse
+                if (parent.classList.contains('collapsed')) {
+                    // EXPAND
+                    if (modKey(ev))
+                        expand(parent.parentNode.children);
+                    else
+                        expand([parent]);
+                }
+                else {
+                    // COLLAPSE
+                    if (modKey(ev))
+                        collapse(parent.parentNode.children);
+                    else
+                        collapse([parent]);
+                }
+
+                // Restore scrollTop somehow
+                // Clear current extra margin, if any
+                div.style.marginBottom = 0;
+
+                // No need to worry if all content fits in viewport
+                if (document.body.offsetHeight < window.innerHeight) {
+                    // console.log('document.body.offsetHeight < window.innerHeight; no need to adjust height') ;
+                    return;
+                }
+
+                // And no need to worry if scrollTop still the same
+                if (document.body.scrollTop === scrollTop) {
+                    // console.log('document.body.scrollTop === scrollTop; no need to adjust height') ;
+                    return;
+                }
+
+                // console.log('Scrolltop HAS changed. document.body.scrollTop is now '+document.body.scrollTop+'; was '+scrollTop) ;
+
+                // The body has got a bit shorter.
+                // We need to increase the body height by a bit (by increasing the bottom margin on the jfContent div). The amount to increase it is whatever is the difference between our previous scrollTop and our new one.
+
+                // Work out how much more our target scrollTop is than this.
+                var difference = scrollTop - document.body.scrollTop + 8; // it always loses 8px; don't know why
+
+                // Add this difference to the bottom margin
+                //var currentMarginBottom = parseInt(div.style.marginBottom) || 0 ;
+                div.style.marginBottom = difference + 'px';
+
+                // Now change the scrollTop back to what it was
+                document.body.scrollTop = scrollTop;
+
+                return;
+            }
+        }
+    }
+
+    /**
+     * 执行代码格式化
+     * @param  {[type]} jsonStr [description]
+     * @return {[type]}
+     */
+    var format = function (jsonStr) {
+
+        // 如果jsonStr和上一次的一模一样,就不用再格式化了
+        try {
+            var str1 = JSON.stringify(JSON.parse(jsonStr));
+            var str2 = JSON.stringify(JSON.parse(pre.innerText));
+            if (str1 == str2) {
+                alert('JSON内容没有变化');
+                return false;
+            }
+        } catch (e) {
+        }
+
+        try {
+            jfContent.innerHTML = '';
+            pre.innerHTML = '';
+            document.querySelector('#boxOpt').remove();
+        } catch (e) {
+        }
+
+        // Send the contents of the PRE to the BG script
+        // Add jfContent DIV, ready to display stuff
+        jfContent = document.getElementById('jfContent');
+        if (!jfContent) {
+            jfContent = document.createElement('div');
+            jfContent.id = 'jfContent';
+            document.body.appendChild(jfContent);
+        }
+        jfContent.style.display = '';
+
+        pre = document.getElementById('jfContent_pre');
+        if (!pre) {
+            pre = document.createElement('pre');
+            pre.id = 'jfContent_pre';
+            document.body.appendChild(pre);
+        }
+        pre.innerHTML = JSON.stringify(JSON.parse(jsonStr), null, 4);
+        pre.style.display = "none";
+
+        jfStyleEl = document.getElementById('jfStyleEl');
+        if (!jfStyleEl) {
+            jfStyleEl = document.createElement('style');
+            document.head.appendChild(jfStyleEl);
+        }
+
+        formattingMsg = document.getElementById('formattingMsg');
+        if (!formattingMsg) {
+            formattingMsg = document.createElement('pre');
+            formattingMsg.id = 'formattingMsg';
+            formattingMsg.innerHTML = '<svg id="spinner" width="16" height="16" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" version="1.1">' +
+                '<path d="M 150,0 a 150,150 0 0,1 106.066,256.066 l -35.355,-35.355 a -100,-100 0 0,0 -70.711,-170.711 z" fill="#3d7fe6"></path></svg> 格式化中...';
+            document.body.appendChild(formattingMsg);
+        }
+
+        // Post the contents of the PRE
+        JsonFormatDealer.postMessage({
+            type: "SENDING TEXT",
+            text: jsonStr,
+            length: jsonStr.length
+        });
+
+        _loadJquery();
+        // 事件绑定
+        _addEvents();
+        // 支持文件下载
+        _downloadSupport(JSON.parse(jsonStr));
+    };
+
+    var _loadJquery = function () {
+        if (typeof Tarp === 'object') {
+            window.jQuery = window.$ = Tarp.require('../static/vendor/jquery/jquery-3.3.1.min.js');
+            Tarp.require('../static/js/core/utils.js');
+        } else {
+            alert('无法加载Tarp.require.js');
+        }
+    };
+
+    /**
+     * 直接下载,能解决中文乱码
+     * @param json
+     * @private
+     */
+    var _downloadSupport = function (json) {
+
+        // 下载链接
+        var localUrl = location.href;
+        var dt = (new Date()).format('yyyyMMddHHmmss');
+        var content = JSON.stringify(json, null, 4);
+        content = ['/* ', localUrl, ' */', '\n', content].join('');
+        var blob = new Blob([content], {type: 'application/octet-stream'});
+
+        var aLink = $('<a id="btnDownload" target="_blank" title="保存到本地">下载JSON数据</a>').prependTo('#optionBar');
+        aLink.attr('download', 'FeHelper-' + dt + '.json');
+        aLink.attr('href', URL.createObjectURL(blob));
+    };
+
+
+    /**
+     * chrome 下复制到剪贴板
+     * @param text
+     */
+    var _copyToClipboard = function (text) {
+        var input = document.createElement('textarea');
+        input.style.position = 'fixed';
+        input.style.opacity = 0;
+        input.value = text;
+        document.body.appendChild(input);
+        input.select();
+        document.execCommand('Copy');
+        document.body.removeChild(input);
+
+        alert('Json片段复制成功,随处粘贴可用!')
+    };
+
+    /**
+     * 给某个节点增加操作项
+     * @param el
+     * @private
+     */
+    var _addOptForItem = function (el) {
+
+        // 下载json片段
+        var fnDownload = function (ec) {
+            var txt = el.text().replace(/":\s/gm, '":').replace(/,$/, '').trim();
+            if (!(/^{/.test(txt) && /\}$/.test(txt)) && !(/^\[/.test(txt) && /\]$/.test(txt))) {
+                txt = '{' + txt + '}';
+            }
+            try {
+                txt = JSON.stringify(JSON.parse(txt), null, 4);
+            } catch (err) {
+            }
+
+            // 下载片段
+            var dt = (new Date()).format('yyyyMMddHHmmss');
+            var blob = new Blob([txt], {type: 'application/octet-stream'});
+
+            $(this).attr('download', 'FeHelper-' + dt + '.json').attr('href', URL.createObjectURL(blob));
+        };
+
+        // 复制json片段
+        var fnCopy = function (ec) {
+            var txt = el.text().replace(/":\s/gm, '":').replace(/,$/, '').trim();
+            if (!(/^{/.test(txt) && /\}$/.test(txt)) && !(/^\[/.test(txt) && /\]$/.test(txt))) {
+                txt = '{' + txt + '}';
+            }
+            try {
+                txt = JSON.stringify(JSON.parse(txt), null, 4);
+            } catch (err) {
+            }
+            _copyToClipboard(txt);
+        };
+
+        // 删除json片段
+        var fnDel = function (ed) {
+            if (el.parent().is('#formattedJson')) {
+                alert('如果连最外层的Json也删掉的话,就没啥意义了哦!');
+                return false;
+            }
+            alert('节点已删除成功!');
+            el.remove();
+            boxOpt.css('top', -1000).hide();
+        };
+
+        var boxOpt = $('#boxOpt');
+        if (!boxOpt.length) {
+            boxOpt = $('<div id="boxOpt"><a class="opt-download" target="_blank">下载</a>|<a class="opt-copy">复制</a>|<a class="opt-del">删除</a></div>').appendTo('body');
+        }
+
+        boxOpt.find('a.opt-download').unbind('click').bind('click', fnDownload);
+        boxOpt.find('a.opt-copy').unbind('click').bind('click', fnCopy);
+        boxOpt.find('a.opt-del').unbind('click').bind('click', fnDel);
+
+        boxOpt.css({
+            left: el.offset().left + el.width() - 90,
+            top: el.offset().top
+        }).show();
+    };
+
+    // 附加操作
+    var _addEvents = function () {
+        $('#jfContent .kvov').bind('click', function (e) {
+            if ($(this).hasClass('x-outline')) {
+                $('#boxOpt').remove();
+                $(this).removeClass('x-outline');
+                return false;
+            }
+
+            $('.x-outline').removeClass('x-outline');
+            var el = $(this).removeClass('x-hover').addClass('x-outline');
+
+            // 增加复制、删除功能
+            _addOptForItem(el);
+
+            if (!$(e.target).is('.kvov .e')) {
+                e.stopPropagation();
+            } else {
+                $(e.target).parent().trigger('click');
+            }
+        }).bind('mouseover', function (e) {
+            $(this).addClass('x-hover');
+            return false;
+        }).bind('mouseout', function (e) {
+            $(this).removeClass('x-hover');
+        });
+
+    };
+
+    return {
+        format: format,
+        postMessage: postMessage
+    }
+})();
+
+var JsonFormatDealer = (function () {
+
+    "use strict";
+
+    // Constants
+    var
+        TYPE_STRING = 1,
+        TYPE_NUMBER = 2,
+        TYPE_OBJECT = 3,
+        TYPE_ARRAY = 4,
+        TYPE_BOOL = 5,
+        TYPE_NULL = 6
+    ;
+
+    // Utility functions
+    function removeComments(str) {
+        str = ('__' + str + '__').split('');
+        var mode = {
+            singleQuote: false,
+            doubleQuote: false,
+            regex: false,
+            blockComment: false,
+            lineComment: false,
+            condComp: false
+        };
+        for (var i = 0, l = str.length; i < l; i++) {
+            if (mode.regex) {
+                if (str[i] === '/' && str[i - 1] !== '\\') {
+                    mode.regex = false;
+                }
+                continue;
+            }
+            if (mode.singleQuote) {
+                if (str[i] === "'" && str[i - 1] !== '\\') {
+                    mode.singleQuote = false;
+                }
+                continue;
+            }
+            if (mode.doubleQuote) {
+                if (str[i] === '"' && str[i - 1] !== '\\') {
+                    mode.doubleQuote = false;
+                }
+                continue;
+            }
+            if (mode.blockComment) {
+                if (str[i] === '*' && str[i + 1] === '/') {
+                    str[i + 1] = '';
+                    mode.blockComment = false;
+                }
+                str[i] = '';
+                continue;
+            }
+            if (mode.lineComment) {
+                if (str[i + 1] === '\n' || str[i + 1] === '\r') {
+                    mode.lineComment = false;
+                }
+                str[i] = '';
+                continue;
+            }
+            if (mode.condComp) {
+                if (str[i - 2] === '@' && str[i - 1] === '*' && str[i] === '/') {
+                    mode.condComp = false;
+                }
+                continue;
+            }
+            mode.doubleQuote = str[i] === '"';
+            mode.singleQuote = str[i] === "'";
+            if (str[i] === '/') {
+                if (str[i + 1] === '*' && str[i + 2] === '@') {
+                    mode.condComp = true;
+                    continue;
+                }
+                if (str[i + 1] === '*') {
+                    str[i] = '';
+                    mode.blockComment = true;
+                    continue;
+                }
+                if (str[i + 1] === '/') {
+                    str[i] = '';
+                    mode.lineComment = true;
+                    continue;
+                }
+                mode.regex = true;
+            }
+        }
+        return str.join('').slice(2, -2);
+    }
+
+    // function spin(seconds) {
+    //   // spin - Hog the CPU for the specified number of seconds
+    //   // (for simulating long processing times in development)
+    //   var stop = +new Date() + (seconds*1000)  ;
+    //   while (new Date() < stop) {}
+    //   return true ;
+    // }
+
+    // Record current version (in case future update wants to know)
+    localStorage.jfVersion = '0.5.6';
+
+    // Template elements
+    var templates,
+        baseDiv = document.createElement('div'),
+        baseSpan = document.createElement('span');
+
+    function getSpanBoth(innerText, className) {
+        var span = baseSpan.cloneNode(false);
+        span.className = className;
+        span.innerText = innerText;
+        return span;
+    }
+
+    function getSpanText(innerText) {
+        var span = baseSpan.cloneNode(false);
+        span.innerText = innerText;
+        return span;
+    }
+
+    function getSpanClass(className) {
+        var span = baseSpan.cloneNode(false);
+        span.className = className;
+        return span;
+    }
+
+    function getDivClass(className) {
+        var span = baseDiv.cloneNode(false);
+        span.className = className;
+        return span;
+    }
+
+    // Create template nodes
+    var templatesObj = {
+        t_kvov: getDivClass('kvov'),
+        t_exp: getSpanClass('e'),
+        t_key: getSpanClass('k'),
+        t_string: getSpanClass('s'),
+        t_number: getSpanClass('n'),
+
+        t_null: getSpanBoth('null', 'nl'),
+        t_true: getSpanBoth('true', 'bl'),
+        t_false: getSpanBoth('false', 'bl'),
+
+        t_oBrace: getSpanBoth('{', 'b'),
+        t_cBrace: getSpanBoth('}', 'b'),
+        t_oBracket: getSpanBoth('[', 'b'),
+        t_cBracket: getSpanBoth(']', 'b'),
+
+        t_ellipsis: getSpanClass('ell'),
+        t_blockInner: getSpanClass('blockInner'),
+
+        t_colonAndSpace: document.createTextNode(':\u00A0'),
+        t_commaText: document.createTextNode(','),
+        t_dblqText: document.createTextNode('"')
+    };
+
+    // Core recursive DOM-building function
+    function getKvovDOM(value, keyName) {
+        var type,
+            kvov,
+            nonZeroSize,
+            templates = templatesObj, // bring into scope for tiny speed boost
+            objKey,
+            keySpan,
+            valueElement
+        ;
+
+        // Establish value type
+        if (typeof value === 'string')
+            type = TYPE_STRING;
+        else if (typeof value === 'number')
+            type = TYPE_NUMBER;
+        else if (value === false || value === true)
+            type = TYPE_BOOL;
+        else if (value === null)
+            type = TYPE_NULL;
+        else if (value instanceof Array)
+            type = TYPE_ARRAY;
+        else
+            type = TYPE_OBJECT;
+
+        // Root node for this kvov
+        kvov = templates.t_kvov.cloneNode(false);
+
+        // Add an 'expander' first (if this is object/array with non-zero size)
+        if (type === TYPE_OBJECT || type === TYPE_ARRAY) {
+            nonZeroSize = false;
+            for (objKey in value) {
+                if (value.hasOwnProperty(objKey)) {
+                    nonZeroSize = true;
+                    break; // no need to keep counting; only need one
+                }
+            }
+            if (nonZeroSize)
+                kvov.appendChild(templates.t_exp.cloneNode(false));
+        }
+
+        // If there's a key, add that before the value
+        if (keyName !== false) { // NB: "" is a legal keyname in JSON
+            // This kvov must be an object property
+            kvov.classList.add('objProp');
+            // Create a span for the key name
+            keySpan = templates.t_key.cloneNode(false);
+            keySpan.textContent = JSON.stringify(keyName).slice(1, -1); // remove quotes
+            // Add it to kvov, with quote marks
+            kvov.appendChild(templates.t_dblqText.cloneNode(false));
+            kvov.appendChild(keySpan);
+            kvov.appendChild(templates.t_dblqText.cloneNode(false));
+            // Also add ":&nbsp;" (colon and non-breaking space)
+            kvov.appendChild(templates.t_colonAndSpace.cloneNode(false));
+        }
+        else {
+            // This is an array element instead
+            kvov.classList.add('arrElem');
+        }
+
+        // Generate DOM for this value
+        var blockInner, childKvov;
+        switch (type) {
+            case TYPE_STRING:
+                // If string is a URL, get a link, otherwise get a span
+                var innerStringEl = baseSpan.cloneNode(false),
+                    escapedString = JSON.stringify(value);
+                escapedString = escapedString.substring(1, escapedString.length - 1); // remove quotes
+                if (value[0] === 'h' && value.substring(0, 4) === 'http') { // crude but fast - some false positives, but rare, and UX doesn't suffer terribly from them.
+                    var innerStringA = document.createElement('A');
+                    innerStringA.href = value;
+                    innerStringA.innerText = escapedString;
+                    innerStringEl.appendChild(innerStringA);
+                }
+                else {
+                    innerStringEl.innerText = escapedString;
+                }
+                valueElement = templates.t_string.cloneNode(false);
+                valueElement.appendChild(templates.t_dblqText.cloneNode(false));
+                valueElement.appendChild(innerStringEl);
+                valueElement.appendChild(templates.t_dblqText.cloneNode(false));
+                kvov.appendChild(valueElement);
+                break;
+
+            case TYPE_NUMBER:
+                // Simply add a number element (span.n)
+                valueElement = templates.t_number.cloneNode(false);
+                valueElement.innerText = value;
+                kvov.appendChild(valueElement);
+                break;
+
+            case TYPE_OBJECT:
+                // Add opening brace
+                kvov.appendChild(templates.t_oBrace.cloneNode(true));
+                // If any properties, add a blockInner containing k/v pair(s)
+                if (nonZeroSize) {
+                    // Add ellipsis (empty, but will be made to do something when kvov is collapsed)
+                    kvov.appendChild(templates.t_ellipsis.cloneNode(false));
+                    // Create blockInner, which indents (don't attach yet)
+                    blockInner = templates.t_blockInner.cloneNode(false);
+                    // For each key/value pair, add as a kvov to blockInner
+                    var count = 0, k, comma;
+                    for (k in value) {
+                        if (value.hasOwnProperty(k)) {
+                            count++;
+                            childKvov = getKvovDOM(value[k], k);
+                            // Add comma
+                            comma = templates.t_commaText.cloneNode();
+                            childKvov.appendChild(comma);
+                            blockInner.appendChild(childKvov);
+                        }
+                    }
+                    // Now remove the last comma
+                    childKvov.removeChild(comma);
+                    // Add blockInner
+                    kvov.appendChild(blockInner);
+                }
+
+                // Add closing brace
+                kvov.appendChild(templates.t_cBrace.cloneNode(true));
+                break;
+
+            case TYPE_ARRAY:
+                // Add opening bracket
+                kvov.appendChild(templates.t_oBracket.cloneNode(true));
+                // If non-zero length array, add blockInner containing inner vals
+                if (nonZeroSize) {
+                    // Add ellipsis
+                    kvov.appendChild(templates.t_ellipsis.cloneNode(false));
+                    // Create blockInner (which indents) (don't attach yet)
+                    blockInner = templates.t_blockInner.cloneNode(false);
+                    // For each key/value pair, add the markup
+                    for (var i = 0, length = value.length, lastIndex = length - 1; i < length; i++) {
+                        // Make a new kvov, with no key
+                        childKvov = getKvovDOM(value[i], false);
+                        // Add comma if not last one
+                        if (i < lastIndex)
+                            childKvov.appendChild(templates.t_commaText.cloneNode());
+                        // Append the child kvov
+                        blockInner.appendChild(childKvov);
+                    }
+                    // Add blockInner
+                    kvov.appendChild(blockInner);
+                }
+                // Add closing bracket
+                kvov.appendChild(templates.t_cBracket.cloneNode(true));
+                break;
+
+            case TYPE_BOOL:
+                if (value)
+                    kvov.appendChild(templates.t_true.cloneNode(true));
+                else
+                    kvov.appendChild(templates.t_false.cloneNode(true));
+                break;
+
+            case TYPE_NULL:
+                kvov.appendChild(templates.t_null.cloneNode(true));
+                break;
+        }
+
+        return kvov;
+    }
+
+    // Function to convert object to an HTML string
+    function jsonObjToHTML(obj, jsonpFunctionName) {
+
+        // spin(5) ;
+
+        // Format object (using recursive kvov builder)
+        var rootKvov = getKvovDOM(obj, false);
+
+        // The whole DOM is now built.
+
+        // Set class on root node to identify it
+        rootKvov.classList.add('rootKvov');
+
+        // Make div#formattedJson and append the root kvov
+        var divFormattedJson = document.createElement('DIV');
+        divFormattedJson.id = 'formattedJson';
+        divFormattedJson.appendChild(rootKvov);
+
+        // Convert it to an HTML string (shame about this step, but necessary for passing it through to the content page)
+        var returnHTML = divFormattedJson.outerHTML;
+
+        // Top and tail with JSONP padding if necessary
+        if (jsonpFunctionName !== null) {
+            returnHTML =
+                '<div id="jsonpOpener">' + jsonpFunctionName + ' ( </div>' +
+                returnHTML +
+                '<div id="jsonpCloser">)</div>';
+        }
+
+        // Return the HTML
+        return returnHTML;
+    }
+
+    // Listen for requests from content pages wanting to set up a port
+    var postMessage = function (msg) {
+        var jsonpFunctionName = null;
+
+        if (msg.type === 'SENDING TEXT') {
+            // Try to parse as JSON
+            var obj,
+                text = msg.text;
+            try {
+                obj = new Function('return ' + text)();
+            }
+            catch (e) {
+                // Not JSON; could be JSONP though.
+
+                // Try stripping 'padding' (if any), and try parsing it again
+                text = text.trim();
+                // Find where the first paren is (and exit if none)
+                var indexOfParen;
+                if (!(indexOfParen = text.indexOf('('))) {
+                    JsonFormatEntrance.postMessage(['NOT JSON', 'no opening parenthesis']);
+                    return;
+                }
+
+                // Get the substring up to the first "(", with any comments/whitespace stripped out
+                var firstBit = removeComments(text.substring(0, indexOfParen)).trim();
+                if (!firstBit.match(/^[a-zA-Z_$][\.\[\]'"0-9a-zA-Z_$]*$/)) {
+                    // The 'firstBit' is NOT a valid function identifier.
+                    JsonFormatEntrance.postMessage(['NOT JSON', 'first bit not a valid function name']);
+                    return;
+                }
+
+                // Find last parenthesis (exit if none)
+                var indexOfLastParen;
+                if (!(indexOfLastParen = text.lastIndexOf(')'))) {
+                    JsonFormatEntrance.postMessage(['NOT JSON', 'no closing paren']);
+                    return;
+                }
+
+                // Check that what's after the last parenthesis is just whitespace, comments, and possibly a semicolon (exit if anything else)
+                var lastBit = removeComments(text.substring(indexOfLastParen + 1)).trim();
+                if (lastBit !== "" && lastBit !== ';') {
+                    JsonFormatEntrance.postMessage(['NOT JSON', 'last closing paren followed by invalid characters']);
+                    return;
+                }
+
+                // So, it looks like a valid JS function call, but we don't know whether it's JSON inside the parentheses...
+                // Check if the 'argument' is actually JSON (and record the parsed result)
+                text = text.substring(indexOfParen + 1, indexOfLastParen);
+                try {
+                    obj = JSON.parse(text);
+                }
+                catch (e2) {
+                    // Just some other text that happens to be in a function call.
+                    // Respond as not JSON, and exit
+                    JsonFormatEntrance.postMessage(['NOT JSON', 'looks like a function call, but the parameter is not valid JSON']);
+                    return;
+                }
+
+                jsonpFunctionName = firstBit;
+            }
+
+            // If still running, we now have obj, which is valid JSON.
+
+            // Ensure it's not a number or string (technically valid JSON, but no point prettifying it)
+            if (typeof obj !== 'object' && typeof obj !== 'array') {
+                JsonFormatEntrance.postMessage(['NOT JSON', 'technically JSON but not an object or array']);
+                return;
+            }
+
+            // And send it the message to confirm that we're now formatting (so it can show a spinner)
+            JsonFormatEntrance.postMessage(['FORMATTING' /*, JSON.stringify(localStorage)*/]);
+
+            // Do formatting
+            var html = jsonObjToHTML(obj, jsonpFunctionName);
+
+            // Post the HTML string to the content script
+            JsonFormatEntrance.postMessage(['FORMATTED', html]);
+
+        }
+    };
+
+    return {
+        postMessage: postMessage
+    };
+})();
+
+module.exports = {
+    format: JsonFormatEntrance.format
+};

+ 70 - 0
v2.0/apps/json-format/index.css

@@ -0,0 +1,70 @@
+@import url("../static/css/bootstrap.min.css");
+@import url("./without-ui.css");
+
+body {
+    background: #fff;
+}
+.wp-json {
+    width:auto;
+}
+.wp-json .mod-json {
+    position: absolute;
+    top: 60px;
+    bottom: 0;
+    right:0;
+    left:20px;
+}
+.wp-json .mod-json .panel-txt {
+    position: absolute;
+    width: 500px;
+    top: 15px;
+    bottom: 0;
+}
+
+#modJsonResult {
+    margin-left: 500px;
+    position: relative;
+    overflow-y: auto;
+    padding-top: 0;
+    padding-bottom:20px;
+}
+
+#optionBar {
+    position: fixed;
+    top: 26px;
+    right: 30px;
+}
+#jfContent_pre {
+    display: none;
+    padding: 10px;
+}
+#jsonSource {
+    height: calc(100% - 50px);
+    font-size: 10px;
+}
+#errorMsg {
+    color: #f00;
+    margin-left: 10px;
+    float: right;
+}
+#jfContent {
+    margin-right: 10px;
+}
+.x-placeholder {
+    padding-top: 50px;
+    text-align: center;
+}
+.x-placeholder img{
+    width: 400px;
+    opacity: 0.15;
+}
+.x-xdemo,a.x-xdemo {
+    margin-left: 30px;
+    font-size: 12px;
+    color: blue;
+    cursor: pointer;
+    text-decoration: underline;
+}
+.x-xdemo:hover {
+    text-decoration: underline;
+}

+ 136 - 0
v2.0/apps/json-format/index.html

@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html lang="zh-CN">
+    <head>
+        <title>Json格式化查看工具</title>
+        <meta charset="UTF-8">
+        <link rel="stylesheet" href="index.css" />
+        <script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
+        <script src="../static/vendor/require/require.js"></script>
+    </head>
+    <body>
+        <div class="wrapper wp-json" id="pageContainer">
+            <div class="panel panel-default" style="margin-bottom: 0px;">
+                <div class="panel-heading">
+                    <h3 class="panel-title">
+                        <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-high">
+                            <img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:JSON格式化查看
+                        <span class="x-xdemo" ref="demoLink1" @click="jsonSource=$refs.demoBox.value">示例1:JSON片段</span>
+                        <a class="x-xdemo" href="https://www.sojson.com/open/api/weather/json.shtml?city=%E5%8C%97%E4%BA%AC" target="_blank">示例2:在线JSON-1</a>
+                        <a class="x-xdemo" href="https://suggest.taobao.com/sug?code=utf-8&q=iphonex&callback=thisIsACallbackFunction" target="_blank">示例3:在线JSON-2</a></h3>
+                </div>
+            </div>
+
+            <div class="panel-body mod-json">
+                <div class="row panel-txt">
+                    <textarea class="form-control mod-textarea" id="jsonSource" placeholder="在这里粘贴您需要进行格式化的JSON代码" ref="jsonBox" v-model="jsonSource"></textarea>
+                    <div><button id="btnFormat" class="btn btn-primary ui-mt-10" @click="format">格式化</button><span id="errorMsg" v-html="errorMsg"></span></div>
+                </div>
+
+                <div class="row rst-item" id="modJsonResult" v-show="canResultShow">
+                    <div id="formattingMsg">
+                        <svg id="spinner" width="16" height="16" viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" version="1.1">
+                            <path d="M 150,0 a 150,150 0 0,1 106.066,256.066 l -35.355,-35.355 a -100,-100 0 0,0 -70.711,-170.711 z" fill="#3d7fe6"></path>
+                        </svg>
+                        加载中...
+                    </div>
+                    <div id="jfCallbackName_start" class="callback-name">{{jfCallbackName_start}}</div>
+                    <div id="jfContent" v-html="resultContent"></div>
+                    <pre id="jfContent_pre"></pre>
+                    <div id="jfCallbackName_end" class="callback-name">{{jfCallbackName_end}}</div>
+                </div>
+            </div>
+
+        <textarea name="demo" id="demo" cols="30" rows="10" class="hide" ref="demoBox">
+{
+    "date": "20180322",
+    "message": "Success !",
+    "status": 200,
+    "city": "北京",
+    "count": 632,
+    "data": {
+        "shidu": "34%",
+        "pm25": 73,
+        "pm10": 91,
+        "quality": "良",
+        "wendu": "5",
+        "ganmao": "极少数敏感人群应减少户外活动",
+        "yesterday": {
+            "date": "21日星期三",
+            "sunrise": "06:19",
+            "high": "高温 11.0℃",
+            "low": "低温 1.0℃",
+            "sunset": "18:26",
+            "aqi": 85,
+            "fx": "南风",
+            "fl": "<3级",
+            "type": "多云",
+            "notice": "阴晴之间,谨防紫外线侵扰"
+        },
+        "forecast": [
+            {
+                "date": "22日星期四",
+                "sunrise": "06:17",
+                "high": "高温 17.0℃",
+                "low": "低温 1.0℃",
+                "sunset": "18:27",
+                "aqi": 98,
+                "fx": "西南风",
+                "fl": "<3级",
+                "type": "晴",
+                "notice": "愿你拥有比阳光明媚的心情"
+            },
+            {
+                "date": "23日星期五",
+                "sunrise": "06:16",
+                "high": "高温 18.0℃",
+                "low": "低温 5.0℃",
+                "sunset": "18:28",
+                "aqi": 118,
+                "fx": "无持续风向",
+                "fl": "<3级",
+                "type": "多云",
+                "notice": "阴晴之间,谨防紫外线侵扰"
+            },
+            {
+                "date": "24日星期六",
+                "sunrise": "06:14",
+                "high": "高温 21.0℃",
+                "low": "低温 7.0℃",
+                "sunset": "18:29",
+                "aqi": 52,
+                "fx": "西南风",
+                "fl": "<3级",
+                "type": "晴",
+                "notice": "愿你拥有比阳光明媚的心情"
+            },
+            {
+                "date": "25日星期日",
+                "sunrise": "06:13",
+                "high": "高温 22.0℃",
+                "low": "低温 7.0℃",
+                "sunset": "18:30",
+                "aqi": 71,
+                "fx": "西南风",
+                "fl": "<3级",
+                "type": "晴",
+                "notice": "愿你拥有比阳光明媚的心情"
+            },
+            {
+                "date": "26日星期一",
+                "sunrise": "06:11",
+                "high": "高温 21.0℃",
+                "low": "低温 8.0℃",
+                "sunset": "18:31",
+                "aqi": 97,
+                "fx": "西南风",
+                "fl": "<3级",
+                "type": "多云",
+                "notice": "阴晴之间,谨防紫外线侵扰"
+            }
+        ]
+    }
+}</textarea>
+        </div>
+        <script src="index.js"></script>
+    </body>
+</html>

+ 98 - 0
v2.0/apps/json-format/index.js

@@ -0,0 +1,98 @@
+/**
+ * FeHelper Json Format Tools
+ */
+new Vue({
+    el: '#pageContainer',
+    data: {
+        defaultResultTpl: '<div class="x-placeholder"><img src="./json-demo.jpg" alt="json-placeholder"></div>',
+        resultContent: '',
+        jsonSource: '',
+        errorMsg: '',
+        canResultShow: false,
+        jfCallbackName_start: '',
+        jfCallbackName_end: ''
+    },
+    mounted: function () {
+        this.resultContent = this.defaultResultTpl;
+
+        // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
+        chrome.runtime.onMessage.addListener((request, sender, callback) => {
+            let MSG_TYPE = Tarp.require('../static/js/core/msg_type');
+            if (request.type === MSG_TYPE.TAB_CREATED_OR_UPDATED && request.event === MSG_TYPE.JSON_FORMAT) {
+                if (request.content) {
+                    this.jsonSource = request.content || this.defaultResultTpl;
+                    this.format();
+                }
+            }
+        });
+
+        //输入框聚焦
+        this.$refs.jsonBox.focus();
+
+    },
+    methods: {
+        format: function () {
+            this.errorMsg = '';
+            this.canResultShow = true;
+            this.resultContent = this.defaultResultTpl;
+
+            let source = this.jsonSource.replace(/\n/gm, ' ');
+            if (!source) {
+                return;
+            }
+
+            // JSONP形式下的callback name
+            let funcName = null;
+            // json对象
+            let jsonObj = null;
+
+            // 下面校验给定字符串是否为一个合法的json
+            try {
+                // 再看看是不是jsonp的格式
+                let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/igm;
+                let matches = reg.exec(source);
+                if (matches != null) {
+                    funcName = matches[1];
+                    let newSource = matches[2];
+                    jsonObj = new Function("return " + newSource)();
+                }
+
+                if (jsonObj == null || typeof jsonObj !== 'object') {
+                    jsonObj = new Function("return " + source)();
+
+                    // 还要防止下面这种情况:  "{\"ret\":\"0\", \"msg\":\"ok\"}"
+                    if (typeof jsonObj === "string") {
+                        // 再来一次
+                        jsonObj = new Function("return " + jsonObj)();
+                    }
+                }
+            } catch (ex) {
+                this.errorMsg = ex.message;
+                return;
+            }
+
+            // 是json格式,可以进行JSON自动格式化
+            if (jsonObj != null && typeof jsonObj === "object") {
+                try {
+                    // 要尽量保证格式化的东西一定是一个json,所以需要把内容进行JSON.stringify处理
+                    source = JSON.stringify(jsonObj);
+                } catch (ex) {
+                    // 通过JSON反解不出来的,一定有问题
+                    return;
+                }
+
+                // 格式化
+                Tarp.require('./format-lib').format(source);
+
+                // 如果是JSONP格式的,需要把方法名也显示出来
+                if (funcName != null) {
+                    this.jfCallbackName_start = funcName + '(';
+                    this.jfCallbackName_end = ')';
+                } else {
+                    this.jfCallbackName_start = '';
+                    this.jfCallbackName_end = '';
+                }
+            }
+        }
+    }
+});

BIN=BIN
v2.0/apps/json-format/json-demo.jpg


+ 128 - 0
v2.0/apps/json-format/without-ui.css

@@ -0,0 +1,128 @@
+#jfContent{-webkit-user-select:text;margin:0;}
+#optionBar{-webkit-user-select:none;display:block;position:absolute;top:0px;right:0px}
+#buttonFormatted,#buttonPlain,#btnDownload,#buttonCollapseAll{-webkit-border-radius:2px;-webkit-box-shadow:0px 1px 3px rgba(0,0,0,0.1);
+    -webkit-user-select:none;background:-webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);outline: none;
+    border:1px solid #aaa;color:#444;font-size:12px;margin-bottom:0px;min-width:4em;padding:3px 0;
+    position:relative;z-index:10;display:inline-block;width:80px;
+    text-shadow:1px 1px rgba(255,255,255,0.3)}
+#buttonCollapseAll{margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0;border-left:0;}
+#buttonFormatted{margin-right:0;border-top-right-radius:0;border-bottom-right-radius:0;border-right:none}
+#btnDownload {
+    padding: 4px 10px;
+    text-decoration: none;
+    color: #454545;
+    margin-right: 10px;
+    display: inline-block;
+}
+#buttonFormatted:hover,#buttonPlain:hover,#btnDownload:hover{-webkit-box-shadow:0px 1px 3px rgba(0,0,0,0.2);
+    background:#ebebeb -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);border-color:#999;color:#222}
+#buttonFormatted:active,#buttonPlain:active{-webkit-box-shadow:inset 0px 1px 3px rgba(0,0,0,0.2);
+    background:#ebebeb -webkit-linear-gradient(#f4f4f4, #efefef 40%, #dcdcdc);color:#333}
+#buttonFormatted.selected,#buttonPlain.selected,#buttonCollapseAll.selected{-webkit-box-shadow:inset 0px 1px 5px rgba(0,0,0,0.2);
+    background:#ebebeb -webkit-linear-gradient(#e4e4e4, #dfdfdf 40%, #dcdcdc);color:#333}
+#jsonpOpener,#jsonpCloser{padding:4px 0 0 8px;color:black;margin-bottom:-6px}
+#jsonpCloser{margin-top:0}
+#formattedJson{padding-left:28px;padding-top:6px}pre{padding:36px 5px 5px 5px}
+.kvov{display:block;padding-left:20px;margin-left:-20px;position:relative;padding-top: 2px;}
+
+#jfContent .kvov .s a {
+    color:#00b;text-decoration: underline;
+}
+#jfContent .kvov .s a:hover {
+    color:#b00;
+}
+.collapsed{white-space:nowrap}.collapsed>.blockInner{display:none}
+.collapsed>.ell:after{content:"\2026";font-weight:bold}
+.collapsed>.ell{margin:0 4px;color:#888}
+.collapsed .kvov{display:inline}
+.e{width:20px;height:18px;display:block;position:absolute;left:-2px;top:1px;z-index:5;
+    background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAATUlEQVQoFWNgYGA4wMDA8B8HPszAwMDghEMSpMkVpAAE9mJRdAwqB6bssSiA64YpRDYFRTdMgQ2SKRi6YYp2MTAwnIRxsNGWyC4HKQAAuIkf/0L9E0IAAAAASUVORK5CYII=");
+    background-repeat:no-repeat;background-position:center center;display:block;opacity:0.15}
+.collapsed>.e{-webkit-transform:rotate(-90deg);width:18px;height:20px;left:0px;top:0px}
+.e:hover{opacity:0.35}.e:active{opacity:0.5}.collapsed .kvov .e{display:none}
+.blockInner{display:block;padding-left:24px;border-left:1px dotted #bbb;margin-left:2px}
+#formattedJson,#jsonpOpener,#jsonpCloser{color:#333;font:13px/18px monospace}
+#formattedJson{color:#444}.b{font-weight:bold}.s{color:#0B7500;word-wrap:break-word}
+#jfContent a:link,#jfContent a:visited{text-decoration:none;color:inherit}
+#jfContent a:hover,#jfContent a:active{text-decoration:underline;color:#050}
+.bl,.nl,.n{font-weight:bold;color:#1A01CC}.k{color:black}
+#formattingMsg{font:13px "Lucida Grande", "Segoe UI", "Tahoma";padding:10px 0 0 8px;margin:0;color:#333;display:none;}
+#formattingMsg>svg{margin:0 7px;position:relative;top:1px}
+[hidden]{display:none !important}
+#jfContentspan{white-space:pre-wrap}
+@-webkit-keyframes spin{from{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}
+#spinner{-webkit-animation:spin 1s 0 infinite}*{-webkit-font-smoothing:antialiased}
+#jfContent .x-json-tips {
+    color:red;
+}
+#jfContent_pre {padding:0;margin:0;
+    word-break:break-word;
+}
+
+html {
+    font-size: 14px;
+    color:#333;
+    direction: ltr;
+}
+html body {
+    direction: inherit;
+}
+.mod-json .format-item button{
+    width:80px;
+    height:30px;
+    float: right;
+}
+.mod-json .rst-item {
+    position: relative;
+    padding-top:30px;
+}
+.mod-contentscript {
+    width: auto;
+}
+#formatTips {
+    color: #888;
+    font-size: 14px;
+    display: block;
+    position: absolute;
+    top: 0px;
+    left: 0px;
+}
+#jsonSource {
+    height: 120px;
+}
+.mod-json .callback-name {
+    font-weight: bolder;
+    color: #a00;
+}
+
+#jfContent .x-hover {
+    outline:1px solid #cdc;
+    background: #fff;
+}
+#jfContent .x-outline {
+    outline:1px solid #8ac;
+    box-shadow: rgba(100, 100, 100, 0.4) -3px 3px 5px;
+    font-weight: bold;
+    background-color: #fffff8;
+}
+#errorMsg {
+    margin-top: 10px;
+    float: left;
+    color: #f00;
+}
+#btnDownload{
+    width: 102px;
+}
+#boxOpt {
+    position: absolute;
+    z-index: 1024;
+}
+#boxOpt a {
+    cursor: pointer;
+    margin: 0 5px;
+    font-size: 12px;
+    color: #00f;
+}
+#boxOpt a:hover {
+    color:#f00;
+}

+ 78 - 0
v2.0/apps/static/js/core/utils.js

@@ -0,0 +1,78 @@
+/**
+ * 让所有字符串支持空白过滤功能:trim
+ * @retrn {String} 返回两端无空白的字符串
+ */
+String.prototype.trim = function(){
+    return this.replace(/^\s*|\s*$/g,"");
+};
+
+/**
+ * 日期格式化
+ * @param {Object} pattern
+ */
+Date.prototype.format = function(pattern){
+    var pad = function (source, length) {
+        var pre = "",
+            negative = (source < 0),
+            string = String(Math.abs(source));
+
+        if (string.length < length) {
+            pre = (new Array(length - string.length + 1)).join('0');
+        }
+
+        return (negative ?  "-" : "") + pre + string;
+    };
+
+    if ('string' != typeof pattern) {
+        return this.toString();
+    }
+
+    var replacer = function(patternPart, result) {
+        pattern = pattern.replace(patternPart, result);
+    }
+
+    var year    = this.getFullYear(),
+        month   = this.getMonth() + 1,
+        date2   = this.getDate(),
+        hours   = this.getHours(),
+        minutes = this.getMinutes(),
+        seconds = this.getSeconds();
+
+    replacer(/yyyy/g, pad(year, 4));
+    replacer(/yy/g, pad(parseInt(year.toString().slice(2), 10), 2));
+    replacer(/MM/g, pad(month, 2));
+    replacer(/M/g, month);
+    replacer(/dd/g, pad(date2, 2));
+    replacer(/d/g, date2);
+
+    replacer(/HH/g, pad(hours, 2));
+    replacer(/H/g, hours);
+    replacer(/hh/g, pad(hours % 12, 2));
+    replacer(/h/g, hours % 12);
+    replacer(/mm/g, pad(minutes, 2));
+    replacer(/m/g, minutes);
+    replacer(/ss/g, pad(seconds, 2));
+    replacer(/s/g, seconds);
+
+    return pattern;
+};
+
+/**
+ * 自动消失的Alert弹窗
+ * @param content
+ */
+window.alert = function (content) {
+    window.clearTimeout(window.feHelperAlertMsgTid);
+    var elAlertMsg = $("#fehelper_alertmsg").hide();
+    if(!elAlertMsg.get(0)) {
+        elAlertMsg = $('<div id="fehelper_alertmsg" style="position:fixed;top:5px;right:5px;z-index:1000000">' +
+            '<p style="background:#000;display:inline-block;color:#fff;text-align:center;' +
+            'padding:10px 10px;margin:0 auto;font-size:14px;border-radius:4px;">' + content + '</p></div>').appendTo('body');
+    }else{
+        elAlertMsg.find('p').text(content).end().show();
+    }
+
+    window.feHelperAlertMsgTid = window.setTimeout(function () {
+        elAlertMsg.hide(100);
+    }, 3000);
+};

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
v2.0/apps/static/vendor/jquery/jquery-3.3.1.min.js


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio