2ch Thread Viewer

2ちゃんねるのスレッドビューワ

  1. // ==UserScript==
  2. // @name 2ch Thread Viewer
  3. // @namespace https://gf.qytechs.cn/users/1009-kengo321
  4. // @version 26
  5. // @description 2ちゃんねるのスレッドビューワ
  6. // @grant GM_getValue
  7. // @grant GM_setValue
  8. // @grant GM.getValue
  9. // @grant GM.setValue
  10. // @match *://*.2ch.net/test/read.cgi/*
  11. // @match *://*.5ch.net/test/read.cgi/*
  12. // @license MIT
  13. // @noframes
  14. // @run-at document-start
  15. // ==/UserScript==
  16.  
  17. ;(function() {
  18. 'use strict'
  19.  
  20. var find = function(predicate, array) {
  21. for (var i = 0; i < array.length; i++) {
  22. var e = array[i]
  23. if (predicate(e)) return e
  24. }
  25. }
  26. var pushIfAbsent = function(array, value) {
  27. if (array.indexOf(value) === -1) array.push(value)
  28. return array
  29. }
  30. var pushSelectedAll = function(document) {
  31. return function(array, selector) {
  32. ;[].push.apply(array, document.querySelectorAll(selector))
  33. return array
  34. }
  35. }
  36. var not = function(fn) {
  37. return function() { return !fn.apply(this, arguments) }
  38. }
  39. var array = Function.prototype.call.bind([].slice)
  40. var curry = (function() {
  41. var applyOrRebind = function(func, arity, args) {
  42. var passed = args.concat(array(arguments, 3)).slice(0, arity)
  43. return arity === passed.length
  44. ? func.apply(this, passed)
  45. : applyOrRebind.bind(this, func, arity, passed)
  46. }
  47. return function(func) {
  48. return applyOrRebind.bind(this, func, func.length, [])
  49. }
  50. })()
  51. var invoke = curry(function(methodName, args, obj) {
  52. return obj[methodName].apply(obj, args)
  53. })
  54. var equalObj = curry(function(o1, o2) {
  55. return Object.keys(o1)
  56. .concat(Object.keys(o2))
  57. .reduce(pushIfAbsent, [])
  58. .every(function(key) { return o1[key] === o2[key] })
  59. })
  60. var prop = curry(function(propName, obj) {
  61. return obj[propName]
  62. })
  63. var listeners = {
  64. set: function(eventTypes, observer) {
  65. eventTypes.forEach(function(t) {
  66. observer[`_${t}Listener`] = observer[`_${t}`].bind(observer)
  67. })
  68. },
  69. add: function(eventTypes, observer, observable) {
  70. eventTypes.forEach(function(t) {
  71. observable.addEventListener(t, observer[`_${t}Listener`])
  72. })
  73. },
  74. remove: function(eventTypes, observer, observable) {
  75. eventTypes.forEach(function(t) {
  76. observable.removeEventListener(t, observer[`_${t}Listener`])
  77. })
  78. },
  79. }
  80. var msPerDay = 86400000
  81. var truncTime = function(dateTime) {
  82. return dateTime - dateTime % msPerDay
  83. }
  84. var toDateString = function(msTime) {
  85. var d = new Date(msTime)
  86. return `${d.getUTCFullYear()}-${d.getUTCMonth() + 1}-${d.getUTCDate()}`
  87. }
  88.  
  89. var Observable = (function() {
  90. var Observable = function() {
  91. this._eventTypeToListeners = Object.create(null)
  92. }
  93. Observable.prototype.addEventListener = function(eventType, listener) {
  94. var m = this._eventTypeToListeners
  95. var v = m[eventType]
  96. if (v) v.push(listener); else m[eventType] = [listener]
  97. }
  98. Observable.prototype.removeEventListener = function(eventType, listener) {
  99. var v = this._eventTypeToListeners[eventType]
  100. if (!v) return
  101. var i = v.indexOf(listener)
  102. if (i >= 0) v.splice(i, 1)
  103. }
  104. Observable.prototype.getEventListeners = function(eventType) {
  105. return this._eventTypeToListeners[eventType] || []
  106. }
  107. Observable.prototype.fireEvent = function(eventType/* , ...args */) {
  108. var v = this._eventTypeToListeners[eventType]
  109. ;(v || []).forEach(invoke('apply', [null, array(arguments, 1)]))
  110. }
  111. return Observable
  112. })()
  113.  
  114. var Parser = (function() {
  115. var mail = function(dt) {
  116. var e = dt.childNodes[1]
  117. return e.tagName === 'FONT' ? '' : Parser._mail(e)
  118. }
  119. var id = function(dt) {
  120. var r = /ID:([\w+/]+)/.exec(dt.childNodes[2].textContent)
  121. return r ? r[1] : ''
  122. }
  123. var createResponse = function(dt, dd) {
  124. var num = parseInt(dt.firstChild.textContent.split(' ')[0])
  125. var name = dt.childNodes[1].textContent
  126. return {
  127. number: num,
  128. name,
  129. mail: mail(dt),
  130. jstTime: Parser._jstTime(dt.childNodes[2].textContent),
  131. id: id(dt),
  132. anchors: Parser._anchors(dd, num),
  133. contentNodes: Parser._contentNodes(dd.childNodes),
  134. korokoro: Parser._korokoro(name),
  135. }
  136. }
  137. var postedResShowElem = function(document) {
  138. return find(function(e) {
  139. return e.textContent === '新着レスの表示'
  140. }, document.getElementsByTagName('center'))
  141. }
  142. var hrAbove = function(e) {
  143. var p = e.previousSibling
  144. if (!p) return null
  145. var hr = p.previousSibling
  146. return hr && hr.tagName === 'HR' ? hr : null
  147. }
  148. var pageSizeElem = function(document) {
  149. return find(function(e) {
  150. return e.textContent.endsWith('KB')
  151. && e.getAttribute('color') === 'red'
  152. && e.getAttribute('face') === 'Arial'
  153. }, document.getElementsByTagName('font'))
  154. }
  155. var pushIfTruthy = function(array, value) {
  156. if (value) array.push(value)
  157. }
  158. var Parser = function(document) {
  159. this.doc = document
  160. }
  161. Parser.prototype._boardId = function() {
  162. if (!this.doc.location) return ''
  163. var r = /\/test\/read.cgi\/([^/]+)/.exec(this.doc.location.pathname)
  164. return r ? r[1] : ''
  165. }
  166. Parser.prototype._threadNumber = function() {
  167. if (!this.doc.location) return 0
  168. var r = /\/test\/read.cgi\/[^/]+\/(\d+)/.exec(this.doc.location.pathname)
  169. return r ? parseInt(r[1]) : 0
  170. }
  171. Parser.prototype._floatedSpan = function() {
  172. return this.doc.querySelector('body > div > span')
  173. }
  174. Parser.prototype._postForm = function() {
  175. return find(function(f) {
  176. return f.getAttribute('action').startsWith('../test/bbs.cgi')
  177. && f.method.toUpperCase() === 'POST'
  178. }, this.doc.querySelectorAll('form'))
  179. }
  180. Parser.prototype._elementsToRemove = function() {
  181. var result = []
  182. var e = postedResShowElem(this.doc)
  183. if (e) {
  184. result.push(e)
  185. pushIfTruthy(result, hrAbove(e))
  186. }
  187. pushIfTruthy(result, pageSizeElem(this.doc))
  188. return result
  189. }
  190. Parser.prototype._ads = function() {
  191. return ['.ad--right', '.js--ad--top', '.js--ad--bottom']
  192. .reduce(pushSelectedAll(this.doc), [])
  193. }
  194. Parser.prototype._hasThreadClosed = function() {
  195. return !postedResShowElem(this.doc)
  196. }
  197. Parser.prototype._responses = function() {
  198. var dl = this.doc.querySelector('.thread')
  199. var dt = dl.getElementsByTagName('dt')
  200. var dd = dl.getElementsByTagName('dd')
  201. var result = []
  202. for (var i = 0; i < dt.length; i++) {
  203. result.push(createResponse(dt[i], dd[i]))
  204. }
  205. return result
  206. }
  207. Parser.prototype._action = function() {
  208. return function() {}
  209. }
  210. Parser.prototype.parse = function() {
  211. return {
  212. responses: this._responses(),
  213. threadClosed: this._hasThreadClosed(),
  214. ads: this._ads(),
  215. elementsToRemove: this._elementsToRemove(),
  216. threadRootElement: this.doc.querySelector('.thread'),
  217. postForm: this._postForm(),
  218. boardId: this._boardId(),
  219. threadNumber: this._threadNumber(),
  220. floatedSpan: this._floatedSpan(),
  221. action: this._action(),
  222. }
  223. }
  224. Parser.of = function(document) {
  225. if (document.querySelector('dl.thread')) return new Parser(document)
  226. if (document.querySelector('div.thread')) return new Parser6(document)
  227. return null
  228. }
  229. Parser._mail = function(anchor) {
  230. var s = anchor.href.slice('mailto:'.length)
  231. try {
  232. return decodeURI(s)
  233. } catch (e) {
  234. return s
  235. }
  236. }
  237. Parser._jstTime = function(text) {
  238. var datetime = /(\d{4})\/(\d{2})\/(\d{2})\(.\)/.exec(text)
  239. if (!datetime) return NaN
  240. var year = datetime[1]
  241. var month = datetime[2] - 1
  242. var date = datetime[3]
  243. var time = /(\d{2}):(\d{2}):(\d{2})/.exec(text)
  244. var hour = time ? time[1] : 0
  245. var minute = time ? time[2] : 0
  246. var seconds = time ? time[3] : 0
  247. return Date.UTC(year, month, date, hour, minute, seconds)
  248. }
  249. var last = function(array) {
  250. return array[array.length - 1]
  251. }
  252. var isEmptyTextNode = function(node) {
  253. return node.nodeType === Node.TEXT_NODE && node.nodeValue.trim() === ''
  254. }
  255. var isTrimmedRightNode = function(node) {
  256. return node && (node.tagName === 'BR' || isEmptyTextNode(node))
  257. }
  258. Parser._contentNodes = function(childNodes) {
  259. var nodes = Array.from(childNodes)
  260. while (isTrimmedRightNode(last(nodes))) nodes.pop()
  261. return nodes.filter(function(n) {
  262. return !(n.classList && n.classList.contains('banner'))
  263. })
  264. }
  265. Parser._anchors = function(root, responseNumber) {
  266. return Array.from(root.querySelectorAll('a')).filter(function(n) {
  267. return n.textContent.startsWith('>>')
  268. }).map(function(n) {
  269. return parseInt(n.textContent.slice('>>'.length))
  270. }).filter(function(num) {
  271. return num < responseNumber
  272. }).reduce(pushIfAbsent, [])
  273. }
  274. Parser._korokoro = function(name) {
  275. var r = /\s(.{4}-.{4}).*?$/.exec(name)
  276. return r ? r[1] : ''
  277. }
  278. return Parser
  279. })()
  280.  
  281. var Parser6 = (function(_super) {
  282. var mail = function(post) {
  283. var a = post.querySelector('.name a')
  284. return a ? Parser._mail(a) : ''
  285. }
  286. var id = function(post) {
  287. var userid = post.dataset.userid
  288. if (!(userid && userid.startsWith('ID:'))) return ''
  289. var val = userid.slice('ID:'.length)
  290. return val.startsWith('???') ? '' : val
  291. }
  292. var createResponse = function(post) {
  293. var num = parseInt(post.id)
  294. var name = post.querySelector('.name').textContent
  295. return {
  296. number: num,
  297. name,
  298. mail: mail(post),
  299. jstTime: Parser._jstTime(post.querySelector('.date').textContent),
  300. id: id(post),
  301. anchors: Parser._anchors(post.querySelector('.message'), num),
  302. contentNodes: Parser._contentNodes(post.querySelector('.message').childNodes),
  303. korokoro: Parser._korokoro(name),
  304. }
  305. }
  306. var Parser6 = function(document) {
  307. _super.call(this, document)
  308. }
  309. Parser6.prototype = Object.create(_super.prototype)
  310. Parser6.prototype._floatedSpan = function() { return null }
  311. Parser6.prototype._postForm = function() {
  312. return this.doc.querySelector('form')
  313. }
  314. Parser6.prototype._elementsToRemove = function() {
  315. return ['.cLength', '.newposts'].reduce(pushSelectedAll(this.doc), [])
  316. }
  317. Parser6.prototype._ads = function() {
  318. return ['.ad--right', '.ad--bottom', '#banner']
  319. .reduce(pushSelectedAll(this.doc), [])
  320. }
  321. Parser6.prototype._hasThreadClosed = function() {
  322. return !this.doc.querySelector('.newpostbutton')
  323. }
  324. Parser6.prototype._responses = function() {
  325. return Array.from(this.doc.querySelectorAll('.thread .post')
  326. , createResponse)
  327. }
  328. Parser6.prototype._action = function() {
  329. return () => {
  330. const e = this.doc.querySelector('.container_body')
  331. if (e) e.removeAttribute('style')
  332. }
  333. }
  334. return Parser6
  335. })(Parser)
  336.  
  337. var Response = (function(_super) {
  338. var padZero = function(n) {
  339. return (n <= 9 ? '0' : '') + n
  340. }
  341. var content = function(nodes) {
  342. var result = ''
  343. for (var n of nodes)
  344. result += (n.tagName === 'BR' ? '\n' : n.textContent)
  345. return result
  346. }
  347. var Response = function(objParam) {
  348. _super.call(this)
  349. this.number = objParam.number
  350. this.name = objParam.name
  351. this.mail = objParam.mail
  352. this.jstTime = objParam.jstTime
  353. this.id = objParam.id
  354. this.contentNodes = objParam.contentNodes
  355. this.content = content(objParam.contentNodes)
  356. this.anchors = objParam.anchors
  357. this.korokoro = objParam.korokoro
  358. this.children = []
  359. this.sameIdResponses = []
  360. this.sameKorokoroResponses = []
  361. this.ngId = false
  362. this.ngParent = false
  363. this.ngWord = false
  364. this.ngName = false
  365. this.ngKorokoro = false
  366. this.ngIdTail = false
  367. this.ngSlip = false
  368. this.parents = new Set
  369. }
  370. Response.of = function(objParam) {
  371. return new Response(objParam)
  372. }
  373. Response.prototype = Object.create(_super.prototype)
  374. Response.prototype.getDateTimeString = function() {
  375. var d = new Date(this.jstTime)
  376. var y = d.getUTCFullYear()
  377. var mon = padZero(d.getUTCMonth() + 1)
  378. var date = padZero(d.getUTCDate())
  379. var h = padZero(d.getUTCHours())
  380. var min = padZero(d.getUTCMinutes())
  381. var s = padZero(d.getUTCSeconds())
  382. return `${y}-${mon}-${date} ${h}:${min}:${s}`
  383. }
  384. Response.prototype.getIndexOfSameIdResponses = function() {
  385. return this.sameIdResponses.indexOf(this)
  386. }
  387. Response.prototype.getIndexOfSameKorokoroResponses = function() {
  388. return this.sameKorokoroResponses.indexOf(this)
  389. }
  390. Response.prototype.addChildren = function(children) {
  391. if (children.length === 0) return
  392. ;[].push.apply(this.children, children)
  393. children.forEach(invoke('addParent', [this]))
  394. this.fireEvent('childrenAdded', children)
  395. }
  396. Response.prototype.addSameIdResponses = function(sameIdResponses) {
  397. if (sameIdResponses.length === 0) return
  398. ;[].push.apply(this.sameIdResponses, sameIdResponses)
  399. this.fireEvent('sameIdResponsesAdded', sameIdResponses)
  400. }
  401. Response.prototype.addSameKorokoroResponses = function(sameKorokoroResponses) {
  402. if (sameKorokoroResponses.length === 0) return
  403. ;[].push.apply(this.sameKorokoroResponses, sameKorokoroResponses)
  404. this.fireEvent('sameKorokoroResponsesAdded', sameKorokoroResponses)
  405. }
  406. Response.prototype.getNoNgChildren = function() {
  407. return this.children.filter(not(invoke('isNg', [])))
  408. }
  409. Response.prototype.isNg = function() {
  410. return this.ngId
  411. || this.ngParent
  412. || this.ngWord
  413. || this.ngName
  414. || this.ngKorokoro
  415. || this.ngIdTail
  416. || this.ngSlip
  417. }
  418. Response.prototype._setNg = function(propName, newVal) {
  419. var preNg = this.isNg()
  420. this[propName] = Boolean(newVal)
  421. if (preNg !== this.isNg()) this.fireEvent('ngChanged', this.isNg())
  422. }
  423. Response.prototype.setNgId = function(ngId) {
  424. this._setNg('ngId', ngId)
  425. }
  426. Response.prototype.setNgParent = function(ngParent) {
  427. this._setNg('ngParent', ngParent)
  428. }
  429. Response.prototype.setNgWord = function(ngWord) {
  430. this._setNg('ngWord', ngWord)
  431. }
  432. Response.prototype.setNgName = function(ngName) {
  433. this._setNg('ngName', ngName)
  434. }
  435. Response.prototype.setNgKorokoro = function(ngKorokoro) {
  436. this._setNg('ngKorokoro', ngKorokoro)
  437. }
  438. Response.prototype.setNgIdTail = function(ngIdTail) {
  439. this._setNg('ngIdTail', ngIdTail)
  440. }
  441. Response.prototype.setNgSlip = function(ngSlip) {
  442. this._setNg('ngSlip', ngSlip)
  443. }
  444. Response.prototype.updateNgParent = function() {
  445. this.setNgParent([...this.parents].some(p => p.isNg()))
  446. }
  447. Response.prototype.addParent = function(parent) {
  448. if (this.parents.has(parent)) return
  449. this.parents.add(parent)
  450. this.updateNgParent()
  451. parent.addEventListener('ngChanged', () => this.updateNgParent())
  452. }
  453. Response.prototype.hasAsciiArt = function() {
  454. return this.content.includes('\u3000\x20')
  455. }
  456. return Response
  457. })(Observable)
  458.  
  459. var ResponseRequest = (function() {
  460. var HTTP_OK = 200
  461. var parseResponseText = function(responseText) {
  462. var d = new DOMParser().parseFromString(responseText, 'text/html')
  463. var r = Parser.of(d).parse()
  464. return {
  465. responses: r.responses.slice(1),
  466. threadClosed: r.threadClosed,
  467. }
  468. }
  469. var onload = function(xhr, resolve, reject) {
  470. return function() {
  471. if (xhr.status === HTTP_OK) {
  472. try {
  473. resolve(parseResponseText(xhr.responseText))
  474. } catch (e) {
  475. reject(e)
  476. }
  477. } else {
  478. reject(new Error(xhr.status + ' ' + xhr.statusText))
  479. }
  480. }
  481. }
  482. var ResponseRequest = function() {}
  483. ResponseRequest.prototype.send = function(basePath, startResponseNumber) {
  484. return new Promise(function(resolve, reject) {
  485. var xhr = new XMLHttpRequest()
  486. xhr.timeout = 10000
  487. xhr.onload = onload(xhr, resolve, reject)
  488. xhr.onerror = function() { reject(new Error('エラー')) }
  489. xhr.ontimeout = function() { reject(new Error('時間切れ')) }
  490. xhr.open('GET', `${basePath}${startResponseNumber - 1}-n`)
  491. xhr.overrideMimeType('text/html; charset=shift_jis')
  492. xhr.send()
  493. })
  494. }
  495. return ResponseRequest
  496. })()
  497.  
  498. function NgItem() {}
  499. NgItem.prototype.isValidFor = function() {
  500. throw new Error('NgItem#isValidFor(thread) must be implemented')
  501. }
  502. NgItem.prototype.match = function() {
  503. throw new Error('NgItem#match(response) must be implemented')
  504. }
  505.  
  506. function BoundableNgItem(boardId, threadNumber) {
  507. if (boardId) this.boardId = boardId
  508. if (threadNumber) this.threadNumber = parseInt(threadNumber)
  509. }
  510. BoundableNgItem.prototype = Object.create(NgItem.prototype)
  511. BoundableNgItem.prototype.constructor = BoundableNgItem
  512. BoundableNgItem.prototype.isValidFor = function(thread) {
  513. return (!this.boardId || this.boardId === thread.boardId)
  514. && (!this.threadNumber || this.threadNumber === thread.threadNumber)
  515. }
  516.  
  517. function NgWord(ngWord, boardId, threadNumber) {
  518. BoundableNgItem.call(this, boardId, threadNumber)
  519. this.ngWord = ngWord
  520. }
  521. NgWord.of = function(objParam) {
  522. return new NgWord(objParam.ngWord, objParam.boardId, objParam.threadNumber)
  523. }
  524. NgWord.prototype = Object.create(BoundableNgItem.prototype)
  525. NgWord.prototype.constructor = NgWord
  526. NgWord.prototype.match = function(res) {
  527. return res.content.includes(this.ngWord)
  528. }
  529.  
  530. function NgName(ngName, boardId, threadNumber) {
  531. BoundableNgItem.call(this, boardId, threadNumber)
  532. this.ngName = ngName
  533. }
  534. NgName.of = function(objParam) {
  535. return new NgName(objParam.ngName, objParam.boardId, objParam.threadNumber)
  536. }
  537. NgName.prototype = Object.create(BoundableNgItem.prototype)
  538. NgName.prototype.constructor = NgName
  539. NgName.prototype.match = function(res) {
  540. return res.name.includes(this.ngName)
  541. }
  542.  
  543. function BoardScopeNgItem(boardId) {
  544. this.boardId = boardId
  545. }
  546. BoardScopeNgItem.prototype = Object.create(NgItem.prototype)
  547. BoardScopeNgItem.prototype.constructor = BoardScopeNgItem
  548. BoardScopeNgItem.prototype.isValidFor = function(thread) {
  549. return this.boardId === thread.boardId
  550. }
  551.  
  552. var NgId = (function(_super) {
  553. var NgId = function(boardId, jstTime, id) {
  554. _super.call(this, boardId)
  555. this.boardId = boardId
  556. this.activeDate = truncTime(jstTime)
  557. this.id = id
  558. }
  559. NgId.of = function(objParam) {
  560. return new NgId(objParam.boardId, objParam.activeDate, objParam.id)
  561. }
  562. NgId.prototype = Object.create(_super.prototype)
  563. NgId.prototype.constructor = NgId
  564. NgId.prototype.match = function(response) {
  565. return this.id === response.id
  566. && this.activeDate === truncTime(response.jstTime)
  567. }
  568. NgId.prototype.getActiveDateString = function() {
  569. return toDateString(new Date(this.activeDate))
  570. }
  571. return NgId
  572. })(BoardScopeNgItem)
  573.  
  574. function NgIdTail(ngIdTail, boardId, threadNumber) {
  575. BoundableNgItem.call(this, boardId, threadNumber)
  576. this.ngIdTail = ngIdTail
  577. }
  578. NgIdTail.of = function(objParam) {
  579. return new NgIdTail(objParam.ngIdTail, objParam.boardId, objParam.threadNumber)
  580. }
  581. NgIdTail.prototype = Object.create(BoundableNgItem.prototype)
  582. NgIdTail.prototype.constructor = NgIdTail
  583. NgIdTail.prototype.match = function(res) {
  584. return res.id.length >= 9 && res.id.charAt(8) === this.ngIdTail
  585. }
  586.  
  587. var NgKorokoro = (function(_super) {
  588. var msForLastThu = [
  589. msPerDay * 3,
  590. msPerDay * 4,
  591. msPerDay * 5,
  592. msPerDay * 6,
  593. 0,
  594. msPerDay,
  595. msPerDay * 2,
  596. ]
  597. var lastThursday = function(ms) {
  598. return ms - msForLastThu[new Date(ms).getUTCDay()]
  599. }
  600. function NgKorokoro(boardId, jstTime, korokoro) {
  601. _super.call(this, boardId)
  602. this.startOfAvailablePeriod = lastThursday(truncTime(jstTime))
  603. this.korokoro = korokoro
  604. }
  605. NgKorokoro.of = function(objParam) {
  606. var o = objParam
  607. return new NgKorokoro(o.boardId, o.startOfAvailablePeriod, o.korokoro)
  608. }
  609. NgKorokoro.prototype = Object.create(_super.prototype)
  610. NgKorokoro.prototype.constructor = NgKorokoro
  611. NgKorokoro.prototype._endOfAvailablePeriod = function() {
  612. return this.startOfAvailablePeriod + msPerDay * 7
  613. }
  614. NgKorokoro.prototype.match = function(response) {
  615. return this.korokoro === response.korokoro
  616. && this.startOfAvailablePeriod <= response.jstTime
  617. && response.jstTime < this._endOfAvailablePeriod()
  618. }
  619. NgKorokoro.prototype.getAvailablePeriodString = function() {
  620. var start = new Date(this.startOfAvailablePeriod)
  621. var end = new Date(this.startOfAvailablePeriod + msPerDay * 6)
  622. return `${toDateString(start)}_${toDateString(end)}`
  623. }
  624. return NgKorokoro
  625. })(BoardScopeNgItem)
  626.  
  627. function NgSlip(type, boardId, threadNumber) {
  628. BoundableNgItem.call(this, boardId, threadNumber)
  629. this.type = type
  630. }
  631. NgSlip.of = function(objParam) {
  632. return new NgSlip(objParam.type, objParam.boardId, objParam.threadNumber)
  633. }
  634. NgSlip.prototype = Object.create(BoundableNgItem.prototype)
  635. NgSlip.prototype.constructor = NgSlip
  636. NgSlip.prototype.match = function(res) {
  637. return (this.type === 'id' && !res.id)
  638. || (this.type === 'korokoro' && !res.korokoro)
  639. }
  640.  
  641. var NgItems = {
  642. createFactoryMethod(setNgTo) {
  643. var filter = function() {
  644. return result(Array.prototype.filter.apply(this, arguments))
  645. }
  646. var concat = function() {
  647. return result(Array.prototype.concat.apply(this, arguments))
  648. }
  649. var result = function(ngItemArray) {
  650. ngItemArray.setNgTo = setNgTo
  651. ngItemArray.filter = filter
  652. ngItemArray.concat = concat
  653. return ngItemArray
  654. }
  655. return result
  656. },
  657. }
  658.  
  659. var NgWords = {
  660. from: NgItems.createFactoryMethod(function(res) {
  661. res.setNgWord(this.some(function(ngWord) { return ngWord.match(res) }))
  662. }),
  663. }
  664.  
  665. var NgNames = {
  666. from: NgItems.createFactoryMethod(function(res) {
  667. res.setNgName(this.some(function(ngName) { return ngName.match(res) }))
  668. }),
  669. }
  670.  
  671. var NgIds = {
  672. from: NgItems.createFactoryMethod(function(res) {
  673. res.setNgId(this.some(function(ngId) { return ngId.match(res) }))
  674. }),
  675. }
  676.  
  677. var NgKorokoros = {
  678. from: NgItems.createFactoryMethod(function(res) {
  679. res.setNgKorokoro(this.some(function(ngKorokoro) { return ngKorokoro.match(res) }))
  680. }),
  681. }
  682.  
  683. var NgIdTails = {
  684. from: NgItems.createFactoryMethod(function(res) {
  685. res.setNgIdTail(this.some(function(ngIdTail) { return ngIdTail.match(res) }))
  686. }),
  687. }
  688.  
  689. var NgSlips = {
  690. from: NgItems.createFactoryMethod(function(res) {
  691. res.setNgSlip(this.some(function(ngSlip) { return ngSlip.match(res) }))
  692. }),
  693. }
  694.  
  695. function ArrayStore(objParam) {
  696. Observable.call(this)
  697. this.getValue = objParam.getValue
  698. this.setValue = objParam.setValue
  699. this.key = objParam.key
  700. this.valueOf = objParam.valueOf
  701. this.arrayFrom = objParam.arrayFrom
  702. this.equals = objParam.equals
  703. }
  704. ArrayStore.prototype = Object.create(Observable.prototype)
  705. ArrayStore.prototype.constructor = ArrayStore
  706. ArrayStore.prototype.get = async function() {
  707. var values = JSON.parse(await this.getValue(this.key, '[]'))
  708. return this.arrayFrom(values.map(this.valueOf))
  709. }
  710. ArrayStore.prototype._set = async function(values) {
  711. await this.setValue(this.key, JSON.stringify(values))
  712. }
  713. ArrayStore.prototype.set = async function(values) {
  714. await this._set(values)
  715. this.fireEvent('changed', values)
  716. }
  717. ArrayStore.prototype.add = async function(value) {
  718. var addedValues = (await this.get()).concat(value)
  719. await this._set(addedValues)
  720. this.fireEvent('changed', addedValues)
  721. }
  722. ArrayStore.prototype.remove = async function(removedValue) {
  723. var filteredValues = (await this.get()).filter(function(value) {
  724. return !this.equals(removedValue, value)
  725. }, this)
  726. await this._set(filteredValues)
  727. this.fireEvent('changed', filteredValues)
  728. }
  729. ArrayStore.prototype.removeAll = async function() {
  730. await this.setValue(this.key, '[]')
  731. this.fireEvent('changed', this.arrayFrom([]))
  732. }
  733.  
  734. var Config = (function(_super) {
  735. var identity = function(a) { return a }
  736. var Config = function(getValue, setValue) {
  737. _super.call(this)
  738. this._getValue = getValue
  739. this._setValue = setValue
  740. var o = function(key, valueOf, arrayFrom) {
  741. return {getValue, setValue, key, valueOf, arrayFrom, equals: equalObj}
  742. }
  743. this.ngWords = new ArrayStore(o('ngWords', NgWord.of, NgWords.from))
  744. this.ngIds = new ArrayStore(o('ngIds', NgId.of, NgIds.from))
  745. this.ngNames = new ArrayStore(o('ngNames', NgName.of, NgNames.from))
  746. this.ngKorokoros = new ArrayStore(o('ngKorokoros', NgKorokoro.of, NgKorokoros.from))
  747. this.threadHistories = new ArrayStore(o('threadHistories', identity, identity))
  748. this.ngIdTails = new ArrayStore(o('ngIdTails', NgIdTail.of, NgIdTails.from))
  749. this.ngSlips = new ArrayStore(o('ngSlips', NgSlip.of, NgSlips.from))
  750. }
  751. Config.prototype = Object.create(_super.prototype)
  752. Config.prototype.isPageCentering = async function() {
  753. return await this._getValue('pageCentering', true)
  754. }
  755. Config.prototype.setPageCentering = async function(pageCentering) {
  756. await this._setValue('pageCentering', pageCentering)
  757. this.fireEvent('pageCenteringChanged', pageCentering)
  758. }
  759. Config.prototype.getPageMaxWidth = async function() {
  760. return await this._getValue('pageMaxWidth', 600)
  761. }
  762. Config.prototype.setPageMaxWidth = async function(pageMaxWidth) {
  763. await this._setValue('pageMaxWidth', pageMaxWidth)
  764. this.fireEvent('pageMaxWidthChanged', pageMaxWidth)
  765. }
  766. Config.prototype.isNgVisible = async function() {
  767. return await this._getValue('ngVisible', false)
  768. }
  769. Config.prototype.setNgVisible = async function(ngVisible) {
  770. await this._setValue('ngVisible', ngVisible)
  771. this.fireEvent('ngVisibleChanged', ngVisible)
  772. }
  773. Config.prototype.getNgItemArrayStores = function() {
  774. return [
  775. this.ngWords,
  776. this.ngIds,
  777. this.ngNames,
  778. this.ngKorokoros,
  779. this.ngIdTails,
  780. this.ngSlips,
  781. ]
  782. }
  783. return Config
  784. })(Observable)
  785.  
  786. var ThreadHistory = (function() {
  787. var removeCopyrightWarnings = function(s) {
  788. return s.replace(/\[転載禁止\]/g, '')
  789. .replace(/\[無断転載禁止\]/g, '')
  790. .replace(/©2ch.net/g, '')
  791. .trim()
  792. }
  793. function ThreadHistory(threadHistories, location, title) {
  794. this.threadHistories = threadHistories
  795. this.location = location
  796. this.title = removeCopyrightWarnings(title)
  797. }
  798. ThreadHistory.prototype.isValidLocation = function() {
  799. return /^\/test\/read\.cgi\/\w+\/\d+\/$/.test(this.location.pathname)
  800. }
  801. ThreadHistory.prototype.exists = async function() {
  802. return Boolean(await this._history())
  803. }
  804. ThreadHistory.prototype._url = function() {
  805. var l = this.location
  806. return `${l.protocol}//${l.host}${l.pathname}`
  807. }
  808. ThreadHistory.prototype._history = async function() {
  809. var u = this._url()
  810. return (await this.threadHistories.get()).find(function(h) { return h.url === u })
  811. }
  812. ThreadHistory.prototype.getResNum = async function() {
  813. if (this.isValidLocation() && await this.exists())
  814. return (await this._history()).resNum
  815. throw new Error('must be valid location and exists')
  816. }
  817. ThreadHistory.prototype._toObj = function(resNum) {
  818. return {url: this._url(), title: this.title, resNum}
  819. }
  820. ThreadHistory.prototype._removeOldAndAddNew = async function(old, resNum) {
  821. return (await this.threadHistories.get())
  822. .filter(not(this.threadHistories.equals.bind(null, old)))
  823. .concat(this._toObj(resNum))
  824. }
  825. ThreadHistory.prototype.setResNum = async function(resNum) {
  826. if (!this.isValidLocation()) return
  827. var old = await this._history()
  828. if (old) {
  829. if (old.resNum === resNum) return
  830. await this.threadHistories.set(await this._removeOldAndAddNew(old, resNum))
  831. } else {
  832. await this.threadHistories.add(this._toObj(resNum))
  833. }
  834. }
  835. return ThreadHistory
  836. })()
  837.  
  838. var Thread = (function(_super) {
  839. var putAsArray = function(obj, key, value) {
  840. var array = obj[key]
  841. if (array) array.push(value); else obj[key] = [value]
  842. return obj
  843. }
  844. var putResById = function(obj, res) {
  845. return res.id ? putAsArray(obj, res.id, res) : obj
  846. }
  847. var putResByPassedAnchor = curry(function(res, obj, anchor) {
  848. return putAsArray(obj, anchor, res)
  849. })
  850. var putResByAnchor = function(obj, res) {
  851. return res.anchors.reduce(putResByPassedAnchor(res), obj)
  852. }
  853. var putResByNumber = function(obj, res) {
  854. obj[res.number] = res
  855. return obj
  856. }
  857. var putResByKorokoro = function(obj, res) {
  858. return res.korokoro ? putAsArray(obj, res.korokoro, res) : obj
  859. }
  860. var addNewChild = function(responses, addedResponses) {
  861. var all = responses.concat(addedResponses)
  862. var resNumToRes = all.reduce(putResByNumber, {})
  863. var addedAnchors = addedResponses.reduce(putResByAnchor, {})
  864. Object.keys(addedAnchors).forEach(function(anchor) {
  865. var r = resNumToRes[anchor]
  866. if (r) r.addChildren(addedAnchors[anchor])
  867. })
  868. }
  869. var addSameId = curry(function(idToRes, response) {
  870. var sameId = idToRes[response.id]
  871. if (sameId) response.addSameIdResponses(sameId)
  872. })
  873. var addNewSameId = function(responses, addedResponses) {
  874. responses.forEach(addSameId(addedResponses.reduce(putResById, {})))
  875. addedResponses.forEach(
  876. addSameId(responses.concat(addedResponses).reduce(putResById, {})))
  877. }
  878. var addSameKorokoro = curry(function(korokoroToRes, response) {
  879. var sameKorokoro = korokoroToRes[response.korokoro]
  880. if (sameKorokoro) response.addSameKorokoroResponses(sameKorokoro)
  881. })
  882. var addNewSameKorokoro = function(responses, addedResponses) {
  883. responses.forEach(
  884. addSameKorokoro(addedResponses.reduce(putResByKorokoro, {})))
  885. addedResponses.forEach(
  886. addSameKorokoro(responses.concat(addedResponses).reduce(putResByKorokoro, {})))
  887. }
  888. var Thread = function(config, boardId, threadNumber, threadHistory) {
  889. _super.call(this)
  890. this._responses = []
  891. this.boardId = boardId
  892. this.threadNumber = threadNumber
  893. this.config = config
  894. this.threadHistory = threadHistory
  895. this.newResCount = 0
  896. this._addEventListenersToConfig()
  897. }
  898. Thread.prototype = Object.create(_super.prototype)
  899. Thread.prototype._addEventListenersToConfig = function() {
  900. var listener = this._ngItemsChanged.bind(this)
  901. for (var s of this.config.getNgItemArrayStores())
  902. s.addEventListener('changed', listener)
  903. }
  904. Thread.prototype._hasBeenAddedResponses = function() {
  905. return Boolean(this._responses.length)
  906. }
  907. Thread.prototype._getNewResCountBy = async function(addedResCount) {
  908. if (this._hasBeenAddedResponses())
  909. return addedResCount
  910. if (this.threadHistory.isValidLocation() && await this.threadHistory.exists())
  911. return addedResCount - await this.threadHistory.getResNum()
  912. return 0
  913. }
  914. Thread.prototype.addResponses = async function(responses) {
  915. this.newResCount = await this._getNewResCountBy(responses.length)
  916. this._setNgToResponsesBy(await this._getAllNgItemsArray(), responses)
  917. addNewChild(this._responses, responses)
  918. addNewSameId(this._responses, responses)
  919. addNewSameKorokoro(this._responses, responses)
  920. ;[].push.apply(this._responses, responses)
  921. await this.threadHistory.setResNum(this._responses.length)
  922. this.fireEvent('responsesAdded', responses)
  923. }
  924. Thread.prototype._getAllNgItemsArray = function() {
  925. return Promise.all(this.config.getNgItemArrayStores().map(function(arrayStore) {
  926. return arrayStore.get()
  927. }))
  928. }
  929. Thread.prototype._setNgToResponsesBy = function(ngItemsArray, responses) {
  930. responses = responses || this._responses
  931. for (var ngItems of ngItemsArray) {
  932. var filteredNgItems = ngItems.filter(invoke('isValidFor', [this]))
  933. for (var res of responses) filteredNgItems.setNgTo(res)
  934. }
  935. }
  936. Thread.prototype.getLastResponseNumber = function() {
  937. var r = this._responses
  938. var last = r[r.length - 1]
  939. return last ? last.number : -1
  940. }
  941. Thread.prototype.addNgId = async function(jstTime, ngId) {
  942. await this.config.ngIds.add(new NgId(this.boardId, jstTime, ngId))
  943. }
  944. Thread.prototype._ngItemsChanged = function(ngItems) {
  945. this._setNgToResponsesBy([ngItems])
  946. }
  947. Thread.prototype.hasResponse = function(responseNumber) {
  948. return this._responses.some(function(r) {
  949. return r.number === responseNumber
  950. })
  951. }
  952. return Thread
  953. })(Observable)
  954.  
  955. var ResponseView = (function() {
  956. var eventTypes = [
  957. 'childrenAdded',
  958. 'sameIdResponsesAdded',
  959. 'sameKorokoroResponsesAdded',
  960. 'ngChanged',
  961. ]
  962. var ResponseView = function(document, response, root) {
  963. this._doc = document
  964. this._response = response
  965. this._factory = new ResponseView.Factory(document, response, root)
  966. this.rootElement = this._factory.createResponseElement()
  967. this._childResponseViews = []
  968. this._sameIdResponseViews = []
  969. this._sameKorokoroResponseViews = []
  970. listeners.set(eventTypes, this)
  971. listeners.add(eventTypes, this, this._response)
  972. this._childNgChangedListener = this._childNgChanged.bind(this)
  973. this._addListenersToChildren(response.children)
  974. }
  975. ResponseView.new = curry(function(document, response) {
  976. return new ResponseView(document, response)
  977. })
  978. ResponseView.prototype._childrenAdded = function(addedChildren) {
  979. this._addListenersToChildren(addedChildren)
  980. this._updateResNumElem()
  981. this._appendAddedChildren(addedChildren)
  982. }
  983. ResponseView.prototype._sameIdResponsesAdded = function(addedSameId) {
  984. this._updateIdElem()
  985. this._appendAddedSameId(addedSameId)
  986. }
  987. ResponseView.prototype._sameKorokoroResponsesAdded = function(addedSameKorokoro) {
  988. this._updateKorokoroElem()
  989. this._appendAddedSameKorokoro(addedSameKorokoro)
  990. }
  991. ResponseView.prototype._ngChanged = function(ng) {
  992. if (ng) this._destroyAllResponseViews()
  993. this._replaceRootWithNew()
  994. }
  995. ResponseView.prototype._isChildrenVisibleAndAllNg = function() {
  996. return Boolean(this.rootElement.querySelector('.children'))
  997. && this._response.getNoNgChildren().length === 0
  998. }
  999. ResponseView.prototype._childNgChanged = function() {
  1000. this._updateResNumElem()
  1001. if (this._isChildrenVisibleAndAllNg()) this._destroyChildren()
  1002. }
  1003. ResponseView.prototype._addListenersToChildren = function(children) {
  1004. children.forEach(invoke('addEventListener'
  1005. , ['ngChanged', this._childNgChangedListener]))
  1006. }
  1007. ResponseView.prototype._removeListenersFromChildren = function() {
  1008. this._response.children
  1009. .forEach(invoke('removeEventListener'
  1010. , ['ngChanged', this._childNgChangedListener]))
  1011. }
  1012. ResponseView.prototype._removeListenersFromResponse = function() {
  1013. listeners.remove(eventTypes, this, this._response)
  1014. }
  1015. ResponseView.prototype._updateResNumElem = function() {
  1016. if (this._response.isNg()) return
  1017. var numElem = this.rootElement.querySelector('header .headerNumber')
  1018. if (numElem) this._factory.updateHeaderNumClass(numElem)
  1019. }
  1020. ResponseView.prototype._appendAdded = function(added, propName, selector) {
  1021. var views = added.map(ResponseView.new(this._doc))
  1022. ;[].push.apply(this[propName], views)
  1023. var toggled = this.rootElement.querySelector(selector)
  1024. views.map(prop('rootElement')).forEach(toggled.appendChild.bind(toggled))
  1025. }
  1026. ResponseView.prototype._appendAddedChildren = function(addedChildren) {
  1027. if (this._childResponseViews.length) {
  1028. this._appendAdded(addedChildren, '_childResponseViews', '.children')
  1029. }
  1030. }
  1031. ResponseView.prototype._appendAddedSameId = function(addedSameId) {
  1032. if (this._sameIdResponseViews.length) {
  1033. this._appendAdded(addedSameId, '_sameIdResponseViews', '.sameId')
  1034. }
  1035. }
  1036. ResponseView.prototype._appendAddedSameKorokoro = function(addedSameKorokoro) {
  1037. if (this._sameKorokoroResponseViews.length) {
  1038. this._appendAdded(addedSameKorokoro, '_sameKorokoroResponseViews', '.sameKorokoro')
  1039. }
  1040. }
  1041. ResponseView.prototype._getIdValElem = function() {
  1042. return this.rootElement.querySelector('header .id .value')
  1043. }
  1044. ResponseView.prototype._updateIdValueElem = function() {
  1045. this._factory.updateIdValClass(this._getIdValElem())
  1046. }
  1047. ResponseView.prototype._hasIdCountElem = function() {
  1048. return Boolean(this.rootElement.querySelector('header .id .count'))
  1049. }
  1050. ResponseView.prototype._insertIdCountElem = function() {
  1051. var e = this._getIdValElem()
  1052. e.parentNode.insertBefore(this._factory.createIdCount(), e.nextSibling)
  1053. }
  1054. ResponseView.prototype._updateIdTotalElem = function() {
  1055. var e = this.rootElement.querySelector('header .id .count .total')
  1056. e.textContent = this._response.sameIdResponses.length
  1057. }
  1058. ResponseView.prototype._updateIdElem = function() {
  1059. if (this._response.isNg()) return
  1060. this._updateIdValueElem()
  1061. if (this._hasIdCountElem()) {
  1062. this._updateIdTotalElem()
  1063. } else {
  1064. this._insertIdCountElem()
  1065. }
  1066. }
  1067. ResponseView.prototype._getKorokoroValueElem = function() {
  1068. return this.rootElement.querySelector('header .korokoro .value')
  1069. }
  1070. ResponseView.prototype._insertKorokoroCountElem = function() {
  1071. var e = this._getKorokoroValueElem()
  1072. e.parentNode.insertBefore(this._factory.createKorokoroCount(), e.nextSibling)
  1073. }
  1074. ResponseView.prototype._updateKorokoroValueElem = function() {
  1075. this._factory.updateKorokoroValClass(this._getKorokoroValueElem())
  1076. }
  1077. ResponseView.prototype._hasKorokoroCountElem = function() {
  1078. return Boolean(this.rootElement.querySelector('header .korokoro .count'))
  1079. }
  1080. ResponseView.prototype._updateKorokoroTotalElem = function() {
  1081. var totalElem = this.rootElement.querySelector('header .korokoro .count .total')
  1082. totalElem.textContent = this._response.sameKorokoroResponses.length
  1083. }
  1084. ResponseView.prototype._updateKorokoroElem = function() {
  1085. if (this._response.isNg()) return
  1086. this._updateKorokoroValueElem()
  1087. if (this._hasKorokoroCountElem())
  1088. this._updateKorokoroTotalElem()
  1089. else
  1090. this._insertKorokoroCountElem()
  1091. }
  1092. ResponseView.prototype._replaceRootWithNew = function() {
  1093. var old = this.rootElement
  1094. this.rootElement = this._factory.createResponseElement()
  1095. var p = old.parentNode
  1096. if (p) p.replaceChild(this.rootElement, old)
  1097. }
  1098. ResponseView.prototype._destroyResponseViews = function(propName) {
  1099. this[propName].forEach(function(v) {
  1100. v._removeListenersFromResponse()
  1101. v._removeListenersFromChildren()
  1102. v._destroyAllResponseViews()
  1103. })
  1104. this[propName] = []
  1105. }
  1106. ResponseView.prototype._destroyAllResponseViews = function() {
  1107. var propNames = [
  1108. '_childResponseViews',
  1109. '_sameIdResponseViews',
  1110. '_sameKorokoroResponseViews',
  1111. ]
  1112. for (var n of propNames) this._destroyResponseViews(n)
  1113. }
  1114. ResponseView.prototype._newSubResponseViews = function(propName) {
  1115. return this._response[propName].map(ResponseView.new(this._doc))
  1116. }
  1117. ResponseView.prototype._insertAfterContent = function(views, methodName) {
  1118. var responseElems = views.map(prop('rootElement'))
  1119. var toggledElem = this._factory[methodName](responseElems)
  1120. var contentElem = this.rootElement.querySelector('.content')
  1121. this.rootElement.insertBefore(toggledElem, contentElem.nextSibling)
  1122. }
  1123. ResponseView.prototype._destroyChildren = function() {
  1124. this.rootElement.querySelector('.children').remove()
  1125. this._destroyResponseViews('_childResponseViews')
  1126. }
  1127. ResponseView.prototype.toggleChildren = function() {
  1128. if (this._response.children.length === 0) return
  1129. var e = this.rootElement.querySelector('.children')
  1130. if (e) {
  1131. this._destroyChildren()
  1132. } else {
  1133. var views = this._newSubResponseViews('children')
  1134. this._childResponseViews = views
  1135. this._insertAfterContent(views, 'createChildrenElement')
  1136. }
  1137. }
  1138. ResponseView.prototype.toggleSameId = function() {
  1139. if (this._response.sameIdResponses.length < 2) return
  1140. var e = this.rootElement.querySelector('.sameId')
  1141. if (e) {
  1142. e.remove()
  1143. this._destroyResponseViews('_sameIdResponseViews')
  1144. } else {
  1145. var views = this._newSubResponseViews('sameIdResponses')
  1146. this._sameIdResponseViews = views
  1147. this._insertAfterContent(views, 'createSameIdElement')
  1148. }
  1149. }
  1150. ResponseView.prototype.toggleSameKorokoro = function() {
  1151. if (this._response.sameKorokoroResponses.length < 2) return
  1152. var e = this.rootElement.querySelector('.sameKorokoro')
  1153. if (e) {
  1154. e.remove()
  1155. this._destroyResponseViews('_sameKorokoroResponseViews')
  1156. } else {
  1157. var views = this._newSubResponseViews('sameKorokoroResponses')
  1158. this._sameKorokoroResponseViews = views
  1159. this._insertAfterContent(views, 'createSameKorokoroElement')
  1160. }
  1161. }
  1162. ResponseView.prototype._getResponseViewByChild = function(elem, select) {
  1163. var resViews = this._childResponseViews
  1164. .concat(this._sameIdResponseViews)
  1165. .concat(this._sameKorokoroResponseViews)
  1166. for (var i = 0; i < resViews.length; i++) {
  1167. var v = resViews[i]._getResponseViewBy(elem, select)
  1168. if (v) return v
  1169. }
  1170. return null
  1171. }
  1172. ResponseView.prototype._getResponseViewBy = function(elem, select) {
  1173. return select(this.rootElement) === elem
  1174. ? this
  1175. : this._getResponseViewByChild(elem, select)
  1176. }
  1177. ResponseView.prototype.getResponseViewByNumElem = function(numElem) {
  1178. return this._getResponseViewBy(numElem, function(rootElem) {
  1179. return rootElem.querySelector('header .headerNumber')
  1180. })
  1181. }
  1182. ResponseView.prototype.getResponseViewByIdValElem = function(idValElem) {
  1183. return this._getResponseViewBy(idValElem, function(rootElem) {
  1184. var h = rootElem.querySelector('header')
  1185. return h ? h.querySelector('.id .value') : null
  1186. })
  1187. }
  1188. ResponseView.prototype.getResponseViewByKorokoroValElem = function(korokoroValElem) {
  1189. return this._getResponseViewBy(korokoroValElem, function(rootElem) {
  1190. return rootElem.querySelector('header .korokoro .value')
  1191. })
  1192. }
  1193. return ResponseView
  1194. })()
  1195. ResponseView.Factory = (function() {
  1196. var replaceMatchedByCreatedElem = function(textNode, regExp, createElem) {
  1197. var document = textNode.ownerDocument
  1198. var result = document.createDocumentFragment()
  1199. var begin = 0
  1200. var text = textNode.nodeValue
  1201. for (var r; (r = regExp.exec(text));) {
  1202. result.appendChild(document.createTextNode(text.slice(begin, r.index)))
  1203. result.appendChild(createElem(r[0]))
  1204. begin = regExp.lastIndex
  1205. }
  1206. result.appendChild(document.createTextNode(text.slice(begin)))
  1207. result.normalize()
  1208. return result
  1209. }
  1210. const hasAnchorAncestor = node => {
  1211. for (let p = node.parentNode; p; p = p.parentNode)
  1212. if (p.tagName === 'A')
  1213. return true
  1214. return false
  1215. }
  1216. const allTextNodesExceptInAnchor = root => {
  1217. const d = root.ownerDocument
  1218. const filter = {
  1219. acceptNode(textNode) {
  1220. return !hasAnchorAncestor(textNode)
  1221. },
  1222. }
  1223. const i = d.createNodeIterator(root, NodeFilter.SHOW_TEXT, filter)
  1224. const result = []
  1225. let n = i.nextNode()
  1226. while (n) {
  1227. // document#createNodeIterator() に NodeFilter.SHOW_TEXT を渡しているのに、
  1228. // NodeIterator#nextNode() からテキストノード以外のノードが返されてしまう。
  1229. // テキストノードかどうかを調べることで対処する。
  1230. // Google Chrome 60.0.3112.90(Official Build) (64 ビット)
  1231. // Tampermonkey 4.3.6
  1232. if (n.nodeType === Node.TEXT_NODE)
  1233. result.push(n)
  1234. n = i.nextNode()
  1235. }
  1236. return result
  1237. }
  1238. var replaceTextNodeIfMatched = function(node, regExp, createElem) {
  1239. allTextNodesExceptInAnchor(node).forEach(function(textNode) {
  1240. var newNode = replaceMatchedByCreatedElem(textNode, regExp, createElem)
  1241. textNode.parentNode.replaceChild(newNode, textNode)
  1242. }, this)
  1243. return node
  1244. }
  1245. var setLinkType = function(link) {
  1246. link.rel = 'noopener noreferrer'
  1247. }
  1248. var replaceOutsideLinkFromCushionToDirect = function(node) {
  1249. ;[].filter.call(node.querySelectorAll('a'), function(a) {
  1250. var c = a.textContent
  1251. return c.startsWith('http://') || c.startsWith('https://')
  1252. }).forEach(function(a) {
  1253. a.href = a.textContent
  1254. a.target = '_blank'
  1255. setLinkType(a)
  1256. })
  1257. }
  1258. var resAnchors = function(node) {
  1259. return [].reduce.call(node.querySelectorAll('a'), function(result, a) {
  1260. var r = /^>>(\d+)/.exec(a.textContent)
  1261. if (r) result.push({anchor: a, responseNumber: parseInt(r[1])})
  1262. return result
  1263. }, [])
  1264. }
  1265. var replaceResAnchorWithTextNode = function(anchor) {
  1266. var textNode = anchor.ownerDocument.createTextNode(anchor.textContent)
  1267. anchor.parentNode.replaceChild(textNode, anchor)
  1268. }
  1269. var replaceUpwardResAnchorWithTextNode = function(node, responseNumber) {
  1270. for (var resAnchor of resAnchors(node))
  1271. if (resAnchor.responseNumber >= responseNumber)
  1272. replaceResAnchorWithTextNode(resAnchor.anchor)
  1273. }
  1274. var setupResAnchorsClassAndDataset = function(node) {
  1275. for (var resAnchor of resAnchors(node)) {
  1276. resAnchor.anchor.classList.add('resAnchor')
  1277. resAnchor.anchor.dataset.resNum = resAnchor.responseNumber
  1278. }
  1279. }
  1280. var createLink = curry(function(document, matchedText) {
  1281. var result = document.createElement('a')
  1282. result.href = matchedText[0] === 'h' ? matchedText : 'h' + matchedText
  1283. result.target = '_blank'
  1284. setLinkType(result)
  1285. result.textContent = matchedText
  1286. return result
  1287. })
  1288. var Factory = function(document, response, root) {
  1289. this._doc = document
  1290. this._response = response
  1291. this._root = root
  1292. }
  1293. Factory.prototype._createTotal = function(textContent) {
  1294. var result = this._doc.createElement('span')
  1295. result.className = 'total'
  1296. result.textContent = textContent
  1297. return result
  1298. }
  1299. Factory.prototype._createIndex = function(index) {
  1300. return this._doc.createTextNode(`(${index + 1}/`)
  1301. }
  1302. Factory.prototype.updateIdValClass = function(idValElem) {
  1303. var n = this._response.sameIdResponses.length
  1304. var l = idValElem.classList
  1305. if (n >= 2) l.add('sameIdExist')
  1306. if (n >= 5) l.add('sameIdExist5')
  1307. }
  1308. Factory.prototype.updateKorokoroValClass = function(korokoroValElem) {
  1309. if (this._response.sameKorokoroResponses.length >= 2)
  1310. korokoroValElem.classList.add('sameKorokoroExist')
  1311. }
  1312. Factory.prototype._createVal = function(textContent) {
  1313. var result = this._doc.createElement('span')
  1314. result.className = 'value'
  1315. result.textContent = textContent
  1316. return result
  1317. }
  1318. Factory.prototype._createIdVal = function() {
  1319. var result = this._createVal(this._response.id)
  1320. this.updateIdValClass(result)
  1321. return result
  1322. }
  1323. Factory.prototype._createNgButton = function(title, dataset) {
  1324. var result = this._doc.createElement('span')
  1325. result.className = 'ngButton'
  1326. result.textContent = '[×]'
  1327. result.title = title
  1328. Object.assign(result.dataset, dataset)
  1329. return result
  1330. }
  1331. Factory.prototype._createIdNgButton = function() {
  1332. return this._createNgButton('NGID', {
  1333. id: this._response.id,
  1334. jstTime: this._response.jstTime,
  1335. })
  1336. }
  1337. Factory.prototype._createCount = function(len, index) {
  1338. var result = this._doc.createDocumentFragment()
  1339. if (len >= 2) {
  1340. var count = this._doc.createElement('span')
  1341. count.className = 'count'
  1342. count.appendChild(this._createIndex(index))
  1343. count.appendChild(this._createTotal(len))
  1344. count.appendChild(this._doc.createTextNode(')'))
  1345. result.appendChild(count)
  1346. }
  1347. return result
  1348. }
  1349. Factory.prototype.createIdCount = function() {
  1350. return this._createCount(this._response.sameIdResponses.length
  1351. , this._response.getIndexOfSameIdResponses())
  1352. }
  1353. Factory.prototype._createId = function() {
  1354. var result = this._doc.createDocumentFragment()
  1355. if (this._response.id) {
  1356. var id = this._doc.createElement('span')
  1357. id.className = 'id'
  1358. id.appendChild(this._createIdVal())
  1359. id.appendChild(this.createIdCount())
  1360. id.appendChild(this._createIdNgButton())
  1361. result.appendChild(id)
  1362. }
  1363. return result
  1364. }
  1365. Factory.prototype.updateHeaderNumClass = function(numElem) {
  1366. var childNum = this._response.getNoNgChildren().length
  1367. numElem.classList[childNum >= 1 ? 'add' : 'remove']('hasChild')
  1368. numElem.classList[childNum >= 3 ? 'add' : 'remove']('hasChild3')
  1369. }
  1370. Factory.prototype._createHeaderNum = function() {
  1371. var result = this._doc.createElement('span')
  1372. result.className = 'headerNumber'
  1373. this.updateHeaderNumClass(result)
  1374. result.textContent = this._response.number
  1375. return result
  1376. }
  1377. Factory.prototype.createKorokoroCount = function() {
  1378. return this._createCount(this._response.sameKorokoroResponses.length
  1379. , this._response.getIndexOfSameKorokoroResponses())
  1380. }
  1381. Factory.prototype._createKorokoroVal = function() {
  1382. var result = this._createVal(this._response.korokoro)
  1383. this.updateKorokoroValClass(result)
  1384. return result
  1385. }
  1386. Factory.prototype._createKorokoroNgButton = function() {
  1387. return this._createNgButton('NGID(ワッチョイ)', {
  1388. korokoro: this._response.korokoro,
  1389. jstTime: this._response.jstTime,
  1390. })
  1391. }
  1392. Factory.prototype._createKorokoro = function() {
  1393. var result = this._doc.createElement('span')
  1394. result.className = 'korokoro'
  1395. result.appendChild(this._createKorokoroVal())
  1396. result.appendChild(this.createKorokoroCount())
  1397. result.appendChild(this._createKorokoroNgButton())
  1398. return result
  1399. }
  1400. Factory.prototype._setHeaderNameWithKorokoro = function(elem) {
  1401. var n = this._response.name
  1402. var k = this._response.korokoro
  1403. var i = n.indexOf(k)
  1404. if (i === -1) {
  1405. elem.textContent = this._response.name
  1406. return
  1407. }
  1408. elem.appendChild(this._doc.createTextNode(n.slice(0, i)))
  1409. elem.appendChild(this._createKorokoro())
  1410. elem.appendChild(this._doc.createTextNode(n.slice(i + k.length)))
  1411. }
  1412. Factory.prototype._createHeaderName = function() {
  1413. var result = this._doc.createElement('span')
  1414. result.className = 'name'
  1415. if (this._response.korokoro)
  1416. this._setHeaderNameWithKorokoro(result)
  1417. else
  1418. result.textContent = this._response.name
  1419. return result
  1420. }
  1421. Factory.prototype._getHeaderMailText = function() {
  1422. var m = this._response.mail
  1423. return `[${m === 'sage' ? '↓' : m}]`
  1424. }
  1425. Factory.prototype._createHeaderMail = function() {
  1426. var result = this._doc.createDocumentFragment()
  1427. if (this._response.mail) {
  1428. var e = this._doc.createElement('span')
  1429. e.className = 'mail'
  1430. e.textContent = this._getHeaderMailText()
  1431. result.appendChild(e)
  1432. }
  1433. return result
  1434. }
  1435. Factory.prototype._createHeaderTime = function() {
  1436. var result = this._doc.createDocumentFragment()
  1437. if (!Number.isNaN(this._response.jstTime)) {
  1438. var datetime = this._doc.createElement('time')
  1439. datetime.textContent = this._response.getDateTimeString()
  1440. result.appendChild(datetime)
  1441. }
  1442. return result
  1443. }
  1444. Factory.prototype._createHeader = function() {
  1445. var result = this._doc.createElement('header')
  1446. result.appendChild(this._createHeaderNum())
  1447. result.appendChild(this._createHeaderName())
  1448. result.appendChild(this._createHeaderMail())
  1449. result.appendChild(this._createHeaderTime())
  1450. result.appendChild(this._createId())
  1451. return result
  1452. }
  1453. Factory.prototype._createContent = function() {
  1454. var f = this._doc.createDocumentFragment()
  1455. this._response.contentNodes
  1456. .map(invoke('cloneNode', [true]))
  1457. .forEach(function(n) { f.appendChild(n) })
  1458. replaceOutsideLinkFromCushionToDirect(f)
  1459. replaceUpwardResAnchorWithTextNode(f, this._response.number)
  1460. setupResAnchorsClassAndDataset(f)
  1461. replaceTextNodeIfMatched(f, /h?ttps?:\/\/\S+/g, createLink(this._doc))
  1462. var result = this._doc.createElement('div')
  1463. result.className = 'content'
  1464. if (this._response.hasAsciiArt()) result.classList.add('asciiArt')
  1465. result.appendChild(f)
  1466. return result
  1467. }
  1468. Factory.prototype.createResponseElement = function() {
  1469. var result = this._doc.createElement('article')
  1470. if (this._response.isNg()) {
  1471. result.classList.add('ng')
  1472. result.appendChild(this._createNgResponse())
  1473. } else {
  1474. result.appendChild(this._createHeader())
  1475. result.appendChild(this._createContent())
  1476. }
  1477. if (this._root) result.id = result.dataset.id = this._response.number
  1478. return result
  1479. }
  1480. Factory.prototype._createNgResponse = function() {
  1481. var text = this._response.number + ' あぼーん'
  1482. return this._doc.createTextNode(text)
  1483. }
  1484. Factory.prototype._createSubView = function(className, elements) {
  1485. var result = this._doc.createElement('div')
  1486. result.className = className
  1487. elements.forEach(result.appendChild.bind(result))
  1488. return result
  1489. }
  1490. Factory.prototype.createChildrenElement = function(responseElements) {
  1491. return this._createSubView('children', responseElements)
  1492. }
  1493. Factory.prototype.createSameIdElement = function(responseElements) {
  1494. return this._createSubView('sameId', responseElements)
  1495. }
  1496. Factory.prototype.createSameKorokoroElement = function(responseElements) {
  1497. return this._createSubView('sameKorokoro', responseElements)
  1498. }
  1499. return Factory
  1500. })()
  1501.  
  1502. var TableConfigViewHeader = (function() {
  1503. var createRemoveAllButton = function(doc) {
  1504. var result = doc.createElement('span')
  1505. result.className = 'removeAllButton'
  1506. result.textContent = '[すべて削除]'
  1507. return result
  1508. }
  1509. var create = function(doc, text) {
  1510. var result = doc.createElement('h2')
  1511. result.appendChild(doc.createTextNode(text))
  1512. result.appendChild(createRemoveAllButton(doc))
  1513. return result
  1514. }
  1515. return {create}
  1516. })()
  1517.  
  1518. var NgItemAddP = (function() {
  1519. function NgItemAddP(doc) {
  1520. this.doc = doc
  1521. }
  1522. NgItemAddP.prototype._createOption = function(value, text) {
  1523. var result = this.doc.createElement('option')
  1524. result.value = value
  1525. result.textContent = text
  1526. return result
  1527. }
  1528. NgItemAddP.prototype._createTargetSelect = function() {
  1529. var result = this.doc.createElement('select')
  1530. result.appendChild(this._createOption('thread', 'このスレッド'))
  1531. result.appendChild(this._createOption('board', 'この板'))
  1532. result.appendChild(this._createOption('all', '全体'))
  1533. return result
  1534. }
  1535. NgItemAddP.prototype._createNgTextInput = function() {
  1536. var result = this.doc.createElement('input')
  1537. result.className = 'ngTextInput'
  1538. return result
  1539. }
  1540. NgItemAddP.prototype._createNgWordAddButton = function() {
  1541. var result = this.doc.createElement('input')
  1542. result.className = 'addButton'
  1543. result.type = 'button'
  1544. result.value = '追加'
  1545. return result
  1546. }
  1547. NgItemAddP.prototype.elem = function() {
  1548. var result = this.doc.createElement('p')
  1549. result.className = 'add'
  1550. result.appendChild(this._createTargetSelect())
  1551. result.appendChild(this._createNgTextInput())
  1552. result.appendChild(this._createNgWordAddButton())
  1553. return result
  1554. }
  1555. NgItemAddP.create = function(doc) {
  1556. return new NgItemAddP(doc).elem()
  1557. }
  1558. return NgItemAddP
  1559. })()
  1560.  
  1561. var Table = (function() {
  1562. var addTH = curry(function(row, text) {
  1563. var doc = row.ownerDocument
  1564. var th = doc.createElement('th')
  1565. th.textContent = text
  1566. row.appendChild(th)
  1567. })
  1568. var isNode = function(v) {
  1569. return Boolean(v && v.nodeType)
  1570. }
  1571. var addCell = function(row, child) {
  1572. var result = row.insertCell()
  1573. if (isNode(child))
  1574. result.appendChild(child)
  1575. else
  1576. result.textContent = child
  1577. return result
  1578. }
  1579. var addDelCell = function(row) {
  1580. var result = addCell(row, '削除')
  1581. result.className = 'removeButton'
  1582. return result
  1583. }
  1584. function Table(o) {
  1585. Object.assign(this, o)
  1586. }
  1587. Table.create = function(o) {
  1588. return new Table(o).elem()
  1589. }
  1590. Table.prototype.setTHead = function(tHead) {
  1591. this.tHeadTexts.forEach(addTH(tHead.insertRow()))
  1592. }
  1593. Table.prototype.addDelCell = function(tRow, rowDataObj) {
  1594. var c = addDelCell(tRow)
  1595. this.setDelCellDataset(c.dataset, rowDataObj)
  1596. }
  1597. Table.prototype.setTRow = function(tRow, rowDataObj) {
  1598. for (var cellChild of this.cellChildrenOf(rowDataObj, this.doc))
  1599. addCell(tRow, cellChild)
  1600. this.addDelCell(tRow, rowDataObj)
  1601. }
  1602. Table.prototype.setTBody = function(tBody) {
  1603. this.rowDataObjs.slice().reverse().forEach(function(rowDataObj) {
  1604. this.setTRow(tBody.insertRow(), rowDataObj)
  1605. }, this)
  1606. }
  1607. Table.prototype.elem = function() {
  1608. var result = this.doc.createElement('table')
  1609. this.setTHead(result.createTHead())
  1610. this.setTBody(result.createTBody())
  1611. return result
  1612. }
  1613. return Table
  1614. })()
  1615.  
  1616. var ConfigView = (function() {
  1617. var ConfigView = function(document, config) {
  1618. this._doc = document
  1619. this._config = config
  1620. this.rootElement = null
  1621. listeners.set(this._eventTypes, this)
  1622. listeners.add(this._eventTypes, this, config)
  1623. }
  1624. ConfigView.prototype._eventTypes = []
  1625. ConfigView.prototype.createRootElem = async function() {
  1626. return this.rootElement = await this._createRootElem()
  1627. }
  1628. ConfigView.prototype.destroy = function() {
  1629. this.rootElement.remove()
  1630. listeners.remove(this._eventTypes, this, this._config)
  1631. }
  1632. ConfigView.prototype._createRootElem = async function() {
  1633. var result = this._doc.createElement('div')
  1634. result.className = 'config'
  1635. result.appendChild(await this._createRootChild())
  1636. return result
  1637. }
  1638. ConfigView.prototype._createRootChild = async function() {
  1639. throw new Error('ConfigView#_createRootChild() must be implemented')
  1640. }
  1641.  
  1642. ConfigView.createToggle = function(document, configViewConstructor) {
  1643. var result = document.createElement('span')
  1644. result.className = configViewConstructor.toggleClass
  1645. result.textContent = configViewConstructor.toggleText
  1646. return result
  1647. }
  1648. return ConfigView
  1649. })()
  1650.  
  1651. var ViewConfigView = (function(_super) {
  1652. var ViewConfigView = function(document, config) {
  1653. _super.call(this, document, config)
  1654. }
  1655. ViewConfigView.toggleText = '表示'
  1656. ViewConfigView.toggleClass = 'viewToggle'
  1657. ViewConfigView.prototype = Object.create(_super.prototype)
  1658. ViewConfigView.prototype.constructor = ViewConfigView
  1659. ViewConfigView.prototype._createCenteringP = async function() {
  1660. var checkbox = this._doc.createElement('input')
  1661. checkbox.type = 'checkbox'
  1662. checkbox.checked = await this._config.isPageCentering()
  1663. var label = this._doc.createElement('label')
  1664. label.appendChild(checkbox)
  1665. label.appendChild(this._doc.createTextNode('ページ中央に配置'))
  1666. var result = this._doc.createElement('p')
  1667. result.className = 'centering'
  1668. result.appendChild(label)
  1669. return result
  1670. }
  1671. ViewConfigView.prototype._createMaxWidthP = async function() {
  1672. var input = this._doc.createElement('input')
  1673. input.type = 'number'
  1674. input.valueAsNumber = await this._config.getPageMaxWidth()
  1675. var label = this._doc.createElement('label')
  1676. label.appendChild(this._doc.createTextNode('最大幅'))
  1677. label.appendChild(input)
  1678. label.appendChild(this._doc.createTextNode('px'))
  1679. var result = this._doc.createElement('p')
  1680. result.className = 'maxWidth'
  1681. result.appendChild(label)
  1682. return result
  1683. }
  1684. ViewConfigView.prototype._createNgVisibleP = async function() {
  1685. var checkbox = this._doc.createElement('input')
  1686. checkbox.type = 'checkbox'
  1687. checkbox.checked = await this._config.isNgVisible()
  1688. var label = this._doc.createElement('label')
  1689. label.appendChild(checkbox)
  1690. label.appendChild(this._doc.createTextNode('NG設定によるあぼーんを表示'))
  1691. var result = this._doc.createElement('p')
  1692. result.className = 'ngVisible'
  1693. result.appendChild(label)
  1694. return result
  1695. }
  1696. ViewConfigView.prototype._createRootChild = async function() {
  1697. var h2 = this._doc.createElement('h2')
  1698. h2.textContent = '表示'
  1699. var result = this._doc.createElement('section')
  1700. result.className = 'viewSection'
  1701. result.appendChild(h2)
  1702. result.appendChild(await this._createCenteringP())
  1703. result.appendChild(await this._createMaxWidthP())
  1704. result.appendChild(await this._createNgVisibleP())
  1705. return result
  1706. }
  1707. ViewConfigView.prototype.isPageCenteringChecked = function() {
  1708. return this.rootElement
  1709. .querySelector('.viewSection .centering label input')
  1710. .checked
  1711. }
  1712. ViewConfigView.prototype.isNgVisibleChecked = function() {
  1713. return this.rootElement
  1714. .querySelector('.viewSection .ngVisible label input')
  1715. .checked
  1716. }
  1717. ViewConfigView.prototype.getPageMaxWidthValue = function() {
  1718. return this.rootElement
  1719. .querySelector('.viewSection .maxWidth label input')
  1720. .valueAsNumber
  1721. }
  1722. return ViewConfigView
  1723. })(ConfigView)
  1724.  
  1725. var TableConfigView = (function(_super) {
  1726. function TableConfigView(document, config) {
  1727. _super.call(this, document, config)
  1728. this._arrayStoreChangedListener = this._arrayStoreChanged.bind(this)
  1729. this._getArrayStore()
  1730. .addEventListener('changed', this._arrayStoreChangedListener)
  1731. }
  1732. TableConfigView.prototype = Object.create(_super.prototype)
  1733. TableConfigView.prototype.constructor = TableConfigView
  1734. TableConfigView.prototype.destroy = function() {
  1735. _super.prototype.destroy.call(this)
  1736. this._getArrayStore()
  1737. .removeEventListener('changed', this._arrayStoreChangedListener)
  1738. }
  1739. TableConfigView.prototype._getTable = function() {
  1740. return this.rootElement.querySelector('table')
  1741. }
  1742. TableConfigView.prototype._arrayStoreChanged = async function(array) {
  1743. var newTable = await this._createTable(array)
  1744. var oldTable = this._getTable()
  1745. oldTable.parentNode.replaceChild(newTable, oldTable)
  1746. }
  1747. TableConfigView.prototype._getArrayStore = function() {
  1748. throw new Error('TableConfigView#_getArrayStore() must be implemented')
  1749. }
  1750. TableConfigView.prototype._createTable = function() {
  1751. throw new Error('TableConfigView#_createTable() must be implemented')
  1752. }
  1753. return TableConfigView
  1754. })(ConfigView)
  1755.  
  1756. var NgIdConfigView = (function(_super) {
  1757. var NgIdConfigView = function(document, config) {
  1758. _super.call(this, document, config)
  1759. }
  1760. NgIdConfigView.toggleText = 'NGID'
  1761. NgIdConfigView.toggleClass = 'ngIdToggle'
  1762. NgIdConfigView.prototype = Object.create(_super.prototype)
  1763. NgIdConfigView.prototype.constructor = NgIdConfigView
  1764. NgIdConfigView.prototype._getArrayStore = function() {
  1765. return this._config.ngIds
  1766. }
  1767. NgIdConfigView.prototype._createTable = async function(ngIds) {
  1768. var tHeadTexts = ['板', '有効日', 'ID', '']
  1769. var rowDataObjs = ngIds || await this._config.ngIds.get()
  1770. var cellChildrenOf = function(ngId) {
  1771. return [ngId.boardId, ngId.getActiveDateString(), ngId.id]
  1772. }
  1773. var setDelCellDataset = function(dataset, ngId) {
  1774. dataset.boardId = ngId.boardId
  1775. dataset.activeDate = ngId.activeDate
  1776. dataset.id = ngId.id
  1777. }
  1778. return Table.create({
  1779. doc: this._doc,
  1780. tHeadTexts,
  1781. rowDataObjs,
  1782. cellChildrenOf,
  1783. setDelCellDataset,
  1784. })
  1785. }
  1786. NgIdConfigView.prototype._createRootChild = async function() {
  1787. var result = this._doc.createElement('section')
  1788. result.className = 'ngIdSection'
  1789. result.appendChild(TableConfigViewHeader.create(this._doc, 'NGID'))
  1790. result.appendChild(await this._createTable())
  1791. return result
  1792. }
  1793. return NgIdConfigView
  1794. })(TableConfigView)
  1795.  
  1796. var NgKorokoroConfigView = (function(_super) {
  1797. var NgKorokoroConfigView = function(document, config) {
  1798. _super.call(this, document, config)
  1799. }
  1800. NgKorokoroConfigView.toggleText = 'NGID(ワッチョイ)'
  1801. NgKorokoroConfigView.toggleClass = 'ngKorokoroToggle'
  1802. NgKorokoroConfigView.prototype = Object.create(_super.prototype)
  1803. NgKorokoroConfigView.prototype.constructor = NgKorokoroConfigView
  1804. NgKorokoroConfigView.prototype._getArrayStore = function() {
  1805. return this._config.ngKorokoros
  1806. }
  1807. NgKorokoroConfigView.prototype._createTable = async function(ngKorokoros) {
  1808. var tHeadTexts = ['板', '有効期間', 'ID', '']
  1809. var rowDataObjs = ngKorokoros || await this._getArrayStore().get()
  1810. var cellChildrenOf = function(ngKorokoro) {
  1811. return [
  1812. ngKorokoro.boardId,
  1813. ngKorokoro.getAvailablePeriodString(),
  1814. ngKorokoro.korokoro,
  1815. ]
  1816. }
  1817. var setDelCellDataset = function(dataset, ngKorokoro) {
  1818. dataset.boardId = ngKorokoro.boardId
  1819. dataset.startOfAvailablePeriod = ngKorokoro.startOfAvailablePeriod
  1820. dataset.korokoro = ngKorokoro.korokoro
  1821. }
  1822. return Table.create({
  1823. doc: this._doc,
  1824. tHeadTexts,
  1825. rowDataObjs,
  1826. cellChildrenOf,
  1827. setDelCellDataset,
  1828. })
  1829. }
  1830. NgKorokoroConfigView.prototype._createRootChild = async function() {
  1831. var result = this._doc.createElement('section')
  1832. result.className = 'ngKorokoroSection'
  1833. result.appendChild(TableConfigViewHeader.create(this._doc, 'NGID(ワッチョイ)'))
  1834. result.appendChild(await this._createTable())
  1835. return result
  1836. }
  1837. return NgKorokoroConfigView
  1838. })(TableConfigView)
  1839.  
  1840. var BoundableNgItemConfigView = (function(_super) {
  1841. var BoundableNgItemConfigView = function(document, config) {
  1842. _super.call(this, document, config)
  1843. }
  1844. BoundableNgItemConfigView.prototype = Object.create(_super.prototype)
  1845. BoundableNgItemConfigView.prototype.constructor = BoundableNgItemConfigView
  1846. BoundableNgItemConfigView.prototype._createTable = async function(boundableNgItems) {
  1847. var tHeadTexts = ['板', 'スレッド', this._getHeaderText(), '']
  1848. var rowDataObjs = boundableNgItems || (await this._getArrayStore().get())
  1849. var cellChildrenOf = function(ngItem) {
  1850. return [ngItem.boardId, ngItem.threadNumber, this._getNgValueOf(ngItem)]
  1851. }.bind(this)
  1852. var setDelCellDataset = function(dataset, ngItem) {
  1853. if (ngItem.boardId) dataset.boardId = ngItem.boardId
  1854. if (ngItem.threadNumber) dataset.threadNumber = ngItem.threadNumber
  1855. this._setNgValueToDataset(this._getNgValueOf(ngItem), dataset)
  1856. }.bind(this)
  1857. return Table.create({
  1858. doc: this._doc,
  1859. tHeadTexts,
  1860. rowDataObjs,
  1861. cellChildrenOf,
  1862. setDelCellDataset,
  1863. })
  1864. }
  1865. BoundableNgItemConfigView.prototype._createRootChild = async function() {
  1866. var result = this._doc.createElement('section')
  1867. result.className = this._getSectionClassName()
  1868. result.appendChild(TableConfigViewHeader.create(this._doc, this._getHeaderText()))
  1869. result.appendChild(NgItemAddP.create(this._doc))
  1870. result.appendChild(await this._createTable())
  1871. return result
  1872. }
  1873. BoundableNgItemConfigView.prototype._getNgTextInput = function() {
  1874. return this.rootElement.querySelector('.add .ngTextInput')
  1875. }
  1876. BoundableNgItemConfigView.prototype.getNgTextInputValue = function() {
  1877. return this._getNgTextInput().value.trim()
  1878. }
  1879. BoundableNgItemConfigView.prototype.clearNgTextInputValue = function() {
  1880. this._getNgTextInput().value = ''
  1881. }
  1882. BoundableNgItemConfigView.prototype.getNgItemAddTarget = function() {
  1883. return this.rootElement.querySelector('.add select').value
  1884. }
  1885. BoundableNgItemConfigView.prototype._getNgValueOf = function() {
  1886. throw new Error('BoundableNgItemConfigView#_getNgValueOf(boundableNgItem) must be implemented')
  1887. }
  1888. BoundableNgItemConfigView.prototype._setNgValueToDataset = function() {
  1889. throw new Error('BoundableNgItemConfigView#_setNgValueToDataset(ngValue, dataset) must be implemented')
  1890. }
  1891. BoundableNgItemConfigView.prototype._getSectionClassName = function() {
  1892. throw new Error('BoundableNgItemConfigView#_getSectionClassName() must be implemented')
  1893. }
  1894. BoundableNgItemConfigView.prototype._getHeaderText = function() {
  1895. throw new Error('BoundableNgItemConfigView#_getHeaderText() must be implemented')
  1896. }
  1897. return BoundableNgItemConfigView
  1898. })(TableConfigView)
  1899.  
  1900. var NgWordConfigView = (function(_super) {
  1901. var NgWordConfigView = function(document, config) {
  1902. _super.call(this, document, config)
  1903. }
  1904. NgWordConfigView.toggleText = 'NGワード'
  1905. NgWordConfigView.toggleClass = 'ngWordToggle'
  1906. NgWordConfigView.prototype = Object.create(_super.prototype)
  1907. NgWordConfigView.prototype.constructor = NgWordConfigView
  1908. NgWordConfigView.prototype._getArrayStore = function() {
  1909. return this._config.ngWords
  1910. }
  1911. NgWordConfigView.prototype._getNgValueOf = function(ngWord) {
  1912. return ngWord.ngWord
  1913. }
  1914. NgWordConfigView.prototype._setNgValueToDataset = function(ngValue, dataset) {
  1915. dataset.ngWord = ngValue
  1916. }
  1917. NgWordConfigView.prototype._getSectionClassName = function() {
  1918. return 'ngWordSection'
  1919. }
  1920. NgWordConfigView.prototype._getHeaderText = function() {
  1921. return 'NGワード'
  1922. }
  1923. return NgWordConfigView
  1924. })(BoundableNgItemConfigView)
  1925.  
  1926. var NgNameConfigView = (function(_super) {
  1927. var NgNameConfigView = function(document, config) {
  1928. _super.call(this, document, config)
  1929. }
  1930. NgNameConfigView.toggleText = 'NGName'
  1931. NgNameConfigView.toggleClass = 'ngNameToggle'
  1932. NgNameConfigView.prototype = Object.create(_super.prototype)
  1933. NgNameConfigView.prototype.constructor = NgNameConfigView
  1934. NgNameConfigView.prototype._getArrayStore = function() {
  1935. return this._config.ngNames
  1936. }
  1937. NgNameConfigView.prototype._getNgValueOf = function(ngName) {
  1938. return ngName.ngName
  1939. }
  1940. NgNameConfigView.prototype._setNgValueToDataset = function(ngValue, dataset) {
  1941. dataset.ngName = ngValue
  1942. }
  1943. NgNameConfigView.prototype._getSectionClassName = function() {
  1944. return 'ngNameSection'
  1945. }
  1946. NgNameConfigView.prototype._getHeaderText = function() {
  1947. return 'NGName'
  1948. }
  1949. return NgNameConfigView
  1950. })(BoundableNgItemConfigView)
  1951.  
  1952. var NgIdTailConfigView = (function(_super) {
  1953. var NgIdTailConfigView = function(document, config) {
  1954. _super.call(this, document, config)
  1955. }
  1956. NgIdTailConfigView.toggleText = 'NGID(末尾)'
  1957. NgIdTailConfigView.toggleClass = 'ngIdTailToggle'
  1958. NgIdTailConfigView.prototype = Object.create(_super.prototype)
  1959. NgIdTailConfigView.prototype.constructor = NgIdTailConfigView
  1960. NgIdTailConfigView.prototype._getArrayStore = function() {
  1961. return this._config.ngIdTails
  1962. }
  1963. NgIdTailConfigView.prototype._getNgValueOf = function(ngIdTail) {
  1964. return ngIdTail.ngIdTail
  1965. }
  1966. NgIdTailConfigView.prototype._setNgValueToDataset = function(ngValue, dataset) {
  1967. dataset.ngIdTail = ngValue
  1968. }
  1969. NgIdTailConfigView.prototype._getSectionClassName = function() {
  1970. return 'ngIdTailSection'
  1971. }
  1972. NgIdTailConfigView.prototype._getHeaderText = function() {
  1973. return 'NGID(末尾)'
  1974. }
  1975. return NgIdTailConfigView
  1976. })(BoundableNgItemConfigView)
  1977.  
  1978. var NgSlipConfigView = (function(_super) {
  1979. var textOf = function(type) {
  1980. switch (type) {
  1981. case 'id': return 'ID'
  1982. case 'korokoro': return 'ID(ワッチョイ)'
  1983. default: throw new TypeError(String(type))
  1984. }
  1985. }
  1986. var createOption = function(doc, value, text) {
  1987. var result = doc.createElement('option')
  1988. result.value = value
  1989. result.textContent = text
  1990. return result
  1991. }
  1992. var createTypeSelect = function(doc) {
  1993. var result = doc.createElement('select')
  1994. result.className = 'type'
  1995. result.appendChild(createOption(doc, 'korokoro', 'ID(ワッチョイ)'))
  1996. result.appendChild(createOption(doc, 'id', 'ID'))
  1997. return result
  1998. }
  1999. var createTargetSelect = function(doc) {
  2000. var result = doc.createElement('select')
  2001. result.className = 'target'
  2002. result.appendChild(createOption(doc, 'thread', 'このスレッド'))
  2003. result.appendChild(createOption(doc, 'board', 'この板'))
  2004. return result
  2005. }
  2006. var createAddButton = function(doc) {
  2007. var result = doc.createElement('input')
  2008. result.className = 'addButton'
  2009. result.type = 'button'
  2010. result.value = 'NG'
  2011. return result
  2012. }
  2013. var createInput = function(doc) {
  2014. var result = doc.createElement('p')
  2015. result.className = 'add'
  2016. result.appendChild(createTargetSelect(doc))
  2017. result.appendChild(doc.createTextNode('内で'))
  2018. result.appendChild(createTypeSelect(doc))
  2019. result.appendChild(doc.createTextNode('を持たないレスを'))
  2020. result.appendChild(createAddButton(doc))
  2021. return result
  2022. }
  2023. var NgSlipConfigView = function(document, config) {
  2024. _super.call(this, document, config)
  2025. }
  2026. NgSlipConfigView.toggleText = 'NGSlip'
  2027. NgSlipConfigView.toggleClass = 'ngSlipToggle'
  2028. NgSlipConfigView.prototype = Object.create(_super.prototype)
  2029. NgSlipConfigView.prototype.constructor = NgSlipConfigView
  2030. NgSlipConfigView.prototype._getArrayStore = function() {
  2031. return this._config.ngSlips
  2032. }
  2033. NgSlipConfigView.prototype._createTable = async function(ngSlips) {
  2034. var tHeadTexts = ['板', 'スレッド', '対象', '']
  2035. var rowDataObjs = ngSlips || await this._getArrayStore().get()
  2036. var cellChildrenOf = function(ngSlip) {
  2037. return [ngSlip.boardId, ngSlip.threadNumber, textOf(ngSlip.type)]
  2038. }
  2039. var setDelCellDataset = function(dataset, ngSlip) {
  2040. dataset.boardId = ngSlip.boardId
  2041. if (ngSlip.threadNumber) dataset.threadNumber = ngSlip.threadNumber
  2042. dataset.type = ngSlip.type
  2043. }
  2044. return Table.create({
  2045. doc: this._doc,
  2046. tHeadTexts,
  2047. rowDataObjs,
  2048. cellChildrenOf,
  2049. setDelCellDataset,
  2050. })
  2051. }
  2052. NgSlipConfigView.prototype._createRootChild = async function() {
  2053. var result = this._doc.createElement('section')
  2054. result.className = 'ngSlipSection'
  2055. result.appendChild(TableConfigViewHeader.create(this._doc, 'NGSlip'))
  2056. result.appendChild(createInput(this._doc))
  2057. result.appendChild(await this._createTable())
  2058. return result
  2059. }
  2060. NgSlipConfigView.prototype.getNgItemAddTarget = function() {
  2061. return this.rootElement.querySelector('.target').value
  2062. }
  2063. NgSlipConfigView.prototype.getNgSlipType = function() {
  2064. return this.rootElement.querySelector('.type').value
  2065. }
  2066. return NgSlipConfigView
  2067. })(TableConfigView)
  2068.  
  2069. var NgConfigView = (function(_super) {
  2070. function NgConfigView(document, config) {
  2071. _super.call(this, document, config)
  2072. this.configView = null
  2073. }
  2074. NgConfigView.toggleText = 'NG設定'
  2075. NgConfigView.toggleClass = 'ngToggle'
  2076. NgConfigView.prototype = Object.create(_super.prototype)
  2077. NgConfigView.prototype.constructor = NgConfigView
  2078. NgConfigView.prototype.destroy = function() {
  2079. _super.prototype.destroy.call(this)
  2080. if (this.configView) this.configView.destroy()
  2081. }
  2082. NgConfigView.prototype._createRootChild = function() {
  2083. var ngToggleBar = this._doc.createElement('div')
  2084. ngToggleBar.className = 'ngToggleBar'
  2085. ;[ NgWordConfigView,
  2086. NgNameConfigView,
  2087. NgIdConfigView,
  2088. NgKorokoroConfigView,
  2089. NgIdTailConfigView,
  2090. NgSlipConfigView,
  2091. ].forEach(function(constructor) {
  2092. var toggle = ConfigView.createToggle(this._doc, constructor)
  2093. ngToggleBar.appendChild(toggle)
  2094. }, this)
  2095. var result = this._doc.createElement('section')
  2096. result.className = 'ngSection'
  2097. result.appendChild(ngToggleBar)
  2098. return result
  2099. }
  2100. NgConfigView.prototype._addConfigViewOf = async function(constructor) {
  2101. this.configView = new constructor(this._doc, this._config)
  2102. this.rootElement
  2103. .querySelector('.ngSection')
  2104. .appendChild(await this.configView.createRootElem())
  2105. var toggle = this.rootElement
  2106. .querySelector(`.ngToggleBar .${constructor.toggleClass}`)
  2107. toggle.textContent = constructor.toggleText
  2108. toggle.classList.add('isSelected')
  2109. }
  2110. NgConfigView.prototype._deleteConfigView = function() {
  2111. this.configView.destroy()
  2112. var toggle = this.rootElement
  2113. .querySelector(`.ngToggleBar .${this.configView.constructor.toggleClass}`)
  2114. toggle.textContent = this.configView.constructor.toggleText
  2115. toggle.classList.remove('isSelected')
  2116. this.configView = null
  2117. }
  2118. NgConfigView.prototype._toggleConfigViewOf = async function(constructor) {
  2119. if (this.configView) {
  2120. var replace = !(this.configView instanceof constructor)
  2121. this._deleteConfigView()
  2122. if (replace) {
  2123. await this._addConfigViewOf(constructor)
  2124. }
  2125. } else {
  2126. await this._addConfigViewOf(constructor)
  2127. }
  2128. }
  2129. NgConfigView.prototype.toggleNgWordConfig = async function() {
  2130. await this._toggleConfigViewOf(NgWordConfigView)
  2131. }
  2132. NgConfigView.prototype.toggleNgNameConfig = async function() {
  2133. await this._toggleConfigViewOf(NgNameConfigView)
  2134. }
  2135. NgConfigView.prototype.toggleNgIdConfig = async function() {
  2136. await this._toggleConfigViewOf(NgIdConfigView)
  2137. }
  2138. NgConfigView.prototype.toggleNgKorokoroConfig = async function() {
  2139. await this._toggleConfigViewOf(NgKorokoroConfigView)
  2140. }
  2141. NgConfigView.prototype.toggleNgIdTailConfig = async function() {
  2142. await this._toggleConfigViewOf(NgIdTailConfigView)
  2143. }
  2144. NgConfigView.prototype.toggleNgSlipConfig = async function() {
  2145. await this._toggleConfigViewOf(NgSlipConfigView)
  2146. }
  2147. NgConfigView.prototype.getNgTextInputValue = function() {
  2148. return this.configView.getNgTextInputValue()
  2149. }
  2150. NgConfigView.prototype.clearNgTextInputValue = function() {
  2151. this.configView.clearNgTextInputValue()
  2152. }
  2153. NgConfigView.prototype.getNgItemAddTarget = function() {
  2154. return this.configView.getNgItemAddTarget()
  2155. }
  2156. NgConfigView.prototype.getNgSlipType = function() {
  2157. return this.configView.getNgSlipType()
  2158. }
  2159. return NgConfigView
  2160. })(ConfigView)
  2161.  
  2162. var ThreadHistoryConfigView = (function(_super) {
  2163. var ThreadHistoryConfigView = function(document, config) {
  2164. _super.call(this, document, config)
  2165. }
  2166. ThreadHistoryConfigView.toggleText = '履歴'
  2167. ThreadHistoryConfigView.toggleClass = 'threadHistoryToggle'
  2168. ThreadHistoryConfigView.prototype = Object.create(_super.prototype)
  2169. ThreadHistoryConfigView.prototype.constructor = ThreadHistoryConfigView
  2170. ThreadHistoryConfigView.prototype._getArrayStore = function() {
  2171. return this._config.threadHistories
  2172. }
  2173. ThreadHistoryConfigView.prototype._createTable = async function(threadHistories) {
  2174. var tHeadTexts = ['タイトル', 'レス', '']
  2175. var rowDataObjs = threadHistories || await this._getArrayStore().get()
  2176. var cellChildrenOf = function(threadHistory, doc) {
  2177. var a = doc.createElement('a')
  2178. a.href = threadHistory.url
  2179. a.textContent = threadHistory.title
  2180. return [a, threadHistory.resNum]
  2181. }
  2182. var setDelCellDataset = function(dataset, threadHistory) {
  2183. dataset.url = threadHistory.url
  2184. dataset.title = threadHistory.title
  2185. dataset.resNum = threadHistory.resNum
  2186. }
  2187. return Table.create({
  2188. doc: this._doc,
  2189. tHeadTexts,
  2190. rowDataObjs,
  2191. cellChildrenOf,
  2192. setDelCellDataset,
  2193. })
  2194. }
  2195. ThreadHistoryConfigView.prototype._createRootChild = async function() {
  2196. var result = this._doc.createElement('section')
  2197. result.className = 'threadHistorySection'
  2198. result.appendChild(TableConfigViewHeader.create(this._doc, '履歴'))
  2199. result.appendChild(await this._createTable())
  2200. return result
  2201. }
  2202. return ThreadHistoryConfigView
  2203. })(TableConfigView)
  2204.  
  2205. var ThreadView = (function() {
  2206. var createTopBar = function(document) {
  2207. var createToggle = ConfigView.createToggle
  2208. var result = document.createElement('div')
  2209. result.className = 'topBar'
  2210. result.appendChild(createToggle(document, NgConfigView))
  2211. result.appendChild(createToggle(document, ViewConfigView))
  2212. result.appendChild(createToggle(document, ThreadHistoryConfigView))
  2213. return result
  2214. }
  2215. var createBottomBar = function(document) {
  2216. var reloadButton = document.createElement('input')
  2217. reloadButton.type = 'button'
  2218. reloadButton.value = '新着レスの取得'
  2219. reloadButton.className = 'reloadButton'
  2220. var reloadMessage = document.createElement('span')
  2221. reloadMessage.className = 'reloadMessage'
  2222. var result = document.createElement('div')
  2223. result.className = 'bottomBar'
  2224. result.appendChild(reloadButton)
  2225. result.appendChild(reloadMessage)
  2226. return result
  2227. }
  2228. var createRoot = function(document) {
  2229. var main = document.createElement('div')
  2230. main.className = 'main'
  2231. var result = document.createElement('div')
  2232. result.className = 'threadView'
  2233. result.appendChild(createTopBar(document))
  2234. result.appendChild(main)
  2235. result.appendChild(createBottomBar(document))
  2236. return result
  2237. }
  2238. var ThreadView = function(document, thread) {
  2239. this.doc = document
  2240. this._thread = thread
  2241. this.rootElement = createRoot(document)
  2242. this._responseViews = []
  2243. this.configView = null
  2244. this.responsePostForm = null
  2245. thread.addEventListener('responsesAdded', this._responsesAdded.bind(this))
  2246. thread.config.addEventListener('pageCenteringChanged'
  2247. , this.setPageCentering.bind(this))
  2248. thread.config.addEventListener('pageMaxWidthChanged'
  2249. , this._updateStyle.bind(this))
  2250. thread.config.addEventListener('ngVisibleChanged'
  2251. , this._updateStyle.bind(this))
  2252. }
  2253. ThreadView.prototype.getReloadButton = function() {
  2254. return this.rootElement.querySelector('.reloadButton')
  2255. }
  2256. ThreadView.prototype.getReloadMessageElement = function() {
  2257. return this.rootElement.querySelector('.reloadMessage')
  2258. }
  2259. ThreadView.prototype._getTopBar = function() {
  2260. return this.rootElement.querySelector('.topBar')
  2261. }
  2262. ThreadView.prototype.replace = function(threadRootElement) {
  2263. var p = threadRootElement.parentNode
  2264. p.replaceChild(this.rootElement, threadRootElement)
  2265. }
  2266. ThreadView.prototype.disableReload = function() {
  2267. this.rootElement.querySelector('.bottomBar').remove()
  2268. }
  2269. ThreadView.prototype._createResponseViews = function(responses) {
  2270. return responses.map(function(r) {
  2271. return new ResponseView(this.doc, r, true)
  2272. }, this)
  2273. }
  2274. ThreadView.prototype._getMainElement = function() {
  2275. return this.rootElement.querySelector('.main')
  2276. }
  2277. ThreadView.prototype._addResponseViewsToMainElement = function(views) {
  2278. var main = this._getMainElement()
  2279. views.map(prop('rootElement')).forEach(main.appendChild.bind(main))
  2280. }
  2281. ThreadView.prototype._getNewResponseBar = function() {
  2282. return this.rootElement.querySelector('#new')
  2283. }
  2284. ThreadView.prototype._removeNewResponseBar = function() {
  2285. var e = this._getNewResponseBar()
  2286. if (e) e.remove()
  2287. }
  2288. ThreadView.prototype._addNewResponseBarIfRequired = function() {
  2289. var newResCount = this._thread.newResCount
  2290. if (newResCount === 0) return
  2291. const newResBarText = this.doc.createElement('p')
  2292. newResBarText.textContent = `${newResCount} 件の新着レス`
  2293. var newResBar = this.doc.createElement('div')
  2294. newResBar.id = 'new'
  2295. newResBar.appendChild(newResBarText)
  2296. var views = this._responseViews
  2297. var e = views[views.length - newResCount].rootElement
  2298. e.parentNode.insertBefore(newResBar, e)
  2299. }
  2300. ThreadView.prototype._scrollToNewResponseBar = function() {
  2301. if (!this._getNewResponseBar()) return
  2302. this.doc.location.hash = ''
  2303. this.doc.location.hash = '#new'
  2304. }
  2305. ThreadView.prototype._responsesAdded = function(addedResponses) {
  2306. var views = this._createResponseViews(addedResponses)
  2307. ;[].push.apply(this._responseViews, views)
  2308. this._addResponseViewsToMainElement(views)
  2309. this._removeNewResponseBar()
  2310. this._addNewResponseBarIfRequired()
  2311. this._scrollToNewResponseBar()
  2312. }
  2313. ThreadView.prototype._toggleSubView = function(getView, toggle) {
  2314. for (var i = 0; i < this._responseViews.length; i++) {
  2315. var v = getView(this._responseViews[i])
  2316. if (v) {
  2317. toggle(v)
  2318. break
  2319. }
  2320. }
  2321. }
  2322. ThreadView.prototype.toggleResponseChildren = function(numElem) {
  2323. this._toggleSubView(invoke('getResponseViewByNumElem', [numElem])
  2324. , invoke('toggleChildren', []))
  2325. }
  2326. ThreadView.prototype.toggleSameIdResponses = function(idValElem) {
  2327. this._toggleSubView(invoke('getResponseViewByIdValElem', [idValElem])
  2328. , invoke('toggleSameId', []))
  2329. }
  2330. ThreadView.prototype.toggleSameKorokoroResponses = function(korokoroValElem) {
  2331. this._toggleSubView(invoke('getResponseViewByKorokoroValElem', [korokoroValElem])
  2332. , invoke('toggleSameKorokoro', []))
  2333. }
  2334. ThreadView.prototype._removeConfigView = function(constructor) {
  2335. var v = this.configView
  2336. if (!v) return true
  2337. this.configView = null
  2338. v.destroy()
  2339. var toggle = this.rootElement
  2340. .querySelector(`.topBar .${v.constructor.toggleClass}`)
  2341. toggle.textContent = v.constructor.toggleText
  2342. toggle.classList.remove('isSelected')
  2343. return !(v instanceof constructor)
  2344. }
  2345. ThreadView.prototype._addConfigView = async function(constructor) {
  2346. this.configView = new constructor(this.doc, this._thread.config)
  2347. this._getTopBar().appendChild(await this.configView.createRootElem())
  2348. var toggle = this.rootElement
  2349. .querySelector(`.topBar .${constructor.toggleClass}`)
  2350. toggle.textContent = constructor.toggleText
  2351. toggle.classList.add('isSelected')
  2352. }
  2353. ThreadView.prototype._toggleConfig = async function(constructor) {
  2354. if (this._removeConfigView(constructor)) await this._addConfigView(constructor)
  2355. }
  2356. ThreadView.prototype.toggleNgConfig = async function() {
  2357. await this._toggleConfig(NgConfigView)
  2358. }
  2359. ThreadView.prototype.toggleViewConfig = async function() {
  2360. await this._toggleConfig(ViewConfigView)
  2361. }
  2362. ThreadView.prototype.toggleNgWordConfig = function() {
  2363. if (!(this.configView instanceof NgConfigView)) throw new Error()
  2364. this.configView.toggleNgWordConfig()
  2365. }
  2366. ThreadView.prototype.toggleNgNameConfig = function() {
  2367. if (!(this.configView instanceof NgConfigView)) throw new Error()
  2368. this.configView.toggleNgNameConfig()
  2369. }
  2370. ThreadView.prototype.toggleNgIdConfig = function() {
  2371. if (!(this.configView instanceof NgConfigView)) throw new Error()
  2372. this.configView.toggleNgIdConfig()
  2373. }
  2374. ThreadView.prototype.toggleNgKorokoroConfig = function() {
  2375. if (!(this.configView instanceof NgConfigView)) throw new Error()
  2376. this.configView.toggleNgKorokoroConfig()
  2377. }
  2378. ThreadView.prototype.toggleNgIdTailConfig = function() {
  2379. if (!(this.configView instanceof NgConfigView)) throw new Error()
  2380. this.configView.toggleNgIdTailConfig()
  2381. }
  2382. ThreadView.prototype.toggleNgSlipConfig = function() {
  2383. if (!(this.configView instanceof NgConfigView)) throw new Error()
  2384. this.configView.toggleNgSlipConfig()
  2385. }
  2386. ThreadView.prototype.toggleThreadHistoryConfig = async function() {
  2387. await this._toggleConfig(ThreadHistoryConfigView)
  2388. }
  2389. ThreadView.prototype.close = function() {
  2390. if (this.responsePostForm) this.responsePostForm.remove()
  2391. this.responsePostForm = null
  2392. this.disableReload()
  2393. }
  2394. ThreadView.prototype.setPageCentering = function(pageCentering) {
  2395. var methodName = pageCentering ? 'add' : 'remove'
  2396. this.doc.documentElement.classList[methodName]('centering')
  2397. }
  2398. ThreadView.prototype.addStyle = async function() {
  2399. var e = this.doc.createElement('style')
  2400. e.id = 'threadViewerStyle'
  2401. e.textContent = await this._getStyleText()
  2402. this.doc.head.appendChild(e)
  2403. }
  2404. ThreadView.prototype._updateStyle = async function() {
  2405. this.doc.getElementById('threadViewerStyle')
  2406. .textContent = await this._getStyleText()
  2407. }
  2408. ThreadView.prototype._getNgVisibleStyle = async function() {
  2409. return await this._thread.config.isNgVisible()
  2410. ? ''
  2411. : '.threadView .main article.ng { display: none; }'
  2412. }
  2413. ThreadView.prototype._getStyleText = async function() {
  2414. return `
  2415. body {
  2416. font-family: initial;
  2417. font-size: initial;
  2418. line-height: initial;
  2419. color: initial;
  2420. background-color: rgb(239, 239, 239);
  2421. margin: 8px;
  2422. }
  2423. .navbar-fixed-top, .navbar-fixed-bottom {
  2424. position: absolute;
  2425. }
  2426. .container_body {
  2427. min-width: initial;
  2428. left: initial;
  2429. position: initial;
  2430. width: initial;
  2431. max-width: initial;
  2432. margin-right: initial;
  2433. margin-left: initial;
  2434. padding-left: initial;
  2435. padding-right: initial;
  2436. background: initial;
  2437. }
  2438. .escaped {
  2439. background-color: rgb(239, 239, 239);
  2440. }
  2441. html.centering {
  2442. max-width: ${await this._thread.config.getPageMaxWidth()}px;
  2443. margin: 0 auto;
  2444. }
  2445. .threadView,
  2446. .post_hover {
  2447. line-height: 1.5em;
  2448. }
  2449. .threadView .topBar {
  2450. position: sticky;
  2451. top: -1px;
  2452. background-color: rgb(239, 239, 239);
  2453. }
  2454. ${await this._getNgVisibleStyle()}
  2455. .threadView .main article header,
  2456. .threadView .topBar .config table .removeButton,
  2457. .threadView .topBar .config .threadHistorySection table th:nth-child(2),
  2458. .threadView .main article header time,
  2459. .threadView .main article header .id {
  2460. white-space: nowrap;
  2461. }
  2462. .threadView .main article header .name,
  2463. .post_hover header .name {
  2464. color: green;
  2465. font-weight: bold;
  2466. }
  2467. .threadView .topBar .ngToggle,
  2468. .threadView .topBar .ngWordToggle,
  2469. .threadView .topBar .ngNameToggle,
  2470. .threadView .topBar .ngIdToggle,
  2471. .threadView .topBar .ngKorokoroToggle,
  2472. .threadView .topBar .ngIdTailToggle,
  2473. .threadView .topBar .ngSlipToggle,
  2474. .threadView .topBar .viewToggle,
  2475. .threadView .topBar .threadHistoryToggle {
  2476. margin-right: 0.5em;
  2477. display: inline-block;
  2478. height: 1.5em;
  2479. }
  2480. .threadView .main article header .name,
  2481. .threadView .main article header time,
  2482. .threadView .main article header .id,
  2483. .post_hover header .name,
  2484. .post_hover header time,
  2485. .post_hover header .id {
  2486. margin-left: 0.5em;
  2487. }
  2488. .threadView .main article header .headerNumber.hasChild,
  2489. .threadView .main article header .id .value.sameIdExist,
  2490. .threadView .main article header .id .ngButton,
  2491. .threadView .main article header .korokoro .value.sameKorokoroExist,
  2492. .threadView .main article header .korokoro .ngButton,
  2493. .threadView .topBar .ngToggle,
  2494. .threadView .topBar .viewToggle,
  2495. .threadView .topBar .ngIdToggle,
  2496. .threadView .topBar .ngWordToggle,
  2497. .threadView .topBar .ngNameToggle,
  2498. .threadView .topBar .ngKorokoroToggle,
  2499. .threadView .topBar .ngIdTailToggle,
  2500. .threadView .topBar .ngSlipToggle,
  2501. .threadView .topBar .threadHistoryToggle,
  2502. .threadView .topBar .config h2 .removeAllButton,
  2503. .threadView .topBar .config table .removeButton {
  2504. cursor: pointer;
  2505. text-decoration: underline;
  2506. }
  2507. .threadView .topBar .ngToggle.isSelected,
  2508. .threadView .topBar .viewToggle.isSelected,
  2509. .threadView .topBar .ngIdToggle.isSelected,
  2510. .threadView .topBar .ngWordToggle.isSelected,
  2511. .threadView .topBar .ngNameToggle.isSelected,
  2512. .threadView .topBar .ngKorokoroToggle.isSelected,
  2513. .threadView .topBar .ngIdTailToggle.isSelected,
  2514. .threadView .topBar .ngSlipToggle.isSelected,
  2515. .threadView .topBar .threadHistoryToggle.isSelected {
  2516. background-color: black;
  2517. color: white;
  2518. }
  2519. .threadView .main article header .headerNumber.hasChild3,
  2520. .threadView .main article header .id .value.sameIdExist5 {
  2521. font-weight: bold;
  2522. color: red;
  2523. }
  2524. .threadView .main article .content,
  2525. .post_hover .content {
  2526. margin: 0 0 1em 1em;
  2527. }
  2528. .threadView .main article .content a,
  2529. .post_hover .content a {
  2530. color: blue;
  2531. text-decoration: underline !important;
  2532. }
  2533. .threadView .main article .content a:visited,
  2534. .post_hover .content a:visited {
  2535. color: purple;
  2536. }
  2537. .threadView .main article .content.asciiArt,
  2538. .post_hover .content.asciiArt {
  2539. white-space: nowrap;
  2540. /* https://ja.wikipedia.org/wiki/アスキーアート */
  2541. font-family: IPAMonaPGothic, "IPA モナー Pゴシック", Monapo, Mona, "MS PGothic", "MS Pゴシック", sans-serif;
  2542. font-size: 16px;
  2543. line-height: 18px;
  2544. }
  2545. .threadView .main article .sameId,
  2546. .threadView .main article .children,
  2547. .threadView .main article .sameKorokoro {
  2548. border-top: solid black thin;
  2549. border-left: solid black thin;
  2550. padding: 5px 0 0 5px;
  2551. }
  2552. .post_hover header .id .ngButton,
  2553. .post_hover header .korokoro .ngButton,
  2554. .post_hover .sameId,
  2555. .post_hover .children,
  2556. .post_hover .sameKorokoro {
  2557. display: none;
  2558. }
  2559. .threadView .main article .sameId > article > header .id .value,
  2560. .threadView .main article .sameKorokoro > article > header .korokoro .value {
  2561. color: black;
  2562. background-color: yellow;
  2563. }
  2564. .threadView .main article.ng,
  2565. .threadView .main article header .name,
  2566. .threadView .main article header .mail,
  2567. .threadView .main article header time,
  2568. .threadView .main article header .id,
  2569. .post_hover header .name,
  2570. .post_hover header .mail,
  2571. .post_hover header time,
  2572. .post_hover header .id,
  2573. .threadView .topBar .config h2 .removeAllButton {
  2574. font-size: smaller;
  2575. }
  2576. .threadView .topBar .config {
  2577. border: solid black thin;
  2578. padding: 0 0.5em;
  2579. }
  2580. .threadView .topBar .config h2 {
  2581. font-size: medium;
  2582. }
  2583. .threadView .topBar .config table {
  2584. border-collapse: collapse;
  2585. }
  2586. .threadView .topBar .config table th,
  2587. .threadView .topBar .config table td {
  2588. border: solid thin black;
  2589. line-height: 1.5em;
  2590. padding: 0 0.5em;
  2591. }
  2592. .threadView .topBar .config .ngWordSection table td:nth-child(3),
  2593. .threadView .topBar .config .ngNameSection table td:nth-child(3),
  2594. .threadView .topBar .config .threadHistorySection table td:nth-child(1) {
  2595. word-break: break-all;
  2596. }
  2597. .threadView .topBar .config .threadHistorySection table td:nth-child(2) {
  2598. text-align: right;
  2599. }
  2600. .threadView .topBar .config .viewSection .maxWidth {
  2601. margin-left: 2em;
  2602. }
  2603. .threadView .bottomBar {
  2604. padding: 1em 0;
  2605. }
  2606. .postTarget {
  2607. width: 100%;
  2608. }
  2609. .postTarget.loading {
  2610. display: none;
  2611. }
  2612. #new {
  2613. padding-top: 3em;
  2614. }
  2615. #new > p {
  2616. background-color: lightblue;
  2617. padding-left: 0.5em;
  2618. }
  2619. .threadView .topBar .config .threadHistorySection a:link,
  2620. .threadView .topBar .config .threadHistorySection a:visited {
  2621. color: black;
  2622. text-decoration: none;
  2623. }
  2624. .threadView .topBar .config .threadHistorySection a:hover {
  2625. color: purple;
  2626. text-decoration: underline;
  2627. }
  2628. .threadView .topBar .viewToggle.isSelected ~ .config,
  2629. .threadView .topBar .threadHistoryToggle.isSelected ~ .config {
  2630. max-height: calc(90vh - 1.5em);
  2631. overflow: auto;
  2632. }
  2633. .threadView .topBar .ngToggle.isSelected ~ .config .ngSection .config {
  2634. max-height: calc(90vh - 1.5em - 1.5em);
  2635. overflow: auto;
  2636. }
  2637. `
  2638. }
  2639. return ThreadView
  2640. })()
  2641.  
  2642. var ResponsePostForm = (function(_super) {
  2643. var ResponsePostForm = function(form) {
  2644. _super.call(this)
  2645. this._form = this._initForm(form)
  2646. this._progress = this._createProgress()
  2647. this._target = null
  2648. }
  2649. ResponsePostForm.prototype = Object.create(_super.prototype)
  2650. ResponsePostForm.prototype._initForm = function(form) {
  2651. form.target = 'postTarget'
  2652. form.addEventListener('submit', this._formSubmitted.bind(this))
  2653. return form
  2654. }
  2655. ResponsePostForm.prototype._getDoc = function() {
  2656. return this._form.ownerDocument
  2657. }
  2658. ResponsePostForm.prototype._createProgress = function() {
  2659. var result = this._getDoc().createElement('p')
  2660. result.textContent = '書き込み中...'
  2661. return result
  2662. }
  2663. ResponsePostForm.prototype._insertProgress = function() {
  2664. var f = this._form
  2665. f.parentNode.insertBefore(this._progress, f.nextSibling)
  2666. }
  2667. ResponsePostForm.prototype._createTarget = function() {
  2668. var result = this._getDoc().createElement('iframe')
  2669. result.name = this._form.target
  2670. result.className = 'postTarget loading'
  2671. result.addEventListener('load', this._targetLoaded.bind(this))
  2672. return result
  2673. }
  2674. ResponsePostForm.prototype._hideOrCreateTarget = function() {
  2675. if (this._target) {
  2676. this._target.classList.add('loading')
  2677. } else {
  2678. this._target = this._createTarget()
  2679. var p = this._progress
  2680. p.parentNode.insertBefore(this._target, p.nextSibling)
  2681. }
  2682. }
  2683. ResponsePostForm.prototype._formSubmitted = function() {
  2684. this._form.submit.disabled = true
  2685. this._insertProgress()
  2686. this._hideOrCreateTarget()
  2687. }
  2688. ResponsePostForm.prototype._getTargetLocation = function() {
  2689. return this._target.contentDocument.location.toString()
  2690. }
  2691. ResponsePostForm.prototype._isPostDone = function() {
  2692. return this._target.contentDocument.title.indexOf('書きこみました') >= 0
  2693. }
  2694. ResponsePostForm.prototype._targetLoaded = function() {
  2695. if (this._getTargetLocation() === 'about:blank') return
  2696. this._form.submit.disabled = false
  2697. this._progress.remove()
  2698. if (this._isPostDone()) {
  2699. this._target.remove()
  2700. this._target = null
  2701. this._form.MESSAGE.value = ''
  2702. this.fireEvent('postDone')
  2703. } else {
  2704. this._target.classList.remove('loading')
  2705. }
  2706. }
  2707. ResponsePostForm.prototype.remove = function() {
  2708. ;[this._form, this._progress, this._target]
  2709. .filter(Boolean)
  2710. .forEach(invoke('remove', []))
  2711. }
  2712. return ResponsePostForm
  2713. })(Observable)
  2714.  
  2715. var ThreadController = (function() {
  2716. var ThreadController = function(thread, threadView) {
  2717. this.thread = thread
  2718. this.threadView = threadView
  2719. }
  2720. ThreadController.prototype.addCallback = function() {
  2721. var r = this.threadView.rootElement
  2722. r.addEventListener('click', this.callback.bind(this))
  2723. r.addEventListener('keydown', this.keydownCallback.bind(this))
  2724. r.addEventListener('change', this.changeCallback.bind(this))
  2725. }
  2726. ThreadController.prototype._getBaseURL = function() {
  2727. const l = this.threadView.doc.location
  2728. const p = l.pathname
  2729. return `${l.protocol}//${l.host}${p.slice(0, p.lastIndexOf('/') + 1)}`
  2730. }
  2731. ThreadController.prototype.requestNewResponses = function() {
  2732. this.threadView.getReloadButton().disabled = true
  2733. this.threadView.getReloadMessageElement().textContent = ''
  2734. var _this = this
  2735. new ResponseRequest()
  2736. .send(this._getBaseURL()
  2737. , this.thread.getLastResponseNumber() + 1)
  2738. .then(function(result) {
  2739. _this.thread.addResponses(result.responses.map(Response.of))
  2740. if (result.threadClosed) _this.threadView.close()
  2741. })
  2742. .catch(function(error) {
  2743. _this.threadView.getReloadMessageElement().textContent = error
  2744. })
  2745. .then(function() {
  2746. _this.threadView.getReloadButton().disabled = false
  2747. })
  2748. }
  2749. ThreadController.prototype._addBoundableNgItem = function(o) {
  2750. var view = this.threadView.configView
  2751. var val = view.getNgTextInputValue()
  2752. if (!val) return
  2753. var boardId = view.getNgItemAddTarget() !== 'all'
  2754. ? this.thread.boardId : undefined
  2755. var threadNumber = view.getNgItemAddTarget() === 'thread'
  2756. ? this.thread.threadNumber : undefined
  2757. o.arrayStore(this.thread.config)
  2758. .add(o.boundableNgItem(val, boardId, threadNumber))
  2759. view.clearNgTextInputValue()
  2760. }
  2761. ThreadController.prototype._addNgWord = function() {
  2762. this._addBoundableNgItem({
  2763. arrayStore(config) {
  2764. return config.ngWords
  2765. },
  2766. boundableNgItem(val, boardId, threadNumber) {
  2767. return new NgWord(val, boardId, threadNumber)
  2768. },
  2769. })
  2770. }
  2771. ThreadController.prototype._addNgName = function() {
  2772. this._addBoundableNgItem({
  2773. arrayStore(config) {
  2774. return config.ngNames
  2775. },
  2776. boundableNgItem(val, boardId, threadNumber) {
  2777. return new NgName(val, boardId, threadNumber)
  2778. },
  2779. })
  2780. }
  2781. ThreadController.prototype._addNgIdTail = function() {
  2782. this._addBoundableNgItem({
  2783. arrayStore(config) {
  2784. return config.ngIdTails
  2785. },
  2786. boundableNgItem(val, boardId, threadNumber) {
  2787. return new NgIdTail(val, boardId, threadNumber)
  2788. },
  2789. })
  2790. }
  2791. ThreadController.prototype._addNgKorokoro = function(target) {
  2792. var s = target.dataset
  2793. this.thread.config.ngKorokoros.add(
  2794. new NgKorokoro(this.thread.boardId, s.jstTime, s.korokoro))
  2795. }
  2796. ThreadController.prototype._removeNgWord = function(target) {
  2797. this.thread.config.ngWords.remove(NgWord.of(target.dataset))
  2798. }
  2799. ThreadController.prototype._removeNgName = function(target) {
  2800. this.thread.config.ngNames.remove(NgName.of(target.dataset))
  2801. }
  2802. ThreadController.prototype._addNgId = function(target) {
  2803. this.thread.addNgId(target.dataset.jstTime, target.dataset.id)
  2804. }
  2805. ThreadController.prototype._removeNgId = function(target) {
  2806. this.thread.config.ngIds.remove(NgId.of(target.dataset))
  2807. }
  2808. ThreadController.prototype._removeNgKorokoro = function(target) {
  2809. this.thread.config.ngKorokoros.remove(NgKorokoro.of(target.dataset))
  2810. }
  2811. ThreadController.prototype._removeNgIdTail = function(target) {
  2812. this.thread.config.ngIdTails.remove(NgIdTail.of(target.dataset))
  2813. }
  2814. ThreadController.prototype._removeThreadHistory = function(target) {
  2815. this.thread.config.threadHistories.remove({
  2816. title: target.dataset.title,
  2817. url: target.dataset.url,
  2818. resNum: parseInt(target.dataset.resNum),
  2819. })
  2820. }
  2821. ThreadController.prototype._addNgSlip = function() {
  2822. var view = this.threadView.configView
  2823. var type = view.getNgSlipType()
  2824. var boardId = this.thread.boardId
  2825. var threadNumber = view.getNgItemAddTarget() === 'thread'
  2826. ? this.thread.threadNumber : undefined
  2827. this.thread.config.ngSlips.add(new NgSlip(type, boardId, threadNumber))
  2828. }
  2829. ThreadController.prototype._removeNgSlip = function(target) {
  2830. this.thread.config.ngSlips.remove(NgSlip.of(target.dataset))
  2831. }
  2832. ThreadController.prototype._resAnchorClicked = function(target, event) {
  2833. if (!this.thread.hasResponse(Number(target.dataset.resNum))) return
  2834. event.preventDefault()
  2835. this.threadView.doc.location.hash = '#' + target.dataset.resNum
  2836. }
  2837. ThreadController.prototype._actionMap = function() {
  2838. var view = this.threadView
  2839. var cfg = this.thread.config
  2840. var article = '.threadView .main article'
  2841. var header = `${article} header`
  2842. var topBar = '.threadView .topBar'
  2843. var config = `${topBar} .config`
  2844. var ngIdSection = `${config} .ngIdSection`
  2845. var ngWordSection = `${config} .ngWordSection`
  2846. var ngNameSection = `${config} .ngNameSection`
  2847. var ngKorokoroSection = `${config} .ngKorokoroSection`
  2848. var ngIdTailSection = `${config} .ngIdTailSection`
  2849. var ngSlipSection = `${config} .ngSlipSection`
  2850. var threadHistorySection = `${config} .threadHistorySection`
  2851. return {
  2852. [`${article} .resAnchor`]: this._resAnchorClicked.bind(this),
  2853. [`${header} .headerNumber`]: view.toggleResponseChildren.bind(view),
  2854. [`${header} .id .value`]: view.toggleSameIdResponses.bind(view),
  2855. [`${header} .korokoro .value`]: view.toggleSameKorokoroResponses.bind(view),
  2856. [`${header} .id .ngButton`]: this._addNgId.bind(this),
  2857. [`${header} .korokoro .ngButton`]: this._addNgKorokoro.bind(this),
  2858. '.threadView .bottomBar .reloadButton': this.requestNewResponses.bind(this),
  2859. [`${topBar} .viewToggle`]: view.toggleViewConfig.bind(view),
  2860. [`${topBar} .ngToggle`]: view.toggleNgConfig.bind(view),
  2861. [`${topBar} .ngWordToggle`]: view.toggleNgWordConfig.bind(view),
  2862. [`${topBar} .ngNameToggle`]: view.toggleNgNameConfig.bind(view),
  2863. [`${topBar} .ngIdToggle`]: view.toggleNgIdConfig.bind(view),
  2864. [`${topBar} .ngKorokoroToggle`]: view.toggleNgKorokoroConfig.bind(view),
  2865. [`${topBar} .ngIdTailToggle`]: view.toggleNgIdTailConfig.bind(view),
  2866. [`${topBar} .ngSlipToggle`]: view.toggleNgSlipConfig.bind(view),
  2867. [`${topBar} .threadHistoryToggle`]: view.toggleThreadHistoryConfig.bind(view),
  2868. [`${ngIdSection} table .removeButton`]: this._removeNgId.bind(this),
  2869. [`${ngIdSection} h2 .removeAllButton`]: cfg.ngIds.removeAll.bind(cfg.ngIds),
  2870. [`${ngKorokoroSection} table .removeButton`]: this._removeNgKorokoro.bind(this),
  2871. [`${ngKorokoroSection} h2 .removeAllButton`]: cfg.ngKorokoros.removeAll.bind(cfg.ngKorokoros),
  2872. [`${ngIdTailSection} .add .addButton`]: this._addNgIdTail.bind(this),
  2873. [`${ngIdTailSection} table .removeButton`]: this._removeNgIdTail.bind(this),
  2874. [`${ngIdTailSection} h2 .removeAllButton`]: cfg.ngIdTails.removeAll.bind(cfg.ngIdTails),
  2875. [`${ngWordSection} .add .addButton`]: this._addNgWord.bind(this),
  2876. [`${ngWordSection} table .removeButton`]: this._removeNgWord.bind(this),
  2877. [`${ngWordSection} h2 .removeAllButton`]: cfg.ngWords.removeAll.bind(cfg.ngWords),
  2878. [`${ngNameSection} .add .addButton`]: this._addNgName.bind(this),
  2879. [`${ngNameSection} table .removeButton`]: this._removeNgName.bind(this),
  2880. [`${ngNameSection} h2 .removeAllButton`]: cfg.ngNames.removeAll.bind(cfg.ngNames),
  2881. [`${ngSlipSection} .add .addButton`]: this._addNgSlip.bind(this),
  2882. [`${ngSlipSection} table .removeButton`]: this._removeNgSlip.bind(this),
  2883. [`${ngSlipSection} h2 .removeAllButton`]: cfg.ngSlips.removeAll.bind(cfg.ngSlips),
  2884. [`${threadHistorySection} table .removeButton`]: this._removeThreadHistory.bind(this),
  2885. [`${threadHistorySection} h2 .removeAllButton`]: cfg.threadHistories.removeAll.bind(cfg.threadHistories),
  2886. }
  2887. }
  2888. ThreadController.prototype._getAction = function(map, target) {
  2889. var selectors = Object.keys(map)
  2890. for (var i = 0; i < selectors.length; i++) {
  2891. var s = selectors[i]
  2892. if (target.matches(s)) return map[s]
  2893. }
  2894. }
  2895. ThreadController.prototype.callback = function(event) {
  2896. var action = this._getAction(this._actionMap(), event.target)
  2897. if (action) action(event.target, event)
  2898. }
  2899. ThreadController.prototype.keydownCallback = function(event) {
  2900. var enterKeyCode = 13
  2901. if (event.keyCode !== enterKeyCode) return
  2902. var config = '.threadView .topBar .config'
  2903. var map = {
  2904. [`${config} .ngWordSection .add .ngTextInput`]: this._addNgWord.bind(this),
  2905. [`${config} .ngNameSection .add .ngTextInput`]: this._addNgName.bind(this),
  2906. [`${config} .ngIdTailSection .add .ngTextInput`]: this._addNgIdTail.bind(this),
  2907. }
  2908. var a = this._getAction(map, event.target)
  2909. if (a) a(event.target)
  2910. }
  2911. ThreadController.prototype._setPageCentering = function() {
  2912. this.thread.config.setPageCentering(
  2913. this.threadView.configView.isPageCenteringChecked())
  2914. }
  2915. ThreadController.prototype._setNgVisible = function() {
  2916. this.thread.config.setNgVisible(
  2917. this.threadView.configView.isNgVisibleChecked())
  2918. }
  2919. ThreadController.prototype._setPageMaxWidth = function() {
  2920. this.thread.config.setPageMaxWidth(
  2921. this.threadView.configView.getPageMaxWidthValue())
  2922. }
  2923. ThreadController.prototype._changeActionMap = function() {
  2924. var viewSection = '.threadView .topBar .config .viewSection'
  2925. return {
  2926. [`${viewSection} .centering label input`]: this._setPageCentering.bind(this),
  2927. [`${viewSection} .maxWidth label input`]: this._setPageMaxWidth.bind(this),
  2928. [`${viewSection} .ngVisible label input`]: this._setNgVisible.bind(this),
  2929. }
  2930. }
  2931. ThreadController.prototype.changeCallback = function(event) {
  2932. var action = this._getAction(this._changeActionMap(), event.target)
  2933. if (action) action(event.target)
  2934. }
  2935. return ThreadController
  2936. })()
  2937.  
  2938. async function contentLoaded() {
  2939. var parsed = Parser.of(document).parse()
  2940. parsed.action()
  2941. parsed.ads.forEach(function(e) { e.style.display = 'none' })
  2942. parsed.elementsToRemove.forEach(invoke('remove', []))
  2943. if (parsed.floatedSpan) parsed.floatedSpan.style.cssFloat = ''
  2944. var config = new Config(typeof GM_getValue === 'undefined' ? GM.getValue : GM_getValue
  2945. , typeof GM_setValue === 'undefined' ? GM.setValue : GM_setValue)
  2946. var threadHistory = new ThreadHistory(
  2947. config.threadHistories, document.location, document.title)
  2948. var thread = new Thread(config, parsed.boardId, parsed.threadNumber, threadHistory)
  2949. var threadView = new ThreadView(document, thread)
  2950. threadView.setPageCentering(await config.isPageCentering())
  2951. await threadView.addStyle()
  2952. if (parsed.threadClosed) threadView.disableReload()
  2953. threadView.replace(parsed.threadRootElement)
  2954. await thread.addResponses(parsed.responses.map(Response.of))
  2955. var ctrl = new ThreadController(thread, threadView)
  2956. ctrl.addCallback()
  2957. if (parsed.postForm) {
  2958. var postForm = new ResponsePostForm(parsed.postForm)
  2959. postForm.addEventListener('postDone'
  2960. , ctrl.requestNewResponses.bind(ctrl))
  2961. threadView.responsePostForm = postForm
  2962. }
  2963. }
  2964. var main = function() {
  2965. if (document.location.protocol === 'http:') {
  2966. window.stop()
  2967. document.location.replace('https' + document.location.href.slice('http'.length))
  2968. return
  2969. }
  2970. if (['interactive', 'complete'].includes(document.readyState))
  2971. contentLoaded()
  2972. else
  2973. document.addEventListener('DOMContentLoaded', contentLoaded)
  2974. }
  2975.  
  2976. main()
  2977. })()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址