No more having to hack the contents of the coffee-generated JS.

This commit is contained in:
ccd0 2016-04-19 16:27:05 -07:00
parent f51ca57f2f
commit 64bb62a075
93 changed files with 554 additions and 444 deletions

View File

@ -131,7 +131,7 @@ tmp/declaration.js : .events/declare
$(if $(wildcard $@),,node tools/declare.js && echo -> $^) $(if $(wildcard $@),,node tools/declare.js && echo -> $^)
define force_compile define force_compile
$$(call dests_of,$1) : $1 $$(call imports,$$(call part_of,$1)) $$(template_deps) $$(coffee_deps) tools/globalize.js tools/chain.js $$(call dests_of,$1) : $1 $$(call imports,$$(call part_of,$1)) $$(template_deps) $$(coffee_deps) tools/chain.js
$(RM) $$(subst $$$$,$$(ESC_DOLLAR),$$@) $(RM) $$(subst $$$$,$$(ESC_DOLLAR),$$@)
endef endef

View File

@ -114,3 +114,5 @@ Redirect =
location.replace url location.replace url
else if alternative else if alternative
location.replace alternative location.replace alternative
return Redirect

View File

@ -24,3 +24,5 @@ Anonymize =
name.textContent = 'Anonymous' for name in $$ '.name' name.textContent = 'Anonymous' for name in $$ '.name'
$.rm trip for trip in $$ '.postertrip' $.rm trip for trip in $$ '.postertrip'
return return
return Anonymize

View File

@ -247,3 +247,5 @@ Filter =
tl = ta.textLength tl = ta.textLength
ta.setSelectionRange tl, tl ta.setSelectionRange tl, tl
ta.focus() ta.focus()
return Filter

View File

@ -215,3 +215,5 @@ PostHiding =
for quotelink in Get.allQuotelinksLinkingTo post for quotelink in Get.allQuotelinksLinkingTo post
$.rmClass quotelink, 'filtered' $.rmClass quotelink, 'filtered'
return return
return PostHiding

View File

@ -32,3 +32,5 @@ Recursive =
g.posts.forEach (post) -> g.posts.forEach (post) ->
if fullID in post.quotes if fullID in post.quotes
recursive post, args... recursive post, args...
return Recursive

View File

@ -189,3 +189,5 @@ ThreadHiding =
threadRoot = thread.OP.nodes.root.parentNode threadRoot = thread.OP.nodes.root.parentNode
threadRoot.hidden = thread.isHidden = false threadRoot.hidden = thread.isHidden = false
Index.updateHideLabel() if Conf['JSON Index'] Index.updateHideLabel() if Conf['JSON Index']
return ThreadHiding

View File

@ -260,3 +260,5 @@ Build =
$.addClass $('.file-count', root), 'warning' $.addClass $('.file-count', root), 'warning'
root root
return Build

View File

@ -67,3 +67,5 @@ Get =
for script in $$ 'script:not([src])', d.head for script in $$ 'script:not([src])', d.head
return script.textContent if /\bcooldowns *=/.test script.textContent return script.textContent if /\bcooldowns *=/.test script.textContent
'' ''
return Get

View File

@ -539,3 +539,5 @@ Header =
$.set 'Desktop Notifications', false $.set 'Desktop Notifications', false
notice.close() notice.close()
notice = new Notice 'info', el notice = new Notice 'info', el
return Header

View File

@ -773,3 +773,5 @@ Index =
for keyword in keywords for keyword in keywords
return false if -1 is text.indexOf keyword return false if -1 is text.indexOf keyword
return true return true
return Index

View File

@ -13,3 +13,5 @@ Polyfill =
ui8a[i] = data.charCodeAt i ui8a[i] = data.charCodeAt i
cb new Blob [ui8a], {type} cb new Blob [ui8a], {type}
$.globalEval "HTMLCanvasElement.prototype.toBlob = (#{HTMLCanvasElement::toBlob});" $.globalEval "HTMLCanvasElement.prototype.toBlob = (#{HTMLCanvasElement::toBlob});"
return Polyfill

View File

@ -613,3 +613,5 @@ Settings =
return unless (key = Keybinds.keyCode e)? return unless (key = Keybinds.keyCode e)?
@value = key @value = key
$.cb.value.call @ $.cb.value.call @
return Settings

View File

@ -1,390 +1,389 @@
UI = do -> dialog = (id, position, properties) ->
dialog = (id, position, properties) -> el = $.el 'div',
el = $.el 'div', className: 'dialog'
id: id
$.extend el, properties
el.style.cssText = position
$.get "#{id}.position", position, (item) ->
el.style.cssText = item["#{id}.position"]
move = $ '.move', el
$.on move, 'touchstart mousedown', dragstart
for child in move.children
continue unless child.tagName
$.on child, 'touchstart mousedown', (e) ->
e.stopPropagation()
el
class Menu
currentMenu = null
lastToggledButton = null
constructor: (@type) ->
# XXX AddMenuEntry event is deprecated
$.on d, 'AddMenuEntry', ({detail}) =>
return if detail.type isnt @type
delete detail.open
@addEntry detail
@entries = []
makeMenu: ->
menu = $.el 'div',
className: 'dialog' className: 'dialog'
id: id id: 'menu'
$.extend el, properties tabIndex: 0
el.style.cssText = position $.on menu, 'click', (e) -> e.stopPropagation()
$.get "#{id}.position", position, (item) -> $.on menu, 'keydown', @keybinds
el.style.cssText = item["#{id}.position"] menu
move = $ '.move', el toggle: (e, button, data) ->
$.on move, 'touchstart mousedown', dragstart e.preventDefault()
for child in move.children e.stopPropagation()
continue unless child.tagName
$.on child, 'touchstart mousedown', (e) ->
e.stopPropagation()
el if currentMenu
# Close if it's already opened.
# Reopen if we clicked on another button.
previousButton = lastToggledButton
currentMenu.close()
return if previousButton is button
class Menu return unless @entries.length
@open button, data
open: (button, data) ->
menu = @menu = @makeMenu()
currentMenu = @
lastToggledButton = button
@entries.sort (first, second) ->
first.order - second.order
for entry in @entries
@insertEntry entry, menu, data
$.addClass lastToggledButton, 'active'
$.on d, 'click CloseMenu', @close
$.on d, 'scroll', @close unless @type is 'gallery'
$.add button, menu
# Position
mRect = menu.getBoundingClientRect()
bRect = button.getBoundingClientRect()
bTop = window.scrollY + bRect.top
bLeft = window.scrollX + bRect.left
cHeight = doc.clientHeight
cWidth = doc.clientWidth
[top, bottom] = if bRect.top + bRect.height + mRect.height < cHeight
[bRect.bottom, null]
else
[null, cHeight - bRect.top]
[left, right] = if bRect.left + mRect.width < cWidth
[bRect.left, null]
else
[null, cWidth - bRect.right]
{style} = menu
style.top = "#{top}px"
style.right = "#{right}px"
style.bottom = "#{bottom}px"
style.left = "#{left}px"
if right
$.addClass menu, 'left'
entry = $ '.entry', menu
# We've removed flexbox, so we don't use order anymore.
# while prevEntry = @findNextEntry entry, -1
# entry = prevEntry
@focus entry
menu.focus()
insertEntry: (entry, parent, data) ->
if typeof entry.open is 'function'
try
return unless entry.open data
catch err
Main.handleErrors
message: "Error in building the #{@type} menu."
error: err
return
$.add parent, entry.el
return unless entry.subEntries
if submenu = $ '.submenu', entry.el
# Reset sub menu, remove irrelevant entries.
$.rm submenu
submenu = $.el 'div',
className: 'dialog submenu'
for subEntry in entry.subEntries
@insertEntry subEntry, submenu, data
$.add entry.el, submenu
return
close: =>
$.rm @menu
delete @menu
$.rmClass lastToggledButton, 'active'
currentMenu = null currentMenu = null
lastToggledButton = null lastToggledButton = null
$.off d, 'click scroll CloseMenu', @close
constructor: (@type) -> findNextEntry: (entry, direction) ->
# XXX AddMenuEntry event is deprecated entries = [entry.parentNode.children...]
$.on d, 'AddMenuEntry', ({detail}) => entries.sort (first, second) -> first.style.order - second.style.order
return if detail.type isnt @type entries[entries.indexOf(entry) + direction]
delete detail.open
@addEntry detail
@entries = []
makeMenu: -> keybinds: (e) =>
menu = $.el 'div', entry = $ '.focused', @menu
className: 'dialog' while subEntry = $ '.focused', entry
id: 'menu' entry = subEntry
tabIndex: 0
$.on menu, 'click', (e) -> e.stopPropagation()
$.on menu, 'keydown', @keybinds
menu
toggle: (e, button, data) -> switch e.keyCode
e.preventDefault() when 27 # Esc
e.stopPropagation() lastToggledButton.focus()
@close()
if currentMenu when 13, 32 # Enter, Space
# Close if it's already opened. entry.click()
# Reopen if we clicked on another button. when 38 # Up
previousButton = lastToggledButton if next = @findNextEntry entry, -1
currentMenu.close() @focus next
return if previousButton is button when 40 # Down
if next = @findNextEntry entry, +1
return unless @entries.length @focus next
@open button, data when 39 # Right
if (submenu = $ '.submenu', entry) and next = submenu.firstElementChild
open: (button, data) -> while nextPrev = @findNextEntry next, -1
menu = @menu = @makeMenu() next = nextPrev
currentMenu = @ @focus next
lastToggledButton = button when 37 # Left
if next = $.x 'parent::*[contains(@class,"submenu")]/parent::*', entry
@entries.sort (first, second) -> @focus next
first.order - second.order
for entry in @entries
@insertEntry entry, menu, data
$.addClass lastToggledButton, 'active'
$.on d, 'click CloseMenu', @close
$.on d, 'scroll', @close unless @type is 'gallery'
$.add button, menu
# Position
mRect = menu.getBoundingClientRect()
bRect = button.getBoundingClientRect()
bTop = window.scrollY + bRect.top
bLeft = window.scrollX + bRect.left
cHeight = doc.clientHeight
cWidth = doc.clientWidth
[top, bottom] = if bRect.top + bRect.height + mRect.height < cHeight
[bRect.bottom, null]
else else
[null, cHeight - bRect.top]
[left, right] = if bRect.left + mRect.width < cWidth
[bRect.left, null]
else
[null, cWidth - bRect.right]
{style} = menu
style.top = "#{top}px"
style.right = "#{right}px"
style.bottom = "#{bottom}px"
style.left = "#{left}px"
if right
$.addClass menu, 'left'
entry = $ '.entry', menu
# We've removed flexbox, so we don't use order anymore.
# while prevEntry = @findNextEntry entry, -1
# entry = prevEntry
@focus entry
menu.focus()
insertEntry: (entry, parent, data) ->
if typeof entry.open is 'function'
try
return unless entry.open data
catch err
Main.handleErrors
message: "Error in building the #{@type} menu."
error: err
return
$.add parent, entry.el
return unless entry.subEntries
if submenu = $ '.submenu', entry.el
# Reset sub menu, remove irrelevant entries.
$.rm submenu
submenu = $.el 'div',
className: 'dialog submenu'
for subEntry in entry.subEntries
@insertEntry subEntry, submenu, data
$.add entry.el, submenu
return
close: =>
$.rm @menu
delete @menu
$.rmClass lastToggledButton, 'active'
currentMenu = null
lastToggledButton = null
$.off d, 'click scroll CloseMenu', @close
findNextEntry: (entry, direction) ->
entries = [entry.parentNode.children...]
entries.sort (first, second) -> first.style.order - second.style.order
entries[entries.indexOf(entry) + direction]
keybinds: (e) =>
entry = $ '.focused', @menu
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 = @findNextEntry entry, -1
@focus next
when 40 # Down
if next = @findNextEntry entry, +1
@focus next
when 39 # Right
if (submenu = $ '.submenu', entry) and next = submenu.firstElementChild
while nextPrev = @findNextEntry next, -1
next = nextPrev
@focus next
when 37 # Left
if next = $.x 'parent::*[contains(@class,"submenu")]/parent::*', entry
@focus next
else
return
e.preventDefault()
e.stopPropagation()
onFocus: (e) =>
e.stopPropagation()
@focus e.target
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 = doc.clientHeight
cWidth = doc.clientWidth
[top, bottom] = if eRect.top + sRect.height < cHeight
['0px', 'auto']
else
['auto', '0px']
[left, right] = if eRect.right + sRect.width < cWidth - 150
['100%', 'auto']
else
['auto', '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, subEntries} = entry
$.addClass el, 'entry'
$.on el, 'focus mouseover', @onFocus
el.style.order = entry.order or 100
return unless subEntries
$.addClass el, 'has-submenu'
for subEntry in subEntries
@parseEntry subEntry
return
dragstart = (e) ->
return if e.type is 'mousedown' and e.button isnt 0 # not LMB
# prevent text selection
e.preventDefault()
if isTouching = e.type is 'touchstart'
e = e.changedTouches[e.changedTouches.length - 1]
# distance from pointer to el edge is constant; calculate it here.
el = $.x 'ancestor::div[contains(@class,"dialog")][1]', @
rect = el.getBoundingClientRect()
screenHeight = doc.clientHeight
screenWidth = doc.clientWidth
o = {
id: el.id
style: el.style
dx: e.clientX - rect.left
dy: e.clientY - rect.top
height: screenHeight - rect.height
width: screenWidth - rect.width
screenHeight: screenHeight
screenWidth: screenWidth
isTouching: isTouching
}
[o.topBorder, o.bottomBorder] = if Conf['Header auto-hide'] or not Conf['Fixed Header']
[0, 0]
else if Conf['Bottom Header']
[0, Header.bar.getBoundingClientRect().height]
else
[Header.bar.getBoundingClientRect().height, 0]
if isTouching
o.identifier = e.identifier
o.move = touchmove.bind o
o.up = touchend.bind o
$.on d, 'touchmove', o.move
$.on d, 'touchend touchcancel', o.up
else # mousedown
o.move = drag.bind o
o.up = dragend.bind o
$.on d, 'mousemove', o.move
$.on d, 'mouseup', o.up
touchmove = (e) ->
for touch in e.changedTouches
if touch.identifier is @identifier
drag.call @, touch
return return
drag = (e) -> e.preventDefault()
{clientX, clientY} = e e.stopPropagation()
left = clientX - @dx onFocus: (e) =>
left = if left < 10 e.stopPropagation()
0 @focus e.target
else if @width - left < 10
null 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 = doc.clientHeight
cWidth = doc.clientWidth
[top, bottom] = if eRect.top + sRect.height < cHeight
['0px', 'auto']
else else
left / @screenWidth * 100 + '%' ['auto', '0px']
[left, right] = if eRect.right + sRect.width < cWidth - 150
top = clientY - @dy ['100%', 'auto']
top = if top < (10 + @topBorder)
@topBorder + 'px'
else if @height - top < (10 + @bottomBorder)
null
else else
top / @screenHeight * 100 + '%' ['auto', '100%']
{style} = submenu
right = if left is null
0
else
null
bottom = if top is null
@bottomBorder + 'px'
else
null
{style} = @
style.left = left
style.right = right
style.top = top style.top = top
style.bottom = bottom style.bottom = bottom
style.left = left
style.right = right
touchend = (e) -> addEntry: (entry) =>
for touch in e.changedTouches @parseEntry entry
if touch.identifier is @identifier @entries.push entry
dragend.call @
return
dragend = -> parseEntry: (entry) ->
if @isTouching {el, subEntries} = entry
$.off d, 'touchmove', @move $.addClass el, 'entry'
$.off d, 'touchend touchcancel', @up $.on el, 'focus mouseover', @onFocus
else # mouseup el.style.order = entry.order or 100
$.off d, 'mousemove', @move return unless subEntries
$.off d, 'mouseup', @up $.addClass el, 'has-submenu'
$.set "#{@id}.position", @style.cssText for subEntry in subEntries
@parseEntry subEntry
return
hoverstart = ({root, el, latestEvent, endEvents, height, cb, noRemove}) -> dragstart = (e) ->
o = { return if e.type is 'mousedown' and e.button isnt 0 # not LMB
root # prevent text selection
el e.preventDefault()
style: el.style if isTouching = e.type is 'touchstart'
isImage: el.nodeName in ['IMG', 'VIDEO'] e = e.changedTouches[e.changedTouches.length - 1]
cb # distance from pointer to el edge is constant; calculate it here.
endEvents el = $.x 'ancestor::div[contains(@class,"dialog")][1]', @
latestEvent rect = el.getBoundingClientRect()
clientHeight: doc.clientHeight screenHeight = doc.clientHeight
clientWidth: doc.clientWidth screenWidth = doc.clientWidth
height o = {
noRemove id: el.id
} style: el.style
o.hover = hover.bind o dx: e.clientX - rect.left
o.hoverend = hoverend.bind o dy: e.clientY - rect.top
height: screenHeight - rect.height
o.hover o.latestEvent width: screenWidth - rect.width
new MutationObserver(-> screenHeight: screenHeight
o.hover o.latestEvent if el.parentNode screenWidth: screenWidth
).observe el, {childList: true} isTouching: isTouching
$.on root, endEvents, o.hoverend
if $.x 'ancestor::div[contains(@class,"inline")][1]', root
$.on d, 'keydown', o.hoverend
$.on root, 'mousemove', o.hover
# Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=674955
o.workaround = (e) -> o.hoverend(e) unless root.contains e.target
$.on doc, 'mousemove', o.workaround
hoverstart.padding = 25
hover = (e) ->
@latestEvent = e
height = (@height or @el.offsetHeight) + hoverstart.padding
{clientX, clientY} = e
top = if @isImage
Math.max 0, clientY * (@clientHeight - height) / @clientHeight
else
Math.max 0, Math.min(@clientHeight - height, clientY - 120)
threshold = @clientWidth / 2
threshold = Math.max threshold, @clientWidth - 400 unless @isImage
[left, right] = if clientX <= threshold
[clientX + 45 + 'px', null]
else
[null, @clientWidth - clientX + 45 + 'px']
{style} = @
style.top = top + 'px'
style.left = left
style.right = right
hoverend = (e) ->
return if e.type is 'keydown' and e.keyCode isnt 13 or e.target.nodeName is "TEXTAREA"
$.rm @el unless @noRemove
$.off @root, @endEvents, @hoverend
$.off d, 'keydown', @hoverend
$.off @root, 'mousemove', @hover
# Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=674955
$.off doc, 'mousemove', @workaround
@cb.call @ if @cb
checkbox = (name, text, checked) ->
checked = Conf[name] unless checked?
label = $.el 'label'
input = $.el 'input', {type: 'checkbox', name, checked}
$.add label, [input, $.tn " #{text}"]
label
return {
dialog: dialog
Menu: Menu
hover: hoverstart
checkbox: checkbox
} }
[o.topBorder, o.bottomBorder] = if Conf['Header auto-hide'] or not Conf['Fixed Header']
[0, 0]
else if Conf['Bottom Header']
[0, Header.bar.getBoundingClientRect().height]
else
[Header.bar.getBoundingClientRect().height, 0]
if isTouching
o.identifier = e.identifier
o.move = touchmove.bind o
o.up = touchend.bind o
$.on d, 'touchmove', o.move
$.on d, 'touchend touchcancel', o.up
else # mousedown
o.move = drag.bind o
o.up = dragend.bind o
$.on d, 'mousemove', o.move
$.on d, 'mouseup', o.up
touchmove = (e) ->
for touch in e.changedTouches
if touch.identifier is @identifier
drag.call @, touch
return
drag = (e) ->
{clientX, clientY} = e
left = clientX - @dx
left = if left < 10
0
else if @width - left < 10
null
else
left / @screenWidth * 100 + '%'
top = clientY - @dy
top = if top < (10 + @topBorder)
@topBorder + 'px'
else if @height - top < (10 + @bottomBorder)
null
else
top / @screenHeight * 100 + '%'
right = if left is null
0
else
null
bottom = if top is null
@bottomBorder + 'px'
else
null
{style} = @
style.left = left
style.right = right
style.top = top
style.bottom = bottom
touchend = (e) ->
for touch in e.changedTouches
if touch.identifier is @identifier
dragend.call @
return
dragend = ->
if @isTouching
$.off d, 'touchmove', @move
$.off d, 'touchend touchcancel', @up
else # mouseup
$.off d, 'mousemove', @move
$.off d, 'mouseup', @up
$.set "#{@id}.position", @style.cssText
hoverstart = ({root, el, latestEvent, endEvents, height, cb, noRemove}) ->
o = {
root
el
style: el.style
isImage: el.nodeName in ['IMG', 'VIDEO']
cb
endEvents
latestEvent
clientHeight: doc.clientHeight
clientWidth: doc.clientWidth
height
noRemove
}
o.hover = hover.bind o
o.hoverend = hoverend.bind o
o.hover o.latestEvent
new MutationObserver(->
o.hover o.latestEvent if el.parentNode
).observe el, {childList: true}
$.on root, endEvents, o.hoverend
if $.x 'ancestor::div[contains(@class,"inline")][1]', root
$.on d, 'keydown', o.hoverend
$.on root, 'mousemove', o.hover
# Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=674955
o.workaround = (e) -> o.hoverend(e) unless root.contains e.target
$.on doc, 'mousemove', o.workaround
hoverstart.padding = 25
hover = (e) ->
@latestEvent = e
height = (@height or @el.offsetHeight) + hoverstart.padding
{clientX, clientY} = e
top = if @isImage
Math.max 0, clientY * (@clientHeight - height) / @clientHeight
else
Math.max 0, Math.min(@clientHeight - height, clientY - 120)
threshold = @clientWidth / 2
threshold = Math.max threshold, @clientWidth - 400 unless @isImage
[left, right] = if clientX <= threshold
[clientX + 45 + 'px', null]
else
[null, @clientWidth - clientX + 45 + 'px']
{style} = @
style.top = top + 'px'
style.left = left
style.right = right
hoverend = (e) ->
return if e.type is 'keydown' and e.keyCode isnt 13 or e.target.nodeName is "TEXTAREA"
$.rm @el unless @noRemove
$.off @root, @endEvents, @hoverend
$.off d, 'keydown', @hoverend
$.off @root, 'mousemove', @hover
# Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=674955
$.off doc, 'mousemove', @workaround
@cb.call @ if @cb
checkbox = (name, text, checked) ->
checked = Conf[name] unless checked?
label = $.el 'label'
input = $.el 'input', {type: 'checkbox', name, checked}
$.add label, [input, $.tn " #{text}"]
label
return {
dialog: dialog
Menu: Menu
hover: hoverstart
checkbox: checkbox
}

View File

@ -49,3 +49,5 @@ FappeTyme =
toggle: (type) -> toggle: (type) ->
@set type, !@enabled[type] @set type, !@enabled[type]
$.cb.checked.call @nodes[type] if type is 'werk' $.cb.checked.call @nodes[type] if type is 'werk'
return FappeTyme

View File

@ -368,3 +368,5 @@ Gallery =
subEntries.push el: delayLabel subEntries.push el: delayLabel
subEntries subEntries
return Gallery

View File

@ -88,3 +88,5 @@ ImageCommon =
@click() @click()
else else
new Notice 'error', "Could not download #{@href}", 30 new Notice 'error', "Could not download #{@href}", 30
return ImageCommon

View File

@ -302,3 +302,5 @@ ImageExpand =
$.event 'change', null, input $.event 'change', null, input
$.on input, 'change', $.cb.checked $.on input, 'change', $.cb.checked
el: label el: label
return ImageExpand

View File

@ -72,3 +72,5 @@ ImageHover =
@src = URL + if @src is URL then '?' + Date.now() else '' @src = URL + if @src is URL then '?' + Date.now() else ''
else else
$.rm @ $.rm @
return ImageHover

View File

@ -89,3 +89,5 @@ ImageLoader =
{thumb} = post.file {thumb} = post.file
if Header.isNodeVisible(thumb) or post.nodes.root is qpClone then thumb.play() else thumb.pause() if Header.isNodeVisible(thumb) or post.nodes.root is qpClone then thumb.play() else thumb.pause()
return return
return ImageLoader

View File

@ -60,3 +60,5 @@ Metadata =
else unless element in [0x8538067, 0x549A966] # Segment, Info else unless element in [0x8538067, 0x549A966] # Segment, Info
i += size i += size
null null
return Metadata

View File

@ -17,3 +17,5 @@ RevealSpoilers =
thumb.src = @file.thumbURL thumb.src = @file.thumbURL
else else
thumb.dataset.src = @file.thumbURL thumb.dataset.src = @file.thumbURL
return RevealSpoilers

View File

@ -87,3 +87,5 @@ Sauce =
name: (post) -> post.file.name name: (post) -> post.file.name
'%': -> '%' '%': -> '%'
semi: -> ';' semi: -> ';'
return Sauce

View File

@ -78,3 +78,5 @@ Volume =
volume /= 1.1 if e.deltaY > 0 volume /= 1.1 if e.deltaY > 0
el.volume = $.minmax volume - 0.1, 0, 1 el.volume = $.minmax volume - 0.1, 0, 1
e.preventDefault() e.preventDefault()
return Volume

View File

@ -395,3 +395,5 @@ Embedding =
src: a.dataset.href src: a.dataset.href
loop: /^https?:\/\/i\.4cdn\.org\//.test a.dataset.href loop: /^https?:\/\/i\.4cdn\.org\//.test a.dataset.href
] ]
return Embedding

View File

@ -155,3 +155,5 @@ Linkify =
range.insertNode a range.insertNode a
a a
return Linkify

View File

@ -51,3 +51,5 @@ ArchiveLink =
el: el el: el
open: open open: open
} }
return ArchiveLink

View File

@ -132,3 +132,5 @@ DeleteLink =
for fileOnly in [false, true] when DeleteLink.auto[+fileOnly][post.fullID] for fileOnly in [false, true] when DeleteLink.auto[+fileOnly][post.fullID]
DeleteLink.delete post, fileOnly DeleteLink.delete post, fileOnly
return return
return DeleteLink

View File

@ -17,3 +17,5 @@ DownloadLink =
a.href = file.url a.href = file.url
a.download = file.name a.download = file.name
true true
return DownloadLink

View File

@ -31,3 +31,5 @@ Menu =
$.on button, 'click', (e) -> $.on button, 'click', (e) ->
Menu.menu.toggle e, @, post Menu.menu.toggle e, @, post
button button
return Menu

View File

@ -32,3 +32,5 @@ ReportLink =
id = Date.now() id = Date.now()
set = "toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1,#{dims}" set = "toolbar=0,scrollbars=1,location=0,status=1,menubar=0,resizable=1,#{dims}"
window.open url, id, set window.open url, id, set
return ReportLink

View File

@ -31,3 +31,5 @@ AntiAutoplay =
object.data = object.data.replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', '') object.data = object.data.replace(/\?autoplay=1&?/, '?').replace('&autoplay=1', '')
$.addClass object, 'autoplay-removed' $.addClass object, 'autoplay-removed'
return return
return AntiAutoplay

View File

@ -107,3 +107,5 @@ Banner =
child.textContent = data.title child.textContent = data.title
else else
Banner.db.delete {boardID: g.BOARD.ID, threadID: className} Banner.db.delete {boardID: g.BOARD.ID, threadID: className}
return Banner

View File

@ -86,3 +86,5 @@ CatalogLinks =
if g.BOARD.ID is board and g.VIEW is 'index' then '#index' else "/#{board}/#index" if g.BOARD.ID is board and g.VIEW is 'index' then '#index' else "/#{board}/#index"
else else
"/#{board}/" "/#{board}/"
return CatalogLinks

View File

@ -15,3 +15,5 @@ CustomCSS =
unless @style unless @style
return @addStyle() return @addStyle()
@style.textContent = Conf['usercss'] @style.textContent = Conf['usercss']
return CustomCSS

View File

@ -71,3 +71,5 @@ ExpandComment =
for callback in ExpandComment.callbacks for callback in ExpandComment.callbacks
callback.call post callback.call post
return return
return ExpandComment

View File

@ -103,3 +103,5 @@ ExpandThread =
postsCount = postsRoot.length postsCount = postsRoot.length
a.textContent = Build.summaryText '-', postsCount, filesCount a.textContent = Build.summaryText '-', postsCount, filesCount
return ExpandThread

View File

@ -48,3 +48,5 @@ FileInfo =
r: -> <%= html('${this.file.dimensions || "PDF"}') %> r: -> <%= html('${this.file.dimensions || "PDF"}') %>
g: -> <%= html('?{this.file.tag}{, ${this.file.tag}}{}') %> g: -> <%= html('?{this.file.tag}{, ${this.file.tag}}{}') %>
'%': -> <%= html('%') %> '%': -> <%= html('%') %>
return FileInfo

View File

@ -10,3 +10,5 @@ Flash =
if g.VIEW is 'thread' if g.VIEW is 'thread'
$.global -> window.Main.tid = location.pathname.split(/\/+/)[3] $.global -> window.Main.tid = location.pathname.split(/\/+/)[3]
$.global -> window.SWFEmbed.init() $.global -> window.SWFEmbed.init()
return Flash

View File

@ -73,3 +73,5 @@ Fourchan =
$.event 'mathjax', null, @nodes.comment $.event 'mathjax', null, @nodes.comment
$.on d, 'PostsInserted', cb $.on d, 'PostsInserted', cb
cb() cb()
return Fourchan

View File

@ -47,3 +47,5 @@ IDColor =
while i < 8 while i < 8
msg = (msg << 5) - msg + uid.charCodeAt i++ msg = (msg << 5) - msg + uid.charCodeAt i++
msg msg
return IDColor

View File

@ -21,3 +21,5 @@ IDHighlight =
uniqueID = post.info.uniqueID or post.info.capcode uniqueID = post.info.uniqueID or post.info.capcode
IDHighlight.uniqueID = if IDHighlight.uniqueID is uniqueID then null else uniqueID IDHighlight.uniqueID = if IDHighlight.uniqueID is uniqueID then null else uniqueID
g.posts.forEach IDHighlight.set g.posts.forEach IDHighlight.set
return IDHighlight

View File

@ -324,3 +324,5 @@ Keybinds =
focus: (post) -> focus: (post) ->
$.addClass post, 'highlight' $.addClass post, 'highlight'
return Keybinds

View File

@ -77,3 +77,5 @@ Nav =
d.body.style.marginBottom = null d.body.style.marginBottom = null
delete Nav.haveExtra delete Nav.haveExtra
$.off d, 'scroll', Nav.removeExtra $.off d, 'scroll', Nav.removeExtra
return Nav

View File

@ -12,3 +12,5 @@ NormalizeURL =
pathname = pathname.join '/' pathname = pathname.join '/'
if location.pathname isnt pathname if location.pathname isnt pathname
history.replaceState history.state, '', "#{location.protocol}//#{location.host}#{pathname}#{location.hash}" history.replaceState history.state, '', "#{location.protocol}//#{location.host}#{pathname}#{location.hash}"
return NormalizeURL

View File

@ -54,3 +54,5 @@ PSAHiding =
$.after $.id('globalToggle'), psa $.after $.id('globalToggle'), psa
PSAHiding.hr?.hidden = PSAHiding.hidden PSAHiding.hr?.hidden = PSAHiding.hidden
return return
return PSAHiding

View File

@ -119,3 +119,5 @@ RelativeDates =
return if data in RelativeDates.stale # We can call RelativeDates.update() multiple times. return if data in RelativeDates.stale # We can call RelativeDates.update() multiple times.
return if data instanceof Post and !g.posts[data.fullID] # collected post. return if data instanceof Post and !g.posts[data.fullID] # collected post.
RelativeDates.stale.push data RelativeDates.stale.push data
return RelativeDates

View File

@ -26,3 +26,5 @@ RemoveSpoilers =
$.replace spoiler, span $.replace spoiler, span
$.add span, [spoiler.childNodes...] $.add span, [spoiler.childNodes...]
return return
return RemoveSpoilers

View File

@ -65,3 +65,5 @@ Report =
if types = $.id('reportTypes') if types = $.id('reportTypes')
$.on types, 'change', (e) -> $.on types, 'change', (e) ->
$('form').action = if e.target.value is 'illegal' then '#redirect' else '' $('form').action = if e.target.value is 'illegal' then '#redirect' else ''
return Report

View File

@ -18,3 +18,5 @@ ThreadLinks =
process: (link) -> process: (link) ->
link.target = '_blank' link.target = '_blank'
return ThreadLinks

View File

@ -62,3 +62,5 @@ Time =
y: -> @getFullYear().toString()[2..] y: -> @getFullYear().toString()[2..]
Y: -> @getFullYear() Y: -> @getFullYear()
'%': -> '%' '%': -> '%'
return Time

View File

@ -80,3 +80,5 @@ Favicon =
dead: 'data:image/gif;base64,<%= readBase64("src/Monitoring/Favicon/dead.gif") %>' dead: 'data:image/gif;base64,<%= readBase64("src/Monitoring/Favicon/dead.gif") %>'
logo: 'data:image/png;base64,<%= readBase64("src/meta/icon128.png") %>' logo: 'data:image/png;base64,<%= readBase64("src/meta/icon128.png") %>'
return Favicon

View File

@ -40,3 +40,5 @@ MarkNewIPs =
markOld: (post) -> markOld: (post) ->
post.nodes.nameBlock.title = 'Not the first post from this IP.' post.nodes.nameBlock.title = 'Not the first post from this IP.'
$.addClass post.nodes.root, 'old-ip' $.addClass post.nodes.root, 'old-ip'
return MarkNewIPs

View File

@ -119,3 +119,5 @@ ReplyPruning =
else else
Build.summaryText '-', ReplyPruning.total, ReplyPruning.totalFiles Build.summaryText '-', ReplyPruning.total, ReplyPruning.totalFiles
ReplyPruning.summary.hidden = (ReplyPruning.total <= +Conf["Max Replies"]) ReplyPruning.summary.hidden = (ReplyPruning.total <= +Conf["Max Replies"])
return ReplyPruning

View File

@ -6,3 +6,5 @@ ThreadExcerpt =
name: 'Thread Excerpt' name: 'Thread Excerpt'
cb: @node cb: @node
node: -> d.title = Get.threadExcerpt @ node: -> d.title = Get.threadExcerpt @
return ThreadExcerpt

View File

@ -103,3 +103,5 @@ ThreadStats =
if g.BOARD.ID isnt 'f' and ThreadStats.lastPost > ThreadStats.lastPageUpdate and ThreadStats.pageCountEl?.textContent isnt '1' if g.BOARD.ID isnt 'f' and ThreadStats.lastPost > ThreadStats.lastPageUpdate and ThreadStats.pageCountEl?.textContent isnt '1'
clearTimeout ThreadStats.timeout clearTimeout ThreadStats.timeout
ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 5 * $.SECOND ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 5 * $.SECOND
return ThreadStats

View File

@ -356,3 +356,5 @@ ThreadUpdater =
postCount: OP.replies + 1 postCount: OP.replies + 1
fileCount: OP.images + !!OP.fsize fileCount: OP.images + !!OP.fsize
ipCount: OP.unique_ips ipCount: OP.unique_ips
return ThreadUpdater

View File

@ -482,3 +482,5 @@ ThreadWatcher =
$.on input, 'change', ThreadWatcher.refresh if name in ['Current Board', 'Show Unread Count'] $.on input, 'change', ThreadWatcher.refresh if name in ['Current Board', 'Show Unread Count']
$.on input, 'change', ThreadWatcher.fetchAuto if name in ['Show Unread Count', 'Auto Update Thread Watcher'] $.on input, 'change', ThreadWatcher.fetchAuto if name in ['Show Unread Count', 'Auto Update Thread Watcher']
entry entry
return ThreadWatcher

View File

@ -252,3 +252,5 @@ Unread =
Favicon[if isDead then 'dead' else 'default'] Favicon[if isDead then 'dead' else 'default']
# `favicon.href = href` doesn't work on Firefox. # `favicon.href = href` doesn't work on Firefox.
$.add d.head, Favicon.el $.add d.head, Favicon.el
return Unread

View File

@ -1 +0,0 @@
Captcha = {}

1
src/Posting/Captcha.js Normal file
View File

@ -0,0 +1 @@
Captcha = {};

View File

@ -14,3 +14,5 @@ PassLink =
Date.now() Date.now()
'width=500,height=280,toolbar=0' 'width=500,height=280,toolbar=0'
$.before styleSelector.previousSibling, [passLink, $.tn('\u00A0\u00A0')] $.before styleSelector.previousSibling, [passLink, $.tn('\u00A0\u00A0')]
return PassLink

View File

@ -16,3 +16,5 @@ PostSuccessful =
threadID: threadID threadID: threadID
postID: postID postID: postID
val: true val: true
return PostSuccessful

View File

@ -827,3 +827,5 @@ QR =
QR.cooldown.auto = false QR.cooldown.auto = false
QR.notifications.push new Notice 'info', 'QR upload aborted.', 5 QR.notifications.push new Notice 'info', 'QR upload aborted.', 5
QR.status() QR.status()
return QR

View File

@ -58,3 +58,5 @@ QuoteBacklink =
getContainer: (id) -> getContainer: (id) ->
@containers[id] or= @containers[id] or=
$.el 'span', className: 'container' $.el 'span', className: 'container'
return QuoteBacklink

View File

@ -23,3 +23,5 @@ QuoteCT =
if boardID is board.ID and threadID isnt thread.ID if boardID is board.ID and threadID isnt thread.ID
$.add quotelink, $.tn QuoteCT.text $.add quotelink, $.tn QuoteCT.text
return return
return QuoteCT

View File

@ -110,3 +110,5 @@ QuoteInline =
QuoteInline.rm inlined, boardID, threadID, postID, context QuoteInline.rm inlined, boardID, threadID, postID, context
$.rmClass inlined, 'inlined' $.rmClass inlined, 'inlined'
return return
return QuoteInline

View File

@ -34,3 +34,5 @@ QuoteOP =
if "#{boardID}.#{postID}" is fullID if "#{boardID}.#{postID}" is fullID
$.add quotelink, $.tn QuoteOP.text $.add quotelink, $.tn QuoteOP.text
return return
return QuoteOP

View File

@ -53,3 +53,5 @@ QuotePreview =
for post in [post].concat post.clones for post in [post].concat post.clones
$.rmClass post.nodes.post, 'qphl' $.rmClass post.nodes.post, 'qphl'
return return
return QuotePreview

View File

@ -14,3 +14,5 @@ QuoteStrikeThrough =
if g.posts["#{boardID}.#{postID}"]?.isHidden if g.posts["#{boardID}.#{postID}"]?.isHidden
$.addClass quotelink, 'filtered' $.addClass quotelink, 'filtered'
return return
return QuoteStrikeThrough

View File

@ -146,3 +146,5 @@ QuoteThreading =
Unread.setLine true Unread.setLine true
Unread.read() Unread.read()
Unread.update() Unread.update()
return QuoteThreading

View File

@ -72,3 +72,5 @@ QuoteYou =
Header.scrollTo post Header.scrollTo post
$.addClass post, 'highlight' $.addClass post, 'highlight'
return true return true
return QuoteYou

View File

@ -84,3 +84,5 @@ Quotify =
$.before deadlink, green $.before deadlink, green
$.add green, deadlink $.add green, deadlink
$.replace deadlink, [deadlink.childNodes...] $.replace deadlink, [deadlink.childNodes...]
return Quotify

View File

@ -6,3 +6,5 @@ class Board
@posts = new SimpleDict() @posts = new SimpleDict()
g.boards[@] = @ g.boards[@] = @
return Board

View File

@ -21,3 +21,5 @@ class Callbacks
error: err error: err
Main.handleErrors errors if errors Main.handleErrors errors if errors
return Callbacks

View File

@ -13,3 +13,5 @@ class CatalogThread
pageCount: $ '.page-count', root pageCount: $ '.page-count', root
comment: $ '.comment', root comment: $ '.comment', root
@thread.catalogView = @ @thread.catalogView = @
return CatalogThread

View File

@ -20,3 +20,5 @@ class Connection
for type, value of data for type, value of data
@cb[type]? value @cb[type]? value
return return
return Connection

View File

@ -105,3 +105,5 @@ class DataBoard
onSync: (data) => onSync: (data) =>
@data = data or boards: {} @data = data or boards: {}
@sync?() @sync?()
return DataBoard

View File

@ -200,3 +200,5 @@ class Fetcher
'[/moot]': <%= html('</div>') %> '[/moot]': <%= html('</div>') %>
'[banned]': <%= html('<strong style="color: red;">') %> '[banned]': <%= html('<strong style="color: red;">') %>
'[/banned]': <%= html('</strong>') %> '[/banned]': <%= html('</strong>') %>
return Fetcher

View File

@ -30,3 +30,5 @@ class Notice
$.off d, 'visibilitychange', @add $.off d, 'visibilitychange', @add
$.rm @el $.rm @el
@onclose?() @onclose?()
return Notice

View File

@ -268,3 +268,5 @@ class Post
for clone in @clones[index..] for clone in @clones[index..]
clone.nodes.root.dataset.clone = index++ clone.nodes.root.dataset.clone = index++
return return
return Post

View File

@ -87,3 +87,5 @@ class RandomAccessList
next.prev = prev next.prev = prev
else else
@last = prev @last = prev
return RandomAccessList

View File

@ -14,3 +14,5 @@ class ShimSet
@size-- @size--
window.Set = ShimSet unless 'Set' of window window.Set = ShimSet unless 'Set' of window
return ShimSet

View File

@ -16,3 +16,5 @@ class SimpleDict
forEach: (fn) -> forEach: (fn) ->
fn @[key] for key in [@keys...] fn @[key] for key in [@keys...]
return return
return SimpleDict

View File

@ -76,3 +76,5 @@ class Thread
@posts.forEach (post) -> post.collect() @posts.forEach (post) -> post.collect()
g.threads.rm @fullID g.threads.rm @fullID
@board.threads.rm @ @board.threads.rm @
return Thread

View File

@ -1022,3 +1022,5 @@ Config =
'Max Replies': 1000 'Max Replies': 1000
'Autohiding Scrollbar': false 'Autohiding Scrollbar': false
return Config

View File

@ -470,4 +470,4 @@ Main =
<% } %> <% } %>
] ]
Main.init() return Main

1
src/main/Main.init.js Normal file
View File

@ -0,0 +1 @@
Main.init();

View File

@ -1,2 +1,2 @@
$$ = (selector, root=d.body) -> return (selector, root=d.body) ->
[root.querySelectorAll(selector)...] [root.querySelectorAll(selector)...]

View File

@ -593,3 +593,5 @@ $.clear = (cb) ->
$.delete $.listValues().map (key) -> key.replace g.NAMESPACE, '' $.delete $.listValues().map (key) -> key.replace g.NAMESPACE, ''
cb?() cb?()
<% } %> <% } %>
return $

View File

@ -1,15 +1,15 @@
CrossOrigin = do -> <% if (type === 'crx') { %>
<% if (type === 'crx') { %> eventPageRequest = do ->
eventPageRequest = do -> callbacks = []
callbacks = [] chrome.runtime.onMessage.addListener (data) ->
chrome.runtime.onMessage.addListener (data) -> callbacks[data.id] data
callbacks[data.id] data delete callbacks[data.id]
delete callbacks[data.id] (url, responseType, cb) ->
(url, responseType, cb) -> chrome.runtime.sendMessage {url, responseType}, (id) ->
chrome.runtime.sendMessage {url, responseType}, (id) -> callbacks[id] = cb
callbacks[id] = cb
<% } %>
<% } %>
CrossOrigin =
binary: (url, cb, headers={}) -> binary: (url, cb, headers={}) ->
# XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310 # XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
url = url.replace /^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/' url = url.replace /^((?:https?:)?\/\/(?:\w+\.)?4c(?:ha|d)n\.org)\/adv\//, '$1//adv/'
@ -126,3 +126,5 @@ CrossOrigin = do ->
delete callbacks[url] delete callbacks[url]
responses[url] = response responses[url] = response
<% } %> <% } %>
return CrossOrigin

View File

@ -1,7 +1,6 @@
var fs = require('fs'); var fs = require('fs');
var template = require('./template'); var template = require('./template');
var coffee = require('coffee-script'); var coffee = require('coffee-script');
var globalize = require('./globalize');
for (var name of process.argv.slice(2)) { for (var name of process.argv.slice(2)) {
try { try {
@ -12,8 +11,8 @@ for (var name of process.argv.slice(2)) {
script = template(script, {type: parts[2]}); script = template(script, {type: parts[2]});
if (/\.coffee$/.test(basename)) { if (/\.coffee$/.test(basename)) {
script = coffee.compile(script); script = coffee.compile(script);
if (/^([$A-Z][$\w]*)\.coffee$/.test(basename)) { if (/^[$A-Z][$\w]*$/.test(parts[3])) {
script = globalize.globalize(script, [parts[3]]); script = `${parts[3]} = ${script}`;
} }
} }
fs.writeFileSync(name, script); fs.writeFileSync(name, script);

View File

@ -1,57 +0,0 @@
var fs = require('fs');
function getNames(part) {
var basename = part.split('_')[0]; // e.g. template_crx -> template
var sources = fs.readdirSync(`src/${basename}`);
// Extract variables to be made global from source file list
// e.g. ImageExpand from src/Images/ImageExpand.coffee
// but not QR.post or eventPage
var names = [];
for (var f of sources) {
var m = f.match(/^([$A-Z][$\w]*)\.coffee$/);
if (m) names.push(m[1]);
}
return names;
}
function globalize(script, names) {
var replaced = 0;
script = script.replace(
// matches declaration at the start of the function, not including helper function assignments
/^( *var )(.*)(,\n *|;\n)/m,
function(declaration, v, n, e) {
replaced++;
var n0 = names.sort().join(', ');
if (n0 !== n) throw new Error(`expected variables (${n0}) found (${n})`);
return (e[0] === ',') ? v : '';
}
);
if (replaced !== 1) {
throw new Error(`no declaration found`);
}
return script;
}
module.exports = {
getNames: getNames,
globalize: globalize
};
if (require.main === module) {
(function() {
for (var part of process.argv.slice(2)) {
var filename = `tmp/${part}.js`;
var names = getNames(part);
var script = fs.readFileSync(filename, 'utf8').replace(/\r\n/g, '\n');
script = globalize(script, names);
fs.writeFileSync(filename, script);
}
})();
}