Most of the Header is done, see changelog file.
Add 4chan and 4chan X settings links in the header's menu.
Add $.asap.
Add UI.Menu.
The menu API now requires a type ("post" or "header").
Add Get.boardsConfig.
This commit is contained in:
parent
c54ddff83d
commit
b6cf7220c4
785
4chan_x.user.js
785
4chan_x.user.js
File diff suppressed because one or more lines are too long
@ -1,11 +1,16 @@
|
|||||||
alpha
|
alpha
|
||||||
- Mayhem
|
- Mayhem
|
||||||
|
Major rewrite of 4chan X.
|
||||||
|
New feature, the Header:
|
||||||
|
Access the list of boards directly from the Header.
|
||||||
|
Access to settings and extra features easily from the Header's menu.
|
||||||
|
Can be auto-hidden.
|
||||||
Added touch and multi-touch support for dragging windows.
|
Added touch and multi-touch support for dragging windows.
|
||||||
The Thread Updater will pause when offline, and resume when online.
|
The Thread Updater will pause when offline, and resume when online.
|
||||||
Added Thread & Post Hiding in the Menu, with individual settings.
|
Added Thread & Post Hiding in the Menu, with individual settings.
|
||||||
Thread & Post Hiding Buttons can now be disabled in the settings.
|
Thread & Post Hiding Buttons can now be disabled in the settings.
|
||||||
Recursive Hiding will be automatically applied when manually hiding a post.
|
Recursive Hiding will be automatically applied when manually hiding a post.
|
||||||
Fix Chrome's install warning that 4chan X would execute on all domains.
|
Fix Chrome's install warning saying that 4chan X would execute on all domains.
|
||||||
Fix Quote Backlinks not affecting inlined quotes.
|
Fix Quote Backlinks not affecting inlined quotes.
|
||||||
Fix Quote Highlighting not affecting inlined quotes.
|
Fix Quote Highlighting not affecting inlined quotes.
|
||||||
|
|
||||||
|
|||||||
@ -31,17 +31,20 @@ a[href="javascript:;"] {
|
|||||||
/* fixed, z-index */
|
/* fixed, z-index */
|
||||||
#qp, #ihover,
|
#qp, #ihover,
|
||||||
#updater, #stats,
|
#updater, #stats,
|
||||||
#boardNavDesktop.reply,
|
#header,
|
||||||
#qr, #watcher {
|
#qr, #watcher {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
#qp, #ihover {
|
#qp, #ihover {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
#menu {
|
||||||
|
z-index: 95;
|
||||||
|
}
|
||||||
#updater, #stats {
|
#updater, #stats {
|
||||||
z-index: 90;
|
z-index: 90;
|
||||||
}
|
}
|
||||||
#boardNavDesktop.reply:hover {
|
#header:hover {
|
||||||
z-index: 80;
|
z-index: 80;
|
||||||
}
|
}
|
||||||
#qr {
|
#qr {
|
||||||
@ -50,38 +53,70 @@ a[href="javascript:;"] {
|
|||||||
#watcher {
|
#watcher {
|
||||||
z-index: 30;
|
z-index: 30;
|
||||||
}
|
}
|
||||||
#boardNavDesktop.reply {
|
#header {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* XXX support different styles */
|
||||||
|
#header-bar {
|
||||||
|
font-size: 9pt;
|
||||||
|
color: #89A;
|
||||||
|
background-color: #D6DAF0;
|
||||||
|
border-color: #B7C5D9;
|
||||||
|
border-width: 0 0 1px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
/* header */
|
/* header */
|
||||||
body.fourchan_x {
|
body.fourchan_x {
|
||||||
margin-top: 2.5em;
|
margin-top: 2em;
|
||||||
}
|
}
|
||||||
#boardNavDesktop.reply {
|
#header {
|
||||||
border-width: 0 0 1px;
|
|
||||||
padding: 4px;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
transition: opacity .1s ease-in-out;
|
|
||||||
-o-transition: opacity .1s ease-in-out;
|
|
||||||
-moz-transition: opacity .1s ease-in-out;
|
|
||||||
-webkit-transition: opacity .1s ease-in-out;
|
|
||||||
}
|
}
|
||||||
#boardNavDesktop.reply:not(:hover) {
|
#header-bar {
|
||||||
opacity: .4;
|
padding: 4px;
|
||||||
transition: opacity 1.5s .5s ease-in-out;
|
position: relative;
|
||||||
-o-transition: opacity 1.5s .5s ease-in-out;
|
transition: all .1s ease-in-out;
|
||||||
-moz-transition: opacity 1.5s .5s ease-in-out;
|
-o-transition: all .1s ease-in-out;
|
||||||
-webkit-transition: opacity 1.5s .5s ease-in-out;
|
-moz-transition: all .1s ease-in-out;
|
||||||
|
-webkit-transition: all .1s ease-in-out;
|
||||||
}
|
}
|
||||||
#boardNavDesktop.reply a {
|
#header-bar.autohide:not(:hover) {
|
||||||
margin: -1px;
|
transform: translateY(-100%);
|
||||||
|
-o-transform: translateY(-100%);
|
||||||
|
-moz-transform: translateY(-100%);
|
||||||
|
-webkit-transform: translateY(-100%);
|
||||||
|
transition: all .75s .25s ease-in-out;
|
||||||
|
-o-transition: all .75s .25s ease-in-out;
|
||||||
|
-moz-transition: all .75s .25s ease-in-out;
|
||||||
|
-webkit-transition: all .75s .25s ease-in-out;
|
||||||
}
|
}
|
||||||
#settings {
|
#toggle-header-bar {
|
||||||
|
cursor: n-resize;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: -8px;
|
||||||
|
height: 10px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#header-bar.autohide #toggle-header-bar {
|
||||||
|
cursor: s-resize;
|
||||||
|
}
|
||||||
|
#header-bar a {
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
#header-bar > .menu-button {
|
||||||
float: right;
|
float: right;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
body > #boardNavDesktop,
|
||||||
|
#navtopright,
|
||||||
|
#boardNavDesktopFoot {
|
||||||
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* thread updater */
|
/* thread updater */
|
||||||
|
|||||||
18
lib/$.coffee
18
lib/$.coffee
@ -78,19 +78,19 @@ $.extend $,
|
|||||||
value: ->
|
value: ->
|
||||||
$.set @name, @value.trim()
|
$.set @name, @value.trim()
|
||||||
Conf[@name] = @value
|
Conf[@name] = @value
|
||||||
|
asap: (test, cb) ->
|
||||||
|
if test()
|
||||||
|
cb()
|
||||||
|
else
|
||||||
|
setTimeout $.asap, 25, test, cb
|
||||||
addStyle: (css) ->
|
addStyle: (css) ->
|
||||||
style = $.el 'style',
|
style = $.el 'style',
|
||||||
textContent: css
|
textContent: css
|
||||||
# That's terrible.
|
# XXX fix for scriptish:
|
||||||
# XXX tmp fix for scriptish:
|
|
||||||
# https://github.com/scriptish/scriptish/issues/16
|
# https://github.com/scriptish/scriptish/issues/16
|
||||||
f = ->
|
$.asap (-> d.head), (->
|
||||||
# XXX Only Chrome has no d.head on document-start.
|
$.add d.head, style
|
||||||
if root = d.head or d.documentElement
|
)
|
||||||
$.add root, style
|
|
||||||
else
|
|
||||||
setTimeout f, 20
|
|
||||||
f()
|
|
||||||
style
|
style
|
||||||
x: (path, root=d.body) ->
|
x: (path, root=d.body) ->
|
||||||
# XPathResult.ANY_UNORDERED_NODE_TYPE === 8
|
# XPathResult.ANY_UNORDERED_NODE_TYPE === 8
|
||||||
|
|||||||
262
lib/ui.coffee
262
lib/ui.coffee
@ -10,6 +10,180 @@ UI = (->
|
|||||||
move.addEventListener 'mousedown', dragstart, false
|
move.addEventListener 'mousedown', dragstart, false
|
||||||
el
|
el
|
||||||
|
|
||||||
|
|
||||||
|
class Menu
|
||||||
|
currentMenu = null
|
||||||
|
lastToggledButton = null
|
||||||
|
|
||||||
|
constructor: (@type) ->
|
||||||
|
# Doc here: https://github.com/MayhemYDG/4chan-x/wiki/Menu-API
|
||||||
|
$.on d, 'AddMenuEntry', @addEntryListener.bind @
|
||||||
|
@close = close.bind @
|
||||||
|
@entries = []
|
||||||
|
|
||||||
|
makeMenu: ->
|
||||||
|
menu = $.el 'div',
|
||||||
|
className: 'reply dialog'
|
||||||
|
id: 'menu'
|
||||||
|
tabIndex: 0
|
||||||
|
$.on menu, 'click', (e) -> e.stopPropagation()
|
||||||
|
$.on menu, 'keydown', @keybinds.bind @
|
||||||
|
menu
|
||||||
|
|
||||||
|
toggle: (e, button, data) ->
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
if currentMenu
|
||||||
|
# Close if it's already opened.
|
||||||
|
# Reopen if we clicked on another button.
|
||||||
|
previousButton = lastToggledButton
|
||||||
|
@close()
|
||||||
|
return if previousButton is button
|
||||||
|
|
||||||
|
return unless @entries.length
|
||||||
|
@open button, data
|
||||||
|
|
||||||
|
open: (button, data) ->
|
||||||
|
menu = @makeMenu()
|
||||||
|
currentMenu = menu
|
||||||
|
lastToggledButton = button
|
||||||
|
|
||||||
|
for entry in @entries
|
||||||
|
@insertEntry entry, menu, data
|
||||||
|
|
||||||
|
@focus $ '.entry', menu
|
||||||
|
$.on d, 'click', @close
|
||||||
|
$.add d.body, menu
|
||||||
|
|
||||||
|
# Position
|
||||||
|
mRect = menu.getBoundingClientRect()
|
||||||
|
bRect = button.getBoundingClientRect()
|
||||||
|
bTop = d.documentElement.scrollTop + d.body.scrollTop + bRect.top
|
||||||
|
bLeft = d.documentElement.scrollLeft + d.body.scrollLeft + bRect.left
|
||||||
|
cHeight = d.documentElement.clientHeight
|
||||||
|
cWidth = d.documentElement.clientWidth
|
||||||
|
top =
|
||||||
|
if bRect.top + bRect.height + mRect.height < cHeight
|
||||||
|
bTop + bRect.height + 2
|
||||||
|
else
|
||||||
|
bTop - mRect.height - 2
|
||||||
|
left =
|
||||||
|
if bRect.left + mRect.width < cWidth
|
||||||
|
bLeft
|
||||||
|
else
|
||||||
|
bLeft + bRect.width - mRect.width
|
||||||
|
{style} = menu
|
||||||
|
style.top = top + 'px'
|
||||||
|
style.left = left + 'px'
|
||||||
|
|
||||||
|
menu.focus()
|
||||||
|
|
||||||
|
insertEntry: (entry, parent, data) ->
|
||||||
|
if typeof entry.open is 'function'
|
||||||
|
return unless entry.open data
|
||||||
|
$.add parent, entry.el
|
||||||
|
|
||||||
|
return unless entry.children
|
||||||
|
if submenu = $ '.submenu', entry.el
|
||||||
|
# Reset sub menu, remove irrelevant entries.
|
||||||
|
$.rm submenu
|
||||||
|
submenu = $.el 'div',
|
||||||
|
className: 'reply dialog submenu'
|
||||||
|
for child in entry.children
|
||||||
|
@insertEntry child, submenu, data
|
||||||
|
$.add entry.el, submenu
|
||||||
|
return
|
||||||
|
|
||||||
|
close = ->
|
||||||
|
$.rm currentMenu
|
||||||
|
currentMenu = null
|
||||||
|
lastToggledButton = null
|
||||||
|
$.off d, 'click', @close
|
||||||
|
|
||||||
|
keybinds: (e) ->
|
||||||
|
entry = $ '.focused', currentMenu
|
||||||
|
while subEntry = $ '.focused', entry
|
||||||
|
entry = subEntry
|
||||||
|
|
||||||
|
switch e.keyCode
|
||||||
|
when 27 # Esc
|
||||||
|
lastToggledButton.focus()
|
||||||
|
@close()
|
||||||
|
when 13, 32 # Enter, Space
|
||||||
|
entry.click()
|
||||||
|
when 38 # Up
|
||||||
|
if next = entry.previousElementSibling
|
||||||
|
@focus next
|
||||||
|
when 40 # Down
|
||||||
|
if next = entry.nextElementSibling
|
||||||
|
@focus next
|
||||||
|
when 39 # Right
|
||||||
|
if (submenu = $ '.submenu', entry) and next = submenu.firstElementChild
|
||||||
|
@focus next
|
||||||
|
when 37 # Left
|
||||||
|
if next = $.x 'parent::*[contains(@class,"submenu")]/parent::*', entry
|
||||||
|
@focus next
|
||||||
|
else
|
||||||
|
return
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
focus: (entry) ->
|
||||||
|
while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry
|
||||||
|
$.rmClass focused, 'focused'
|
||||||
|
for focused in $$ '.focused', entry
|
||||||
|
$.rmClass focused, 'focused'
|
||||||
|
$.addClass entry, 'focused'
|
||||||
|
|
||||||
|
# Submenu positioning.
|
||||||
|
return unless submenu = $ '.submenu', entry
|
||||||
|
sRect = submenu.getBoundingClientRect()
|
||||||
|
eRect = entry.getBoundingClientRect()
|
||||||
|
cHeight = d.documentElement.clientHeight
|
||||||
|
cWidth = d.documentElement.clientWidth
|
||||||
|
if eRect.top + sRect.height < cHeight
|
||||||
|
top = '0px'
|
||||||
|
bottom = 'auto'
|
||||||
|
else
|
||||||
|
top = 'auto'
|
||||||
|
bottom = '0px'
|
||||||
|
if eRect.right + sRect.width < cWidth
|
||||||
|
left = '100%'
|
||||||
|
right = 'auto'
|
||||||
|
else
|
||||||
|
left = 'auto'
|
||||||
|
right = '100%'
|
||||||
|
{style} = submenu
|
||||||
|
style.top = top
|
||||||
|
style.bottom = bottom
|
||||||
|
style.left = left
|
||||||
|
style.right = right
|
||||||
|
|
||||||
|
addEntry: (entry) ->
|
||||||
|
@parseEntry entry
|
||||||
|
@entries.push entry
|
||||||
|
|
||||||
|
parseEntry: (entry) ->
|
||||||
|
{el, children} = entry
|
||||||
|
$.addClass el, 'entry'
|
||||||
|
$.on el, 'focus mouseover', ((e) ->
|
||||||
|
e.stopPropagation()
|
||||||
|
@focus el
|
||||||
|
).bind @
|
||||||
|
return unless children
|
||||||
|
$.addClass el, 'has-submenu'
|
||||||
|
for child in children
|
||||||
|
@parseEntry child
|
||||||
|
return
|
||||||
|
|
||||||
|
addEntryListener: (e) ->
|
||||||
|
entry = e.detail
|
||||||
|
return if entry.type isnt @type
|
||||||
|
@addEntry entry
|
||||||
|
|
||||||
|
|
||||||
dragstart = (e) ->
|
dragstart = (e) ->
|
||||||
# prevent text selection
|
# prevent text selection
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@ -49,24 +223,42 @@ UI = (->
|
|||||||
drag.call @, touch
|
drag.call @, touch
|
||||||
return
|
return
|
||||||
drag = (e) ->
|
drag = (e) ->
|
||||||
left = e.clientX - @dx
|
{clientX, clientY} = e
|
||||||
top = e.clientY - @dy
|
|
||||||
if left < 10 then left = 0
|
left = clientX - @dx
|
||||||
else if @width - left < 10 then left = null
|
left =
|
||||||
if top < 10 then top = 0
|
if left < 10
|
||||||
else if @height - top < 10 then top = null
|
0
|
||||||
if left is null
|
else if @width - left < 10
|
||||||
@style.left = null
|
null
|
||||||
@style.right = '0%'
|
else
|
||||||
else
|
left / @screenWidth * 100 + '%'
|
||||||
@style.left = left / @screenWidth * 100 + '%'
|
|
||||||
@style.right = null
|
top = clientY - @dy
|
||||||
if top is null
|
top =
|
||||||
@style.top = null
|
if top < 10
|
||||||
@style.bottom = '0%'
|
0
|
||||||
else
|
else if @height - top < 10
|
||||||
@style.top = top / @screenHeight * 100 + '%'
|
null
|
||||||
@style.bottom = null
|
else
|
||||||
|
top / @screenHeight * 100 + '%'
|
||||||
|
|
||||||
|
right =
|
||||||
|
if left is null
|
||||||
|
0
|
||||||
|
else
|
||||||
|
null
|
||||||
|
bottom =
|
||||||
|
if top is null
|
||||||
|
0
|
||||||
|
else
|
||||||
|
null
|
||||||
|
|
||||||
|
{style} = @
|
||||||
|
style.left = left
|
||||||
|
style.right = right
|
||||||
|
style.top = top
|
||||||
|
style.bottom = bottom
|
||||||
touchend = (e) ->
|
touchend = (e) ->
|
||||||
for touch in e.changedTouches
|
for touch in e.changedTouches
|
||||||
if touch.identifier is @identifier
|
if touch.identifier is @identifier
|
||||||
@ -99,22 +291,28 @@ UI = (->
|
|||||||
root.addEventListener 'mousemove', o.hover, false
|
root.addEventListener 'mousemove', o.hover, false
|
||||||
hover = (e) ->
|
hover = (e) ->
|
||||||
height = @el.offsetHeight
|
height = @el.offsetHeight
|
||||||
top = e.clientY - 120
|
{clientX, clientY} = e
|
||||||
@style.top =
|
|
||||||
if @clientHeight <= height or top <= 0
|
top = clientY - 120
|
||||||
'0px'
|
top =
|
||||||
else if top + height >= @clientHeight
|
if @clientHeight <= height or top <= 0
|
||||||
@clientHeight - height + 'px'
|
0
|
||||||
else
|
else if top + height >= @clientHeight
|
||||||
top + 'px'
|
@clientHeight - height
|
||||||
|
else
|
||||||
|
top
|
||||||
|
|
||||||
{clientX} = e
|
|
||||||
if clientX <= @clientWidth - 400
|
if clientX <= @clientWidth - 400
|
||||||
@style.left = clientX + 45 + 'px'
|
left = clientX + 45 + 'px'
|
||||||
@style.right = null
|
right = null
|
||||||
else
|
else
|
||||||
@style.left = null
|
left = null
|
||||||
@style.right = @clientWidth - clientX + 45 + 'px'
|
right = @clientWidth - clientX + 45 + 'px'
|
||||||
|
|
||||||
|
{style} = @
|
||||||
|
style.top = top + 'px'
|
||||||
|
style.left = left
|
||||||
|
style.right = right
|
||||||
hoverend = ->
|
hoverend = ->
|
||||||
@el.parentNode.removeChild @el
|
@el.parentNode.removeChild @el
|
||||||
for event in @events
|
for event in @events
|
||||||
@ -122,8 +320,10 @@ UI = (->
|
|||||||
@root.removeEventListener 'mousemove', @hover, false
|
@root.removeEventListener 'mousemove', @hover, false
|
||||||
@cb.call @ if @cb
|
@cb.call @ if @cb
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dialog: dialog
|
dialog: dialog
|
||||||
|
Menu: Menu
|
||||||
hover: hoverstart
|
hover: hoverstart
|
||||||
}
|
}
|
||||||
)()
|
)()
|
||||||
|
|||||||
@ -1,3 +1,109 @@
|
|||||||
|
Header =
|
||||||
|
init: ->
|
||||||
|
@menu = new UI.Menu 'header'
|
||||||
|
|
||||||
|
@headerEl = $.el 'div',
|
||||||
|
id: 'header'
|
||||||
|
innerHTML: '<div id=header-bar></div><div id=notifications></div>'
|
||||||
|
|
||||||
|
headerBar = $('#header-bar', @headerEl)
|
||||||
|
if $.get 'autohideHeaderBar', false
|
||||||
|
$.addClass headerBar, 'autohide'
|
||||||
|
|
||||||
|
menuButton = $.el 'a',
|
||||||
|
className: 'menu-button'
|
||||||
|
innerHTML: '[<span></span>]'
|
||||||
|
href: 'javascript:;'
|
||||||
|
$.on menuButton, 'click', @menuToggle
|
||||||
|
|
||||||
|
boardListButton = $.el 'span',
|
||||||
|
className: 'show-board-list-button'
|
||||||
|
innerHTML: '[<a href=javascript:;>+</a>]'
|
||||||
|
title: 'Toggle the board list.'
|
||||||
|
$.on boardListButton, 'click', @toggleBoardList
|
||||||
|
|
||||||
|
boardTitle = $.el 'a',
|
||||||
|
className: 'board-name'
|
||||||
|
innerHTML: "<span class=board-path>/#{g.BOARD}/</span> - <span class=board-title>...</span>"
|
||||||
|
href: "/#{g.BOARD}/"
|
||||||
|
boardList = $.el 'span',
|
||||||
|
className: 'board-list'
|
||||||
|
hidden: true
|
||||||
|
|
||||||
|
toggleBar = $.el 'div',
|
||||||
|
id: 'toggle-header-bar'
|
||||||
|
title: 'Toggle the header bar.'
|
||||||
|
$.on toggleBar, 'click', @toggleBar
|
||||||
|
|
||||||
|
$.prepend headerBar, [menuButton, boardListButton, $.tn(' '), boardTitle, boardList, toggleBar]
|
||||||
|
|
||||||
|
try
|
||||||
|
@setBoardList()
|
||||||
|
catch err
|
||||||
|
# XXX handle error
|
||||||
|
$.log err, 'Header - board list'
|
||||||
|
|
||||||
|
$.asap (-> d.body), ->
|
||||||
|
$.prepend d.body, Header.headerEl
|
||||||
|
|
||||||
|
setBoardList: ->
|
||||||
|
Get.boardsConfig (boardsConfig) ->
|
||||||
|
$('.board-title', Header.headerEl).textContent = boardsConfig[g.BOARD].title
|
||||||
|
$.ready ->
|
||||||
|
if nav = $.id 'boardNavDesktop'
|
||||||
|
$("a[href$='/#{g.BOARD}/']", nav)?.className = 'current'
|
||||||
|
$.add $('.board-list', Header.headerEl),
|
||||||
|
Array::slice.call nav.childNodes
|
||||||
|
|
||||||
|
toggleBoardList: ->
|
||||||
|
node = @firstElementChild.firstChild
|
||||||
|
if showBoardList = $.hasClass @, 'show-board-list-button'
|
||||||
|
@className = 'hide-board-list-button'
|
||||||
|
node.data = node.data.replace '+', '-'
|
||||||
|
else
|
||||||
|
@className = 'show-board-list-button'
|
||||||
|
node.data = node.data.replace '-', '+'
|
||||||
|
{headerEl} = Header
|
||||||
|
$('.board-name', headerEl).hidden = showBoardList
|
||||||
|
$('.board-list', headerEl).hidden = !showBoardList
|
||||||
|
|
||||||
|
toggleBar: ->
|
||||||
|
bool = $.id('header-bar').classList.toggle 'autohide'
|
||||||
|
$.set 'autohideHeaderBar', bool
|
||||||
|
|
||||||
|
menuToggle: (e) ->
|
||||||
|
Header.menu.toggle e, @, g
|
||||||
|
|
||||||
|
Settings =
|
||||||
|
init: ->
|
||||||
|
# 4chan X settings link
|
||||||
|
link = $.el 'a',
|
||||||
|
className: 'settings-link'
|
||||||
|
textContent: '4chan X Settings'
|
||||||
|
href: 'javascript:;'
|
||||||
|
$.on link, 'click', Settings.open
|
||||||
|
Header.menu.addEntry
|
||||||
|
el: link
|
||||||
|
|
||||||
|
# 4chan settings link
|
||||||
|
link = $.el 'a',
|
||||||
|
className: 'fourchan-settings-link'
|
||||||
|
textContent: '4chan Settings'
|
||||||
|
href: 'javascript:;'
|
||||||
|
$.on link, 'click', -> $.id('settingsWindowLink').click()
|
||||||
|
Header.menu.addEntry
|
||||||
|
el: link
|
||||||
|
open: -> !Conf['Disable 4chan\'s extension']
|
||||||
|
|
||||||
|
return unless Conf['Disable 4chan\'s extension']
|
||||||
|
settings = JSON.parse(localStorage.getItem '4chan-settings') or {}
|
||||||
|
return if settings.disableAll
|
||||||
|
settings.disableAll = true
|
||||||
|
localStorage.setItem '4chan-settings', JSON.stringify settings
|
||||||
|
open: ->
|
||||||
|
Header.menu.close()
|
||||||
|
# Here be settings
|
||||||
|
|
||||||
Filter =
|
Filter =
|
||||||
filters: {}
|
filters: {}
|
||||||
init: ->
|
init: ->
|
||||||
@ -195,7 +301,7 @@ Filter =
|
|||||||
# Add a sub entry for each filter type.
|
# Add a sub entry for each filter type.
|
||||||
entry.children.push Filter.menu.createSubEntry type[0], type[1]
|
entry.children.push Filter.menu.createSubEntry type[0], type[1]
|
||||||
|
|
||||||
Menu.addEntry entry
|
Menu.menu.addEntry entry
|
||||||
|
|
||||||
createSubEntry: (text, type) ->
|
createSubEntry: (text, type) ->
|
||||||
el = $.el 'a',
|
el = $.el 'a',
|
||||||
@ -345,7 +451,7 @@ ThreadHiding =
|
|||||||
makeStub = $.el 'label',
|
makeStub = $.el 'label',
|
||||||
innerHTML: "<input type=checkbox checked=#{Conf['Stubs']}> Make stub"
|
innerHTML: "<input type=checkbox checked=#{Conf['Stubs']}> Make stub"
|
||||||
|
|
||||||
Menu.addEntry
|
Menu.menu.addEntry
|
||||||
el: div
|
el: div
|
||||||
open: (post) ->
|
open: (post) ->
|
||||||
{thread} = post
|
{thread} = post
|
||||||
@ -493,7 +599,7 @@ ReplyHiding =
|
|||||||
makeStub = $.el 'label',
|
makeStub = $.el 'label',
|
||||||
innerHTML: "<input type=checkbox name=makeStub checked=#{Conf['Stubs']}> Make stub"
|
innerHTML: "<input type=checkbox name=makeStub checked=#{Conf['Stubs']}> Make stub"
|
||||||
|
|
||||||
Menu.addEntry
|
Menu.menu.addEntry
|
||||||
el: div
|
el: div
|
||||||
open: (post) ->
|
open: (post) ->
|
||||||
if !post.isReply or post.isClone
|
if !post.isReply or post.isClone
|
||||||
@ -619,20 +725,18 @@ Recursive =
|
|||||||
return
|
return
|
||||||
|
|
||||||
Menu =
|
Menu =
|
||||||
entries: []
|
|
||||||
init: ->
|
init: ->
|
||||||
# Doc here: https://github.com/MayhemYDG/4chan-x/wiki/Menu-API
|
@menu = new UI.Menu 'post'
|
||||||
$.on d, 'AddMenuEntry', (e) -> Menu.addEntry e.detail
|
|
||||||
Post::callbacks.push
|
Post::callbacks.push
|
||||||
name: 'Menu'
|
name: 'Menu'
|
||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
a = Menu.makeButton @
|
button = Menu.makeButton @
|
||||||
if @isClone
|
if @isClone
|
||||||
$.replace $('.menu-button', @nodes.info), a
|
$.replace $('.menu-button', @nodes.info), button
|
||||||
return
|
return
|
||||||
$.add @nodes.info, [$.tn('\u00A0'), a]
|
$.add @nodes.info, [$.tn('\u00A0'), button]
|
||||||
|
|
||||||
makeButton: (post) ->
|
makeButton: (post) ->
|
||||||
a = $.el 'a',
|
a = $.el 'a',
|
||||||
@ -644,158 +748,13 @@ Menu =
|
|||||||
$.on a, 'click', Menu.toggle
|
$.on a, 'click', Menu.toggle
|
||||||
a
|
a
|
||||||
|
|
||||||
makeMenu: ->
|
|
||||||
menu = $.el 'div',
|
|
||||||
className: 'reply dialog'
|
|
||||||
id: 'menu'
|
|
||||||
tabIndex: 0
|
|
||||||
$.on menu, 'click', (e) -> e.stopPropagation()
|
|
||||||
$.on menu, 'keydown', Menu.keybinds
|
|
||||||
menu
|
|
||||||
|
|
||||||
toggle: (e) ->
|
toggle: (e) ->
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
if Menu.currentMenu
|
|
||||||
# Close if it's already opened.
|
|
||||||
# Reopen if we clicked on another button.
|
|
||||||
{lastToggledButton} = Menu
|
|
||||||
Menu.close()
|
|
||||||
return if lastToggledButton is @
|
|
||||||
|
|
||||||
Menu.lastToggledButton = @
|
|
||||||
post =
|
post =
|
||||||
if @dataset.clone
|
if @dataset.clone
|
||||||
Get.postFromRoot $.x 'ancestor::div[contains(@class,"postContainer")][1]', @
|
Get.postFromRoot $.x 'ancestor::div[contains(@class,"postContainer")][1]', @
|
||||||
else
|
else
|
||||||
g.posts[@dataset.postid]
|
g.posts[@dataset.postid]
|
||||||
Menu.open @, post
|
Menu.menu.toggle e, @, post
|
||||||
|
|
||||||
open: (button, post) ->
|
|
||||||
menu = Menu.makeMenu()
|
|
||||||
Menu.currentMenu = menu
|
|
||||||
|
|
||||||
for entry in Menu.entries
|
|
||||||
Menu.insertEntry entry, menu, post
|
|
||||||
|
|
||||||
Menu.focus $ '.entry', menu
|
|
||||||
$.on d, 'click', Menu.close
|
|
||||||
$.add d.body, menu
|
|
||||||
|
|
||||||
# Position
|
|
||||||
mRect = menu.getBoundingClientRect()
|
|
||||||
bRect = button.getBoundingClientRect()
|
|
||||||
bTop = d.documentElement.scrollTop + d.body.scrollTop + bRect.top
|
|
||||||
bLeft = d.documentElement.scrollLeft + d.body.scrollLeft + bRect.left
|
|
||||||
menu.style.top =
|
|
||||||
if bRect.top + bRect.height + mRect.height < d.documentElement.clientHeight
|
|
||||||
bTop + bRect.height + 2 + 'px'
|
|
||||||
else
|
|
||||||
bTop - mRect.height - 2 + 'px'
|
|
||||||
menu.style.left =
|
|
||||||
if bRect.left + mRect.width < d.documentElement.clientWidth
|
|
||||||
bLeft + 'px'
|
|
||||||
else
|
|
||||||
bLeft + bRect.width - mRect.width + 'px'
|
|
||||||
|
|
||||||
menu.focus()
|
|
||||||
|
|
||||||
insertEntry: (entry, parent, post) ->
|
|
||||||
if typeof entry.open is 'function'
|
|
||||||
return unless entry.open post
|
|
||||||
$.add parent, entry.el
|
|
||||||
|
|
||||||
return unless entry.children
|
|
||||||
if submenu = $ '.submenu', entry.el
|
|
||||||
# Reset sub menu, remove irrelevant entries.
|
|
||||||
$.rm submenu
|
|
||||||
submenu = $.el 'div',
|
|
||||||
className: 'reply dialog submenu'
|
|
||||||
$.add entry.el, submenu
|
|
||||||
for child in entry.children
|
|
||||||
Menu.insertEntry child, submenu, post
|
|
||||||
return
|
|
||||||
|
|
||||||
close: ->
|
|
||||||
$.rm Menu.currentMenu
|
|
||||||
delete Menu.currentMenu
|
|
||||||
delete Menu.lastToggledButton
|
|
||||||
$.off d, 'click', Menu.close
|
|
||||||
|
|
||||||
keybinds: (e) ->
|
|
||||||
entry = $ '.focused', Menu.currentMenu
|
|
||||||
while subEntry = $ '.focused', entry
|
|
||||||
entry = subEntry
|
|
||||||
|
|
||||||
switch Keybinds.keyCode(e) or e.keyCode
|
|
||||||
when 'Esc'
|
|
||||||
Menu.lastToggledButton.focus()
|
|
||||||
Menu.close()
|
|
||||||
when 13, 32 # 'Enter', 'Space'
|
|
||||||
entry.click()
|
|
||||||
when 'Up'
|
|
||||||
if next = entry.previousElementSibling
|
|
||||||
Menu.focus next
|
|
||||||
when 'Down'
|
|
||||||
if next = entry.nextElementSibling
|
|
||||||
Menu.focus next
|
|
||||||
when 'Right'
|
|
||||||
if (submenu = $ '.submenu', entry) and next = submenu.firstElementChild
|
|
||||||
Menu.focus next
|
|
||||||
when 'Left'
|
|
||||||
if next = $.x 'parent::*[contains(@class,"submenu")]/parent::*', entry
|
|
||||||
Menu.focus next
|
|
||||||
else
|
|
||||||
return
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
focus: (entry) ->
|
|
||||||
while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry
|
|
||||||
$.rmClass focused, 'focused'
|
|
||||||
for focused in $$ '.focused', entry
|
|
||||||
$.rmClass focused, 'focused'
|
|
||||||
$.addClass entry, 'focused'
|
|
||||||
|
|
||||||
# Submenu positioning.
|
|
||||||
return unless submenu = $ '.submenu', entry
|
|
||||||
sRect = submenu.getBoundingClientRect()
|
|
||||||
eRect = entry.getBoundingClientRect()
|
|
||||||
if eRect.top + sRect.height < d.documentElement.clientHeight
|
|
||||||
top = '0px'
|
|
||||||
bottom = 'auto'
|
|
||||||
else
|
|
||||||
top = 'auto'
|
|
||||||
bottom = '0px'
|
|
||||||
if eRect.right + sRect.width < d.documentElement.clientWidth
|
|
||||||
left = '100%'
|
|
||||||
right = 'auto'
|
|
||||||
else
|
|
||||||
left = 'auto'
|
|
||||||
right = '100%'
|
|
||||||
{style} = submenu
|
|
||||||
style.top = top
|
|
||||||
style.bottom = bottom
|
|
||||||
style.left = left
|
|
||||||
style.right = right
|
|
||||||
|
|
||||||
addEntry: (entry) ->
|
|
||||||
Menu.parseEntry entry
|
|
||||||
Menu.entries.push entry
|
|
||||||
|
|
||||||
parseEntry: (entry) ->
|
|
||||||
{el, children} = entry
|
|
||||||
$.addClass el, 'entry'
|
|
||||||
$.on el, 'focus mouseover', (e) ->
|
|
||||||
e.stopPropagation()
|
|
||||||
Menu.focus @
|
|
||||||
return unless children
|
|
||||||
$.addClass el, 'has-submenu'
|
|
||||||
for child in children
|
|
||||||
Menu.parseEntry child
|
|
||||||
return
|
|
||||||
|
|
||||||
ReportLink =
|
ReportLink =
|
||||||
init: ->
|
init: ->
|
||||||
@ -804,7 +763,7 @@ ReportLink =
|
|||||||
href: 'javascript:;'
|
href: 'javascript:;'
|
||||||
textContent: 'Report this post'
|
textContent: 'Report this post'
|
||||||
$.on a, 'click', ReportLink.report
|
$.on a, 'click', ReportLink.report
|
||||||
Menu.addEntry
|
Menu.menu.addEntry
|
||||||
el: a
|
el: a
|
||||||
open: (post) ->
|
open: (post) ->
|
||||||
ReportLink.post = post
|
ReportLink.post = post
|
||||||
@ -841,7 +800,7 @@ DeleteLink =
|
|||||||
$.on fileEl, 'click', DeleteLink.delete
|
$.on fileEl, 'click', DeleteLink.delete
|
||||||
!!post.file
|
!!post.file
|
||||||
|
|
||||||
Menu.addEntry
|
Menu.menu.addEntry
|
||||||
el: div
|
el: div
|
||||||
open: (post) ->
|
open: (post) ->
|
||||||
return false if post.isDead
|
return false if post.isDead
|
||||||
@ -927,7 +886,7 @@ DownloadLink =
|
|||||||
a = $.el 'a',
|
a = $.el 'a',
|
||||||
className: 'download-link'
|
className: 'download-link'
|
||||||
textContent: 'Download file'
|
textContent: 'Download file'
|
||||||
Menu.addEntry
|
Menu.menu.addEntry
|
||||||
el: a
|
el: a
|
||||||
open: (post) ->
|
open: (post) ->
|
||||||
return false unless post.file
|
return false unless post.file
|
||||||
@ -962,7 +921,7 @@ ArchiveLink =
|
|||||||
# Add a sub entry for each type.
|
# Add a sub entry for each type.
|
||||||
entry.children.push @createSubEntry type[0], type[1]
|
entry.children.push @createSubEntry type[0], type[1]
|
||||||
|
|
||||||
Menu.addEntry entry
|
Menu.menu.addEntry entry
|
||||||
|
|
||||||
createSubEntry: (text, type) ->
|
createSubEntry: (text, type) ->
|
||||||
el = $.el 'a',
|
el = $.el 'a',
|
||||||
@ -1341,6 +1300,40 @@ Build =
|
|||||||
container
|
container
|
||||||
|
|
||||||
Get =
|
Get =
|
||||||
|
boardsConfig: (->
|
||||||
|
boardsConfig = null
|
||||||
|
callbacks = []
|
||||||
|
|
||||||
|
parseBoardsConfig = ->
|
||||||
|
return if @status isnt 200
|
||||||
|
boardsConfig = {}
|
||||||
|
for board in JSON.parse(@response).boards
|
||||||
|
boardName = board.board
|
||||||
|
delete board.board
|
||||||
|
boardsConfig[boardName] = board
|
||||||
|
for callback in callbacks
|
||||||
|
callback boardsConfig
|
||||||
|
callbacks = null
|
||||||
|
boardsConfig.lastModified = @getResponseHeader 'Last-Modified'
|
||||||
|
$.set 'boardsConfig', boardsConfig
|
||||||
|
|
||||||
|
(cb) ->
|
||||||
|
# Configs were already loaded previously, callback and stop.
|
||||||
|
if boardsConfig
|
||||||
|
cb boardsConfig
|
||||||
|
return
|
||||||
|
|
||||||
|
# Load configs, callback, check for updates.
|
||||||
|
if boardsConfig = $.get 'boardsConfig', null
|
||||||
|
cb boardsConfig
|
||||||
|
lastModified = boardsConfig.lastModified
|
||||||
|
else
|
||||||
|
return if callbacks.push(cb) > 1
|
||||||
|
lastModified = 0
|
||||||
|
|
||||||
|
$.ajax '//api.4chan.org/boards.json', onloadend: parseBoardsConfig,
|
||||||
|
headers: 'If-Modified-Since': lastModified
|
||||||
|
)()
|
||||||
postFromRoot: (root) ->
|
postFromRoot: (root) ->
|
||||||
link = $ 'a[title="Highlight this post"]', root
|
link = $ 'a[title="Highlight this post"]', root
|
||||||
board = link.pathname.split('/')[1]
|
board = link.pathname.split('/')[1]
|
||||||
|
|||||||
@ -158,6 +158,7 @@ class Post
|
|||||||
# Get quote/backlinks to this post
|
# Get quote/backlinks to this post
|
||||||
# and paint them (Dead).
|
# and paint them (Dead).
|
||||||
for quotelink in Get.allQuotelinksLinkingTo @
|
for quotelink in Get.allQuotelinksLinkingTo @
|
||||||
|
continue if $.hasClass quotelink, 'deadlink'
|
||||||
$.add quotelink, $.tn '\u00A0(Dead)'
|
$.add quotelink, $.tn '\u00A0(Dead)'
|
||||||
$.addClass quotelink, 'deadlink'
|
$.addClass quotelink, 'deadlink'
|
||||||
return
|
return
|
||||||
@ -267,10 +268,6 @@ Main =
|
|||||||
g.THREAD = +pathname[3]
|
g.THREAD = +pathname[3]
|
||||||
|
|
||||||
switch location.hostname
|
switch location.hostname
|
||||||
when 'boards.4chan.org'
|
|
||||||
Main.initHeader()
|
|
||||||
return if g.VIEW is 'catalog'
|
|
||||||
Main.initFeatures()
|
|
||||||
when 'sys.4chan.org'
|
when 'sys.4chan.org'
|
||||||
return
|
return
|
||||||
when 'images.4chan.org'
|
when 'images.4chan.org'
|
||||||
@ -280,41 +277,25 @@ Main =
|
|||||||
location.href = url if url
|
location.href = url if url
|
||||||
return
|
return
|
||||||
|
|
||||||
initHeader: ->
|
return if g.VIEW is 'catalog'
|
||||||
|
|
||||||
$.addStyle Main.css
|
$.addStyle Main.css
|
||||||
Main.header = $.el 'div',
|
$.asap (-> d.body), (->
|
||||||
className: 'reply'
|
$.addClass d.body, $.engine
|
||||||
innerHTML: '<div class=extra></div>'
|
$.addClass d.body, 'fourchan_x'
|
||||||
$.ready Main.initHeaderReady
|
)
|
||||||
|
|
||||||
initHeaderReady: ->
|
try
|
||||||
header = Main.header
|
Header.init()
|
||||||
$.prepend d.body, header
|
catch err
|
||||||
|
# XXX handle error
|
||||||
|
$.log err, 'Header'
|
||||||
|
|
||||||
if nav = $.id 'boardNavDesktop'
|
try
|
||||||
header.id = nav.id
|
Settings.init()
|
||||||
$.prepend header, nav
|
catch err
|
||||||
nav.id = nav.className = null
|
# XXX handle error
|
||||||
nav.lastElementChild.hidden = true
|
$.log err, 'Settings'
|
||||||
settings = $.el 'span',
|
|
||||||
id: 'settings'
|
|
||||||
innerHTML: '[<a href=javascript:;>Settings</a>]'
|
|
||||||
$.on settings.firstElementChild, 'click', Main.settings
|
|
||||||
$.add nav, settings
|
|
||||||
$("a[href$='/#{g.BOARD}/']", nav)?.className = 'current'
|
|
||||||
|
|
||||||
$.addClass d.body, $.engine
|
|
||||||
$.addClass d.body, 'fourchan_x'
|
|
||||||
|
|
||||||
# disable the mobile layout
|
|
||||||
$('link[href*=mobile]', d.head)?.disabled = true
|
|
||||||
$.id('boardNavDesktopFoot')?.hidden = true
|
|
||||||
|
|
||||||
initFeatures: ->
|
|
||||||
if Conf['Disable 4chan\'s extension']
|
|
||||||
settings = JSON.parse(localStorage.getItem '4chan-settings') or {}
|
|
||||||
settings.disableAll = true
|
|
||||||
localStorage.setItem '4chan-settings', JSON.stringify settings
|
|
||||||
|
|
||||||
if Conf['Resurrect Quotes']
|
if Conf['Resurrect Quotes']
|
||||||
try
|
try
|
||||||
@ -497,9 +478,9 @@ Main =
|
|||||||
# XXX handle error
|
# XXX handle error
|
||||||
$.log err, 'Thread Updater'
|
$.log err, 'Thread Updater'
|
||||||
|
|
||||||
$.ready Main.initFeaturesReady
|
$.ready Main.initReady
|
||||||
|
|
||||||
initFeaturesReady: ->
|
initReady: ->
|
||||||
if d.title is '4chan - 404 Not Found'
|
if d.title is '4chan - 404 Not Found'
|
||||||
if Conf['404 Redirect'] and g.VIEW is 'thread'
|
if Conf['404 Redirect'] and g.VIEW is 'thread'
|
||||||
location.href = Redirect.to
|
location.href = Redirect.to
|
||||||
@ -508,7 +489,8 @@ Main =
|
|||||||
postID: location.hash
|
postID: location.hash
|
||||||
return
|
return
|
||||||
|
|
||||||
return unless $.id 'navtopright'
|
# disable the mobile layout
|
||||||
|
$('link[href*=mobile]', d.head)?.disabled = true
|
||||||
|
|
||||||
threads = []
|
threads = []
|
||||||
posts = []
|
posts = []
|
||||||
@ -543,9 +525,6 @@ Main =
|
|||||||
$.log err.stack
|
$.log err.stack
|
||||||
return
|
return
|
||||||
|
|
||||||
settings: ->
|
|
||||||
alert 'Here be settings'
|
|
||||||
|
|
||||||
css: """<%= grunt.file.read('css/style.css') %>"""
|
css: """<%= grunt.file.read('css/style.css') %>"""
|
||||||
|
|
||||||
Main.init()
|
Main.init()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user