您需要先安装一个扩展,例如 篡改猴、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或关注我们的公众号极客氢云获取最新地址