// ==UserScript==
// @name Tategaki Novel
// @namespace http://userscripts.org/users/121129
// @description 小説投稿サイトに縦書き表示機能を追加
// @match *://ncode.syosetu.com/n*
// @match *://novel18.syosetu.com/n*
// @match *://www.mai-net.net/bbs/sst/sst.php?*
// @match *://syosetu.org/*
// @match *://www.pixiv.net/novel/show.php?*
// @match *://www.akatsuki-novels.com/stories/view/*
// @version 28
// @license MIT
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// @noframes
// ==/UserScript==
;(function() {
'use strict'
var onIFrame = window.top !== window.self
if (onIFrame) return
var Default = { LINE_NUM: 22
, LINE_CHAR_NUM: 28
, FONT_TYPE: 'gothic'
, GOTHIC_FONT_FAMILY: [ '"メイリオ"'
, '"IPAexゴシック"'
, '"IPAゴシック"'
, '"MS ゴシック"'
, '"SimSun"'
, 'monospace'
].join(', ')
, MINCHO_FONT_FAMILY: [ '"IPAex明朝"'
, '"IPA明朝"'
, '"MS 明朝"'
, '"SimSun"'
, 'serif'
].join(', ')
, FONT_SIZE: '20px'
, FONT_WEIGHT: 'normal'
, CHAR_HEIGHT: '1.1em'
, MARGIN_TOP: '50px'
, SPACE_BETWEEN_LINES: '1em'
, COLOR: '#2F4F4F'
, BACKGROUND_COLOR: '#D3D3D3'
, TOOLBAR_VISIBLE: true
, AUTO_VERTICAL_WRITING: true
}
const {_getValue, _syncValue, _setValue, _deleteValue} = (function() {
function gm() {
return typeof GM_deleteValue === 'undefined'
? [GM.getValue, GM.setValue, GM.deleteValue]
: [GM_getValue, GM_setValue, GM_deleteValue]
}
const [gmGetValue, gmSetValue, gmDeleteValue] = gm()
const data = new Map()
return {
_getValue(key, defaultValue) {
return data.has(key) ? data.get(key) : defaultValue
},
async _syncValue(key) {
const v = await gmGetValue(key)
if (v !== undefined) data.set(key, v)
},
_setValue(key, value) {
data.set(key, value)
gmSetValue(key, value)
},
_deleteValue(key) {
data.delete(key)
gmDeleteValue(key)
},
}
})()
function GM_getLineNum() { return _getValue('lineNum', Default.LINE_NUM) }
function GM_getLineCharNum() {
return _getValue('lineCharNum', Default.LINE_CHAR_NUM)
}
function GM_getFontType() { return _getValue('fontType', Default.FONT_TYPE) }
function GM_getGothicFontFamily() {
return _getValue('gothicFontFamily', Default.GOTHIC_FONT_FAMILY)
}
function GM_getMinchoFontFamily() {
return _getValue('minchoFontFamily', Default.MINCHO_FONT_FAMILY)
}
function GM_getFontFamily(fontType) {
switch (fontType || GM_getFontType()) {
case 'gothic': return GM_getGothicFontFamily()
case 'mincho': return GM_getMinchoFontFamily()
default: throw new Error(fontType || GM_getFontType())
}
}
function GM_getColor() {
return _getValue('color', Default.COLOR)
}
function GM_getBackgroundColor() {
return _getValue('backgroundColor', Default.BACKGROUND_COLOR)
}
function GM_isToolbarVisible() {
return _getValue('toolbarVisible', Default.TOOLBAR_VISIBLE)
}
function GM_isAutoVerticalWriting() {
return _getValue('autoVerticalWriting', Default.AUTO_VERTICAL_WRITING)
}
function GM_getFontSize() {
return _getValue('fontSize', Default.FONT_SIZE)
}
function GM_getCharHeight() {
return _getValue('charHeight', Default.CHAR_HEIGHT)
}
function GM_getMarginTop() {
return _getValue('marginTop', Default.MARGIN_TOP)
}
function GM_getSpaceBetweenLines() {
return _getValue('spaceBetweenLines', Default.SPACE_BETWEEN_LINES)
}
function GM_getFontWeight() {
return _getValue('fontWeight', Default.FONT_WEIGHT)
}
function GM_keys() {
return [
'lineNum',
'lineCharNum',
'fontType',
'gothicFontFamily',
'minchoFontFamily',
'color',
'backgroundColor',
'toolbarVisible',
'fontSize',
'charHeight',
'marginTop',
'spaceBetweenLines',
'auto',
'autoVerticalWriting',
'fontWeight',
]
}
function GM_clear() {
for (const key of GM_keys()) _deleteValue(key)
}
function GM_sync() {
return Promise.all(GM_keys().map(key => _syncValue(key)))
}
function hasParam(location, name, val) {
return location.search.substring(1).split('&').some(function(param) {
var i = param.indexOf('=')
if (name !== param.substring(0, i)) return false
var v = param.substring(i + 1)
return val instanceof RegExp ? val.test(v) : val === v
})
}
function getHRef(selector, textContent) {
var anchors = document.querySelectorAll(selector)
for (var i = 0; i < anchors.length; i++) {
var a = anchors[i]
if (!textContent || a.textContent === textContent) return a.href
}
return ''
}
function insertCell(row, child) {
var result = row.insertCell(-1)
result.style.border = '1px solid'
result.style.padding = '5px'
result.style.fontSize = '18px'
if (child) {
if (child.nodeType) result.appendChild(child)
else result.textContent = child
}
return result
}
var newTextNode = document.createTextNode.bind(document)
function getLine(selector) {
var n = document.querySelector(selector)
return n ? Line.parse(n.childNodes) : null
}
function getLines(selector) {
var n = document.querySelector(selector)
return n ? Lines.parse(n.childNodes) : []
}
function newRotatedElem(text, doc) {
var result = (doc || document).createElement('span')
result.textContent = text
result.style.display = 'inline-block'
result.style.transform = 'rotate(90deg)'
result.style.webkitTransform = 'rotate(90deg)'
return result
}
var pixelCharSize = (function() {
var doc = document
var height = 0
var char2width = Object.create(null)
function calcHeight() {
var e = doc.createElement('div')
e.style.position = 'absolute'
e.style.fontSize = GM_getFontSize()
e.style.lineHeight = GM_getCharHeight()
doc.body.appendChild(e)
try {
var s = doc.defaultView.getComputedStyle(e)
var h = s.lineHeight === 'normal' ? s.fontSize : s.lineHeight
return parseFloat(h)
} finally {
doc.body.removeChild(e)
}
}
function calcWidth(ch) {
var e = doc.createElement('pre')
e.style.position = 'absolute'
e.appendChild(doc.createTextNode(ch))
e.style.fontFamily = GM_getFontFamily()
e.style.fontSize = GM_getFontSize()
e.style.fontWeight = GM_getFontWeight()
doc.body.appendChild(e)
try {
return parseFloat(doc.defaultView.getComputedStyle(e).width)
} finally {
doc.body.removeChild(e)
}
}
return {
get doc() { return doc }
, set doc(v) { doc = v || document }
, height: function() { return height || (height = calcHeight()) }
, width: function(ch) {
return char2width[ch] || (char2width[ch] = calcWidth(ch))
}
, clearCache: function() {
height = 0
char2width = Object.create(null)
}
}
})()
function Block() {}
Block.prototype.startsWithBadChar = function() { return false }
Block.prototype.endsWithBadChar = function() { return false }
Block.prototype.setLine = function() { return this }
Block.prototype.trimHead = function() { return this }
Block.prototype.isEmpty = function() { return false }
const CombinationChar = (() => {
const map = new Map([
['\u309b', '\u309b'],
['\u3099', '\u309b'],
['\u309c', '\u309c'],
['\u309a', '\u309c'],
])
const regexp = (() => {
let s = ''
for (const [k] of map) s += k
return new RegExp(`[^${s}][${s}]`, 'g')
})()
return class CombinationChar extends Block {
constructor(base, combination) {
super()
this.base = base
this.combination = map.get(combination)
}
isEmpty() { return false }
getLength() { return 1 }
canCut() { return false }
newNode(doc) {
const result = doc.createElement('div')
result.style.display = 'inline-block'
result.style.position = 'relative'
result.style.width = '2em'
result.textContent = this.base + this.combination
return result
}
static get regexp() { return regexp }
}
})()
var Text = (function() {
var replaceByVerticalChars = (function() {
var map = { '「':'﹁', '」':'﹂', '「':'﹁', '」':'﹂'
, '『':'﹃', '』':'﹄', '(':'︵', ')':'︶'
, '{':'︷', '}':'︸', '〈':'︿', '〉':'﹀'
, '<':'︿', '>':'﹀', '《':'︽', '》':'︾'
, '≪':'︽', '≫':'︾', '〔':'︹', '〕':'︺'
, '【':'︻', '】':'︼', '-':'︱', '―':'︱'
, '─':'︱', '—':'︱', 'ー':'丨', '−':'︱'
, '…':'︙', '‥':'︰'
, '、':'︑', '。':'︒', ',':'︐'
, '\u3016': '\ufe17'
, '\u3017': '\ufe18'
}
, regExp = new RegExp(Object.keys(map).join('|'), 'g')
return function(str) {
return str.replace(regExp, function(match) { return map[match] })
}
})()
var replaceIsolatedAsciiByZenkaku = (function() {
var regExp = /(^|[^\x20-\x7e])([\x21-\x7e])(?=$|[^\x20-\x7e])/g
, diff = '!'.charCodeAt(0) - '!'.charCodeAt(0)
return function(str) {
return str.replace(regExp, function(match, p1, p2) {
return p1 + String.fromCharCode(p2.charCodeAt(0) + diff)
})
}
})()
function removeAllWhiteSpace(str) {
return str.replace(/[ \t\r\n\u00A0]/g, '')
}
var replaceTwoExclamationOrQuestion = (function() {
var map = { '!!': '‼', '??': '⁇', '?!': '⁈', '!?': '⁉' }
, regExp = /(^|[^!?])([!?]{2})(?=$|[^!?])/g
return function(str) {
return str.replace(regExp, function(match, p1, p2) {
return p1 + map[p2]
})
}
})()
var reverseQuotationMark = (function() {
var map = { '“':'”', '”':'“', '‘':'’', '’':'‘'
, '〝':'”', '〟':'“', '〞':'“'
}
, regExp = new RegExp(Object.keys(map).join('|'), 'g')
return function(str) {
return str.replace(regExp, function(match) { return map[match] })
}
})()
var replaceHalfwidthKatakanaByFullwidth = (function() {
var fullwidth = '。「」、・ヲァィゥェォャュョッー'
+ 'アイウエオカキクケコサシスセソタチツテト'
+ 'ナニヌネノハヒフヘホマミムメモヤユヨラリルレロ'
+ 'ワン゛゜'
, halfwidth = /([。-゚])([゙゚]?)/g
, dakuten_able = /[カ-チツ-トハ-ホ]/
, handakuten_able = /[ハ-ホ]/
, halfwidthBase = '。'.charCodeAt(0)
function plusCharCode(ch, n) {
return String.fromCharCode(ch.charCodeAt(0) + n)
}
return function(str) {
return str.replace(halfwidth, function(match, p1, p2) {
var c = fullwidth.charAt(p1.charCodeAt(0) - halfwidthBase)
if (!p2) return c
if (p2 === '゙') {
if (dakuten_able.test(c)) return plusCharCode(c, 1)
switch (c) {
case 'ウ': return 'ヴ'
case 'ワ': return 'ヷ'
case 'ヲ': return 'ヺ'
default: return c + '゛'
}
}
if (handakuten_able.test(c)) return plusCharCode(c, 2)
return c + '゜'
})
}
})()
var rotateArrows = (function() {
var map = { '↑': '→', '↓': '←', '→': '↓', '←': '↑' }
, regExp = new RegExp(Object.keys(map).join('|'), 'g')
return function(str) {
return str.replace(regExp, function(match) { return map[match] })
}
})()
var insertFullwidthSpaceAfterExclamationOrQuestion = (function() {
var re = /[!?‼⁇⁈⁉](?![!?‼⁇⁈⁉」』)}〉>》≫〕】”’〟〞\u3000]|$)/g
return function(str) {
return str.replace(re, function(match) { return match + ' ' })
}
})()
function replace(text) {
var str = removeAllWhiteSpace(text)
str = replaceIsolatedAsciiByZenkaku(str)
str = replaceHalfwidthKatakanaByFullwidth(str)
str = insertFullwidthSpaceAfterExclamationOrQuestion(str)
str = replaceByVerticalChars(str)
str = replaceTwoExclamationOrQuestion(str)
str = reverseQuotationMark(str)
str = rotateArrows(str)
return str
}
function Text(text, replaced) {
this.val = replaced ? text : replace(text)
Object.freeze(this)
}
Text.prototype = Object.create(Block.prototype)
Text.prototype.isEmpty = function() { return !this.val }
Text.prototype.getLength = function() { return this.val.length }
Text.prototype.canCut = function(index) {
return 0 < index && index < this.getLength()
}
Text.prototype.cut = function(index) {
return { before: new Text(this.val.substring(0, index), true)
, after: new Text(this.val.substring(index), true)
}
}
Text.prototype.startsWithBadChar = (function() {
var regExp = new RegExp('[﹂﹄︶︸﹀︾︺︼︑︒︐]“‘!?‼⁇⁈⁉丨'
+ 'ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶ'
+ '〜~]')
return function() { return regExp.test(this.val[0]) }
})()
Text.prototype.endsWithBadChar = function() {
return /[﹁﹃︵︷︿︽︹︻[”’]/.test(this.val[this.val.length - 1])
}
Text.prototype.trimHead = function() {
return /^\u3000+/.test(this.val) ? new Text(this.val.trimLeft(), true)
: this
}
Text.prototype.newNode = (function() {
function replaceMatchedTextByCreatedElem(textNode
, regExp
, createElem
, doc) {
var result = doc.createDocumentFragment()
, begin = 0
, text = textNode.nodeValue
for (var r; r = regExp.exec(text);) {
result.appendChild(doc.createTextNode(text.substring(begin
, r.index)))
result.appendChild(createElem(r[0], doc))
begin = regExp.lastIndex
}
result.appendChild(doc.createTextNode(text.substring(begin)))
result.normalize()
return result
}
function replaceTextNodeIfMatched(node, regExp, createElem, doc) {
Array.prototype.filter.call(node.childNodes, function(child) {
return child.nodeType === Node.TEXT_NODE
}).forEach(function(textNode) {
node.replaceChild(replaceMatchedTextByCreatedElem(textNode
, regExp
, createElem
, doc)
, textNode)
})
return node
}
function rotateNoVerticalChars(node, doc) {
return replaceTextNodeIfMatched(node
, /[[]=:;〜~]/g
, newRotatedElem
, doc)
}
var translateSutegana = (function() {
var regExp = /[ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヵヶ]/g
function newSuteganaElem(text, doc) {
var result = doc.createElement('span')
result.textContent = text
result.style.position = 'relative'
result.style.left = '0.1em'
result.style.top = '-0.1em'
return result
}
return function(node, doc) {
return replaceTextNodeIfMatched(node, regExp, newSuteganaElem, doc)
}
})()
var translateQuotationMark = (function() {
var regExp = /[”“’‘]/g
function newQuotationMarkElem(text, doc) {
var result = doc.createElement('span')
result.textContent = text
result.style.position = 'relative'
if (text === '”' || text === '’') {
result.style.top = '0.5em'
if (text === '”') result.style.left = '0.5em'
else result.style.left = '0.7em'
} else {
result.style.top = '0.2em'
if (text === '“') result.style.right = '0.5em'
else result.style.right = '0.7em'
}
return result
}
return function(node, doc) {
return replaceTextNodeIfMatched(node
, regExp
, newQuotationMarkElem
, doc)
}
})()
return function(doc) {
doc = doc || document
var df = doc.createDocumentFragment()
df.appendChild(doc.createTextNode(this.val))
df = rotateNoVerticalChars(df, doc)
df = translateSutegana(df, doc)
df = translateQuotationMark(df, doc)
return df
}
})()
return Text
})()
var Ascii = (function() {
function toIntEmLength(pxLength) {
return Math.ceil(pxLength / pixelCharSize.height())
}
function getTextPxWidth(str) {
return Array.prototype.reduce.call(str, function(pre, ch) {
return pre + pixelCharSize.width(ch)
}, 0)
}
function getIntEmLength(str) { return toIntEmLength(getTextPxWidth(str)) }
function compressWhiteSpaces(str) {
return str.replace(/[ \t\r\n]+/g, ' ')
}
function Ascii(asciiText) {
this.val = compressWhiteSpaces(asciiText)
Object.freeze(this)
}
Ascii.prototype = Object.create(Block.prototype)
Ascii.prototype.getLength = function() { return getIntEmLength(this.val) }
Ascii.prototype.canCut = function(index) {
var v = this.val, i = v.indexOf(' ')
if (i === -1) return false
return getIntEmLength(v.substring(0, i)) <= index
}
Ascii.prototype.cut = function(index) {
var sub = this.val.split(' ')
for (var i = 2, n = sub.length; i <= n; i++) {
var pxLen = getTextPxWidth(sub.slice(0, i).join(' '))
if (toIntEmLength(pxLen) >= index) break
}
return { before: new Ascii(sub.slice(0, i - 1).join(' '))
, after: new Ascii(sub.slice(i - 1).join(' ')) }
}
Ascii.prototype.newNode = function(doc) {
var result = newRotatedElem(this.val, doc)
result.style.transformOrigin = '0.5em 0.5em 0px'
result.style.webkitTransformOriginX = '0.5em'
result.style.webkitTransformOriginY = '0.5em'
result.style.textAlign = 'center'
result.style.lineHeight = '1'
var len = this.getLength()
var h = pixelCharSize.height()
result.style.width = len * h + 'px'
result.style.marginBottom = (len - 1) * h + 'px'
return result
}
return Ascii
})()
var Ruby = (function() {
function rubyCharPxHeight() {
return pixelCharSize.height() / 2
}
function Ruby(base, val, line) {
this.base = base
this.val = val
this.line = line
Object.freeze(this)
}
Ruby.parse = (function() {
var map = { 'ぁ': 'あ', 'ぃ': 'い', 'ぅ': 'う', 'ぇ': 'え', 'ぉ': 'お'
, 'っ': 'つ', 'ゃ': 'や', 'ゅ': 'ゆ', 'ょ': 'よ', 'ゎ': 'わ'
, 'ァ': 'ア', 'ィ': 'イ', 'ゥ': 'ウ', 'ェ': 'エ', 'ォ': 'オ'
, 'ッ': 'ツ', 'ャ': 'ヤ', 'ュ': 'ユ', 'ョ': 'ヨ', 'ヮ': 'ワ'
, 'ヵ': 'カ', 'ヶ': 'ケ'
}
var re = new RegExp(Object.keys(map).join('|'), 'g')
function replaceSuteganaByBigChar(str) {
return str.replace(re, function(match) { return map[match] })
}
return function(rubyElem) {
var rb = '', rt = ''
Array.prototype.forEach.call(rubyElem.childNodes, function(n) {
if (n.tagName === 'RB') rb += n.textContent
else if (n.tagName === 'RT') rt += n.textContent
else if (n.nodeType === Node.TEXT_NODE) rb += n.nodeValue
})
rb = rb.trim()
rt = replaceSuteganaByBigChar(rt.trim())
return rb && rt ? new Ruby(Line.newByStr(rb), Line.newByStr(rt)) : null
}
})()
Ruby.prototype = Object.create(Block.prototype)
Ruby.prototype.hasRubyNeighbor = function() {
if (!this.line) return false
var blocks = this.line.blocks, i = blocks.indexOf(this)
if (i === -1) return false
return (blocks[i - 1] && blocks[i - 1] instanceof Ruby)
|| (blocks[i + 1] && blocks[i + 1] instanceof Ruby)
}
Ruby.prototype.getLength = function() {
var rubyLen = this.val.getLength()
var baseLen = this.base.getLength()
var rubyLenCapacity = this.base.getLength() * 2
+ (this.hasRubyNeighbor() ? 0 : 2)
if (rubyLen <= rubyLenCapacity) return baseLen
return baseLen + Math.ceil((rubyLen - rubyLenCapacity) / 2)
}
Ruby.prototype.canCut = function() { return false }
Ruby.prototype.getRubyNodeTop = function() {
var h = rubyCharPxHeight()
var diff = this.base.getLength() * 2 - this.val.getLength()
if (diff >= 0) return diff * h / 2 + 'px'
var top = diff % 2 ? -h / 2 : -h
return (this.hasRubyNeighbor() ? top + h : top) + 'px'
}
Ruby.prototype.newRubyNode = function(doc) {
var result = doc.createElement('div')
result.style.position = 'absolute'
result.style.width = '1em'
result.style.lineHeight = rubyCharPxHeight() + 'px'
result.style.fontSize = '0.5em'
result.style.top = this.getRubyNodeTop()
result.style.left = '2em'
;[].slice.call(this.val.newNode(doc).childNodes).forEach(function(n) {
result.appendChild(n)
})
return result
}
Ruby.prototype.newBaseNode = function(doc) {
var result = doc.createDocumentFragment()
;[].slice.call(this.base.newNode(doc).childNodes).forEach(function(n) {
result.appendChild(n)
})
return result
}
Ruby.prototype.getNodePadding = function() {
var h = pixelCharSize.height()
var p = (this.getLength() - this.base.getLength()) * h / 2
return p + 'px 0'
}
Ruby.prototype.newNode = function(doc) {
doc = doc || document
var result = doc.createElement('span')
result.style.display = 'inline-block'
result.style.position = 'relative'
result.style.width = '1em'
result.style.padding = this.getNodePadding()
result.appendChild(this.newBaseNode(doc))
result.appendChild(this.newRubyNode(doc))
return result
}
Ruby.prototype.setLine = function(line) {
return new Ruby(this.base, this.val, line)
}
Ruby.prototype.isEmphasized = function() {
var bbs = this.base.blocks, rbs = this.val.blocks
if (!(bbs.length === 1 && rbs.length === 1)) return false
var bb = bbs[0], rb = rbs[0]
if (!(bb instanceof Text && rb instanceof Text)) return false
var bv = bb.val, rv = rb.val
return bv.length === rv.length && /^([・﹅])\1*$/.test(rv)
}
Ruby.prototype.splitIntoEmphasisDots = function() {
var bv = this.base.blocks[0].val
return Array.prototype.map.call(bv, function(c) {
return new Ruby(Line.newByStr(c), Line.newByStr('﹅'), this.line)
}, this)
}
return Ruby
})()
var Link = (function() {
function Link(href, text, clickHandler) {
this.href = href
this.text = text || '︻挿絵表示︼'
this.clickHandler = clickHandler
Object.freeze(this)
}
Link.prototype = Object.create(Block.prototype)
Link.prototype.getLength = function() { return this.text.length }
Link.prototype.canCut = function() { return false }
Link.prototype.newNode = function(doc) {
doc = doc || document
var a = doc.createElement('a')
a.href = this.href
a.textContent = this.text
if (this.clickHandler) {
a.addEventListener('click', this.clickHandler)
} else {
a.target = '_blank'
}
var s = a.style
s.display = 'inline-block'
s.textDecoration = 'none'
s.borderLeftWidth = 'thin'
s.borderLeftStyle = 'solid'
s.width = '1em'
s.color = GM_getColor()
var df = doc.createDocumentFragment()
df.appendChild(a)
return df
}
return Link
})()
var Line = (function() {
function addTextIfNotEmpty(blocks, str) {
if (!str) return
var t = new Text(str)
if (!t.isEmpty()) blocks.push(t)
}
function addCombinationCharIfNotEmpty(blocks, str) {
if (!str) return
let begin = 0
for (let r; r = CombinationChar.regexp.exec(str);) {
addTextIfNotEmpty(blocks, str.substring(begin, r.index))
const base = r[0].charAt(0)
const combination = r[0].charAt(1)
blocks.push(new CombinationChar(base, combination))
begin = CombinationChar.regexp.lastIndex
}
addTextIfNotEmpty(blocks, str.substring(begin))
}
function parseText(blocks, text) {
var re = /[\x21-\x7e][\x20-\x7e\n\t\r]*[\x21-\x7e]/g, begin = 0
for (var r; r = re.exec(text);) {
addCombinationCharIfNotEmpty(blocks, text.substring(begin, r.index))
blocks.push(new Ascii(r[0]))
begin = re.lastIndex
}
addCombinationCharIfNotEmpty(blocks, text.substring(begin))
}
function pushImgLinkInImageContainer(n, blocks) {
var img = n.querySelector('a img')
if (img) {
var r = /^illust_list_(\d+)(\-\d+)?$/.exec(img.id)
var href = '/member_illust.php?mode='
+ (r[2] ? 'manga_big' : 'medium')
+ '&illust_id=' + r[1]
+ (r[2] ? '&page=' + (Math.floor(r[2].slice(1)) - 1) : '')
blocks.push(new Link(href))
}
}
function pushImgLinkInNovelImage(n, blocks) {
var r = /novelimage-(\d+)(\-\d+)?/.exec(n.className)
var href = '/member_illust.php?mode='
+ (r[2] ? 'manga_big' : 'medium')
+ '&illust_id=' + r[1]
+ (r[2] ? '&page=' + (Math.floor(r[2].slice(1)) - 1) : '')
blocks.push(new Link(href))
}
function pushRuby(blocks, ruby) {
Array.prototype.push.apply(blocks, ruby.isEmphasized()
? ruby.splitIntoEmphasisDots()
: [ruby])
}
function Line(blocks) {
this.blocks = Object.freeze((blocks || []).map(function(b) {
return b.setLine(this)
}, this))
Object.freeze(this)
}
Line.EMPTY = new Line()
Line.parse = function(childNodes) {
var blocks = [], text = ''
Array.prototype.forEach.call(childNodes, function(n) {
if (n.tagName === 'RUBY') {
var ruby = Ruby.parse(n)
if (ruby) {
parseText(blocks, text)
text = ''
pushRuby(blocks, ruby)
} else {
text += n.textContent
}
} else if (n.tagName === 'SPAN' && n.classList.contains('.sesame')) {
var rubys = n.getElementsByTagName('ruby')
Array.prototype.forEach.call(rubys, function(n) {
var ruby = Ruby.parse(n)
if (ruby) {
parseText(blocks, text)
text = ''
pushRuby(blocks, ruby)
} else {
text += n.textContent
}
})
} else if (n.tagName === 'A') {
parseText(blocks, text)
text = ''
blocks.push(new Link(n.getAttribute('href')))
} else if (n.tagName === 'IMG') {
parseText(blocks, text)
text = ''
blocks.push(new Link(n.getAttribute('src')))
} else if (n.tagName === 'DIV'
&& n.classList.contains('image_container')) {
parseText(blocks, text)
text = ''
pushImgLinkInImageContainer(n, blocks)
} else if (n.tagName === 'DIV'
&& n.classList.contains('caption')) {
// do nothing
} else if (n.tagName === 'SPAN'
&& n.classList.contains('novelimage')) {
parseText(blocks, text)
text = ''
pushImgLinkInNovelImage(n, blocks)
} else {
text += n.textContent
}
})
parseText(blocks, text)
return blocks.length ? new Line(blocks) : Line.EMPTY
}
Line.newByStr = function(str) { return Line.parse([newTextNode(str)]) }
Line.prototype.getLength = function() {
return this.blocks.reduce(function(pre, block) {
return pre + block.getLength()
}, 0)
}
Line.prototype.cut = (function() {
function cut(block, index, before, after) {
var o = block.cut(index)
before.push(o.before)
after.unshift(o.after)
}
function endsWithBadChar(blocks) {
if (!blocks.length) return false
var last = blocks[blocks.length - 1]
return last.endsWithBadChar()
&& !(last.getLength() === 1 && blocks.length === 1)
}
function startsWithBadChar(after) {
return after.length && after[0].startsWithBadChar()
}
function pollLastChar(before, after) {
var b = before.pop()
if (b.getLength() === 1) after.unshift(b)
else cut(b, b.getLength() - 1, before, after)
}
function pollTopChar(after, before) {
var b = after.shift()
if (b.getLength() === 1) before.push(b)
else cut(b, 1, before, after)
}
function trimHead(after) {
if (!after.length) return
var b = after.shift().trimHead()
if (!b.isEmpty()) after.unshift(b)
}
var MAX_POLL_NUM = 3
return function(index) {
var after = this.blocks.slice(), before = []
for (var b, begin = 0; b = after.shift(); begin += b.getLength()) {
var end = begin + b.getLength()
if (end === index) {
before.push(b)
break
}
if (begin <= index && index < end) {
if (b.canCut(index - begin)) cut(b, index - begin, before, after)
else if (begin === 0) before.push(b)
else after.unshift(b)
break
}
before.push(b)
}
for (var i = 0; i < MAX_POLL_NUM && endsWithBadChar(before); i++) {
pollLastChar(before, after)
}
for (i = 0; i < MAX_POLL_NUM && startsWithBadChar(after); i++) {
pollTopChar(after, before)
}
trimHead(after)
return after.length
? { before: new Line(before), after: new Line(after) }
: { before: this, after: null }
}
})()
Line.prototype.split = function(lineCharNum) {
var result = [], line = this
do {
var o = line.cut(lineCharNum)
result.push(o.before)
} while (line = o.after)
return result
}
Line.prototype.isEmpty = function() { return !this.blocks.length }
Line.prototype.newNode = function(doc) {
doc = doc || document
var result = doc.createElement('div')
result.style.cssFloat = 'right'
result.style.width = '1em'
result.style.wordWrap = 'break-word'
if (this.isEmpty()) {
result.appendChild(doc.createTextNode('\u3000'))
} else {
this.blocks.forEach(function(b) { result.appendChild(b.newNode(doc)) })
}
return result
}
return Line
})()
var Lines =
{ trim: function(lines) {
var begin = 0, end = lines.length
while (lines[begin] && lines[begin].isEmpty()) begin++
while (lines[end - 1] && lines[end - 1].isEmpty()) end--
return lines.slice(begin, end)
}
, parse: (function() {
var pushIfNotEmpty = function(sub, result) {
var l = Line.parse(sub)
if (!l.isEmpty()) result.push(l)
}
var paragraphParser =
{ parseSub: pushIfNotEmpty
, parseNode: function(p, result) {
result.push(Line.parse(p.childNodes))
}
}
var parser =
{ 'BR':
{ parseSub: function(sub, result) { result.push(Line.parse(sub)) }
, parseNode: function() {}
}
, 'P': paragraphParser
, 'CENTER': paragraphParser
, 'H4':
{ parseSub: pushIfNotEmpty
, parseNode: function(h4, result) {
if (result.length && !result[result.length - 1].isEmpty()) {
result.push(Line.EMPTY)
}
result.push(Line.parse(h4.childNodes))
result.push(Line.EMPTY)
}
}
}
return function(childNodes) {
var result = [], sub = []
;[].forEach.call(childNodes, function(n) {
if (Object.keys(parser).indexOf(n.tagName) >= 0) {
var p = parser[n.tagName]
p.parseSub(sub, result)
p.parseNode(n, result)
sub = []
} else {
sub.push(n)
}
})
result.push(Line.parse(sub))
return Lines.trim(result)
}
})()
, split: function(lines, lineCharNum) {
var result = []
lines.forEach(function(line) {
Array.prototype.push.apply(result, line.split(lineCharNum))
})
return result
}
}
function Episode(obj) {
this.novelTitle = obj.novelTitle || null
this.chapterTitle = obj.chapterTitle || null
this.author = obj.author || null
this.title = obj.title || null
this.text = Object.freeze((obj.text || []).slice())
this.preface = Object.freeze((obj.preface || []).slice())
this.postscript = Object.freeze((obj.postscript || []).slice())
this.nextURL = obj.nextURL || ''
this.prevURL = obj.prevURL || ''
this.coverLinkClickHandler = obj.coverLinkClickHandler || ''
this.position = obj.position || null
Object.freeze(this)
}
function Site() {
this.bookViewShown = false
}
Site.prototype.newButton = function() {
var result = document.createElement('button')
result.textContent = '縦書き'
result.style.padding = '0px 6px'
result.addEventListener('click'
, this.showBookView.bind(this, function() {}))
result.addEventListener('click', function() { result.blur() })
return result
}
Site.prototype.showBookView = function(done) {
var iframe = document.createElement('iframe')
iframe.style.position = 'fixed'
iframe.style.top = '0px'
iframe.style.left = '0px'
iframe.style.width = '100%'
iframe.style.height = '100%'
iframe.style.zIndex = String(Math.pow(2, 31) - 1)
iframe.style.borderWidth = '0px'
iframe.addEventListener('load', function() {
pixelCharSize.doc = iframe.contentDocument
var book = new Book(this.parse())
var view = new BookView(book, iframe.contentDocument)
book.setView(view)
document.documentElement.style.overflowY = 'hidden'
view.onhide = function() {
document.documentElement.style.overflowY = ''
iframe.parentNode.removeChild(iframe)
this.bookViewShown = false
document.documentElement.focus()
}.bind(this)
view.show()
setTimeout(iframe.focus.bind(iframe), 0)
if (done) done(iframe)
}.bind(this))
this.bookViewShown = true
document.body.appendChild(iframe)
}
var Narou = (function() {
function isShortNovel() {
return Boolean(document.querySelector('.p-novel .p-novel__author'))
}
function getParser() {
return isShortNovel() ? new ShortParser() : new Parser()
}
const textSelector = '.p-novel__text:not(.p-novel__text--preface, .p-novel__text--afterword)'
function Parser() {}
Parser.prototype.novelTitleSelector = '.c-announce:not(.c-announce--note) a[href^="/n"]'
Parser.prototype.titleSelector = '.p-novel__title.p-novel__title--rensai'
Parser.prototype.getTitle = function() {
var n = document.querySelector(this.titleSelector)
return n && n.firstChild ? Line.parse([n.firstChild]) : null
}
Parser.prototype.getChapterTitle = function() {
return getLine('.c-announce:not(.c-announce--note) span')
}
Parser.prototype.getAuthor = function() {
var e = document.querySelector('.c-announce:not(.c-announce--note) a[href^="https://mypage.syosetu.com/"]')
return e
? Line.newByStr(e.textContent)
: null
}
Parser.prototype.parse = function() {
return new Episode({
novelTitle: getLine(this.novelTitleSelector)
, chapterTitle: this.getChapterTitle()
, author: this.getAuthor()
, title: this.getTitle()
, text: getLines(textSelector)
, preface: getLines('.p-novel__text--preface')
, postscript: getLines('.p-novel__text--afterword')
, nextURL: getHRef('.c-pager__item--next')
, prevURL: getHRef('.c-pager__item--before')
, position: getLine('.p-novel__number')
})
}
function ShortParser() {}
ShortParser.prototype = Object.create(Parser.prototype)
ShortParser.prototype.novelTitleSelector = '.p-novel__series-link'
ShortParser.prototype.titleSelector = '.p-novel__title'
ShortParser.prototype.getChapterTitle = function() { return null }
ShortParser.prototype.getAuthor = function() {
return getLine('.p-novel__author a')
}
function Narou() {}
Narou.prototype = Object.create(Site.prototype)
Narou.prototype.is = function(location) {
var h = location.hostname
return (h === 'ncode.syosetu.com' || h === 'novel18.syosetu.com')
&& /^\/n\d+[a-z]+/.test(location.pathname)
&& Boolean(document.querySelector(textSelector))
}
Narou.prototype.parse = function() { return getParser().parse() }
Narou.prototype.addButton = function() {
document.querySelector('.p-novel__title')?.after(this.newButton())
}
return Narou
})()
var Arcadia = (function() {
function getAuthor() {
var n = document.querySelector('.bgc > table td:first-child tt')
var s = /^Name: ([^◆]+)/.exec(n.textContent)[1]
return Line.parse([newTextNode(s)])
}
function getTitleFontElem() { return document.querySelector('.bgb font') }
function Arcadia() {}
Arcadia.prototype = Object.create(Site.prototype)
Arcadia.prototype.is = function(location) {
return location.hostname === 'www.mai-net.net'
&& location.pathname === '/bbs/sst/sst.php'
&& hasParam(location, 'act', 'dump')
&& hasParam(location, 'all', /[0-9]+/)
}
Arcadia.prototype.parse = function() {
var navSelector = '.bgc > table td[align="right"] a'
return new Episode({ author: getAuthor()
, title: Line.parse(getTitleFontElem().childNodes)
, text: getLines('.bgc blockquote div')
, nextURL: getHRef(navSelector, '次を表示する')
, prevURL: getHRef(navSelector, '前を表示する')
})
}
Arcadia.prototype.addButton = function() {
getTitleFontElem().parentNode.appendChild(this.newButton())
}
return Arcadia
})()
var Hameln = (function() {
function isEpisodeListPage() {
return Boolean(document.querySelector('#maind > .ss > table'))
}
var titleSelector = '#maind .ss > span[style="font-size:120%"]'
function getAuthor() {
var e = document.querySelector('#maind .ss > p')
if (!e) return null
for (var n = e.firstChild; n; n = n.nextSibling) {
if (n.nodeType === Node.TEXT_NODE && n.nodeValue.indexOf('作:') >= 0) {
break
}
}
if (!n) return null
if (n.nextSibling && n.nextSibling.tagName === 'A') {
return Line.newByStr(n.nextSibling.textContent)
}
var r = /作:(.+)/.exec(n.nodeValue)
return Line.newByStr(r ? r[1] : '')
}
var novelTitleAnchorSelector = '#maind .ss > p > span > a'
function Hameln() {}
Hameln.prototype = Object.create(Site.prototype)
Hameln.prototype.is = function(location) {
return location.hostname === 'syosetu.org'
&& /^\/novel\/\d+\/(\d+\.html)?/.test(location.pathname)
&& !isEpisodeListPage()
}
Hameln.prototype.parse = function() {
var navSelector = '#nextpage a'
return new Episode({ novelTitle: getLine(novelTitleAnchorSelector)
, author: getAuthor()
, title: getLine(titleSelector)
, text: getLines('#honbun')
, preface: getLines('#maegaki')
, postscript: getLines('#atogaki')
, nextURL: getHRef(navSelector, '次の話 >>')
, prevURL: getHRef(navSelector, '<< 前の話')
, position: getLine('#maind div[style="text-align:right;font-size:80%"]')
})
}
Hameln.prototype.addButton = function() {
var a = document.querySelector(novelTitleAnchorSelector)
var e = a.parentNode
e.parentNode.insertBefore(this.newButton(), e.nextSibling)
}
return Hameln
})()
var Pixiv = (function() {
var titleSelector = '.work-info .title'
function getTitle() {
var e = document.querySelector(titleSelector)
return e && e.firstChild ? Line.parse([e.firstChild]) : null
}
function newBR() { return document.createElement('br') }
function isNovelTextParsed() {
return !Boolean(document.querySelector('#novel_text'))
}
function getTextLinesByNovelArticles() {
var nodes = document.querySelectorAll('.novel-page')
var children = []
Array.prototype.forEach.call(nodes, function(node) {
Array.prototype.push.apply(children, node.childNodes)
children.push(newBR(), newBR())
})
return Lines.parse(children)
}
function getTextLinesByPlainText() {
var text = document.querySelector('#novel_text')
, lines = []
text.value.split('\n').forEach(function(line) {
var r = null
if (/^\s*\[newpage\]\s*$/.test(line)) {
lines.push(Line.EMPTY)
} else if (r = /^\s*\[chapter:(.+?)\]\s*$/.exec(line)) {
lines.push(Line.EMPTY)
lines.push(Line.newByStr(r[1]))
lines.push(Line.EMPTY)
} else if (r = /^\s*\[pixivimage:(\d+)(\-\d+)?\]\s*$/.exec(line)) {
var a = document.createElement('a')
if (r[2]) {
a.href = '/member_illust.php?mode=manga_big&illust_id=' + r[1]
+ '&page=' + (Math.floor(r[2].slice(1)) - 1)
} else {
a.href = '/member_illust.php?mode=medium&illust_id=' + r[1]
}
lines.push(Line.parse([a]))
} else {
lines.push(Line.newByStr(line))
}
})
return Lines.trim(lines)
}
function getTextLines() {
if (isNovelTextParsed()) return getTextLinesByNovelArticles()
return getTextLinesByPlainText()
}
function getCoverLinkClickHandler() {
var a = document.querySelector('.area_inside > a')
if (!a) return null
var img = a.querySelector('img')
if (!(img && img.src)) return ''
return /^https?:\/\/source\.pixiv\.net/.test(img.src)
? null
: a.click.bind(a)
}
function Pixiv() {}
Pixiv.prototype = Object.create(Site.prototype)
Pixiv.prototype.is = function(location) {
return location.hostname === 'www.pixiv.net'
&& location.pathname === '/novel/show.php'
&& hasParam(location, 'id', /\d+/)
}
Pixiv.prototype.parse = function() {
return new Episode({ title: getTitle()
, author: getLine('.user-name')
, text: getTextLines()
, nextURL: getHRef('.before a')
, prevURL: getHRef('.after a')
, preface: getLines('.work-info .caption')
, coverLinkClickHandler: getCoverLinkClickHandler()
})
}
Pixiv.prototype.addButton = function() {
document.querySelector(titleSelector).appendChild(this.newButton())
}
return Pixiv
})()
var Akatsuki = (function() {
function selector(suffix) {
return '#contents-inner2 > div.box.story > div.box.story ' + suffix
}
function getH2() {
var h2 = document.querySelector(selector('h2'))
var s = ''
for (var n = h2.firstChild; n !== h2.lastChild; n = n.nextSibling) {
if (n.tagName === 'BR') s += ' '
else s += n.textContent
}
return { title: Line.parse([h2.lastChild])
, chapterTitle: s ? Line.newByStr(s.trim()) : null
}
}
function href(selector) {
var a = document.querySelector(selector)
return a ? a.href : ''
}
function getNovelTitle() {
var h1 = document.querySelector(selector('h1'))
return Line.newByStr(h1.firstChild.textContent)
}
function getUnbalancedContent(nodeList) {
if (nodeList[0].previousSibling.textContent === '前書き') {
return { preface: Lines.parse(nodeList[0].childNodes)
, text: Lines.parse(nodeList[1].childNodes)
, postscript: null
}
}
return { preface: null
, text: Lines.parse(nodeList[0].childNodes)
, postscript: Lines.parse(nodeList[1].childNodes)
}
}
function getContent() {
var nl = document.querySelectorAll(selector('.body-novel'))
switch (nl.length) {
case 1: return { text: Lines.parse(nl[0].childNodes)
, preface: null
, postscript: null }
case 2: return getUnbalancedContent(nl)
case 3: return { text: Lines.parse(nl[1].childNodes)
, preface: Lines.parse(nl[0].childNodes)
, postscript: Lines.parse(nl[2].childNodes)
}
default: throw new Error(nl.length)
}
}
function Akatsuki() {}
Akatsuki.prototype = Object.create(Site.prototype)
Akatsuki.prototype.is = function(location) {
return location.hostname === 'www.akatsuki-novels.com'
&& /\/stories\/view\/\d+\/novel_id~\d+/.test(location.pathname)
}
Akatsuki.prototype.parse = function() {
var h2 = getH2(), c = getContent()
return new Episode({ novelTitle: getNovelTitle()
, chapterTitle: h2.chapterTitle
, title: h2.title
, author: getLine(selector('a[href^="/users/view/"]'))
, text: c.text
, preface: c.preface
, postscript: c.postscript
, nextURL: href(selector('.paging_for_view .next a'))
, prevURL: href(selector('.paging_for_view .prev a'))
, position: getLine(selector('div[style="width:100%;text-align:right;"]'))
})
}
Akatsuki.prototype.addButton = function() {
document.querySelector(selector('h1')).appendChild(this.newButton())
}
return Akatsuki
})()
var Book = (function() {
function newPageLines(lines, lineNum) {
var result = []
for (var i = 0, n = lines.length; i < n; i += lineNum) {
result.push(lines.slice(i, i + lineNum))
}
return result
}
function newCoverLine(clickHandler) {
if (clickHandler) {
return new Line([new Link('javascript:void(0)', '︻表紙︼', clickHandler)])
}
return null
}
function joinTopPageItems(episode) {
return [ episode.novelTitle
, episode.chapterTitle
, episode.title
, episode.author
, episode.position
, newCoverLine(episode.coverLinkClickHandler)
].filter(function(line) { return line !== null })
}
function RangedLine(begin, pageLines) {
this.begin = begin
this.end = begin + pageLines.length
this.pageLines = Object.freeze(pageLines.slice())
Object.freeze(this)
}
RangedLine.prototype.contain = function(pageIndex) {
return this.begin <= pageIndex && pageIndex < this.end
}
RangedLine.prototype.getLines = function(pageIndex) {
return this.pageLines[pageIndex - this.begin]
}
function Book(episode) {
this.episode = episode
this.topLines = joinTopPageItems(episode)
this.prefaceLines = episode.preface.length
? [ Line.newByStr('まえがき')
, Line.EMPTY
].concat(episode.preface)
: []
this.textLines = episode.postscript.length === 0 && episode.nextURL
? episode.text.concat([
Line.EMPTY,
new Line([this.createNextLink()]),
])
: episode.text
this.postscriptLines = this.createPostscriptLines()
this.lineCharNum = GM_getLineCharNum()
this.lineNum = GM_getLineNum()
this.rangedLines = this.newRangedLines()
this.pageNum = this.rangedLines[this.rangedLines.length - 1].end
this.pageIndex = 0
this.view = null
Object.seal(this)
}
Book.prototype.createNextLink = function() {
const clickHandler = e => {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)
return
e.preventDefault()
this.loadNextEpisode()
}
return new Link(this.episode.nextURL, '次の話', clickHandler)
}
Book.prototype.createPostscriptLines = function() {
if (this.episode.postscript.length === 0)
return []
const lines = [
Line.newByStr('あとがき'),
Line.EMPTY,
].concat(this.episode.postscript)
return this.episode.nextURL
? lines.concat([
Line.EMPTY,
new Line([this.createNextLink()]),
])
: lines
}
Book.prototype.setView = function(view) {
this.view = view
view.update()
}
Book.prototype.newRangedLine = function(begin, lines) {
return new RangedLine(begin
, newPageLines(Lines.split(lines, this.lineCharNum)
, this.lineNum))
}
Book.prototype.newRangedLines = function() {
var top = this.newRangedLine(0, this.topLines)
var preface = this.newRangedLine(top.end, this.prefaceLines)
var text = this.newRangedLine(preface.end, this.textLines)
var postscript = this.newRangedLine(text.end, this.postscriptLines)
return [top, preface, text, postscript]
}
Book.prototype.update = function() {
this.rangedLines = this.newRangedLines()
this.pageNum = this.rangedLines[this.rangedLines.length - 1].end
this.pageIndex = Math.min(this.pageIndex, this.pageNum - 1)
this.view.update()
}
Book.prototype.setLineCharNum = function(lineCharNum) {
var n = Math.max(lineCharNum, 1)
if (this.lineCharNum === n) return
this.lineCharNum = n
this.update()
}
Book.prototype.setLineNum = function(lineNum) {
var n = Math.max(lineNum, 1)
if (this.lineNum === n) return
this.lineNum = n
this.update()
}
Book.prototype.getRangedLine = function() {
var rls = this.rangedLines
for (var i = 0, n = rls.length; i < n; i++) {
var rl = rls[i]
if (rl.contain(this.pageIndex)) return rl
}
throw new Error(this.pageIndex)
}
Book.prototype.setPageIndex = function(pageIndex) {
var i = Math.min(Math.max(pageIndex, 0), this.pageNum - 1)
if (this.pageIndex === i) return
this.pageIndex = i
this.view.update()
}
Book.prototype.turnPage = function() {
this.setPageIndex(this.pageIndex + 1)
}
Book.prototype.returnPage = function() {
this.setPageIndex(this.pageIndex - 1)
}
Book.prototype.begin = function() { this.setPageIndex(0) }
Book.prototype.end = function() { this.setPageIndex(this.pageNum - 1) }
Book.prototype.loadNextEpisode = function() {
var u = this.episode.nextURL
if (!u) return
_setValue('auto', true)
window.location.assign(u)
}
Book.prototype.loadPrevEpisode = function() {
var u = this.episode.prevURL
if (!u) return
_setValue('auto', true)
window.location.assign(u)
}
Book.prototype.isTop = function() { return this.pageIndex === 0 }
Book.prototype.isLast = function() {
return this.pageIndex === this.pageNum - 1
}
Book.prototype.getLines = function() {
return this.getRangedLine().getLines(this.pageIndex)
}
return Book
})()
var Key = { ESCAPE: 27
, SPACE: 32
, END: 35
, HOME: 36
, LEFT: 37
, UP: 38
, RIGHT: 39
, DOWN: 40
, E: 69
, H: 72
, N: 78
, P: 80
, V: 86
, SLASH: 191
}
function KeyMap() {
this.entries = []
this.keyDownListener = this.keyDowned.bind(this)
Object.freeze(this)
}
KeyMap.prototype.add = function(key, action) {
var entry = { action: action }
if (typeof key === 'object') {
entry.keyCode = key.keyCode
entry.shift = key.shift
} else {
entry.keyCode = key
entry.shift = false
}
this.entries.push(entry)
}
KeyMap.prototype.keyDowned = function(keyEvent) {
if (['SELECT', 'INPUT', 'TEXTAREA'].indexOf(keyEvent.target.tagName) >= 0) return
this.entries.filter(function(entry) {
return !keyEvent.altKey
&& !keyEvent.ctrlKey
&& !keyEvent.metaKey
&& keyEvent.shiftKey === entry.shift
&& keyEvent.keyCode === entry.keyCode
}).forEach(function(entry) {
keyEvent.stopImmediatePropagation()
entry.action()
})
}
var BookView = (function() {
function newMessageBox(doc) {
const text = doc.createElement('div')
text.style.padding = '8px'
text.style.border = 'solid black thin'
text.style.color = 'black'
text.style.backgroundColor = 'white'
text.textContent = '最後のページです'
const result = doc.createElement('div')
result.style.position = 'absolute'
result.style.left = '0px'
result.style.top = '0px'
result.style.width = '100%'
result.style.backgroundColor = GM_getBackgroundColor()
result.style.display = 'none'
result.style.justifyContent = 'center'
result.appendChild(text)
return result
}
function newLineBox(doc) {
var result = (doc || document).createElement('div')
result.style.marginTop = GM_getMarginTop()
result.style.lineHeight = GM_getCharHeight()
result.style.fontSize = GM_getFontSize()
result.style.fontWeight = GM_getFontWeight()
result.style.display = 'inline-block'
result.style.fontFamily = GM_getFontFamily()
return result
}
function newRoot(lineBox, messageBox) {
var result = lineBox.ownerDocument.createElement('div')
result.style.position = 'fixed'
result.style.top = '0px'
result.style.left = '0px'
result.style.width = '100%'
result.style.height = '100%'
result.style.backgroundColor = GM_getBackgroundColor()
result.style.color = GM_getColor()
result.style.textAlign = 'center'
result.appendChild(lineBox)
result.appendChild(messageBox)
return result
}
function newNumElem(doc) {
var result = (doc || document).createElement('span')
result.textContent = '0'
result.style.display = 'inline-block'
result.style.width = '3em'
return result
}
function newPageIndexElem(doc) {
var result = newNumElem(doc)
result.style.textAlign = 'right'
return result
}
function newPageNumElem(doc) {
var result = newNumElem(doc)
result.style.textAlign = 'left'
return result
}
var newButton = (function() {
var preventFocus = function(e) { e.target.blur() }
return function(textContent, clickListener, title, doc) {
var result = (doc || document).createElement('button')
result.textContent = textContent
result.addEventListener('click', clickListener, false)
result.addEventListener('focus', preventFocus, false)
if (title) result.title = title
result.style.width = '3em'
result.style.padding = '0px'
return result
}
})()
function setSpaceBetweenLines(lineBox, spaceBetweenLines) {
var sp = spaceBetweenLines || GM_getSpaceBetweenLines()
var cn = lineBox.childNodes
for (var i = 0, n = cn.length - 1; i < n; i++) {
cn[i].style.marginLeft = sp
}
}
function BookView(book, doc) {
this.book = book
this.doc = doc || document
this.lineBox = newLineBox(this.doc)
this.messageBox = newMessageBox(this.doc)
this.root = newRoot(this.lineBox, this.messageBox)
this.pageIndexElem = newPageIndexElem(this.doc)
this.pageNumElem = newPageNumElem(this.doc)
this.turnPageButton = newButton('<'
, book.turnPage.bind(book)
, '次のページ'
, this.doc)
this.returnPageButton = newButton('>'
, book.returnPage.bind(book)
, '前のページ'
, this.doc)
this.endButton = newButton('|<'
, book.end.bind(book)
, '最後のページ'
, this.doc)
this.beginButton = newButton('>|'
, book.begin.bind(book)
, '最初のページ'
, this.doc)
this.loadNextEpisodeButton = newButton('<<'
, book.loadNextEpisode.bind(book)
, '次の話'
, this.doc)
this.loadPrevEpisodeButton = newButton('>>'
, book.loadPrevEpisode.bind(book)
, '前の話'
, this.doc)
this.listShortcutKeysButton = newButton('?'
, this.listShortcutKeys.bind(this)
, 'ショートカットキー一覧'
, this.doc)
this.showConfigDialogButton = newButton('設定'
, this.showConfigDialog.bind(this)
, this.doc)
this.toolbar = this.newToolbar()
this.keyDownListener = this.newKeyDownListener()
this.wheelListener = this.wheeled.bind(this)
this.mouseMoveListener = this.mouseMoved.bind(this)
this.shortcutKeyList = null
this.configDialog = null
this.onhide = null
this.timeoutId = null
Object.seal(this)
}
BookView.prototype.newNavigator = function() {
var result = this.doc.createElement('span')
result.appendChild(this.loadNextEpisodeButton)
result.appendChild(this.endButton)
result.appendChild(this.turnPageButton)
result.appendChild(this.pageIndexElem)
result.appendChild(this.doc.createTextNode('/'))
result.appendChild(this.pageNumElem)
result.appendChild(this.returnPageButton)
result.appendChild(this.beginButton)
result.appendChild(this.loadPrevEpisodeButton)
return result
}
BookView.prototype.newRightBottomBox = function() {
var result = this.doc.createElement('div')
result.style.position = 'absolute'
result.style.right = '0px'
result.style.bottom = '0px'
result.appendChild(this.listShortcutKeysButton)
result.appendChild(this.showConfigDialogButton)
result.appendChild(newButton('X'
, this.hide.bind(this)
, '閉じる'
, this.doc))
return result
}
BookView.prototype.newToolbar = function() {
var result = this.doc.createElement('div')
result.style.position = 'absolute'
result.style.left = '0px'
result.style.bottom = '0px'
result.style.width = '100%'
result.style.fontFamily = Default.GOTHIC_FONT_FAMILY
result.style.fontSize = '16px'
result.style.backgroundColor = GM_getBackgroundColor()
result.style.display = GM_isToolbarVisible() ? '' : 'none'
result.appendChild(this.newNavigator())
result.appendChild(this.newRightBottomBox())
this.root.appendChild(result)
return result
}
BookView.prototype.escKeyDowned = function() {
if (this.shortcutKeyList) this.shortcutKeyList.hide()
else if (this.configDialog) this.configDialog.hide()
else this.hide()
}
BookView.prototype.turnPage = function() {
if (this.book.isLast()) {
if (this.timeoutId)
return
this.messageBox.style.display = 'flex'
this.timeoutId = setTimeout(() => {
this.messageBox.style.display = 'none'
this.timeoutId = null
}, 2000)
} else {
this.book.turnPage()
}
}
BookView.prototype.newKeyDownListener = function() {
var b = this.book
var km = new KeyMap()
km.add(Key.SPACE, this.turnPage.bind(this))
km.add(Key.LEFT, this.turnPage.bind(this))
km.add(Key.RIGHT, b.returnPage.bind(b))
km.add({ keyCode: Key.SPACE, shift: true }, b.returnPage.bind(b))
km.add(Key.H, b.begin.bind(b))
km.add(Key.HOME, b.begin.bind(b))
km.add(Key.E, b.end.bind(b))
km.add(Key.END, b.end.bind(b))
km.add(Key.N, b.loadNextEpisode.bind(b))
km.add(Key.DOWN, b.loadNextEpisode.bind(b))
km.add(Key.P, b.loadPrevEpisode.bind(b))
km.add(Key.UP, b.loadPrevEpisode.bind(b))
km.add(Key.ESCAPE, this.escKeyDowned.bind(this))
km.add(Key.V, this.escKeyDowned.bind(this))
km.add({ keyCode: Key.SLASH, shift: true }
, this.listShortcutKeys.bind(this))
return km.keyDownListener
}
BookView.prototype.addListeners = function() {
this.doc.addEventListener('keydown', this.keyDownListener, true)
this.doc.addEventListener('mousemove', this.mouseMoveListener, false)
this.doc.addEventListener('wheel', this.wheelListener, false)
}
BookView.prototype.removeListeners = function() {
this.doc.removeEventListener('keydown', this.keyDownListener, true)
this.doc.removeEventListener('mousemove', this.mouseMoveListener, false)
this.doc.removeEventListener('wheel', this.wheelListener, false)
}
BookView.prototype.show = function() {
this.doc.body.appendChild(this.root)
this.addListeners()
}
BookView.prototype.hide = function() {
this.removeListeners()
this.root.parentNode.removeChild(this.root)
if (this.onhide) this.onhide()
}
BookView.prototype.clear = function() {
var b = this.lineBox
while (b.hasChildNodes()) b.removeChild(b.firstChild)
}
BookView.prototype.padLines = function() {
var paddingLineNum = this.book.lineNum - this.book.getLines().length
for (var i = 0; i < paddingLineNum; i++) {
this.lineBox.appendChild(Line.EMPTY.newNode(this.doc))
}
}
BookView.prototype.writeLines = function() {
this.book.getLines().forEach(function(line) {
this.lineBox.appendChild(line.newNode(this.doc))
}, this)
}
BookView.prototype.updateButtonDisabled = function() {
var b = this.book
this.loadNextEpisodeButton.disabled = !b.episode.nextURL
this.loadPrevEpisodeButton.disabled = !b.episode.prevURL
this.turnPageButton.disabled = b.isLast()
this.endButton.disabled = b.isLast()
this.returnPageButton.disabled = b.isTop()
this.beginButton.disabled = b.isTop()
}
BookView.prototype.updatePageIndexAndPageNum = function() {
this.pageIndexElem.textContent = this.book.pageIndex + 1
this.pageNumElem.textContent = this.book.pageNum
}
BookView.prototype.update = function() {
this.clear()
this.writeLines()
if (!this.book.isTop()) this.padLines()
setSpaceBetweenLines(this.lineBox)
this.updateButtonDisabled()
this.updatePageIndexAndPageNum()
}
BookView.prototype.wheeled = (function() {
function convert(e) {
if (e.deltaY) return { down: e.deltaY > 0, up: e.deltaY < 0 }
return e.wheelDelta
? { down: e.wheelDelta < 0, up: e.wheelDelta > 0 }
: null
}
return function(e) {
var wheel = convert(e)
if (!wheel) return
if (wheel.down) this.turnPage()
else if (wheel.up) this.book.returnPage()
}
})()
BookView.prototype.showConfigDialog = function() {
if (this.configDialog) return
this.showConfigDialogButton.disabled = true
this.configDialog = new ConfigDialog(this.book, this, this.doc)
this.configDialog.onhide = function() {
this.configDialog = null
this.showConfigDialogButton.disabled = false
}.bind(this)
this.configDialog.show(this.root)
}
BookView.prototype.listShortcutKeys = function() {
if (this.shortcutKeyList) return
this.listShortcutKeysButton.disabled = true
this.shortcutKeyList = new ShortcutKeyList(this.doc)
this.shortcutKeyList.onhide = function() {
this.shortcutKeyList = null
this.listShortcutKeysButton.disabled = false
}.bind(this)
this.shortcutKeyList.show(this.root)
}
BookView.prototype.setFontType = function(fontType) {
this.lineBox.style.fontFamily = GM_getFontFamily(fontType)
}
BookView.prototype.setFontFamily = function(fontFamily) {
this.lineBox.style.fontFamily = fontFamily
}
BookView.prototype.setFontSize = function(fontSize) {
this.lineBox.style.fontSize = fontSize
}
BookView.prototype.setCharHeight = function(charHeight) {
this.lineBox.style.lineHeight = charHeight
}
BookView.prototype.setMarginTop = function(marginTop) {
this.lineBox.style.marginTop = marginTop
}
BookView.prototype.setSpaceBetweenLines = function(spaceBetweenLines) {
setSpaceBetweenLines(this.lineBox, spaceBetweenLines)
}
BookView.prototype.setColor = function(color) {
this.root.style.color = color
}
BookView.prototype.setBackgroundColor = function(backgroundColor) {
this.root.style.backgroundColor = backgroundColor
this.toolbar.style.backgroundColor = backgroundColor
}
BookView.prototype.setToolbarVisible = function(visible) {
this.toolbar.style.display = visible ? '' : 'none'
}
BookView.prototype.setFontWeight = function(fontWeight) {
this.lineBox.style.fontWeight = fontWeight
}
BookView.prototype.mouseMoved = (function() {
var toolbarVisibleToggleHeight = 50
function onToolbarVisibleArea(clientY) {
return clientY >= window.innerHeight - toolbarVisibleToggleHeight
}
return function(mouseEvent) {
if (GM_isToolbarVisible()) return
this.toolbar.style.display = onToolbarVisibleArea(mouseEvent.clientY)
? ''
: 'none'
}
})()
return BookView
})()
function Dialog(doc) {
this.doc = doc || document
this.root = this.newRoot()
this.onhide = null
}
Dialog.prototype.newRoot = function() {
var result = this.doc.createElement('div')
result.style.position = 'absolute'
result.style.fontFamily = Default.GOTHIC_FONT_FAMILY
result.style.fontSize = '16px'
result.style.backgroundColor = 'white'
result.style.color = 'black'
result.style.padding = '0px 5px 5px'
result.appendChild(this.newTopBar())
return result
}
Dialog.prototype.newTopBar = function() {
var result = this.doc.createElement('div')
result.style.textAlign = 'right'
result.appendChild(this.newCloseButton())
return result
}
Dialog.prototype.newCloseButton = function() {
var result = this.doc.createElement('button')
result.textContent = 'X'
result.style.width = '3em'
result.addEventListener('click', this.hide.bind(this), false)
return result
}
Dialog.prototype.center = function() {
var v = this.doc.defaultView || document.defaultView
var cs = v.getComputedStyle(this.root)
var h = parseInt(cs.height, 10)
var w = parseInt(cs.width, 10)
this.root.style.top = (v.innerHeight - h) / 2 + 'px'
this.root.style.left = (v.innerWidth - w) / 2 + 'px'
}
Dialog.prototype.show = function(owner) {
owner.appendChild(this.root)
this.center()
}
Dialog.prototype.hide = function() {
this.root.parentNode.removeChild(this.root)
if (this.onhide) this.onhide()
}
var ShortcutKeyList = (function() {
function newList(doc) {
var result = (doc || document).createElement('table')
result.style.textAlign = 'left'
result.style.borderCollapse = 'collapse'
result.style.color = 'black'
;[ ['←, スペース', '次のページ']
, ['→, Shift+スペース', '前のページ']
, ['End, e', '最後のページ']
, ['Home, h', '最初のページ']
, ['↓, n', '次の話']
, ['↑, p', '前の話']
, ['?', 'ショートカットキー一覧']
, ['v, Esc', '閉じる']
, ['v', '縦書き表示']
].forEach(function(e) {
var row = result.insertRow(-1)
insertCell(row, e[0])
insertCell(row, e[1])
})
return result
}
function ShortcutKeyList(doc) {
Dialog.call(this, doc)
this.root.appendChild(newList(doc))
Object.seal(this)
}
ShortcutKeyList.prototype = Object.create(Dialog.prototype)
return ShortcutKeyList
})()
var ConfigDialog = (function() {
function newColorCellChild(doc) {
var colorBox = (doc || document).createElement('span')
colorBox.innerHTML = ' '
colorBox.style.display = 'inline-block'
colorBox.style.width = '1em'
colorBox.style.height = '100%'
colorBox.style.marginRight = '8px'
colorBox.style.border = '1px solid'
var result = (doc || document).createDocumentFragment()
result.appendChild(colorBox)
result.appendChild(newTextNode(''))
return result
}
function setColorCell(cell, color) {
cell.firstChild.style.backgroundColor = color
cell.lastChild.nodeValue = color
}
function newRadio(value, clickListener, doc) {
var result = (doc || document).createElement('input')
result.type = 'radio'
result.name = 'font-type'
result.value = value
result.addEventListener('click', clickListener, false)
return result
}
function newLabel(inputElem, text, doc) {
var result = (doc || document).createElement('label')
result.appendChild(inputElem)
result.appendChild(newTextNode(text))
return result
}
function newButton(clickListener, textContent, doc) {
var result = (doc || document).createElement('button')
result.textContent = textContent || '変更'
result.style.padding = '0px 6px'
result.addEventListener('click', clickListener, false)
return result
}
function newCheckbox(clickListener, doc) {
var result = (doc || document).createElement('input')
result.type = 'checkbox'
result.addEventListener('click', clickListener, false)
return result
}
var promptUntilValid = (function() {
function isValidCssPropertyValue(propertyName, propertyValue) {
var e = document.createElement('span')
e.style.setProperty(propertyName, propertyValue, null)
return Boolean(e.style.getPropertyValue(propertyName))
}
function errorMessage(propertyName) {
return 'CSS '
+ propertyName
+ ' プロパティに有効な値を入力して下さい\n'
}
return function(propertyName, message, initValue) {
var r = null
do {
r = window.prompt((r ? errorMessage(propertyName) : '') + message
, r ? r : initValue)
if (!r) return ''
} while (!isValidCssPropertyValue(propertyName, r))
return r
}
})()
function promptInteger(message, initValue) {
var r = parseInt(window.prompt(message, initValue), 10)
return isNaN(r) ? null : r
}
function newDiv(doc) {
var result = (doc || document).createElement('div')
result.style.textAlign = 'left'
result.style.marginTop = '5px'
return result
}
function newOption(text, doc) {
var result = (doc || document).createElement('option')
result.text = text
return result
}
function ConfigDialog(book, bookView, doc) {
Dialog.call(this, doc)
this.book = book
this.bookView = bookView
this.gothicRadio = newRadio('gothic'
, this.fontTypeRadioClicked.bind(this)
, this.doc)
this.minchoRadio = newRadio('mincho'
, this.fontTypeRadioClicked.bind(this)
, this.doc)
this.fontWeightSelect = this.newFontWeightSelect()
this.toolbarVisibleCheckbox = this.newToolbarVisibleCheckbox()
this.autoVerticalWritingCheckbox = this.newAutoVerticalWritingCheckbox()
this.root.appendChild(this.newConfigTable())
this.root.appendChild(this.newToolbarVisibleDiv())
this.root.appendChild(this.newAutoVerticalWritingDiv())
this.root.appendChild(this.newConfigClearDiv())
this.setConfigValues()
Object.seal(this)
}
ConfigDialog.prototype = Object.create(Dialog.prototype)
ConfigDialog.prototype.newToolbarVisibleCheckbox = function() {
var clickListener = function(e) {
this.bookView.setToolbarVisible(e.target.checked)
_setValue('toolbarVisible', e.target.checked)
}.bind(this)
return newCheckbox(clickListener, this.doc)
}
ConfigDialog.prototype.newAutoVerticalWritingCheckbox = function() {
var clickListener = function(e) {
_setValue('autoVerticalWriting', e.target.checked)
}
return newCheckbox(clickListener, this.doc)
}
ConfigDialog.prototype.newToolbarVisibleDiv = function() {
var result = newDiv()
result.appendChild(newLabel(this.toolbarVisibleCheckbox
, 'ツールバーを常に表示する'
, this.doc))
return result
}
ConfigDialog.prototype.newAutoVerticalWritingDiv = function() {
var result = newDiv()
result.appendChild(newLabel(this.autoVerticalWritingCheckbox
, '自動で縦書き表示にする'
, this.doc))
return result
}
ConfigDialog.prototype.newFontWeightSelect = function() {
var result = this.doc.createElement('select')
result.appendChild(newOption('normal', this.doc))
result.appendChild(newOption('bold', this.doc))
for (var i = 1; i <= 9; i++) {
result.appendChild(newOption(i + '00', this.doc))
}
result.value = GM_getFontWeight()
result.addEventListener('change'
, this.fontWeightSelectChanged.bind(this))
return result
}
ConfigDialog.prototype.fontWeightSelectChanged = function() {
var v = this.fontWeightSelect.value
_setValue('fontWeight', v)
this.bookView.setFontWeight(v)
pixelCharSize.clearCache()
this.book.update()
}
ConfigDialog.prototype.insertFontRows = function(table) {
var gothicRow = table.insertRow(-1)
insertCell(gothicRow, 'フォント').rowSpan = 2
insertCell(gothicRow, newLabel(this.gothicRadio, 'ゴシック', this.doc))
insertCell(gothicRow
, newButton(this.gothicFontEditButtonClicked.bind(this)
, null
, this.doc))
var minchoRow = table.insertRow(-1)
insertCell(minchoRow, newLabel(this.minchoRadio, '明朝', this.doc))
insertCell(minchoRow
, newButton(this.minchoFontEditButtonClicked.bind(this)
, null
, this.doc))
}
ConfigDialog.prototype.insertRow = function(table
, configName
, configValueCellName
, clickListener
, valueCellChild) {
var row = table.insertRow(-1)
insertCell(row, configName)
this[configValueCellName] = insertCell(row, valueCellChild)
insertCell(row, newButton(clickListener, null, this.doc))
}
ConfigDialog.prototype.insertFontWeightRow = function(table) {
var row = table.insertRow(-1)
insertCell(row, '文字の太さ')
var c = insertCell(row, this.fontWeightSelect)
c.colSpan = 2
}
ConfigDialog.prototype.newConfigTable = function() {
var result = this.doc.createElement('table')
result.style.textAlign = 'left'
result.style.borderCollapse = 'collapse'
result.style.color = 'black'
this.insertFontRows(result)
this.insertRow(result
, 'フォントサイズ'
, 'fontSizeCell'
, this.fontSizeEditButtonClicked.bind(this))
this.insertRow(result
, '文字の高さ'
, 'charHeightCell'
, this.charHeightEditButtonClicked.bind(this))
this.insertFontWeightRow(result)
this.insertRow(result
, '行数'
, 'lineNumCell'
, this.lineNumEditButtonClicked.bind(this))
this.insertRow(result
, '一行の文字数'
, 'lineCharNumCell'
, this.lineCharNumEditButtonClicked.bind(this))
this.insertRow(result
, '上余白'
, 'marginTopCell'
, this.marginTopEditButtonClicked.bind(this))
this.insertRow(result
, '行間'
, 'spaceBetweenLinesCell'
, this.spaceBetweenLinesEditButtonClicked.bind(this))
this.insertRow(result
, '文字色'
, 'colorCell'
, this.colorEditButtonClicked.bind(this)
, newColorCellChild())
this.insertRow(result
, '背景色'
, 'backgroundColorCell'
, this.backgroundColorEditButtonClicked.bind(this)
, newColorCellChild())
return result
}
ConfigDialog.prototype.newConfigClearDiv = function() {
var result = newDiv()
result.appendChild(newButton(this.clearConfigButtonClicked.bind(this)
, '初期設定に戻す'
, this.doc))
return result
}
ConfigDialog.prototype.setConfigValues = function() {
this.gothicRadio.checked = GM_getFontType() === 'gothic'
this.minchoRadio.checked = GM_getFontType() === 'mincho'
this.fontSizeCell.textContent = GM_getFontSize()
this.charHeightCell.textContent = GM_getCharHeight()
this.lineNumCell.textContent = GM_getLineNum()
this.lineCharNumCell.textContent = GM_getLineCharNum()
this.marginTopCell.textContent = GM_getMarginTop()
this.spaceBetweenLinesCell.textContent = GM_getSpaceBetweenLines()
setColorCell(this.colorCell, GM_getColor())
setColorCell(this.backgroundColorCell, GM_getBackgroundColor())
this.toolbarVisibleCheckbox.checked = GM_isToolbarVisible()
this.autoVerticalWritingCheckbox.checked = GM_isAutoVerticalWriting()
this.fontWeightSelect.value = GM_getFontWeight()
}
ConfigDialog.prototype.notifyConfigCleared = function() {
this.book.setLineNum(GM_getLineNum())
this.book.setLineCharNum(GM_getLineCharNum())
this.bookView.setFontType(GM_getFontType())
this.bookView.setFontSize(GM_getFontSize())
this.bookView.setCharHeight(GM_getCharHeight())
this.bookView.setMarginTop(GM_getMarginTop())
this.bookView.setSpaceBetweenLines(GM_getSpaceBetweenLines())
this.bookView.setColor(GM_getColor())
this.bookView.setBackgroundColor(GM_getBackgroundColor())
this.bookView.setToolbarVisible(GM_isToolbarVisible())
this.bookView.setFontWeight(GM_getFontWeight())
}
ConfigDialog.prototype.clearConfigButtonClicked = function() {
GM_clear()
this.setConfigValues()
this.notifyConfigCleared()
pixelCharSize.clearCache()
this.book.update()
}
ConfigDialog.prototype.fontTypeRadioClicked = function() {
;[this.gothicRadio, this.minchoRadio].forEach(function(e) {
if (!e.checked) return
this.bookView.setFontType(e.value)
_setValue('fontType', e.value)
pixelCharSize.clearCache()
this.book.update()
}, this)
}
ConfigDialog.prototype.gothicFontEditButtonClicked = function() {
var r = promptUntilValid('font-family'
, 'ゴシックフォント'
, GM_getGothicFontFamily())
if (!r) return
_setValue('gothicFontFamily', r)
if (GM_getFontType() !== 'gothic') return
this.bookView.setFontFamily(r)
pixelCharSize.clearCache()
this.book.update()
}
ConfigDialog.prototype.minchoFontEditButtonClicked = function() {
var r = promptUntilValid('font-family'
, '明朝フォント'
, GM_getMinchoFontFamily())
if (!r) return
_setValue('minchoFontFamily', r)
if (GM_getFontType() !== 'mincho') return
this.bookView.setFontFamily(r)
pixelCharSize.clearCache()
this.book.update()
}
ConfigDialog.prototype.fontSizeEditButtonClicked = function() {
var r = promptUntilValid('font-size', 'フォントサイズ', GM_getFontSize())
if (!r) return
this.bookView.setFontSize(r)
this.fontSizeCell.textContent = r
_setValue('fontSize', r)
pixelCharSize.clearCache()
this.book.update()
}
ConfigDialog.prototype.charHeightEditButtonClicked = function() {
var r = promptUntilValid('line-height', '文字の高さ', GM_getCharHeight())
if (!r) return
this.bookView.setCharHeight(r)
this.charHeightCell.textContent = r
_setValue('charHeight', r)
pixelCharSize.clearCache()
this.book.update()
}
ConfigDialog.prototype.lineNumEditButtonClicked = function() {
var r = promptInteger('行数', GM_getLineNum())
if (r === null) return
this.book.setLineNum(r)
this.lineNumCell.textContent = this.book.lineNum
_setValue('lineNum', this.book.lineNum)
}
ConfigDialog.prototype.lineCharNumEditButtonClicked = function() {
var r = promptInteger('一行の文字数', GM_getLineCharNum())
if (r === null) return
this.book.setLineCharNum(r)
this.lineCharNumCell.textContent = this.book.lineCharNum
_setValue('lineCharNum', this.book.lineCharNum)
}
ConfigDialog.prototype.marginTopEditButtonClicked = function() {
var r = promptUntilValid('margin-top', '上余白', GM_getMarginTop())
if (!r) return
this.bookView.setMarginTop(r)
this.marginTopCell.textContent = r
_setValue('marginTop', r)
}
ConfigDialog.prototype.spaceBetweenLinesEditButtonClicked = function() {
var r = promptUntilValid('margin-left'
, '行間'
, GM_getSpaceBetweenLines())
if (!r) return
this.bookView.setSpaceBetweenLines(r)
this.spaceBetweenLinesCell.textContent = r
_setValue('spaceBetweenLines', r)
}
ConfigDialog.prototype.colorEditButtonClicked = function() {
var r = promptUntilValid('color', '文字色', GM_getColor())
if (!r) return
this.bookView.setColor(r)
setColorCell(this.colorCell, r)
_setValue('color', r)
}
ConfigDialog.prototype.backgroundColorEditButtonClicked = function() {
var r = promptUntilValid('background-color'
, '背景色'
, GM_getBackgroundColor())
if (!r) return
this.bookView.setBackgroundColor(r)
setColorCell(this.backgroundColorCell, r)
_setValue('backgroundColor', r)
}
return ConfigDialog
})()
function createWindowKeydownListener(site) {
var result = new KeyMap()
result.add(Key.V, function() {
if (!site.bookViewShown) site.showBookView()
})
return result.keyDowned.bind(result)
}
async function main() {
await GM_sync()
;[ new Narou()
, new Arcadia()
, new Hameln()
, new Pixiv()
, new Akatsuki()
].forEach(function(site) {
if (!site.is(window.location)) return
site.addButton()
window.addEventListener('keydown', createWindowKeydownListener(site), true)
if (!(GM_isAutoVerticalWriting() || _getValue('auto', false))) return
_setValue('auto', false)
site.showBookView()
})
}
main()
})()