Add Notifications.

Add error handling. I don't feel like I did a good job of it, might revisit later.
This commit is contained in:
Nicolas Stepien 2013-02-10 23:15:06 +01:00
parent b6cf7220c4
commit 8b836aec8d
6 changed files with 474 additions and 465 deletions

File diff suppressed because one or more lines are too long

View File

@ -35,25 +35,28 @@ a[href="javascript:;"] {
#qr, #watcher { #qr, #watcher {
position: fixed; position: fixed;
} }
#qp, #ihover { #notifications {
z-index: 100;
}
#menu {
z-index: 95;
}
#updater, #stats {
z-index: 90;
}
#header:hover {
z-index: 80; z-index: 80;
} }
#qr { #qp, #ihover {
z-index: 70;
}
#menu {
z-index: 60;
}
#updater, #stats {
z-index: 50; z-index: 50;
} }
#watcher { #header:hover {
z-index: 40;
}
#qr {
z-index: 30; z-index: 30;
} }
#header { #header {
z-index: 20;
}
#watcher {
z-index: 10; z-index: 10;
} }
@ -68,7 +71,7 @@ a[href="javascript:;"] {
} }
/* header */ /* header */
body.fourchan_x { .fourchan_x body {
margin-top: 2em; margin-top: 2em;
} }
#header { #header {
@ -85,6 +88,7 @@ body.fourchan_x {
-webkit-transition: all .1s ease-in-out; -webkit-transition: all .1s ease-in-out;
} }
#header-bar.autohide:not(:hover) { #header-bar.autohide:not(:hover) {
margin-bottom: -1em;
transform: translateY(-100%); transform: translateY(-100%);
-o-transform: translateY(-100%); -o-transform: translateY(-100%);
-moz-transform: translateY(-100%); -moz-transform: translateY(-100%);
@ -119,6 +123,51 @@ body > #boardNavDesktop,
display: none !important; display: none !important;
} }
/* notifications */
#notifications {
text-align: center;
}
.notification {
color: #FFF;
font-weight: 700;
text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
border-radius: 2px;
margin: 1px auto;
width: 500px;
max-width: 100%;
position: relative;
transition: all .25s ease-in-out;
-o-transition: all .25s ease-in-out;
-moz-transition: all .25s ease-in-out;
-webkit-transition: all .25s ease-in-out;
}
.notification.error {
background-color: hsl(0, 100%, 40%);
}
.notification.warning {
background-color: hsl(36, 100%, 40%);
}
.notification.info {
background-color: hsl(200, 100%, 40%);
}
.notification.success {
background-color: hsl(104, 100%, 40%);
}
.notification > .close {
color: white;
padding: 4px 6px;
top: 0;
right: 0;
position: absolute;
}
.message {
box-sizing: border-box;
padding: 4px 20px;
max-height: 200px;
width: 100%;
overflow: auto;
}
/* thread updater */ /* thread updater */
#updater { #updater {
text-align: right; text-align: right;

View File

@ -22,7 +22,7 @@ $.extend $,
id: (id) -> id: (id) ->
d.getElementById id d.getElementById id
ready: (fc) -> ready: (fc) ->
if /interactive|complete/.test d.readyState if d.readyState in ['interactive', 'complete']
$.queueTask fc $.queueTask fc
return return
cb = -> cb = ->
@ -106,9 +106,6 @@ $.extend $,
tn: (s) -> tn: (s) ->
d.createTextNode s d.createTextNode s
nodes: (nodes) -> nodes: (nodes) ->
# In (at least) Chrome, elements created inside different
# scripts/window contexts inherit from unequal prototypes.
# window_context1.Node !== window_context2.Node
unless nodes instanceof Array unless nodes instanceof Array
return nodes return nodes
frag = d.createDocumentFragment() frag = d.createDocumentFragment()

View File

@ -57,8 +57,8 @@ Config =
'Quote Preview': [true, 'Show quoted post on hover.'] 'Quote Preview': [true, 'Show quoted post on hover.']
'Quote Highlighting': [true, 'Highlight the previewed post.'] 'Quote Highlighting': [true, 'Highlight the previewed post.']
'Resurrect Quotes': [true, 'Linkify dead quotes to archives.'] 'Resurrect Quotes': [true, 'Linkify dead quotes to archives.']
'Indicate OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'] 'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.']
'Indicate Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.'] 'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.']
filter: filter:
name: [ name: [
'# Filter any namefags:' '# Filter any namefags:'

View File

@ -40,8 +40,9 @@ Header =
try try
@setBoardList() @setBoardList()
catch err catch err
# XXX handle error Main.handleErrors
$.log err, 'Header - board list' message: '"Header (board list)" crashed.'
error: err
$.asap (-> d.body), -> $.asap (-> d.body), ->
$.prepend d.body, Header.headerEl $.prepend d.body, Header.headerEl
@ -74,6 +75,31 @@ Header =
menuToggle: (e) -> menuToggle: (e) ->
Header.menu.toggle e, @, g Header.menu.toggle e, @, g
class Notification
constructor: (@type, content, timeout) ->
@el = $.el 'div',
className: "notification #{type}"
innerHTML: '<a href=javascript:; class=close title=Close>×</a><div class=message></div>'
$.on @el.firstElementChild, 'click', @close.bind @
if typeof content is 'string'
content = $.tn content
$.add @el.lastElementChild, content
if timeout
setTimeout @close.bind(@), timeout * $.SECOND
el = @el
$.ready ->
$.add $.id('notifications'), el
setType: (type) ->
$.rmClass @el, @type
$.addClass @el, type
@type = type
close: ->
$.rm @el
Settings = Settings =
init: -> init: ->
# 4chan X settings link # 4chan X settings link
@ -107,6 +133,8 @@ Settings =
Filter = Filter =
filters: {} filters: {}
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Filter']
for key of Config.filter for key of Config.filter
@filters[key] = [] @filters[key] = []
for filter in Conf[key].split '\n' for filter in Conf[key].split '\n'
@ -134,8 +162,7 @@ Filter =
regexp = RegExp regexp[1], regexp[2] regexp = RegExp regexp[1], regexp[2]
catch err catch err
# I warned you, bro. # I warned you, bro.
# XXX handle error new Notification 'warning', err.message, 60
alert err.message
continue continue
# Filter OPs along with their threads, replies only, or both. # Filter OPs along with their threads, replies only, or both.
@ -274,6 +301,8 @@ Filter =
menu: menu:
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Filter']
div = $.el 'div', div = $.el 'div',
textContent: 'Filter' textContent: 'Filter'
@ -376,7 +405,8 @@ Filter =
ThreadHiding = ThreadHiding =
init: -> init: ->
return if g.VIEW isnt 'index' return if g.VIEW isnt 'index' or !Conf['Thread Hiding']
@getHiddenThreads() @getHiddenThreads()
@syncFromCatalog() @syncFromCatalog()
@clean() @clean()
@ -438,7 +468,8 @@ ThreadHiding =
menu: menu:
init: -> init: ->
return if g.VIEW isnt 'index' return if g.VIEW isnt 'index' or !Conf['Menu'] or !Conf['Thread Hiding']
div = $.el 'div', div = $.el 'div',
className: 'hide-thread-link' className: 'hide-thread-link'
textContent: 'Hide thread' textContent: 'Hide thread'
@ -535,6 +566,8 @@ ThreadHiding =
ReplyHiding = ReplyHiding =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Reply Hiding']
@getHiddenPosts() @getHiddenPosts()
@clean() @clean()
Post::callbacks.push Post::callbacks.push
@ -583,6 +616,8 @@ ReplyHiding =
menu: menu:
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding']
div = $.el 'div', div = $.el 'div',
className: 'hide-reply-link' className: 'hide-reply-link'
textContent: 'Hide reply' textContent: 'Hide reply'
@ -726,6 +761,8 @@ Recursive =
Menu = Menu =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Menu']
@menu = new UI.Menu 'post' @menu = new UI.Menu 'post'
Post::callbacks.push Post::callbacks.push
name: 'Menu' name: 'Menu'
@ -758,6 +795,8 @@ Menu =
ReportLink = ReportLink =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Report Link']
a = $.el 'a', a = $.el 'a',
className: 'report-link' className: 'report-link'
href: 'javascript:;' href: 'javascript:;'
@ -777,6 +816,8 @@ ReportLink =
DeleteLink = DeleteLink =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Delete Link']
div = $.el 'div', div = $.el 'div',
className: 'delete-link' className: 'delete-link'
textContent: 'Delete' textContent: 'Delete'
@ -881,6 +922,8 @@ DeleteLink =
DownloadLink = DownloadLink =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Download Link']
# Test for download feature support. # Test for download feature support.
return if $.el('a').download is undefined return if $.el('a').download is undefined
a = $.el 'a', a = $.el 'a',
@ -896,6 +939,8 @@ DownloadLink =
ArchiveLink = ArchiveLink =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Archive Link']
div = $.el 'div', div = $.el 'div',
textContent: 'Archive' textContent: 'Archive'
@ -1554,6 +1599,8 @@ Get =
Quotify = Quotify =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Resurrect Quotes']
Post::callbacks.push Post::callbacks.push
name: 'Resurrect Quotes' name: 'Resurrect Quotes'
cb: @node cb: @node
@ -1621,6 +1668,8 @@ Quotify =
QuoteInline = QuoteInline =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Quote Inline']
Post::callbacks.push Post::callbacks.push
name: 'Quote Inline' name: 'Quote Inline'
cb: @node cb: @node
@ -1701,6 +1750,8 @@ QuoteInline =
QuotePreview = QuotePreview =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Quote Preview']
Post::callbacks.push Post::callbacks.push
name: 'Quote Preview' name: 'Quote Preview'
cb: @node cb: @node
@ -1767,6 +1818,8 @@ QuoteBacklink =
# This is is so that fetched posts can get their backlinks, # This is is so that fetched posts can get their backlinks,
# and that as much backlinks are appended in the background as possible. # and that as much backlinks are appended in the background as possible.
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Quote Backlinks']
format = Conf['backlink'].replace /%id/g, "' + id + '" format = Conf['backlink'].replace /%id/g, "' + id + '"
@funk = Function 'id', "return '#{format}'" @funk = Function 'id', "return '#{format}'"
@containers = {} @containers = {}
@ -1810,10 +1863,12 @@ QuoteBacklink =
QuoteOP = QuoteOP =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Mark OP Quotes']
# \u00A0 is nbsp # \u00A0 is nbsp
@text = '\u00A0(OP)' @text = '\u00A0(OP)'
Post::callbacks.push Post::callbacks.push
name: 'Indicate OP Quotes' name: 'Mark OP Quotes'
cb: @node cb: @node
node: -> node: ->
# Stop there if it's a clone of a post in the same thread. # Stop there if it's a clone of a post in the same thread.
@ -1838,10 +1893,12 @@ QuoteOP =
QuoteCT = QuoteCT =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Mark Cross-thread Quotes']
# \u00A0 is nbsp # \u00A0 is nbsp
@text = '\u00A0(Cross-thread)' @text = '\u00A0(Cross-thread)'
Post::callbacks.push Post::callbacks.push
name: 'Indicate Cross-thread Quotes' name: 'Mark Cross-thread Quotes'
cb: @node cb: @node
node: -> node: ->
# Stop there if it's a clone of a post in the same thread. # Stop there if it's a clone of a post in the same thread.
@ -1862,6 +1919,8 @@ QuoteCT =
Anonymize = Anonymize =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Anonymize']
Post::callbacks.push Post::callbacks.push
name: 'Anonymize' name: 'Anonymize'
cb: @node cb: @node
@ -1882,6 +1941,8 @@ Anonymize =
Time = Time =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Time Formatting']
@funk = @createFunc Conf['time'] @funk = @createFunc Conf['time']
Post::callbacks.push Post::callbacks.push
name: 'Time Formatting' name: 'Time Formatting'
@ -1940,6 +2001,8 @@ Time =
FileInfo = FileInfo =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['File Info Formatting']
@funk = @createFunc Conf['fileInfo'] @funk = @createFunc Conf['fileInfo']
Post::callbacks.push Post::callbacks.push
name: 'File Info Formatting' name: 'File Info Formatting'
@ -1990,6 +2053,8 @@ FileInfo =
Sauce = Sauce =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Sauce']
links = [] links = []
for link in Conf['sauces'].split '\n' for link in Conf['sauces'].split '\n'
continue if link[0] is '#' continue if link[0] is '#'
@ -2031,6 +2096,8 @@ Sauce =
RevealSpoilers = RevealSpoilers =
init: -> init: ->
return if g.VIEW is 'catalog' or !Conf['Reveal Spoilers']
Post::callbacks.push Post::callbacks.push
name: 'Reveal Spoilers' name: 'Reveal Spoilers'
cb: @node cb: @node
@ -2042,7 +2109,8 @@ RevealSpoilers =
AutoGIF = AutoGIF =
init: -> init: ->
return if g.BOARD.ID in ['gif', 'wsg'] return if g.VIEW is 'catalog' or !Conf['Auto-GIF'] or g.BOARD.ID in ['gif', 'wsg']
Post::callbacks.push Post::callbacks.push
name: 'Auto-GIF' name: 'Auto-GIF'
cb: @node cb: @node
@ -2062,7 +2130,8 @@ AutoGIF =
ImageHover = ImageHover =
init: -> init: ->
return if g.BOARD.ID in ['gif', 'wsg'] return if g.VIEW is 'catalog' or !Conf['Image Hover']
Post::callbacks.push Post::callbacks.push
name: 'Auto-GIF' name: 'Auto-GIF'
cb: @node cb: @node
@ -2101,7 +2170,8 @@ ImageHover =
ThreadUpdater = ThreadUpdater =
init: -> init: ->
return if g.VIEW isnt 'thread' return if g.VIEW isnt 'thread' or !Conf['Thread Updater']
Thread::callbacks.push Thread::callbacks.push
name: 'Thread Updater' name: 'Thread Updater'
cb: @node cb: @node

View File

@ -277,211 +277,56 @@ Main =
location.href = url if url location.href = url if url
return return
return if g.VIEW is 'catalog'
$.addStyle Main.css $.addStyle Main.css
$.asap (-> d.body), (-> $.addClass d.documentElement, $.engine
$.addClass d.body, $.engine $.addClass d.documentElement, 'fourchan_x'
$.addClass d.body, 'fourchan_x'
)
try initFeature = (name, module) ->
Header.init() console.time "#{name} initialization"
catch err
# XXX handle error
$.log err, 'Header'
try
Settings.init()
catch err
# XXX handle error
$.log err, 'Settings'
if Conf['Resurrect Quotes']
try try
Quotify.init() module.init()
catch err catch err
# XXX handle error Main.handleErrors
$.log err, 'Resurrect Quotes' message: "\"#{name}\" initialization crashed."
error: err
console.timeEnd "#{name} initialization"
if Conf['Filter'] console.time 'All initializations'
try initFeature 'Header', Header
Filter.init() initFeature 'Settings', Settings
catch err initFeature 'Resurrect Quotes', Quotify
# XXX handle error initFeature 'Filter', Filter
$.log err, 'Filter' initFeature 'Thread Hiding', ThreadHiding
initFeature 'Reply Hiding', ReplyHiding
if Conf['Thread Hiding'] initFeature 'Recursive', Recursive
try initFeature 'Menu', Menu
ThreadHiding.init() initFeature 'Report Link', ReportLink
catch err initFeature 'Thread Hiding (Menu)', ThreadHiding.menu
# XXX handle error initFeature 'Reply Hiding (Menu)', ReplyHiding.menu
$.log err, 'Thread Hiding' initFeature 'Delete Link', DeleteLink
initFeature 'Filter (Menu)', Filter.menu
if Conf['Reply Hiding'] initFeature 'Download Link', DownloadLink
try initFeature 'Archive Link', ArchiveLink
ReplyHiding.init() initFeature 'Quote Inline', QuoteInline
catch err initFeature 'Quote Preview', QuotePreview
# XXX handle error initFeature 'Quote Backlinks', QuoteBacklink
$.log err, 'Reply Hiding' initFeature 'Mark OP Quotes', QuoteOP
initFeature 'Mark Cross-thread Quotes', QuoteCT
try initFeature 'Anonymize', Anonymize
Recursive.init() initFeature 'Time Formatting', Time
catch err initFeature 'File Info Formatting', FileInfo
# XXX handle error initFeature 'Sauce', Sauce
$.log err, 'Recursive' initFeature 'Reveal Spoilers', RevealSpoilers
initFeature 'Auto-GIF', AutoGIF
if Conf['Menu'] initFeature 'Image Hover', ImageHover
try initFeature 'Thread Updater', ThreadUpdater
Menu.init() console.timeEnd 'All initializations'
catch err
# XXX handle error
$.log err, 'Menu'
if Conf['Report Link']
try
ReportLink.init()
catch err
# XXX handle error
$.log err, 'Report Link'
if Conf['Thread Hiding']
try
ThreadHiding.menu.init()
catch err
# XXX handle error
$.log err, 'Thread Hiding - Menu'
if Conf['Reply Hiding']
try
ReplyHiding.menu.init()
catch err
# XXX handle error
$.log err, 'Reply Hiding - Menu'
if Conf['Delete Link']
try
DeleteLink.init()
catch err
# XXX handle error
$.log err, 'Delete Link'
if Conf['Filter']
try
Filter.menu.init()
catch err
# XXX handle error
$.log err, 'Filter - Menu'
if Conf['Download Link']
try
DownloadLink.init()
catch err
# XXX handle error
$.log err, 'Download Link'
if Conf['Archive Link']
try
ArchiveLink.init()
catch err
# XXX handle error
$.log err, 'Archive Link'
if Conf['Quote Inline']
try
QuoteInline.init()
catch err
# XXX handle error
$.log err, 'Quote Inline'
if Conf['Quote Preview']
try
QuotePreview.init()
catch err
# XXX handle error
$.log err, 'Quote Preview'
if Conf['Quote Backlinks']
try
QuoteBacklink.init()
catch err
# XXX handle error
$.log err, 'Quote Backlinks'
if Conf['Indicate OP Quotes']
try
QuoteOP.init()
catch err
# XXX handle error
$.log err, 'Indicate OP Quotes'
if Conf['Indicate Cross-thread Quotes']
try
QuoteCT.init()
catch err
# XXX handle error
$.log err, 'Indicate Cross-thread Quotes'
if Conf['Anonymize']
try
Anonymize.init()
catch e
# XXX handle error
$.log err, 'Anonymize'
if Conf['Time Formatting']
try
Time.init()
catch err
# XXX handle error
$.log err, 'Time Formatting'
if Conf['File Info Formatting']
try
FileInfo.init()
catch err
# XXX handle error
$.log err, 'File Info Formatting'
if Conf['Sauce']
try
Sauce.init()
catch err
# XXX handle error
$.log err, 'Sauce'
if Conf['Reveal Spoilers']
try
RevealSpoilers.init()
catch err
# XXX handle error
$.log err, 'Reveal Spoilers'
if Conf['Auto-GIF']
try
AutoGIF.init()
catch err
# XXX handle error
$.log err, 'Auto-GIF'
if Conf['Image Hover']
try
ImageHover.init()
catch err
# XXX handle error
$.log err, 'Image Hover'
if Conf['Thread Updater']
try
ThreadUpdater.init()
catch err
# XXX handle error
$.log err, 'Thread Updater'
$.ready Main.initReady $.ready Main.initReady
initReady: -> initReady: ->
if d.title is '4chan - 404 Not Found' if d.title is '4chan - 404 Not Found'
$.rmClass d.documentElement, 'fourchan_x'
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
board: g.BOARD board: g.BOARD
@ -505,25 +350,66 @@ Main =
posts.push new Post threadChild, thread, g.BOARD posts.push new Post threadChild, thread, g.BOARD
catch err catch err
# Skip posts that we failed to parse. # Skip posts that we failed to parse.
# XXX handle error unless errors
# Post parser crashed for post No.#{threadChild.id[2..]} errors = []
$.log threadChild, err errors.push
message: "Parsing of Post No.#{threadChild.id.match(/\d+/)} failed. Post will be skipped."
error: err
Main.handleErrors errors if errors
Main.callbackNodes Thread, threads, true Main.callbackNodes Thread, threads
Main.callbackNodes Post, posts, true Main.callbackNodes Post, posts
callbackNodes: (klass, nodes, notify) -> callbackNodes: (klass, nodes) ->
# get the nodes' length only once # get the nodes' length only once
len = nodes.length len = nodes.length
for callback in klass::callbacks for callback in klass::callbacks
try for i in [0...len]
for i in [0...len] node = nodes[i]
callback.cb.call nodes[i] try
catch err callback.cb.call node
# XXX handle error if notify catch err
$.log callback.name, 'crashed. error:', err.message, nodes[i] unless errors
$.log err.stack errors = []
return errors.push
message: "\"#{callback.name}\" crashed on #{klass.name} No.#{node} (/#{node.board}/)."
error: err
Main.handleErrors errors if errors
handleErrors: (errors) ->
unless 'length' of errors
error = errors
else if errors.length is 1
error = errors[0]
if error
new Notification 'error', Main.parseError(error), 15
return
div = $.el 'div',
innerHTML: "#{errors.length} errors occured. [<a href=javascript:;>show</a>]"
$.on div.lastElementChild, 'click', ->
if @textContent is 'show'
@textContent = 'hide'
logs.hidden = false
else
@textContent = 'show'
logs.hidden = true
logs = $.el 'div',
hidden: true
for error in errors
$.add logs, Main.parseError error
new Notification 'error', [div, logs], 30
parseError: (data) ->
{message, error} = data
$.log message, error.stack
message = $.el 'div',
textContent: message
error = $.el 'div',
textContent: error
[message, error]
css: """<%= grunt.file.read('css/style.css') %>""" css: """<%= grunt.file.read('css/style.css') %>"""