123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971 |
- /**
- * 日期格式化
- * @param {Object} pattern
- */
- Date.prototype.format = function (pattern) {
- let pad = function (source, length) {
- let 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();
- }
- let replacer = function (patternPart, result) {
- pattern = pattern.replace(patternPart, result);
- };
- let year = this.getFullYear(),
- month = this.getMonth() + 1,
- date2 = this.getDate(),
- hours = this.getHours(),
- minutes = this.getMinutes(),
- seconds = this.getSeconds(),
- milliSec = this.getMilliseconds();
- 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);
- replacer(/SSS/g, pad(milliSec, 3));
- replacer(/S/g, milliSec);
- return pattern;
- };
- /**
- * 自动消失的Alert弹窗
- * @param content
- */
- window.toast = function (content) {
- window.clearTimeout(window.feHelperAlertMsgTid);
- let elAlertMsg = document.querySelector("#fehelper_alertmsg");
- if (!elAlertMsg) {
- let elWrapper = document.createElement('div');
- elWrapper.innerHTML = '<div id="fehelper_alertmsg" style="position:fixed;bottom:25px;left: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>';
- elAlertMsg = elWrapper.childNodes[0];
- document.body.appendChild(elAlertMsg);
- } else {
- elAlertMsg.querySelector('p').innerHTML = content;
- elAlertMsg.style.display = 'block';
- }
- window.feHelperAlertMsgTid = window.setTimeout(function () {
- elAlertMsg.style.display = 'none';
- }, 1000);
- };
- /**
- * FeHelper Json Format Lib,入口文件
- * @example
- * Formatter.format(jsonString)
- */
- window.Formatter = (function () {
- "use strict";
- let jfContent,
- jfPre,
- jfStyleEl,
- jfStatusBar,
- formattingMsg;
- let lastItemIdGiven = 0;
- let cachedJsonString = '';
-
- // 单例Worker实例
- let workerInstance = null;
- let _initElements = function () {
- jfContent = $('#jfContent');
- if (!jfContent[0]) {
- jfContent = $('<div id="jfContent" />').appendTo('body');
- }
- jfPre = $('#jfContent_pre');
- if (!jfPre[0]) {
- jfPre = $('<pre id="jfContent_pre" />').appendTo('body');
- }
- jfStyleEl = $('#jfStyleEl');
- if (!jfStyleEl[0]) {
- jfStyleEl = $('<style id="jfStyleEl" />').appendTo('head');
- }
- formattingMsg = $('#formattingMsg');
- if (!formattingMsg[0]) {
- formattingMsg = $('<div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>').appendTo('body');
- }
- try {
- jfContent.html('').show();
- jfPre.html('').hide();
- jfStatusBar && jfStatusBar.hide();
- formattingMsg.hide();
- } catch (e) {
- }
- };
- /**
- * HTML特殊字符格式化
- * @param str
- * @returns {*}
- */
- let htmlspecialchars = function (str) {
- str = str.replace(/&/g, '&');
- str = str.replace(/</g, '<');
- str = str.replace(/>/g, '>');
- str = str.replace(/"/g, '"');
- str = str.replace(/'/g, ''');
- str = str.replace(/\\/g, '\');
- return str;
- };
- /**
- * 直接下载,能解决中文乱码
- * @param content
- * @private
- */
- let _downloadSupport = function (content) {
- // 下载链接
- let dt = (new Date()).format('yyyyMMddHHmmss');
- let blob = new Blob([content], {type: 'application/octet-stream'});
- let button = $('<button class="xjf-btn xjf-btn-right">下载JSON</button>').appendTo('#optionBar');
- if (typeof chrome === 'undefined' || !chrome.permissions) {
- button.click(function (e) {
- let aLink = $('#aLinkDownload');
- if (!aLink[0]) {
- aLink = $('<a id="aLinkDownload" target="_blank" title="保存到本地">下载JSON数据</a>').appendTo('body');
- aLink.attr('download', 'FeHelper-' + dt + '.json');
- aLink.attr('href', URL.createObjectURL(blob));
- }
- aLink[0].click();
- });
- } else {
- button.click(function (e) {
- // 请求权限
- chrome.permissions.request({
- permissions: ['downloads']
- }, (granted) => {
- if (granted) {
- chrome.downloads.download({
- url: URL.createObjectURL(blob),
- saveAs: true,
- conflictAction: 'overwrite',
- filename: 'FeHelper-' + dt + '.json'
- });
- } else {
- toast('必须接受授权,才能正常下载!');
- }
- });
- });
- }
- };
- /**
- * chrome 下复制到剪贴板
- * @param text
- */
- let _copyToClipboard = function (text) {
- let 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);
- toast('Json片段复制成功,随处粘贴可用!')
- };
- /**
- * 从el中获取json文本
- * @param el
- * @returns {string}
- */
- let getJsonText = function (el) {
- let txt = el.text().replace(/复制\|下载\|删除/gm,'').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) {
- }
- return txt;
- };
- // 添加json路径
- let _showJsonPath = function (curEl) {
- let keys = [];
- let current = curEl;
-
- // 处理当前节点
- if (current.hasClass('item') && !current.hasClass('rootItem')) {
- if (current.hasClass('item-array-element')) {
- // 这是数组元素,使用data-array-index属性
- let index = current.attr('data-array-index');
- if (index !== undefined) {
- keys.unshift('[' + index + ']');
- }
- } else {
- // 这是对象属性,获取key
- let keyText = current.find('>.key').text();
- if (keyText) {
- keys.unshift(keyText);
- }
- }
- }
-
- // 向上遍历所有祖先节点
- current.parents('.item').each(function() {
- let $this = $(this);
-
- // 跳过根节点
- if ($this.hasClass('rootItem')) {
- return false; // 终止遍历
- }
-
- if ($this.hasClass('item-array-element')) {
- // 这是数组元素,使用data-array-index属性
- let index = $this.attr('data-array-index');
- if (index !== undefined) {
- keys.unshift('[' + index + ']');
- }
- } else if ($this.hasClass('item-object') || $this.hasClass('item-array')) {
- // 这是容器节点,寻找它的key
- let $container = $this.parent().parent(); // 跳过 .kv-list
- if ($container.length && !$container.hasClass('rootItem')) {
- if ($container.hasClass('item-array-element')) {
- // 容器本身是数组元素
- let index = $container.attr('data-array-index');
- if (index !== undefined) {
- keys.unshift('[' + index + ']');
- }
- } else {
- // 容器是对象属性
- let keyText = $container.find('>.key').text();
- if (keyText) {
- keys.unshift(keyText);
- }
- }
- }
- } else {
- // 普通item节点,获取key
- let keyText = $this.find('>.key').text();
- if (keyText) {
- keys.unshift(keyText);
- }
- }
- });
- // 过滤掉空值和无效的key
- let validKeys = keys.filter(key => key && key.trim() !== '');
-
- // 创建或获取语言选择器和路径显示区域
- let jfPathContainer = $('#jsonPathContainer');
- if (!jfPathContainer.length) {
- jfPathContainer = $('<div id="jsonPathContainer"/>').prependTo(jfStatusBar);
-
- // 创建语言选择下拉框
- let langSelector = $('<select id="jsonPathLangSelector" title="选择编程语言格式">' +
- '<option value="javascript">JavaScript</option>' +
- '<option value="php">PHP</option>' +
- '<option value="python">Python</option>' +
- '<option value="java">Java</option>' +
- '<option value="csharp">C#</option>' +
- '<option value="golang">Go</option>' +
- '<option value="ruby">Ruby</option>' +
- '<option value="swift">Swift</option>' +
- '</select>').appendTo(jfPathContainer);
-
- // 创建路径显示区域
- let jfPath = $('<span id="jsonPath"/>').appendTo(jfPathContainer);
-
- // 绑定语言切换事件
- langSelector.on('change', function() {
- // 保存选择的语言到本地存储
- localStorage.setItem('fehelper_json_path_lang', $(this).val());
- // 从容器中获取当前保存的keys,而不是使用闭包中的validKeys
- let currentKeys = jfPathContainer.data('currentKeys') || [];
- _updateJsonPath(currentKeys, $(this).val());
- });
-
- // 从本地存储恢复语言选择
- let savedLang = localStorage.getItem('fehelper_json_path_lang') || 'javascript';
- langSelector.val(savedLang);
- }
-
- // 保存当前的keys到容器的data属性中,供语言切换时使用
- jfPathContainer.data('currentKeys', validKeys);
-
- // 获取当前选择的语言
- let selectedLang = $('#jsonPathLangSelector').val() || 'javascript';
- _updateJsonPath(validKeys, selectedLang);
- };
- // 根据不同编程语言格式化JSON路径
- let _updateJsonPath = function(keys, language) {
- let path = _formatJsonPath(keys, language);
- $('#jsonPath').html('当前节点:' + path);
- };
- // 格式化JSON路径为不同编程语言格式
- let _formatJsonPath = function(keys, language) {
- if (!keys.length) {
- return _getLanguageRoot(language);
- }
- let path = '';
-
- switch (language) {
- case 'javascript':
- path = '$';
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- if (key.startsWith('[') && key.endsWith(']')) {
- // 数组索引
- path += key;
- } else {
- // 对象属性
- if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
- // 有效的标识符,使用点语法
- path += '.' + key;
- } else {
- // 包含特殊字符,使用方括号语法
- path += '["' + key.replace(/"/g, '\\"') + '"]';
- }
- }
- }
- break;
-
- case 'php':
- path = '$data';
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- if (key.startsWith('[') && key.endsWith(']')) {
- // 数组索引
- path += key;
- } else {
- // 对象属性
- path += '["' + key.replace(/"/g, '\\"') + '"]';
- }
- }
- break;
-
- case 'python':
- path = 'data';
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- if (key.startsWith('[') && key.endsWith(']')) {
- // 数组索引
- path += key;
- } else {
- // 对象属性
- if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key) && !/^(and|as|assert|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|not|or|pass|print|raise|return|try|while|with|yield)$/.test(key)) {
- // 有效的标识符且不是关键字,可以使用点语法
- path += '.' + key;
- } else {
- // 使用方括号语法
- path += '["' + key.replace(/"/g, '\\"') + '"]';
- }
- }
- }
- break;
-
- case 'java':
- path = 'jsonObject';
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- if (key.startsWith('[') && key.endsWith(']')) {
- // 数组索引
- let index = key.slice(1, -1);
- path += '.get(' + index + ')';
- } else {
- // 对象属性
- path += '.get("' + key.replace(/"/g, '\\"') + '")';
- }
- }
- break;
-
- case 'csharp':
- path = 'jsonObject';
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- if (key.startsWith('[') && key.endsWith(']')) {
- // 数组索引
- path += key;
- } else {
- // 对象属性
- path += '["' + key.replace(/"/g, '\\"') + '"]';
- }
- }
- break;
-
- case 'golang':
- path = 'data';
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- if (key.startsWith('[') && key.endsWith(']')) {
- // 数组索引
- let index = key.slice(1, -1);
- path += '.(' + index + ')';
- } else {
- // 对象属性
- path += '["' + key.replace(/"/g, '\\"') + '"]';
- }
- }
- break;
-
- case 'ruby':
- path = 'data';
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- if (key.startsWith('[') && key.endsWith(']')) {
- // 数组索引
- path += key;
- } else {
- // 对象属性
- if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
- // 可以使用符号访问
- path += '[:"' + key + '"]';
- } else {
- // 字符串键
- path += '["' + key.replace(/"/g, '\\"') + '"]';
- }
- }
- }
- break;
-
- case 'swift':
- path = 'jsonObject';
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- if (key.startsWith('[') && key.endsWith(']')) {
- // 数组索引
- path += key;
- } else {
- // 对象属性
- path += '["' + key.replace(/"/g, '\\"') + '"]';
- }
- }
- break;
-
- default:
- // 默认使用JavaScript格式
- return _formatJsonPath(keys, 'javascript');
- }
-
- return path;
- };
- // 获取不同语言的根对象表示
- let _getLanguageRoot = function(language) {
- switch (language) {
- case 'javascript': return '$';
- case 'php': return '$data';
- case 'python': return 'data';
- case 'java': return 'jsonObject';
- case 'csharp': return 'jsonObject';
- case 'golang': return 'data';
- case 'ruby': return 'data';
- case 'swift': return 'jsonObject';
- default: return '$';
- }
- };
- // 给某个节点增加操作项
- let _addOptForItem = function (el, show) {
- // 下载json片段
- let fnDownload = function (event) {
- event.stopPropagation();
- let txt = getJsonText(el);
- // 下载片段
- let dt = (new Date()).format('yyyyMMddHHmmss');
- let blob = new Blob([txt], {type: 'application/octet-stream'});
- if (typeof chrome === 'undefined' || !chrome.permissions) {
- // 下载JSON的简单形式
- $(this).attr('download', 'FeHelper-' + dt + '.json').attr('href', URL.createObjectURL(blob));
- } else {
- // 请求权限
- chrome.permissions.request({
- permissions: ['downloads']
- }, (granted) => {
- if (granted) {
- chrome.downloads.download({
- url: URL.createObjectURL(blob),
- saveAs: true,
- conflictAction: 'overwrite',
- filename: 'FeHelper-' + dt + '.json'
- });
- } else {
- toast('必须接受授权,才能正常下载!');
- }
- });
- }
- };
- // 复制json片段
- let fnCopy = function (event) {
- event.stopPropagation();
- _copyToClipboard(getJsonText(el));
- };
- // 删除json片段
- let fnDel = function (event) {
- event.stopPropagation();
- if (el.parent().is('#formattedJson')) {
- toast('如果连最外层的Json也删掉的话,就没啥意义了哦!');
- return false;
- }
- toast('节点已删除成功!');
- el.remove();
- jfStatusBar && jfStatusBar.hide();
- };
- $('.boxOpt').hide();
- if (show) {
- let jfOptEl = el.children('.boxOpt');
- if (!jfOptEl.length) {
- jfOptEl = $('<b class="boxOpt">' +
- '<a class="opt-copy" title="复制当前选中节点的JSON数据">复制</a>|' +
- '<a class="opt-download" target="_blank" title="下载当前选中节点的JSON数据">下载</a>|' +
- '<a class="opt-del" title="删除当前选中节点的JSON数据">删除</a></b>').appendTo(el);
- } else {
- jfOptEl.show();
- }
- jfOptEl.find('a.opt-download').unbind('click').bind('click', fnDownload);
- jfOptEl.find('a.opt-copy').unbind('click').bind('click', fnCopy);
- jfOptEl.find('a.opt-del').unbind('click').bind('click', fnDel);
- }
- };
- // 显示当前节点的Key
- let _toogleStatusBar = function (curEl, show) {
- if (!jfStatusBar) {
- jfStatusBar = $('<div id="statusBar"/>').appendTo('body');
- }
- if (!show) {
- jfStatusBar.hide();
- return;
- } else {
- jfStatusBar.show();
- }
- _showJsonPath(curEl);
- };
- /**
- * 递归折叠所有层级的对象和数组节点
- * @param elements
- */
- function collapse(elements) {
- elements.each(function () {
- var el = $(this);
- if (el.children('.kv-list').length) {
- el.addClass('collapsed');
- // 只给没有id的节点分配唯一id,并生成注释
- if (!el.attr('id')) {
- el.attr('id', 'item' + (++lastItemIdGiven));
- let count = el.children('.kv-list').eq(0).children().length;
- let comment = count + (count === 1 ? ' item' : ' items');
- jfStyleEl[0].insertAdjacentHTML(
- 'beforeend',
- '\n#item' + lastItemIdGiven + '.collapsed:after{color: #aaa; content:" // ' + comment + '"}'
- );
- }
- // 递归对子节点继续折叠,确保所有嵌套层级都被处理
- collapse(el.children('.kv-list').children('.item-object, .item-block'));
- }
- });
- }
- /**
- * 创建几个全局操作的按钮,置于页面右上角即可
- * @private
- */
- let _buildOptionBar = function () {
- let optionBar = $('#optionBar');
- if (optionBar.length) {
- optionBar.html('');
- } else {
- optionBar = $('<span id="optionBar" />').appendTo(jfContent.parent());
- }
- $('<span class="x-split">|</span>').appendTo(optionBar);
- let buttonFormatted = $('<button class="xjf-btn xjf-btn-left">元数据</button>').appendTo(optionBar);
- let buttonCollapseAll = $('<button class="xjf-btn xjf-btn-mid">折叠所有</button>').appendTo(optionBar);
- let plainOn = false;
- buttonFormatted.bind('click', function (e) {
- if (plainOn) {
- plainOn = false;
- jfPre.hide();
- jfContent.show();
- buttonFormatted.text('元数据');
- } else {
- plainOn = true;
- jfPre.show();
- jfContent.hide();
- buttonFormatted.text('格式化');
- }
- jfStatusBar && jfStatusBar.hide();
- });
- buttonCollapseAll.bind('click', function (e) {
- // 如果内容还没有格式化过,需要再格式化一下
- if (plainOn) {
- buttonFormatted.trigger('click');
- }
- if (buttonCollapseAll.text() === '折叠所有') {
- buttonCollapseAll.text('展开所有');
- // 递归折叠所有层级的对象和数组,确保所有内容都被折叠
- collapse($('#jfContent .item-object, #jfContent .item-block'));
- } else {
- buttonCollapseAll.text('折叠所有');
- // 展开所有内容
- $('.item-object,.item-block').removeClass('collapsed');
- }
- jfStatusBar && jfStatusBar.hide();
- });
- };
- // 附加操作
- let _addEvents = function () {
- // 折叠、展开
- $('#jfContent span.expand').bind('click', function (ev) {
- ev.preventDefault();
- ev.stopPropagation();
- let parentEl = $(this).parent();
- parentEl.toggleClass('collapsed');
- if (parentEl.hasClass('collapsed')) {
- collapse(parentEl);
- }
- });
- // 点击选中:高亮
- $('#jfContent .item').bind('click', function (e) {
- let el = $(this);
- if (el.hasClass('x-selected')) {
- _toogleStatusBar(el, false);
- _addOptForItem(el, false);
- el.removeClass('x-selected');
- e.stopPropagation();
- return true;
- }
- $('.x-selected').removeClass('x-selected');
- el.addClass('x-selected');
- // 显示底部状态栏
- _toogleStatusBar(el, true);
- _addOptForItem(el, true);
- if (!$(e.target).is('.item .expand')) {
- e.stopPropagation();
- } else {
- $(e.target).parent().trigger('click');
- }
- // 触发钩子
- if (typeof window._OnJsonItemClickByFH === 'function') {
- window._OnJsonItemClickByFH(getJsonText(el));
- }
- });
- // 行悬停效果:只高亮当前直接悬停的item,避免嵌套冒泡
- let currentHoverElement = null;
-
- $('#jfContent .item').bind('mouseenter', function (e) {
- // 只处理视觉效果,不触发任何其他逻辑
-
- // 清除之前的悬停样式
- if (currentHoverElement) {
- currentHoverElement.removeClass('fh-hover');
- }
-
- // 添加当前悬停样式
- let el = $(this);
- el.addClass('fh-hover');
- currentHoverElement = el;
-
- // 严格阻止事件冒泡和默认行为
- e.stopPropagation();
- e.stopImmediatePropagation();
- e.preventDefault();
- });
-
- $('#jfContent .item').bind('mouseleave', function (e) {
- // 只处理视觉效果,不触发任何其他逻辑
- let el = $(this);
- el.removeClass('fh-hover');
-
- // 如果当前移除的元素是记录的悬停元素,清空记录
- if (currentHoverElement && currentHoverElement[0] === el[0]) {
- currentHoverElement = null;
- }
-
- // 严格阻止事件冒泡和默认行为
- e.stopPropagation();
- e.stopImmediatePropagation();
- });
-
- // 为整个jfContent区域添加鼠标离开事件,确保彻底清除悬停样式
- $('#jfContent').bind('mouseleave', function (e) {
- if (currentHoverElement) {
- currentHoverElement.removeClass('fh-hover');
- currentHoverElement = null;
- }
- });
- // 图片预览功能:针对所有data-is-link=1的a标签
- let $imgPreview = null;
- // 加载缓存
- function getImgCache() {
- try {
- return JSON.parse(sessionStorage.getItem('fehelper-img-preview-cache') || '{}');
- } catch (e) { return {}; }
- }
- function setImgCache(url, isImg) {
- let cache = getImgCache();
- cache[url] = isImg;
- sessionStorage.setItem('fehelper-img-preview-cache', JSON.stringify(cache));
- }
- $('#jfContent').on('mouseenter', 'a[data-is-link="1"]', function(e) {
- const url = $(this).attr('data-link-url');
- if (!url) return;
- let cache = getImgCache();
- if (cache.hasOwnProperty(url)) {
- if (cache[url]) {
- $imgPreview = getOrCreateImgPreview();
- $imgPreview.find('img').attr('src', url);
- $imgPreview.show();
- $(document).on('mousemove.fhimg', function(ev) {
- $imgPreview.css({
- left: ev.pageX + 20 + 'px',
- top: ev.pageY + 20 + 'px'
- });
- });
- $imgPreview.css({
- left: e.pageX + 20 + 'px',
- top: e.pageY + 20 + 'px'
- });
- }
- return;
- }
- // 创建图片对象尝试加载
- const img = new window.Image();
- img.src = url;
- img.onload = function() {
- setImgCache(url, true);
- $imgPreview = getOrCreateImgPreview();
- $imgPreview.find('img').attr('src', url);
- $imgPreview.show();
- $(document).on('mousemove.fhimg', function(ev) {
- $imgPreview.css({
- left: ev.pageX + 20 + 'px',
- top: ev.pageY + 20 + 'px'
- });
- });
- $imgPreview.css({
- left: e.pageX + 20 + 'px',
- top: e.pageY + 20 + 'px'
- });
- };
- img.onerror = function() {
- setImgCache(url, false);
- };
- }).on('mouseleave', 'a[data-is-link="1"]', function(e) {
- if ($imgPreview) $imgPreview.hide();
- $(document).off('mousemove.fhimg');
- });
- // 新增:全局监听,防止浮窗残留
- $(document).on('mousemove.fhimgcheck', function(ev) {
- let $target = $(ev.target).closest('a[data-is-link="1"]');
- if ($target.length === 0) {
- if ($imgPreview) $imgPreview.hide();
- $(document).off('mousemove.fhimg');
- }
- });
- };
-
- /**
- * 初始化或获取Worker实例(异步,兼容Chrome/Edge/Firefox)
- * @returns {Promise<Worker|null>}
- */
- let _getWorkerInstance = async function() {
- if (workerInstance) {
- return workerInstance;
- }
- let workerUrl = chrome.runtime.getURL('json-format/json-worker.js');
- // 判断是否为Firefox
- const isFirefox = typeof InstallTrigger !== 'undefined' || navigator.userAgent.includes('Firefox');
- try {
- if (isFirefox) {
- workerInstance = new Worker(workerUrl);
- return workerInstance;
- } else {
- // Chrome/Edge用fetch+Blob方式
- const resp = await fetch(workerUrl);
- const workerScript = await resp.text();
- const blob = new Blob([workerScript], { type: 'application/javascript' });
- const blobUrl = URL.createObjectURL(blob);
- workerInstance = new Worker(blobUrl);
- return workerInstance;
- }
- } catch (e) {
- console.error('创建Worker失败:', e);
- workerInstance = null;
- return null;
- }
- };
- /**
- * 执行代码格式化
- * 支持异步worker
- */
- let format = async function (jsonStr, skin) {
- cachedJsonString = JSON.stringify(JSON.parse(jsonStr), null, 4);
- _initElements();
- jfPre.html(htmlspecialchars(cachedJsonString));
- try {
- // 获取Worker实例(异步)
- let worker = await _getWorkerInstance();
- if (worker) {
- // 设置消息处理程序
- worker.onmessage = function (evt) {
- let msg = evt.data;
- switch (msg[0]) {
- case 'FORMATTING':
- formattingMsg.show();
- break;
- case 'FORMATTED':
- formattingMsg.hide();
- jfContent.html(msg[1]);
- _buildOptionBar();
- // 事件绑定
- _addEvents();
- // 支持文件下载
- _downloadSupport(cachedJsonString);
- break;
- }
- };
- // 发送格式化请求
- worker.postMessage({
- jsonString: jsonStr,
- skin: skin
- });
- } else {
- // Worker创建失败,回退到同步方式
- formatSync(jsonStr, skin);
- }
- } catch (e) {
- console.error('Worker处理失败:', e);
- // 出现任何错误,回退到同步方式
- formatSync(jsonStr, skin);
- }
- };
- // 同步的方式格式化
- let formatSync = function (jsonStr, skin) {
- cachedJsonString = JSON.stringify(JSON.parse(jsonStr), null, 4);
- _initElements();
- jfPre.html(htmlspecialchars(cachedJsonString));
-
- // 显示格式化进度
- formattingMsg.show();
-
- try {
- // 回退方案:使用简单模式直接显示格式化的JSON
- let formattedJson = JSON.stringify(JSON.parse(jsonStr), null, 4);
- jfContent.html(`<div id="formattedJson"><pre class="rootItem">${htmlspecialchars(formattedJson)}</pre></div>`);
-
- // 隐藏进度提示
- formattingMsg.hide();
-
- // 构建操作栏
- _buildOptionBar();
- // 事件绑定
- _addEvents();
- // 支持文件下载
- _downloadSupport(cachedJsonString);
-
- return;
- } catch (e) {
- jfContent.html(`<div class="error">JSON格式化失败: ${e.message}</div>`);
-
- // 隐藏进度提示
- formattingMsg.hide();
- }
- };
- // 工具函数:获取或创建唯一图片预览浮窗节点
- function getOrCreateImgPreview() {
- let $img = $('#fh-img-preview');
- if (!$img.length) {
- $img = $('<div id="fh-img-preview" style="position:absolute;z-index:999999;border:1px solid #ccc;background:#fff;padding:4px;box-shadow:0 2px 8px #0002;pointer-events:none;"><img style="max-width:300px;max-height:200px;display:block;"></div>').appendTo('body');
- }
- return $img;
- }
- return {
- format: format,
- formatSync: formatSync
- }
- })();
|