您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
新しいタブで開くリンクをCSSセレクタで選べるようにする
// ==UserScript== // @name Open In New Tab // @namespace https://gf.qytechs.cn/users/1009-kengo321 // @version 8 // @description 新しいタブで開くリンクをCSSセレクタで選べるようにする // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_openInTab // @grant GM_setClipboard // @grant GM_info // @grant GM.getValue // @grant GM.setValue // @grant GM.openInTab // @grant GM.setClipboard // @grant GM.info // @match *://*/* // @license MIT // @noframes // @run-at document-start // ==/UserScript== ;(function() { 'use strict' if (window.self !== window.top) return function getter(propName) { return function(o) { return o[propName] } } function not(func) { return function() { return !func.apply(null, arguments) } } const [gmGetValue, gmSetValue, gmOpenInTab, gmSetClipboard, gmInfo] = typeof GM_getValue === 'undefined' ? [GM.getValue, GM.setValue, GM.openInTab, GM.setClipboard, GM.info] : [GM_getValue, GM_setValue, GM_openInTab, GM_setClipboard, GM_info] async function gmGetLinkSelectors() { return JSON.parse(await gmGetValue('linkSelectors', '[]')).map(o => new LinkSelector(o)) } var Config = (function() { function compareLinkSelector(o1, o2) { var m1 = o1.matchUrlForward(), m2 = o2.matchUrlForward() if (m1 && !m2) return -1 if (!m1 && m2) return 1 if (o1.url < o2.url) return -1 if (o1.url > o2.url) return 1 return 0 } function setComputedHeight(win, elem) { elem.style.height = win.getComputedStyle(elem).height } function updateUrlOptionClass(option, linkSelector) { var p = linkSelector.matchUrlForward() ? 'add' : 'remove' option.classList[p]('matched') return option } function addAndSelectOption(selectElem, option) { selectElem.add(option) selectElem.selectedIndex = option.index } function getSelectedIndices(selectElem) { return [].map.call(selectElem.selectedOptions, getter('index')) } function removeSelectedOptions(selectElem) { ;[].slice.call(selectElem.selectedOptions).forEach(function(o) { o.parentNode.removeChild(o) }) } function filterIndices(array, indices) { return array.filter(function(e, i) { return indices.indexOf(i) === -1 }) } function optionAdder(selectElem, newOption) { return function(e) { selectElem.add(newOption(e)) } } function maxZIndex() { return '2147483647' } function Config(doc) { this.doc = doc this.doc.getElementById('insert-p').hidden = (gmInfo.scriptHandler === 'Greasemonkey') this.addCallbacks() } Config.srcdoc = [ '<!DOCTYPE html>', '<html><head><style>', ' html {', ' margin: 0 auto;', ' max-width: 50em;', ' height: 100%;', ' line-height: 1.5em;', ' }', ' body {', ' height: 100%;', ' margin: 0;', ' display: flex;', ' flex-direction: column;', ' justify-content: center;', ' }', ' #dialog {', ' overflow: auto;', ' padding: 8px;', ' background-color: white;', ' }', ' p { margin: 0; }', ' textarea { width: 100%; }', ' #url-list { width: 100%; }', ' #url-list option.matched { text-decoration: underline; }', ' #selector-list { width: 100%; }', ' #confirm-p { text-align: right; }', ' p.description { font-size: smaller; }', ' #import-export-container { display: none; }', ' #import-export-container.show { display: block; }', '</style></head><body><div id=dialog>', '<fieldset>', ' <legend>対象ページのURL(前方一致)</legend>', ' <p><select id=url-list multiple size=10></select></p>', ' <p>', ' <button id=url-add-button type=button>追加</button>', ' <button id=url-edit-button type=button disabled>編集</button>', ' <button id=url-remove-button type=button disabled>削除</button>', ' </p>', '</fieldset>', '<fieldset id=selector-fieldset disabled>', ' <legend>新しいタブで開くリンクのCSSセレクタ</legend>', ' <p class=description>', ' 何も登録していないときは、すべてのリンクが対象になります', ' </p>', ' <p><select id=selector-list multiple size=5></select></p>', ' <p>', ' <button id=selector-add-button type=button>追加</button>', ' <button id=selector-edit-button type=button>編集</button>', ' <button id=selector-remove-button type=button>削除</button>', ' </p>', ' <p>', ' <label>', ' <input type=checkbox id=capture-checkbox>', ' キャプチャフェーズを使用して、イベント伝播を中断する', ' </label>', ' </p>', ' <p class=description>', ' 正しく動作しないときは、これを有効にして試してください', ' </p>', '</fieldset>', '<p id=active-p><label>', ' <input id=active-checkbox type=checkbox>', ' 新しいタブを開いたとき、すぐにそのタブに切り替える', '</label></p>', '<p id=insert-p><label>', ' <input id=insert-checkbox type=checkbox>', ' 現在のタブの後ろに新しいタブを挿入する', '</label></p>', '<p>', ' インポート・エクスポート:', ' <small>', ' <label><input id=import-export-checkbox type=checkbox>表示</label>', ' </small>', '</p>', '<div id=import-export-container>', ' <p><textarea id=import-export-textarea rows=2></textarea></p>', ' <p>', ' <input id=import-button type=button value=インポート>', ' <input id=export-button type=button value=エクスポート>', ' </p>', ' <p><input id=export-to-clipboard-button type=button', ' value=クリップボードへエクスポート></p>', '</div>', '<p id=confirm-p>', ' <button id=ok-button type=button>OK</button>', ' <button id=cancel-button type=button>キャンセル</button>', '</p>', '</div></body></html>', ].join('\n') Config.show = function(done) { var background = document.createElement('div') background.style.backgroundColor = 'black' background.style.opacity = '0.5' background.style.zIndex = maxZIndex() - 1 background.style.position = 'fixed' background.style.top = '0' background.style.left = '0' background.style.width = '100%' background.style.height = '100%' document.body.appendChild(background) var f = document.createElement('iframe') f.style.position = 'fixed' f.style.top = '0' f.style.left = '0' f.style.width = '100%' f.style.height = '100%' f.style.zIndex = maxZIndex() f.srcdoc = Config.srcdoc f.addEventListener('load', async function() { const linkSelectors = await gmGetLinkSelectors() Config.setLinkSelectors(linkSelectors) var config = new Config(f.contentDocument) config.linkSelectors = linkSelectors.sort(compareLinkSelector) config.updateUrlList() config.getActiveCheckbox().checked = await Config.isNewTabActivation() config.getInsertCheckbox().checked = await Config.isNewTabInsertion() config.setIFrame(f) config.background = background if (typeof(done) === 'function') done(config) }) document.body.appendChild(f) } Config.getLinkSelectors = function() { return Config._linkSelectors } Config.setLinkSelectors = function(linkSelectors) { Config._linkSelectors = linkSelectors } Config.isNewTabActivation = function() { return gmGetValue('active', true) } Config.isNewTabInsertion = function() { return gmGetValue('insert', false) } Config.prototype.addCallbacks = function() { var doc = this.doc ;[['url-list', 'change', [ this.updateSelectorList.bind(this), this.updateCaptureCheckbox.bind(this), this.updateDisabled.bind(this), ]], ['selector-list', 'change', this.updateDisabled.bind(this)], ['url-add-button', 'click', [ this.addUrl.bind(this), this.updateDisabled.bind(this), this.updateSelectorList.bind(this), this.updateCaptureCheckbox.bind(this), ]], ['url-edit-button', 'click', this.editUrl.bind(this)], ['url-remove-button', 'click', [ this.removeUrl.bind(this), this.updateDisabled.bind(this), this.updateSelectorList.bind(this), this.updateCaptureCheckbox.bind(this), ]], ['selector-add-button', 'click', [ this.addSelector.bind(this), this.updateDisabled.bind(this), ]], ['selector-edit-button', 'click', this.editSelector.bind(this)], ['selector-remove-button', 'click', [ this.removeSelector.bind(this), this.updateDisabled.bind(this), ]], ['capture-checkbox', 'change', this.updateCapture.bind(this)], ['ok-button', 'click', [ this.save.bind(this), LinkSelector.updateCallback, this.removeIFrame.bind(this), ]], ['cancel-button', 'click', this.removeIFrame.bind(this)], [ 'import-export-checkbox', 'change', this.importExportCheckboxChanged.bind(this), ], [ 'export-to-clipboard-button', 'click', this.exportToClipboard.bind(this), ], ['import-button', 'click', [ this.import.bind(this), this.updateSelectorList.bind(this), this.updateCaptureCheckbox.bind(this), this.updateDisabled.bind(this), ]], ['export-button', 'click', this.export.bind(this)], ].forEach(function(e) { ;[].concat(e[2]).forEach(function(callback) { doc.getElementById(e[0]).addEventListener(e[1], callback) }) }) } Config.prototype.getUrlList = function() { return this.doc.getElementById('url-list') } Config.prototype.getSelectorList = function() { return this.doc.getElementById('selector-list') } Config.prototype.getCaptureCheckbox = function() { return this.doc.getElementById('capture-checkbox') } Config.prototype.getActiveCheckbox = function() { return this.doc.getElementById('active-checkbox') } Config.prototype.getInsertCheckbox = function() { return this.doc.getElementById('insert-checkbox') } Config.prototype.getImpExpTextarea = function() { return this.doc.getElementById('import-export-textarea') } Config.prototype.newOption = function(text) { var result = this.doc.createElement('option') result.textContent = text return result } Config.prototype.newUrlOption = function(linkSelector) { return updateUrlOptionClass(this.newOption(linkSelector.url) , linkSelector) } Config.prototype.updateUrlList = function() { this.getUrlList().length = 0 this.linkSelectors.forEach(optionAdder(this.getUrlList() , this.newUrlOption.bind(this))) } Config.prototype.getSelectedLinkSelector = function() { return this.linkSelectors[this.getUrlList().selectedIndex] } Config.prototype.updateSelectorList = function() { this.clearSelectorList() if (this.getUrlList().selectedOptions.length !== 1) return this.getSelectedLinkSelector() .selectors .forEach(optionAdder(this.getSelectorList() , this.newOption.bind(this))) } Config.prototype.clearSelectorList = function() { var s = this.getSelectorList() while (s.hasChildNodes()) s.removeChild(s.firstChild) } Config.prototype.addUrl = function() { var r = prompt('', document.location.href) if (!r) return var s = new LinkSelector({url: r}) this.linkSelectors.push(s) addAndSelectOption(this.getUrlList(), this.newUrlOption(s)) } Config.prototype.editUrl = function() { if (this.getUrlList().selectedOptions.length !== 1) return var r = prompt('', this.getSelectedLinkSelector().url) if (!r) return this.getUrlList().selectedOptions[0].textContent = r this.getSelectedLinkSelector().url = r updateUrlOptionClass(this.getUrlList().selectedOptions[0] , this.getSelectedLinkSelector()) } Config.prototype.removeUrl = function() { this.linkSelectors = filterIndices(this.linkSelectors , getSelectedIndices(this.getUrlList())) removeSelectedOptions(this.getUrlList()) } Config.prototype.getErrorIfInvalidSelector = function(selector) { try { this.doc.querySelector(selector) return null } catch (e) { return e } } Config.prototype.promptUntilValidSelector = function(defaultValue) { var selector = defaultValue || '' var error = null do { selector = prompt((error || '').toString(), selector) if (!selector) return null } while (error = this.getErrorIfInvalidSelector(selector)) return selector } Config.prototype.addSelector = function() { if (this.getUrlList().selectedOptions.length !== 1) return var r = this.promptUntilValidSelector() if (!r) return this.getSelectedLinkSelector().selectors.push(r) addAndSelectOption(this.getSelectorList(), this.newOption(r)) } Config.prototype.getSelectedSelector = function() { return this.getSelectorList().selectedOptions[0].textContent } Config.prototype.setSelectedSelector = function(selector) { var o = this.getSelectorList().selectedOptions[0] o.textContent = selector this.getSelectedLinkSelector().selectors[o.index] = selector } Config.prototype.editSelector = function() { if (this.getSelectorList().selectedOptions.length !== 1) return var r = this.promptUntilValidSelector(this.getSelectedSelector()) if (!r) return this.setSelectedSelector(r) } Config.prototype.removeSelector = function() { var s = this.getSelectedLinkSelector() s.selectors = filterIndices(s.selectors , getSelectedIndices(this.getSelectorList())) removeSelectedOptions(this.getSelectorList()) } Config.prototype.updateCaptureCheckbox = function() { var s = this.getSelectedLinkSelector() this.getCaptureCheckbox().checked = (s ? s.capture : false) } Config.prototype.updateCapture = function() { var s = this.getSelectedLinkSelector() if (s) s.capture = this.getCaptureCheckbox().checked } Config.prototype.updateDisabled = function() { var selectedUrlNum = this.getUrlList().selectedOptions.length var selectedSelectorNum = this.getSelectorList().selectedOptions.length ;[['url-edit-button', selectedUrlNum !== 1], ['url-remove-button', selectedUrlNum === 0], ['selector-fieldset', selectedUrlNum !== 1], ['selector-edit-button', selectedSelectorNum !== 1], ['selector-remove-button', selectedSelectorNum === 0], ].forEach(function(e) { this.doc.getElementById(e[0]).disabled = e[1] }, this) } Config.prototype.setIFrame = function(iframe) { this.iframe = iframe this.getUrlList().focus() } Config.prototype.removeIFrame = function() { var rm = function(e) { if (e && e.parentNode) e.parentNode.removeChild(e) } rm(this.iframe) rm(this.background) } Config.prototype.save = function() { gmSetValue('linkSelectors', JSON.stringify(this.linkSelectors)) Config.setLinkSelectors(this.linkSelectors) gmSetValue('active', this.getActiveCheckbox().checked) gmSetValue('insert', this.getInsertCheckbox().checked) } Config.prototype.importExportCheckboxChanged = function() { var checkbox = this.doc.getElementById('import-export-checkbox') var container = this.doc.getElementById('import-export-container') container.classList[checkbox.checked ? 'add' : 'remove']('show') this.iframe.height = this.doc.documentElement.offsetHeight } Config.prototype.exportToClipboard = function() { gmSetClipboard(JSON.stringify(this.linkSelectors)) } Config.prototype.import = function() { try { var ta = this.getImpExpTextarea() this.linkSelectors = JSON.parse(ta.value).map(function(o) { return new LinkSelector(o) }) this.updateUrlList() ta.setCustomValidity('') } catch (e) { ta.setCustomValidity(e.toString()) } } Config.prototype.export = function() { this.getImpExpTextarea().value = JSON.stringify(this.linkSelectors) } return Config })() var LinkSelector = (function() { function isLeftMouseButtonWithoutModifierKeys(mouseEvent) { var e = mouseEvent return !(e.button || e.altKey || e.shiftKey || e.ctrlKey || e.metaKey) } function isOpenableLink(elem) { return ['A', 'AREA'].indexOf(elem.tagName) >= 0 && elem.href && elem.protocol !== 'javascript:' } function getAncestorOpenableLink(descendant) { for (var p = descendant.parentNode; p; p = p.parentNode) { if (isOpenableLink(p)) return p } return null } async function openInTab(url) { if (gmInfo.scriptHandler === 'Greasemonkey') { gmOpenInTab(url, !(await Config.isNewTabActivation())) } else { gmOpenInTab(url, { active: await Config.isNewTabActivation(), insert: await Config.isNewTabInsertion(), }) } } function LinkSelector(o) { o = o || {} this.url = o.url || '' this.selectors = o.selectors || [] this.capture = !!o.capture } LinkSelector.getLocatedInstances = function() { return Config.getLinkSelectors().filter(s => s.matchUrlForward()) } LinkSelector.addCallbackIfRequired = function() { var i = LinkSelector.getLocatedInstances() if (i.some(not(getter('capture')))) { document.addEventListener('click', LinkSelector.callback, false) } if (i.some(getter('capture'))) { document.addEventListener('click', LinkSelector.callback, true) } } LinkSelector.updateCallback = function() { document.removeEventListener('click', LinkSelector.callback, false) document.removeEventListener('click', LinkSelector.callback, true) LinkSelector.addCallbackIfRequired() } LinkSelector.callback = function(mouseEvent) { var e = mouseEvent if (!isLeftMouseButtonWithoutModifierKeys(e)) return var link = isOpenableLink(e.target) ? e.target : getAncestorOpenableLink(e.target) if (!link) return var opened = LinkSelector.getLocatedInstances() .some(s => s.openInTabIfMatch(link, e.eventPhase)) if (!opened) return e.preventDefault() if (e.eventPhase === Event.CAPTURING_PHASE) e.stopPropagation() } LinkSelector.prototype.matchUrlForward = function() { return document.location.href.indexOf(this.url) === 0 } LinkSelector.prototype.matchEventPhase = function(eventPhase) { return this.capture ? eventPhase === Event.CAPTURING_PHASE : eventPhase === Event.BUBBLING_PHASE } LinkSelector.prototype.matchLink = function(link) { return !this.selectors.length || this.selectors.some(link.matches.bind(link)) } LinkSelector.prototype.openInTabIfMatch = function(link, eventPhase) { if (this.matchEventPhase(eventPhase) && this.matchLink(link)) { openInTab(link.href) return true } return false } return LinkSelector })() function addConfigButtonIfScriptPage() { if (!location.href.startsWith('https://gf.qytechs.cn/ja/scripts/5591-open-in-new-tab')) return const add = () => { const e = document.createElement('button') e.type = 'button' e.textContent = '設定' e.addEventListener('click', Config.show) document.querySelector('#script-info > header > h2').appendChild(e) } if (['interactive', 'complete'].includes(document.readyState)) add() else document.addEventListener('DOMContentLoaded', add) } async function main() { Config.setLinkSelectors(await gmGetLinkSelectors()) LinkSelector.addCallbackIfRequired() if (typeof GM_registerMenuCommand !== 'undefined') GM_registerMenuCommand('Open In New Tab 設定', Config.show) addConfigButtonIfScriptPage() } main() })()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址