menu.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /**
  2. * FeHelper 右键菜单管理
  3. * @type {{manage}}
  4. * @author zhaoxianlie
  5. */
  6. import CrxDownloader from './crx-download.js';
  7. import Awesome from './awesome.js';
  8. import toolMap from './tools.js';
  9. import Settings from '../options/settings.js';
  10. export default (function () {
  11. let FeJson = {
  12. contextMenuId:"fhm_main",
  13. menuClickHandlers: {}
  14. };
  15. // 邮件菜单配置项
  16. let defaultMenuOptions = {
  17. 'download-crx': {
  18. icon: '♥',
  19. text: '插件下载分享',
  20. onClick: function (info, tab) {
  21. CrxDownloader.downloadCrx(tab);
  22. }
  23. },
  24. 'fehelper-setting': {
  25. icon: '❂',
  26. text: 'FeHelper设置',
  27. onClick: function (info, tab) {
  28. chrome.runtime.openOptionsPage();
  29. }
  30. }
  31. };
  32. // 初始化菜单配置
  33. let _initMenuOptions = (() => {
  34. Object.keys(toolMap).forEach(tool => {
  35. // context-menu
  36. switch (tool) {
  37. case 'json-format':
  38. toolMap[tool].menuConfig[0].onClick = function (info, tab) {
  39. chrome.scripting.executeScript({
  40. target: {tabId:tab.id,allFrames:false},
  41. args: [info.selectionText || ''],
  42. func: (text) => text
  43. }, resp => {
  44. if (chrome.runtime.lastError || !resp || !resp[0]) return;
  45. globalThis.FeHelperBg.DynamicToolRunner({
  46. tool, withContent: resp[0].result
  47. });
  48. });
  49. };
  50. break;
  51. case 'code-beautify':
  52. case 'en-decode':
  53. toolMap[tool].menuConfig[0].onClick = function (info, tab) {
  54. chrome.scripting.executeScript({
  55. target: {tabId:tab.id,allFrames:false},
  56. args: [info.linkUrl || info.srcUrl || info.selectionText || info.pageUrl || ''],
  57. func: (text) => text
  58. }, resp => {
  59. if (chrome.runtime.lastError || !resp || !resp[0]) return;
  60. globalThis.FeHelperBg.DynamicToolRunner({
  61. tool, withContent: resp[0].result
  62. });
  63. });
  64. };
  65. break;
  66. case 'qr-code':
  67. toolMap[tool].menuConfig[0].onClick = function (info, tab) {
  68. chrome.scripting.executeScript({
  69. target: {tabId:tab.id,allFrames:false},
  70. args: [info.linkUrl || info.srcUrl || info.selectionText || info.pageUrl || tab.url || ''],
  71. func: (text) => text
  72. }, resp => {
  73. if (chrome.runtime.lastError || !resp || !resp[0]) return;
  74. globalThis.FeHelperBg.DynamicToolRunner({
  75. tool, withContent: resp[0].result
  76. });
  77. });
  78. };
  79. toolMap[tool].menuConfig[1].onClick = function (info, tab) {
  80. chrome.scripting.executeScript({
  81. target: {tabId:tab.id,allFrames:false},
  82. args: [info.srcUrl || ''],
  83. func: (text) => {
  84. try {
  85. if (typeof window.qrcodeContentScript === 'function') {
  86. let qrcode = window.qrcodeContentScript();
  87. if (typeof qrcode.decode === 'function') {
  88. qrcode.decode(text);
  89. return 1;
  90. }
  91. }
  92. } catch (e) {
  93. return 0;
  94. }
  95. }
  96. });
  97. };
  98. break;
  99. default:
  100. toolMap[tool].menuConfig[0].onClick = function (info, tab) {
  101. globalThis.FeHelperBg.DynamicToolRunner({
  102. tool, withContent: tool === 'image-base64' ? info.srcUrl : ''
  103. })
  104. };
  105. break;
  106. }
  107. });
  108. })();
  109. // MV3: 单一全局监听器,通过 handler map 分发点击事件(避免 SW 重启后监听器丢失)
  110. chrome.contextMenus.onClicked.addListener((info, tab) => {
  111. let handler = FeJson.menuClickHandlers[info.menuItemId];
  112. if (handler) {
  113. handler(info, tab);
  114. }
  115. });
  116. /**
  117. * 创建一个menu 菜单
  118. * @param toolName
  119. * @param menuList
  120. * @returns {boolean}
  121. * @private
  122. */
  123. let _createItem = (toolName, menuList) => {
  124. menuList && menuList.forEach && menuList.forEach(menu => {
  125. let menuItemId = 'fhm_c' + escape(menu.text).replace(/\W/g,'') + Date.now() + Math.floor(Math.random() * 1000);
  126. chrome.contextMenus.create({
  127. id: menuItemId,
  128. title: menu.icon + ' ' + menu.text,
  129. contexts: menu.contexts || ['all'],
  130. parentId: FeJson.contextMenuId
  131. }, () => {
  132. if (chrome.runtime.lastError) return;
  133. FeJson.menuClickHandlers[menuItemId] = menu.onClick || (() => {
  134. globalThis.FeHelperBg.DynamicToolRunner({ tool: toolName });
  135. });
  136. });
  137. });
  138. };
  139. /**
  140. * 绘制一条分割线
  141. * @private
  142. */
  143. let _createSeparator = function () {
  144. chrome.contextMenus.create({
  145. id: 'fhm_s' + Math.ceil(Math.random()*10e9),
  146. type: 'separator',
  147. parentId: FeJson.contextMenuId
  148. }, () => { chrome.runtime.lastError; });
  149. };
  150. /**
  151. * 创建扩展专属的右键菜单(debounce 防并发)
  152. */
  153. let _initMenusTimer = null;
  154. let _initMenus = function () {
  155. clearTimeout(_initMenusTimer);
  156. _initMenusTimer = setTimeout(_doInitMenus, 80);
  157. };
  158. let _doInitMenus = function () {
  159. FeJson.menuClickHandlers = {};
  160. _removeContextMenu(() => {
  161. chrome.contextMenus.create({
  162. id: FeJson.contextMenuId,
  163. title: "FeHelper",
  164. contexts: ['page', 'selection', 'editable', 'link', 'image'],
  165. documentUrlPatterns: ['http://*/*', 'https://*/*', 'file://*/*']
  166. }, () => {
  167. if (chrome.runtime.lastError) return;
  168. _buildChildMenus();
  169. });
  170. });
  171. };
  172. let _buildChildMenus = function () {
  173. Awesome.getInstalledTools().then(tools => {
  174. let allMenus = Object.keys(tools).filter(tool => tools[tool].installed && tools[tool].menu);
  175. let onlineTools = allMenus.filter(tool => tool !== 'devtools' && !tools[tool].hasOwnProperty('_devTool'));
  176. let devTools = allMenus.filter(tool => tool === 'devtools' || tools[tool].hasOwnProperty('_devTool'));
  177. onlineTools.forEach(tool => _createItem(tool, tools[tool].menuConfig));
  178. devTools.length && _createSeparator();
  179. devTools.forEach(tool => {
  180. if(!tools[tool].menuConfig) {
  181. tools[tool].menuConfig = [{
  182. icon: tools[tool].icon,
  183. text: tools[tool].name,
  184. onClick: (info, tab) => {
  185. globalThis.FeHelperBg.DynamicToolRunner({
  186. page: 'dynamic',
  187. noPage: !!tools[tool].noPage,
  188. query: `tool=${tool}`
  189. });
  190. if (tools[tool].noPage && typeof self !== 'undefined' && typeof self.close === 'function') {
  191. setTimeout(() => self.close(), 200);
  192. }
  193. }
  194. }];
  195. }
  196. _createItem(tool, tools[tool].menuConfig);
  197. });
  198. });
  199. let sysMenu = ['download-crx', 'fehelper-setting'];
  200. let arrPromises = sysMenu.map(menu => Awesome.menuMgr(menu, 'get'));
  201. Promise.all(arrPromises).then(values => {
  202. _createSeparator();
  203. String(values[0]) === '1' && _createItem(sysMenu[0], [defaultMenuOptions[sysMenu[0]]]);
  204. String(values[1]) !== '0' && _createItem(sysMenu[1], [defaultMenuOptions[sysMenu[1]]]);
  205. });
  206. };
  207. /**
  208. * 移除扩展专属的右键菜单
  209. */
  210. let _removeContextMenu = function (callback) {
  211. chrome.contextMenus.removeAll(callback);
  212. };
  213. /**
  214. * 创建或移除扩展专属的右键菜单
  215. */
  216. let _createOrRemoveContextMenu = function () {
  217. Settings.getOptions((opts) => {
  218. if (String(opts['OPT_ITEM_CONTEXTMENUS']) !== 'false') {
  219. _initMenus();
  220. } else {
  221. _removeContextMenu();
  222. }
  223. });
  224. };
  225. return {
  226. rebuild: _createOrRemoveContextMenu
  227. };
  228. })();