您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
ウェブページを中央配置
// ==UserScript== // @name Page Centering // @namespace https://greasyfork.org/users/1009-kengo321 // @version 4 // @description ウェブページを中央配置 // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM.getValue // @grant GM.setValue // @match *://*/* // @license MIT // @noframes // @run-at document-start // ==/UserScript== ;(function() { 'use strict' var createObject = function(prototype, properties) { var descriptors = function() { return Object.keys(properties).reduce(function(descriptors, key) { descriptors[key] = Object.getOwnPropertyDescriptor(properties, key) return descriptors }, {}) } return Object.defineProperties(Object.create(prototype), descriptors()) } var invoke = function(methodName, args) { return function(target) { return target[methodName].apply(target, args) } } var EventEmitter = (function() { var EventEmitter = function() { this._eventNameToListeners = new Map() } EventEmitter.prototype = { on(eventName, listener) { var m = this._eventNameToListeners var v = m.get(eventName) if (v) { v.add(listener) } else { m.set(eventName, new Set([listener])) } return this }, off(eventName, listener) { var v = this._eventNameToListeners.get(eventName) if (v) v.delete(listener) return this }, emit(eventName) { var m = this._eventNameToListeners var args = Array.from(arguments).slice(1) for (var l of m.get(eventName) || []) l(...args) }, } return EventEmitter })() var PageCentering = (function() { var PageCentering = function(obj) { this.url = obj.url this.maxWidth = obj.maxWidth this.matched = false } PageCentering.prototype = { setMatchedIfStarted(url) { this.matched = url.startsWith(this.url) return this }, center(doc) { var s = doc.documentElement.style s.maxWidth = this.maxWidth s.position = 'relative' s.margin = '0px auto' }, } Object.assign(PageCentering, { clear(doc) { var s = doc.documentElement.style s.maxWidth = '' s.position = '' s.margin = '' }, MATCHED_URL_ORDER(o1, o2) { if (o1.matched && !o2.matched) return -1 if (!o1.matched && o2.matched) return 1 if (o1.url < o2.url) return -1 if (o1.url > o2.url) return 1 return 0 }, takeLongerURL(o1, o2) { if (o1) return o1.url.length >= o2.url.length ? o1 : o2 return o2 }, }) return PageCentering })() var Config = (function(_super) { var Config = function(get, set) { _super.call(this) this.get = get this.set = set } Config.prototype = createObject(_super.prototype, { async _getPageCenteringObjs() { return JSON.parse(await this.get('pageCenterings', '[]')) }, async getPageCenterings() { return (await this._getPageCenteringObjs()).map(function(o) { return new PageCentering(o) }) }, async _emitPageCenteringsChanged() { this.emit('pageCenteringsChanged', await this.getPageCenterings()) }, async setPageCentering(pageCentering) { var f = function(o) { return o.url !== pageCentering.url } var newObj = {url: pageCentering.url, maxWidth: pageCentering.maxWidth} var newObjs = (await this._getPageCenteringObjs()).filter(f).concat(newObj) await this.set('pageCenterings', JSON.stringify(newObjs)) await this._emitPageCenteringsChanged() }, async deletePageCentering(url) { var f = function(o) { return o.url !== url } var newObjs = (await this._getPageCenteringObjs()).filter(f) await this.set('pageCenterings', JSON.stringify(newObjs)) await this._emitPageCenteringsChanged() }, }) return Config })(EventEmitter) var ConfigDialog = (function(_super) { var initUrlCell = function(urlCell, pageCentering) { urlCell.textContent = pageCentering.url if (pageCentering.matched) urlCell.className = 'matched' } var focusAndSelect = function(elem) { elem.focus() elem.select() } var ENTER_KEY = 13 var ESCAPE_KEY = 27 var invokeIf = function(key, func) { return function(event) { if (event.which === key) func() } } var ConfigDialog = function(doc) { _super.call(this) this.doc = doc this.targetURL = this._currentURL this._addEventListeners() focusAndSelect(this.targetUrlInput) } ConfigDialog.prototype = createObject(_super.prototype, { get _currentURL() { return this.doc.defaultView.frameElement.ownerDocument.location.href }, _addEventListeners() { var emitPageCenteringChangedIfValid = this._emitPageCenteringChangedIfValid.bind(this) ;[[this.closeButton, [ ['click', this._close.bind(this)], ]], [this.changeButton, [ ['click', emitPageCenteringChangedIfValid], ]], [this.targetUrlInput, [ ['keydown', invokeIf(ENTER_KEY, emitPageCenteringChangedIfValid)], ]], [this.maxWidthInput, [ ['keydown', invokeIf(ENTER_KEY, emitPageCenteringChangedIfValid)], ['input', this._updateMaxWidthInputValidity.bind(this)], ['input', this._updateChangeButtonDisabled.bind(this)], ]], [this.doc, [ ['keydown', invokeIf(ESCAPE_KEY, this._close.bind(this))], ]], ].forEach(function(a) { a[1].forEach(function(b) { a[0].addEventListener(b[0], b[1]) }) }) }, get targetUrlInput() { return this.doc.getElementById('targetUrlInput') }, get targetURL() { return this.targetUrlInput.value }, set targetURL(targetURL) { this.targetUrlInput.value = targetURL }, get maxWidthInput() { return this.doc.getElementById('maxWidthInput') }, get maxWidth() { return this.maxWidthInput.value }, set maxWidth(maxWidth) { this.maxWidthInput.value = maxWidth }, get closeButton() { return this.doc.getElementById('closeButton') }, get changeButton() { return this.doc.getElementById('changeButton') }, _emitPageCenteringChangedIfValid() { if (!this._hasValidMaxWidth()) return var o = {url: this.targetURL, maxWidth: this.maxWidth} this.emit('pageCenteringChanged', new PageCentering(o)) }, _sort(pageCenterings) { return pageCenterings .map(invoke('setMatchedIfStarted', [this._currentURL])) .sort(PageCentering.MATCHED_URL_ORDER) }, _initDelCell(delCell, pageCentering) { delCell.textContent = '削除' delCell.addEventListener('click', function() { this.emit('pageCenteringDeleted', pageCentering.url) }.bind(this)) }, _initSettingCell(settingCell, pageCentering) { settingCell.textContent = 'セット' settingCell.addEventListener('click', function() { this.targetURL = pageCentering.url this.maxWidth = pageCentering.maxWidth focusAndSelect(this.maxWidthInput) this._updateMaxWidthInputValidity() this._updateChangeButtonDisabled() }.bind(this)) }, get dataTable() { return this.doc.getElementById('dataTable') }, set pageCenterings(pageCenterings) { var t = this.dataTable for (var r of Array.from(t.rows)) r.remove() for (var c of this._sort(pageCenterings)) { var tr = t.insertRow(-1) initUrlCell(tr.insertCell(-1), c) tr.insertCell(-1).textContent = c.maxWidth this._initSettingCell(tr.insertCell(-1), c) this._initDelCell(tr.insertCell(-1), c) } }, _close() { this.doc.defaultView.frameElement.remove() this.emit('closed') }, _hasValidMaxWidth() { var e = this.doc.createElement('div') e.style.maxWidth = this.maxWidthInput.value return e.style.maxWidth !== '' }, _updateMaxWidthInputValidity() { var MESSAGE = 'CSS の max-width プロパティに有効な値のみを設定できます。' if (this._hasValidMaxWidth()) { this.maxWidthInput.setCustomValidity('') } else { this.maxWidthInput.setCustomValidity(MESSAGE) } }, _updateChangeButtonDisabled() { this.changeButton.disabled = !this._hasValidMaxWidth() }, }) var frameLoaded = function(frame, back, config) { var set = function(target, propName) { return function(value) { target[propName] = value } } var clear = function(updateDialog) { return function() { back.remove() config.off('pageCenteringsChanged', updateDialog) } } return async function() { var dialog = new ConfigDialog(frame.contentDocument) .on('pageCenteringDeleted', config.deletePageCentering.bind(config)) .on('pageCenteringChanged', config.setPageCentering.bind(config)) dialog.pageCenterings = await config.getPageCenterings() var updateDialog = set(dialog, 'pageCenterings') config.on('pageCenteringsChanged', updateDialog) dialog.on('closed', clear(updateDialog)) } } ConfigDialog.show = function(doc, config) { var MAX_Z_INDEX = 2147483647 var back = doc.createElement('div') back.style.backgroundColor = 'black' back.style.opacity = '0.5' back.style.zIndex = MAX_Z_INDEX - 1 back.style.position = 'fixed' back.style.top = '0' back.style.left = '0' back.style.width = '100%' back.style.height = '100%' doc.body.appendChild(back) var f = doc.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 = MAX_Z_INDEX f.srcdoc = ConfigDialog.SRC_DOC f.addEventListener('load', frameLoaded(f, back, config)) doc.body.appendChild(f) } ConfigDialog.SRC_DOC = `<!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; } p { margin: 0; } #dialog { overflow: auto; padding: 8px; background-color: white; } #top { text-align: right; } #targetUrlInput { width: 100%; } #maxWidthInput:invalid { color: red; font-weight: bold; } #dataTable { border-collapse: collapse; } #dataTable td { border: solid thin; padding: 0 0.5em; line-height: 1.5em; } #dataTable td:nth-child(1) { word-break: break-all; } #dataTable td:nth-child(2) { text-align: right; white-space: nowrap; } #dataTable td:nth-child(3), #dataTable td:nth-child(4) { text-decoration: underline; cursor: pointer; white-space: nowrap; } .matched { font-weight: bold; } </style></head><body> <div id=dialog> <p id=top><input type=button value=閉じる id=closeButton></p> <p><label for=targetUrlInput>対象URL(前方一致):</label></p> <p><input type=url id=targetUrlInput></p> <p><label for=maxWidthInput>最大幅:</label></p> <p><input value=1000px id=maxWidthInput></p> <p><input type=button value=追加/変更 id=changeButton></p> <table id=dataTable></table> </div> </body></html>` return ConfigDialog })(EventEmitter) function addConfigButtonIfScriptPage(config) { if (!location.href.startsWith('https://greasyfork.org/ja/scripts/15722-page-centering')) return const add = () => { const e = document.createElement('button') e.type = 'button' e.textContent = '設定' e.addEventListener('click', function() { ConfigDialog.show(document, config) }) document.querySelector('#script-info > header > h2').appendChild(e) } if (['interactive', 'complete'].includes(document.readyState)) add() else document.addEventListener('DOMContentLoaded', add) } var main = async function() { const [get, set] = typeof GM_getValue === 'undefined' ? [GM.getValue, GM.setValue] : [GM_getValue, GM_setValue] var config = new Config(get, set) var updateCentering = function(pageCenterings) { var c = pageCenterings .map(invoke('setMatchedIfStarted', [document.location.href])) .filter(function(c) { return c.matched }) .reduce(PageCentering.takeLongerURL, null) if (c) c.center(document); else PageCentering.clear(document) } updateCentering(await config.getPageCenterings()) config.on('pageCenteringsChanged', updateCentering) if (typeof GM_registerMenuCommand !== 'undefined') { GM_registerMenuCommand('Page Centering 設定', function() { ConfigDialog.show(document, config) }) } addConfigButtonIfScriptPage(config) } main() })()