Spotify Web - 复制歌曲信息

在上下文菜单中添加一个条目,将选定的歌曲名称和歌手复制到剪贴板

安装此脚本
作者推荐脚本

您可能也喜欢Spotify Genius Lyrics

安装此脚本
  1. // ==UserScript==
  2. // @name Spotify Web - Copy track info to clipboard
  3. // @name:es Spotify Web - Copiar info de la canción
  4. // @name:pt Spotify Web - Copiar info da canción
  5. // @name:it Spotify Web - Copia l'informazione sul brano
  6. // @name:fr Spotify Web - Copier les informations de titre
  7. // @name:zh-TW Spotify Web - 複製歌曲信息
  8. // @name:zh-CN Spotify Web - 复制歌曲信息
  9. // @name:zh Spotify Web - 复制歌曲信息
  10. // @name:ar Spotify Web - انسخ معلومات الأغنية
  11. // @name:iw Spotify Web - העתקת מידע השיר
  12. // @name:ru Spotify Web - Копировать данные трека
  13. // @name:id Spotify Web - Salin Informasi Lagu
  14. // @name:ms Spotify Web - Salin Maklumat Lagu
  15. // @name:de Spotify Web - Songinformation kopieren
  16. // @name:ja Spotify Web - 曲情報をコピー
  17. // @name:pl Spotify Web - Skopiuj informacje o utworze
  18. // @name:cs Spotify Web - Kopírovat informace o skladbě
  19. // @name:el Spotify Web - Αντιγραφή πληροφοριών τραγουδιού
  20. // @name:hu Spotify Web - Dal adat másolása
  21. // @name:tr Spotify Web - Şarkı Bilgilerini Kopyala
  22. // @name:th Spotify Web - คัดลอกข้อมูลเพลง
  23. // @name:vi Spotify Web - Sao chép Thông tin Bài hát
  24. // @name:sv Spotify Web - Kopiera sånginfoen
  25. // @name:nl Spotify Web - Info van nummer kopiëren
  26. // @description Adds an entry in the context menu that copies the selected song name and artist to the clipboard
  27. // @description:es Agrega una entrada en el menú contextual que copia el nombre de la canción y el artista seleccionados al portapapeles
  28. // @description:pt Adiciona uma entrada no menu de contexto que copia o nome da música selecionada e o artista para a área de transferência
  29. // @description:it Aggiunge una voce nel menu contestuale che copia il nome del brano e l'artista selezionati negli appunti
  30. // @description:fr Ajoute une entrée dans le menu contextuel qui copie le nom de la chanson et l'artiste sélectionnés dans le presse-papiers
  31. // @description:zh-TW 在上下文菜單中添加一個條目,該條目將選定的歌曲名稱和歌手複製到剪貼板
  32. // @description:zh-CN 在上下文菜单中添加一个条目,将选定的歌曲名称和歌手复制到剪贴板
  33. // @description:zh 在上下文菜单中添加一个条目,将选定的歌曲名称和歌手复制到剪贴板
  34. // @description:ar أضف إدخالاً في قائمة السياق ينسخ اسم الأغنية والفنان المحدد إلى الحافظة
  35. // @description:iw הוסף ערך בתפריט ההקשר שמעתיק ללוח הלוח את שם השיר והאמן שנבחרו
  36. // @description:ru Добавить пункт контекстного меню, копирующий имя выбранной песни и исполнителя в буфер обмена.
  37. // @description:id Tambahkan entri menu konteks yang menyalin nama lagu dan artis yang dipilih ke clipboard
  38. // @description:ms Tambahkan entri menu konteks yang menyalin nama lagu dan artis yang dipilih ke papan keratan
  39. // @description:de Fügt einen Eintrag im Kontextmenü hinzu, der den ausgewählten Songnamen und Interpreten in die Zwischenablage kopiert
  40. // @description:ja 選択した曲名とアーティストをクリップボードにコピーするエントリをコンテキストメニューに追加します
  41. // @description:pl Dodaje wpis w menu kontekstowym, który kopiuje wybrany tytuł utworu i wykonawcę do schowka
  42. // @description:cs Přidá položku do místní nabídky, která zkopíruje název vybrané skladby a umělce do schránky
  43. // @description:el Προσθέτει μια καταχώριση στο μενού περιβάλλοντος που αντιγράφει το επιλεγμένο όνομα τραγουδιού και τον καλλιτέχνη στο πρόχειρο
  44. // @description:hu Hozzáad egy bejegyzést a helyi menübe, amely átmásolja a kiválasztott dal nevét és előadót a vágólapra
  45. // @description:tr Bağlam menüsüne seçili şarkı adını ve sanatçıyı panoya kopyalayan bir giriş ekler
  46. // @description:th เพิ่มรายการในเมนูบริบทที่คัดลอกชื่อเพลงและศิลปินที่เลือกไปยังคลิปบอร์ด
  47. // @description:vi Thêm một mục vào menu ngữ cảnh để sao chép tên bài hát và nghệ sĩ đã chọn vào khay nhớ tạm
  48. // @description:sv Lägger till en post i snabbmenyn som kopierar det valda låtnamnet och artisten till Urklipp
  49. // @description:nl Voegt een item toe aan het contextmenu dat de geselecteerde songnaam en artiest naar het klembord kopieert
  50. // @namespace https://openuserjs.org/users/cuzi
  51. // @icon https://open.spotify.com/favicon.ico
  52. // @version 26
  53. // @license MIT
  54. // @copyright 2020, cuzi (https://openuserjs.org/users/cuzi)
  55. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js
  56. // @grant GM.setClipboard
  57. // @grant GM_setClipboard
  58. // @match https://open.spotify.com/*
  59. // @sandbox JavaScript
  60. // ==/UserScript==
  61.  
  62. // ==OpenUserJS==
  63. // @author cuzi
  64. // ==/OpenUserJS==
  65.  
  66. /* globals $, GM, GM_setClipboard, MouseEvent */
  67. /* jshint asi: true, esversion: 8 */
  68.  
  69. 'use strict';
  70.  
  71. (function () {
  72. const translations = {
  73. es: ['Copiar info de la canción', 'Copiado: %s'],
  74. pt: ['Copiar info da canción', 'Copiado: %s'],
  75. it: ['Copia l\'informazione', 'Copiato: %s'],
  76. fr: ['Copier les informations de titre', '%s copié'],
  77. 'zh-HK': ['複製歌曲信息', '已復制: %s'],
  78. 'zh-TW': ['複製歌曲信息', '已復制: %s'],
  79. zh: ['复制歌曲信息', '已複製: %s'],
  80. ar: ['انسخ معلومات الأغنية', '%s :تمّ نسخ'],
  81. iw: ['העתקת מידע השיר', '%s :הועתק'],
  82. ru: ['Копировать данные трека', 'Скопирована: %s'],
  83. id: ['Salin Informasi Lagu', 'Disalin: %s'],
  84. ms: ['Salin Maklumat Lagu', 'Disalin: %s'],
  85. de: ['Songinformation kopieren', '%s kopiert'],
  86. ja: ['曲情報をコピー', '%s をコピーしました'],
  87. pl: ['Skopiuj informacje o utworze', '%s skopiowano'],
  88. cs: ['Kopírovat informace o skladbě', '%s byl zkopírován'],
  89. el: ['Αντιγραφή πληροφοριών τραγουδιού', '%s αντιγράφηκε'],
  90. hu: ['Dal adat másolása', '%s másolva'],
  91. tr: ['Şarkı Bilgilerini Kopyala', '%s kopyalandı'],
  92. th: ['คัดลอกข้อมูลเพลง', '%s ไปที่คลิปบอร์ดแล้ว'],
  93. vi: ['Sao chép Thông tin Bài hát', '%s đã được sao chép'],
  94. sv: ['Kopiera sånginfoen', '%s kopierad'],
  95. nl: ['Info van nummer kopiëren', '%s gekopieerd'],
  96. en: ['Copy track info', 'Copied: %s']
  97. }
  98. let [menuString, copiedString] = translations.en
  99.  
  100. const htmlTag = document.querySelector('html[lang]')
  101. if (htmlTag && htmlTag.lang in translations) {
  102. [menuString, copiedString] = translations[htmlTag.lang]
  103. } else {
  104. for (const lang in translations) {
  105. if (navigator.language.startsWith(lang)) {
  106. [menuString, copiedString] = translations[lang]
  107. break
  108. }
  109. }
  110. }
  111.  
  112. let showInfoID
  113. const showInfo = function (str) {
  114. window.clearTimeout(showInfoID)
  115. if (!document.getElementById('copied_song_info_outer')) {
  116. document.head.appendChild(document.createElement('style')).innerHTML = `
  117.  
  118. #copied_song_info_outer {
  119. margin: -32px calc(var(--panel-gap)*-1) 0;
  120. display: grid;
  121. grid-area: 1/1/now-playing-bar-start/-1;
  122. pointer-events: none;
  123. position: relative;
  124. z-index: 5;
  125. }
  126.  
  127. #copied_song_info_inner {
  128. margin-bottom: 16px;
  129. place-self: end center;
  130. pointer-events: none;
  131. z-index: 100;
  132. }
  133.  
  134. #copied_song_info_text {
  135. background: #2e77d0;
  136. border-radius: 8px;
  137. -webkit-box-shadow: 0 4px 12px 4px rgba(0,0,0,.5);
  138. box-shadow: 0 4px 12px 4px rgba(0,0,0,.5);
  139. color: #fff;
  140. display: inline-block;
  141. font-size: 16px;
  142. line-height: 20px;
  143. max-width: 450px;
  144. padding: 12px 36px;
  145. text-align: center;
  146. -webkit-transition: none .5s cubic-bezier(.3,0,.4,1);
  147. transition: none .5s cubic-bezier(.3,0,.4,1);
  148. transition-property: none;
  149. -webkit-transition-property: opacity;
  150. transition-property: opacity;
  151. }
  152. `
  153.  
  154. const node = $('<div id="copied_song_info_outer"><div id="copied_song_info_inner"><div id="copied_song_info_text"></div></div></div>')
  155.  
  156. if (document.querySelector('.Root [data-testid="now-playing-bar"]')) {
  157. $('.Root [data-testid="now-playing-bar"]').parent().after(node)
  158. } else {
  159. node.appendTo('.Root')
  160. }
  161. }
  162. const copiedSongInfoOuter = $('#copied_song_info_outer')
  163. const copiedSongInfoText = $('#copied_song_info_text')
  164.  
  165. copiedSongInfoOuter.css('display', 'grid')
  166. copiedSongInfoText.css('opacity', 1)
  167. copiedSongInfoText.html(str.replace('\n', '<br>\n'))
  168.  
  169. showInfoID = window.setTimeout(function () {
  170. copiedSongInfoText.css('opacity', 0)
  171. showInfoID = window.setTimeout(function () {
  172. copiedSongInfoOuter.css('display', 'none')
  173. }, 700)
  174. }, 4000)
  175. }
  176.  
  177. const getSongTitle = function ($titlenodes) {
  178. let titleText
  179.  
  180. if ($titlenodes && $titlenodes.length > 0) {
  181. titleText = $titlenodes.text()
  182. if (titleText && titleText.trim()) {
  183. return titleText.trim()
  184. }
  185. }
  186.  
  187. if ($('.track-info__name').length > 0) {
  188. titleText = $('.track-info__name')[0].innerText
  189. if (titleText && titleText.trim()) {
  190. return titleText.trim()
  191. }
  192. }
  193.  
  194. return ''
  195. }
  196.  
  197. const getArtistName = function ($artistnodes) {
  198. let artistText
  199.  
  200. if (typeof $artistnodes === 'string') {
  201. return $artistnodes.trim()
  202. }
  203.  
  204. if ($artistnodes) {
  205. const artistTextNodes = $artistnodes.not((i, e) => e.className)
  206. if (artistTextNodes.length === 1) {
  207. artistText = artistTextNodes.text()
  208. if (artistText && artistText.trim()) {
  209. return artistText.trim()
  210. }
  211. } else if (artistTextNodes.length > 1) {
  212. artistText = artistTextNodes.map((i, e) => e.textContent.trim()).get()
  213. artistText = artistText.join(', ')
  214. return artistText.trim()
  215. }
  216.  
  217. // In playlist:
  218. if ($artistnodes.find('.ellipsis-one-line').length > 0) {
  219. artistText = $artistnodes.find('.ellipsis-one-line')[0].innerText
  220. if (artistText && artistText.trim()) {
  221. return artistText.trim()
  222. }
  223. }
  224. if ($artistnodes.find('.standalone-ellipsis-one-line').length > 0) {
  225. artistText = $artistnodes.find('.standalone-ellipsis-one-line')[0].innerText
  226. if (artistText && artistText.trim()) {
  227. return artistText.trim()
  228. }
  229. }
  230.  
  231. // Something else, just accumulate all artist links: <a href="/artist/ARTISTID">Artistname</a>
  232. if ($artistnodes.find('a[href*="/artist/"]').length > 0) {
  233. return $.map($artistnodes.find('a[href*="/artist/"]'), (element) => $(element).text().trim()).join(', ')
  234. }
  235. }
  236.  
  237. if (document.location.pathname.startsWith('/artist/')) {
  238. if ($('.content.artist>div h1').length > 0) {
  239. artistText = $('.content.artist>div h1')[0].textContent
  240. if (artistText && artistText.trim()) {
  241. return artistText.trim()
  242. }
  243. } else {
  244. if ($('.Root main .contentSpacing [data-testid="adaptiveEntityTitle"]').length > 0) {
  245. artistText = $('.Root main .contentSpacing [data-testid="adaptiveEntityTitle"]')[0].textContent
  246. if (artistText && artistText.trim()) {
  247. return artistText.trim()
  248. }
  249. }
  250. }
  251. }
  252.  
  253. if (document.location.pathname.startsWith('/album/')) {
  254. artistText = document.querySelector('.os-content h1').textContent
  255. if (artistText && artistText.trim()) {
  256. return artistText.trim()
  257. }
  258. }
  259.  
  260. if ($('.track-info__artists').length > 0) {
  261. artistText = $('.track-info__artists')[0].innerText
  262. if (artistText && artistText.trim()) {
  263. return artistText.trim()
  264. }
  265. }
  266.  
  267. return ''
  268. }
  269.  
  270. const findSongInfo = function ($row) {
  271. console.debug('findSongInfo for row', $row[0])
  272. let title = $row.find('.tracklist-name')
  273. if (title.length === 0 && $row.find('a[href*="/track/"]').length > 0) {
  274. title = $($row.find('a[href*="/track/"]')[0])
  275. }
  276. if (title.length === 0) {
  277. title = $row.find('div[data-testid="tracklist-row"] .standalone-ellipsis-one-line')
  278. }
  279. if (title.length === 0) {
  280. title = $row.find('div[role="gridcell"] img').parent().find('.standalone-ellipsis-one-line')
  281. }
  282. if (title.length === 0 && $row.hasClass('now-playing')) {
  283. title = $row.find('.ellipsis-one-line>.ellipsis-one-line').eq(0)
  284. }
  285. let artist = $row.find('.artists-album span')
  286. if (artist.length === 0 && $row.hasClass('now-playing')) {
  287. artist = $row.find('.ellipsis-one-line>.ellipsis-one-line').eq(1)
  288. }
  289. if (artist.length === 0 && title.length === 0 && $row.find('[data-testid="nowplaying-track-link"]')) {
  290. title = $row.find('[data-testid="nowplaying-track-link"]')
  291. artist = $row.find('[data-testid="nowplaying-artist"]')
  292. }
  293. if (artist.length === 0) {
  294. if ($row.find('.second-line').length !== 0) {
  295. artist = $row.find('.second-line') // in playlist
  296. }
  297. if ($row.parents('.now-playing').length !== 0) {
  298. // Now playing bar
  299. $row = $($row.parents('.now-playing')[0])
  300. if ($row.find('.ellipsis-one-line a[href*="/artist/"]').length !== 0) {
  301. artist = $row.find('.ellipsis-one-line a[href*="/artist/"]')
  302. title = $row.find('a[data-testid="nowplaying-track-link"]')
  303. }
  304. }
  305. if ($row.parents('.Root [data-testid="now-playing-bar"]').length !== 0) {
  306. // New: Now playing bar 2021-09
  307. $row = $($row.parents('.Root [data-testid="now-playing-bar"]')[0])
  308. if ($row.find('.ellipsis-one-line a[href*="/artist/"],.standalone-ellipsis-one-line a[href*="/artist/"]').length !== 0) {
  309. artist = $row.find('.ellipsis-one-line a[href*="/artist/"],.standalone-ellipsis-one-line a[href*="/artist/"]')
  310. title = $row.find('.ellipsis-one-line a[href*="/album/"],.ellipsis-one-line a[href*="/track/"],.standalone-ellipsis-one-line a[href*="/album/"],.standalone-ellipsis-one-line a[href*="/track/"]')
  311. } else if ($row.find('[data-testid="context-item-info-artist"]').length !== 0) {
  312. artist = $row.find('a[data-testid="context-item-info-artist"][href*="/artist/"],[data-testid="context-item-info-artist"] a[href*="/artist/"]')
  313. title = $row.find('[data-testid="context-item-info-title"] a[href*="/album/"],[data-testid="context-item-info-title"] a[href*="/track/"]')
  314. } else if ($row.find('a[href*="/artist/"],a[href*="/album/"],a[href*="/track/"]').length > 1) {
  315. artist = $row.find('a[href*="/artist/"]')
  316. title = $row.find('a[href*="/album/"],a[href*="/track/"]')
  317. }
  318. }
  319.  
  320. if (artist.length === 0 && $row.find('[class*="ListRowTitle__ListRowText"]').length !== 0) {
  321. // "Now playing view", the next in queue item
  322. title = $row.find('[class*="ListRowTitle__ListRowText"]')
  323. artist = $row.find('[class*="ListRowDetails__ListRowDetailText"]')
  324. }
  325.  
  326. if (artist.length === 0 && $row.parents('[data-testid="NPV_Panel_OpenDiv"]').length !== 0) {
  327. // "Now playing view", the current playing item
  328. $row = $($row.parents('[data-testid="NPV_Panel_OpenDiv"]')[0]).find('[data-testid="minimized-track-visual-enhancement"]').parent()
  329. artist = $row.find('a[data-testid="context-item-info-artist"][href*="/artist/"],[data-testid="context-item-info-artist"] a[href*="/artist/"]')
  330. title = $row.find('[data-testid="context-item-info-title"] a[href*="/album/"],[data-testid="context-item-info-title"] a[href*="/track/"]')
  331. }
  332.  
  333. const artistGridCell = $row.find('*[role="gridcell"] a[href*="/artist/"]')
  334. if (artistGridCell.length > 0) {
  335. // New playlist design
  336. artist = artistGridCell.parent()
  337. if (title.length === 0) {
  338. title = $(artistGridCell.parent().parent().find('span')[0])
  339. }
  340. if (artist.has(title)) {
  341. // title is child of artist, so it's the same node, the real title is somewhere else
  342. // This happens on album page
  343. if (artist.parent().parent().find('div.standalone-ellipsis-one-line').length) {
  344. title = $(artist.parent().parent().find('div.standalone-ellipsis-one-line')[0])
  345. }
  346. }
  347. }
  348.  
  349. const artistContent = $('.content.artist>div h1')
  350. if (artistContent.length > 0) {
  351. // Artist page
  352. artist = artistContent[0].textContent
  353. }
  354. const artistPageHeader = $('main>section[data-testid="artist-page"] [data-testid="adaptiveEntityTitle"]')
  355. if (artistPageHeader.length > 0) {
  356. // Artist page
  357. artist = artistPageHeader[0].textContent
  358. }
  359. }
  360.  
  361. if (artist.length === 0 && document.location.pathname.startsWith('/track/')) {
  362. // Single track page
  363. artist = $('section [data-testid="creator-link"][href*="/artist/"]')
  364. }
  365.  
  366. if (title && artist) {
  367. const titleText = getSongTitle(title)
  368. const artistText = getArtistName(artist)
  369.  
  370. if (titleText && artistText) {
  371. return artistText + ' - ' + titleText
  372. }
  373. }
  374.  
  375. return null
  376. }
  377.  
  378. const populateContextMenu = function (ev) {
  379. console.debug('populateContextMenu')
  380. const $this = $(this)
  381.  
  382. let prettyString = null
  383.  
  384. if (document.querySelectorAll('.main-view-container [role="row"][aria-selected="true"]').length > 1) {
  385. // Multiple tracks selected
  386. $('.main-view-container [role="row"][aria-selected="true"]').each(function () {
  387. const songInfo = findSongInfo($(this))
  388. if (songInfo) {
  389. if (prettyString) {
  390. prettyString += '\n'
  391. } else {
  392. prettyString = ''
  393. }
  394. prettyString += songInfo
  395. }
  396. })
  397. } else {
  398. // Single track
  399. prettyString = findSongInfo($this)
  400. }
  401.  
  402. let menu = $('.react-contextmenu--visible')
  403. if (!menu[0]) {
  404. menu = $('#context-menu-root')
  405. }
  406. if (!menu[0]) {
  407. menu = $('#context-menu')
  408. }
  409.  
  410. if (prettyString && menu[0]) {
  411. // Create context menu entry
  412. let entry = menu.find('.gmcopytrackinfo')
  413. if (entry.length === 0 || !entry[0]) {
  414. const liButton = menu.find('li button')
  415. let li = $(liButton[0]).parent()
  416. if (liButton.length > 4) {
  417. li = $(liButton[4]).parent()
  418. }
  419. entry = $(`
  420. <li role="presentation">
  421. <button role="menuitem" tabindex="-1">
  422. <div style="filter: grayscale(100%);font-size: 1.2rem;padding: 0px;margin: 0px 0px 0px -0.5rem;">🍝</div>
  423. <span as="span" dir="auto" style="flex:1">${menuString}</span>
  424. </button>
  425. </li>`)
  426. .appendTo(li.parent())
  427. .click(function (ev) {
  428. // Copy string to clipboard
  429. const s = entry.data('gmcopy')
  430. if (typeof GM_setClipboard !== 'undefined') { // eslint-disable-line camelcase
  431. GM_setClipboard(s)
  432. } else if (GM.setClipboard) {
  433. GM.setClipboard(s)
  434. } else {
  435. navigator.clipboard.writeText(s)
  436. }
  437. menu.parent().remove()
  438. let infoString = s
  439. if (s.split('\n').length > 2) {
  440. infoString = s.split('\n').slice(0, 2).join('\n') + '\n...'
  441. }
  442. showInfo(copiedString.replace('%s', infoString))
  443. })
  444. .mouseenter(function () {
  445. // Find the buttons that are currently not expanded
  446. const buttons = Array.from(document.querySelectorAll('#context-menu button[role=menuitem]:not([aria-expanded])'))
  447. // Remove the ones that are inside a submenu
  448. const buttonsFirstLevel = buttons.filter(button => $(button).parents('ul').length === 1)
  449. if (buttonsFirstLevel.length > 0) {
  450. // Fire mouseover event on the first button to close the other submenus
  451. buttonsFirstLevel[0].dispatchEvent(new MouseEvent('mouseover', { bubbles: true }))
  452. }
  453. })
  454. // Copy classes from an existing entry
  455. entry.attr('class', li.attr('class'))
  456. entry.addClass('gmcopytrackinfo')
  457. entry.find('button').attr('class', li.find('button').attr('class'))
  458. entry.find('button span').attr('class', li.find('button span').attr('class'))
  459. }
  460. entry.data('gmcopy', prettyString)
  461. menu.css('margin-top', '-26px')
  462. }
  463. }
  464.  
  465. const onContextMenu = function (ev) {
  466. // Wait for the React context menu to open
  467. const t = this
  468. window.setTimeout(function () {
  469. populateContextMenu.call(t, ev)
  470. }, 200)
  471. }
  472.  
  473. let lastNode = null
  474. const searchForOpenContextMenu = function () {
  475. const node = document.querySelector('[data-context-menu-open]')
  476. if (node && node !== lastNode) {
  477. lastNode = node
  478. populateContextMenu.call(node, null)
  479. }
  480. }
  481.  
  482. const bindEvents = function () {
  483. // Remove all events and then reattach them
  484. $('*[data-testid="tracklist-row"],.now-playing,*[data-testid="now-playing-widget"]').unbind('.gmcopytrackinfo').bind('contextmenu.gmcopytrackinfo', onContextMenu)
  485. }
  486.  
  487. window.setTimeout(bindEvents, 500)
  488.  
  489. window.setInterval(bindEvents, 1000)
  490.  
  491. let searchIv = window.setInterval(searchForOpenContextMenu, 50)
  492.  
  493. document.addEventListener('visibilitychange', function () {
  494. clearInterval(searchIv)
  495. if (!document.hidden) {
  496. searchIv = window.setInterval(searchForOpenContextMenu, 50)
  497. }
  498. })
  499. document.addEventListener('focus', function () {
  500. clearInterval(searchIv)
  501. if (!document.hidden) {
  502. searchIv = window.setInterval(searchForOpenContextMenu, 50)
  503. }
  504. })
  505. })()