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 {
position: fixed;
}
#qp, #ihover {
z-index: 100;
}
#menu {
z-index: 95;
}
#updater, #stats {
z-index: 90;
}
#header:hover {
#notifications {
z-index: 80;
}
#qr {
#qp, #ihover {
z-index: 70;
}
#menu {
z-index: 60;
}
#updater, #stats {
z-index: 50;
}
#watcher {
#header:hover {
z-index: 40;
}
#qr {
z-index: 30;
}
#header {
z-index: 20;
}
#watcher {
z-index: 10;
}
@ -68,7 +71,7 @@ a[href="javascript:;"] {
}
/* header */
body.fourchan_x {
.fourchan_x body {
margin-top: 2em;
}
#header {
@ -85,6 +88,7 @@ body.fourchan_x {
-webkit-transition: all .1s ease-in-out;
}
#header-bar.autohide:not(:hover) {
margin-bottom: -1em;
transform: translateY(-100%);
-o-transform: translateY(-100%);
-moz-transform: translateY(-100%);
@ -119,6 +123,51 @@ body > #boardNavDesktop,
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 */
#updater {
text-align: right;

View File

@ -22,7 +22,7 @@ $.extend $,
id: (id) ->
d.getElementById id
ready: (fc) ->
if /interactive|complete/.test d.readyState
if d.readyState in ['interactive', 'complete']
$.queueTask fc
return
cb = ->
@ -106,9 +106,6 @@ $.extend $,
tn: (s) ->
d.createTextNode s
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
return nodes
frag = d.createDocumentFragment()

View File

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

View File

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

View File

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