Skip to content

Commit 39c80c6

Browse files
committed
fix: on-click reinjection, json extract label, base64 copy; bump version 2026.5.23
1 parent 875142a commit 39c80c6

9 files changed

Lines changed: 146 additions & 37 deletions

File tree

apps/background/background.js

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ let BgPageInstance = (function () {
9696
};
9797

9898
// 往当前页面直接注入脚本,不再使用content-script的配置了
99-
let _injectContentScripts = function (tabId) {
99+
let _injectContentScripts = function (tabId, opts) {
100+
opts = opts || {};
101+
const silent = opts.silent !== false;
100102

101103
// FH工具脚本注入
102104
Awesome.getInstalledTools().then(tools => {
@@ -111,6 +113,14 @@ let BgPageInstance = (function () {
111113
files: jsFiles,
112114
func: (names) => { names.forEach(n => { try { let fn = window[n]; fn && fn(); } catch(e) {} }); },
113115
args: [initFuncNames]
116+
}, () => {
117+
if (!silent && chrome.runtime.lastError) {
118+
console.warn('inject content scripts failed:', chrome.runtime.lastError);
119+
notifyText({
120+
message: '内容脚本注入失败:请检查扩展“网站访问权限”(若为“点击时”,请再次点击图标触发;或将该站点设为“在所有网站上”)。',
121+
autoClose: 4000
122+
});
123+
}
114124
});
115125
});
116126

@@ -121,7 +131,11 @@ let BgPageInstance = (function () {
121131
// 注入js脚本
122132
list.filter(tool => (tools[tool].contentScriptJs || tools[tool].contentScript))
123133
.map(tool => Awesome.getContentScript(tool).then(js => {
124-
InjectTools.inject(tabId, { js });
134+
InjectTools.inject(tabId, { js }, () => {
135+
if (!silent && chrome.runtime.lastError) {
136+
console.warn('inject devtool content script failed:', chrome.runtime.lastError);
137+
}
138+
});
125139
}));
126140
});
127141
};
@@ -489,6 +503,35 @@ let BgPageInstance = (function () {
489503
_updateBrowserAction(request.action, request.showTips, request.menuOnly);
490504
callback && callback();
491505
}
506+
// Popup 打开:兜底注入内容脚本(兼容 Chrome「站点访问权限=点击时」导致的注入缺失)
507+
else if (request.type === 'fh-popup-opened') {
508+
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
509+
const activeTab = tabs && tabs[0];
510+
const tabId = activeTab && activeTab.id;
511+
const url = activeTab && activeTab.url;
512+
513+
// 不可注入页面(chrome:// / edge:// / about: 等)直接提示
514+
const injectable = /^(http(s)?|file):\/\//.test(url || '') && blacklist.every(reg => !reg.test(url || ''));
515+
if (!tabId || !injectable) {
516+
notifyText({
517+
message: '当前页面不支持注入脚本(可能是浏览器内置页面或未授权站点)。请在普通网页中使用,或在扩展管理里为该站点放行“网站访问权限”。',
518+
autoClose: 3500
519+
});
520+
callback && callback({ok: false, reason: 'not_injectable', url});
521+
return;
522+
}
523+
524+
// 先注入 tabId 标记,再注入工具内容脚本
525+
InjectTools.inject(tabId, { func: (id) => { window.__FH_TAB_ID__ = id; }, args: [tabId] }, () => {
526+
if (chrome.runtime.lastError) {
527+
console.warn('fh-popup-opened: inject __FH_TAB_ID__ failed:', chrome.runtime.lastError);
528+
}
529+
_injectContentScripts(tabId, {silent: false});
530+
callback && callback({ok: true});
531+
});
532+
});
533+
return true;
534+
}
492535
// 截屏
493536
else if (request.type === MSG_TYPE.CAPTURE_VISIBLE_PAGE) {
494537
_captureVisibleTab(callback);
@@ -676,7 +719,7 @@ let BgPageInstance = (function () {
676719
if (String(changeInfo.status).toLowerCase() === "complete") {
677720
if(/^(http(s)?|file):\/\//.test(tab.url) && blacklist.every(reg => !reg.test(tab.url))){
678721
InjectTools.inject(tabId, { func: (id) => { window.__FH_TAB_ID__ = id; }, args: [tabId] });
679-
_injectContentScripts(tabId);
722+
_injectContentScripts(tabId, {silent: true});
680723
}
681724
}
682725
});
@@ -1037,4 +1080,4 @@ let BgPageInstance = (function () {
10371080
};
10381081
})();
10391082

1040-
BgPageInstance.init();
1083+
BgPageInstance.init();

apps/background/inject-tools.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export default (() => {
1010
};
1111
if (codeConfig.world) opts.world = codeConfig.world;
1212
chrome.scripting.executeScript(opts, function () {
13+
if (chrome.runtime.lastError) {
14+
console.warn('InjectTools._execFunc failed:', chrome.runtime.lastError);
15+
}
1316
callback && callback.apply(this, arguments);
1417
});
1518
};
@@ -26,6 +29,9 @@ export default (() => {
2629
};
2730
if (world) opts.world = world;
2831
chrome.scripting.executeScript(opts, function () {
32+
if (chrome.runtime.lastError) {
33+
console.warn('InjectTools._execJs failed:', chrome.runtime.lastError);
34+
}
2935
callback && callback.apply(this, arguments);
3036
});
3137
};
@@ -60,13 +66,19 @@ export default (() => {
6066
target: {tabId, allFrames: af},
6167
files: codeConfig.files
6268
}, function () {
69+
if (chrome.runtime.lastError) {
70+
console.warn('InjectTools.insertCSS(files) failed:', chrome.runtime.lastError);
71+
}
6372
callback && callback.apply(this, arguments);
6473
});
6574
} else {
6675
chrome.scripting.executeScript({
6776
target: {tabId, allFrames: af},
6877
files: codeConfig.files
6978
}, function () {
79+
if (chrome.runtime.lastError) {
80+
console.warn('InjectTools.executeScript(files) failed:', chrome.runtime.lastError);
81+
}
7082
if (codeConfig.func) {
7183
_execFunc(tabId, af, codeConfig, callback);
7284
} else if (codeConfig.js) {
@@ -81,6 +93,9 @@ export default (() => {
8193
target: {tabId, allFrames: af},
8294
css: codeConfig.css
8395
}, function () {
96+
if (chrome.runtime.lastError) {
97+
console.warn('InjectTools.insertCSS(css) failed:', chrome.runtime.lastError);
98+
}
8499
callback && callback.apply(this, arguments);
85100
});
86101
} else if (codeConfig.func) {

apps/image-base64/index.css

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,23 @@ td .x-panel .x-tips {
6868
}
6969
#preview {max-width: 260px;max-height: 260px;margin-top:50px;}
7070
form {display: none}
71-
#base64Result,#base64Input {
72-
height:460px;
73-
padding:4px;
74-
margin:0;
75-
resize:none;outline:none;
76-
background: #fcfcfc;
77-
border:1px solid #ddd;
78-
}
79-
.-e-x-footer {
80-
text-align:center;
81-
font-size:12px;
82-
margin-top:30px;
83-
color:#00c;
71+
#base64Result,#base64Input {
72+
height:460px;
73+
padding:4px;
74+
margin:0;
75+
resize:none;outline:none;
76+
background: #fcfcfc;
77+
border:1px solid #ddd;
78+
}
79+
.x-result-actions {
80+
margin-top: 8px;
81+
text-align: right;
82+
}
83+
.-e-x-footer {
84+
text-align:center;
85+
font-size:12px;
86+
margin-top:30px;
87+
color:#00c;
8488
padding:5px;
8589
display:none;
8690
}

apps/image-base64/index.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ <h3 class="panel-title">
4646
<div class="result-section">
4747
<div class="section-title">转换结果</div>
4848
<textarea id="base64Result" title="点击自动选择" placeholder="内容会自动生成..." readonly ref="resultBox" @click="select()" v-model="resultContent" class="form-control mod-textarea"></textarea>
49+
<div class="x-result-actions">
50+
<button type="button" class="btn btn-primary btn-xs" @click="copyResult" :disabled="!resultContent || !resultContent.length">复制内容</button>
51+
</div>
4952
<div class="x-result-info">
5053
<div class="x-item">
5154
<span class="x-title">原始图片大小:</span><span id="sizeOri">{{sizeOri}}</span>
@@ -83,7 +86,8 @@ <h3 class="panel-title">
8386
<div v-show="!!error.length" v-cloak class="x-error" v-html="error"></div>
8487
</div>
8588

86-
<script type="text/javascript" src="index.js"></script>
8789
<script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
90+
<script type="text/javascript" src="../static/js/utils.js"></script>
91+
<script type="text/javascript" src="index.js"></script>
8892
</body>
8993
</html>

apps/image-base64/index.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,33 @@ new Vue({
187187
this.$refs.resultBox.select();
188188
},
189189

190+
copyResult: async function () {
191+
let text = String(this.resultContent || '');
192+
if (!text.length) return;
193+
194+
try {
195+
if (navigator.clipboard && navigator.clipboard.writeText) {
196+
await navigator.clipboard.writeText(text);
197+
window.toast && window.toast('已复制到剪贴板');
198+
return;
199+
}
200+
} catch (e) {
201+
// fallthrough
202+
}
203+
204+
// fallback: execCommand
205+
try {
206+
let el = this.$refs.resultBox;
207+
if (el && el.focus) el.focus();
208+
if (el && el.select) el.select();
209+
if (el && el.setSelectionRange) el.setSelectionRange(0, text.length);
210+
let ok = document.execCommand('copy');
211+
window.toast && window.toast(ok ? '已复制到剪贴板' : '复制失败');
212+
} catch (e) {
213+
window.toast && window.toast('复制失败');
214+
}
215+
},
216+
190217
upload: function (evt) {
191218
evt.preventDefault();
192219
this.$refs.fileBox.click();

apps/json-format/index.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,23 @@ html.fh-jf .x-toolbar {
152152
html.fh-jf .x-toolbar.x-inpage {
153153
margin:10px 20px;
154154
}
155+
156+
/* 小屏适配:工具栏/标题过长时允许横向滚动(修复“导航无法滑动/滚动”类问题) */
157+
@media (max-width: 900px) {
158+
html.fh-jf .panel-heading {
159+
overflow-x: auto;
160+
-webkit-overflow-scrolling: touch;
161+
}
162+
html.fh-jf .panel-title {
163+
white-space: nowrap;
164+
}
165+
html.fh-jf .x-toolbar.x-inpage {
166+
overflow-x: auto;
167+
overflow-y: hidden;
168+
white-space: nowrap;
169+
-webkit-overflow-scrolling: touch;
170+
}
171+
}
155172
.panel-title>a.x-other-tools {
156173
top: 0px;
157174
}

apps/json-format/index.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ <h3 class="panel-title">
5454
<button class="xjf-btn xjf-btn-left" @click="uniEncode">Uni编码</button><button class="xjf-btn xjf-btn-mid" @click="uniDecode">Uni解码</button><button class="xjf-btn xjf-btn-right" @click="urlDecode">URL解码</button>
5555
</span>
5656
<span class="x-split">|</span>
57-
<button class="xjf-btn" @click="openJsonPathModal">JSONPath查询</button>
57+
<button class="xjf-btn" @click="openJsonPathModal">JSON提取(JSONPath / jq替代)</button>
5858

5959
<span id="optionBar"></span>
6060
</div>
@@ -73,11 +73,11 @@ <h3 class="panel-title">
7373
</div>
7474
</div>
7575

76-
<!-- JSONPath查询模态框 -->
76+
<!-- JSON 提取(JSONPath / jq替代)模态框 -->
7777
<div id="jsonPathModal" class="jsonpath-modal" v-show="showJsonPathModal" @click="closeJsonPathModal">
7878
<div class="jsonpath-modal-content" @click.stop>
7979
<div class="jsonpath-modal-header">
80-
<h3>JSONPath查询</h3>
80+
<h3>JSON提取(JSONPath / jq替代)</h3>
8181
<div class="jsonpath-header-controls">
8282
<input type="text" class="xjf-input jsonpath-header-input" v-model="jsonPathQuery"
8383
placeholder="JSONPath表达式 (如: $.data.items[*].name)"
@@ -128,11 +128,11 @@ <h3>JSONPath查询</h3>
128128
</div>
129129
</div>
130130

131-
<!-- JSONPath示例模态框 -->
131+
<!-- JSON 提取示例模态框 -->
132132
<div id="jsonPathExamplesModal" class="jsonpath-modal v-cloak" v-show="showJsonPathExamplesModal" @click="closeJsonPathExamplesModal">
133133
<div class="jsonpath-modal-content" @click.stop>
134134
<div class="jsonpath-modal-header">
135-
<h3>JSONPath查询示例</h3>
135+
<h3>JSON提取示例(JSONPath)</h3>
136136
<span class="jsonpath-modal-close" @click="closeJsonPathExamplesModal">&times;</span>
137137
</div>
138138
<div class="jsonpath-modal-body">

apps/json-format/index.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,18 @@ new Vue({
3333
jsonPathResults: [],
3434
jsonPathError: '',
3535
copyButtonState: 'normal', // normal, copying, success, error
36-
jsonPathExamples: [
37-
{ path: '$', description: '根对象' },
38-
{ path: '$.data', description: '获取data属性' },
39-
{ path: '$.data.*', description: '获取data下的所有属性' },
40-
{ path: '$.data[0]', description: '获取data数组的第一个元素' },
41-
{ path: '$.data[*]', description: '获取data数组的所有元素' },
42-
{ path: '$.data[?(@.name)]', description: '获取data数组中有name属性的元素' },
43-
{ path: '$..name', description: '递归查找所有name属性' },
44-
{ path: '$.data[0:3]', description: '获取data数组的前3个元素' },
45-
{ path: '$.data[-1]', description: '获取data数组的最后一个元素' },
46-
{ path: '$.*.price', description: '获取所有子对象的price属性' }
47-
]
36+
jsonPathExamples: [
37+
{ path: '$', description: '根对象(类似 jq: .)' },
38+
{ path: '$.data', description: '获取data属性(类似 jq: .data)' },
39+
{ path: '$.data.*', description: '获取data下的所有属性' },
40+
{ path: '$.data[0]', description: '获取data数组的第一个元素' },
41+
{ path: '$.data[*]', description: '获取data数组的所有元素(类似 jq: .data[])' },
42+
{ path: '$.data[?(@.name)]', description: '获取data数组中有name属性的元素' },
43+
{ path: '$..name', description: '递归查找所有name属性' },
44+
{ path: '$.data[0:3]', description: '获取data数组的前3个元素' },
45+
{ path: '$.data[-1]', description: '获取data数组的最后一个元素' },
46+
{ path: '$.*.price', description: '获取所有子对象的price属性' }
47+
]
4848
},
4949
mounted: function () {
5050
// 自动开关灯控制

apps/manifest.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "FeHelper - JSON格式化、编解码、二维码、开发者工具箱",
33
"short_name": "FeHelper",
4-
"version": "2026.4.2920",
4+
"version": "2026.5.23",
55
"manifest_version": 3,
66
"description": "30+开发者工具集:JSON格式化/BigInt精度/对比、编解码(Base64/URL/Unicode/Gzip/JWT)、时间戳/FILETIME转换、二维码/条形码、UUID/雪花ID生成、代码美化、正则、取色、Postman、进制转换等,按需安装!",
77
"icons": {
@@ -97,4 +97,3 @@
9797
"homepage_url": "https://fehelper.com/",
9898
"author": "nicetool.net"
9999
}
100-

0 commit comments

Comments
 (0)