4chan-x/src/General/Main.coffee
2013-08-07 15:53:15 -05:00

377 lines
13 KiB
CoffeeScript

Main =
init: (items) ->
pathname = location.pathname.split '/'
g.BOARD = new Board pathname[1]
return if g.BOARD.ID in ['z', 'fk']
g.VIEW =
switch pathname[2]
when 'res'
'thread'
when 'catalog'
'catalog'
else
'index'
if g.VIEW is 'thread'
g.THREADID = +pathname[3]
# flatten Config into Conf
# and get saved or default values
flatten = (parent, obj) ->
if obj instanceof Array
Conf[parent] = obj[0]
else if typeof obj is 'object'
for key, val of obj
flatten key, val
else # string or number
Conf[parent] = obj
return
flatten null, Config
for db in DataBoards
Conf[db] = boards: {}
Conf['selectedArchives'] = {}
Conf['archives'] = Redirect.archives
$.get Conf, Main.initFeatures
$.on d, '4chanMainInit', Main.initStyle
initFeatures: (items) ->
Conf = items
switch location.hostname
when 'api.4chan.org'
return
when 'sys.4chan.org'
Report.init()
return
when 'images.4chan.org'
$.ready ->
if Conf['404 Redirect'] and d.title is '4chan - 404 Not Found'
Redirect.init()
pathname = location.pathname.split '/'
URL = Redirect.to 'file',
boardID: g.BOARD.ID
filename: pathname[pathname.length - 1]
location.replace URL if URL
return
initFeature = (name, module) ->
# c.time "#{name} initialization"
try
module.init()
catch err
Main.handleErrors
message: "\"#{name}\" initialization crashed."
error: err
# finally
# c.timeEnd "#{name} initialization"
# c.time 'All initializations'
initFeature 'Polyfill', Polyfill
initFeature 'Header', Header
initFeature 'Settings', Settings
initFeature 'Announcement Hiding', PSAHiding
initFeature 'Fourchan thingies', Fourchan
initFeature 'Custom CSS', CustomCSS
initFeature 'Redirect', Redirect
initFeature 'Resurrect Quotes', Quotify
initFeature 'Filter', Filter
initFeature 'Thread Hiding', ThreadHiding
initFeature 'Reply Hiding', PostHiding
initFeature 'Recursive', Recursive
initFeature 'Strike-through Quotes', QuoteStrikeThrough
initFeature 'Quick Reply', QR
initFeature 'Menu', Menu
initFeature 'Report Link', ReportLink
initFeature 'Thread Hiding (Menu)', ThreadHiding.menu
initFeature 'Reply Hiding (Menu)', PostHiding.menu
initFeature 'Delete Link', DeleteLink
initFeature 'Filter (Menu)', Filter.menu
initFeature 'Download Link', DownloadLink
initFeature 'Archive Link', ArchiveLink
initFeature 'Quote Inlining', QuoteInline
initFeature 'Quote Previewing', QuotePreview
initFeature 'Quote Backlinks', QuoteBacklink
initFeature 'Mark Quotes of You', QuoteYou
initFeature 'Mark OP Quotes', QuoteOP
initFeature 'Mark Cross-thread Quotes', QuoteCT
initFeature 'Anonymize', Anonymize
initFeature 'Time Formatting', Time
initFeature 'Relative Post Dates', RelativeDates
initFeature 'File Info Formatting', FileInfo
initFeature 'Sauce', Sauce
initFeature 'Image Expansion', ImageExpand
initFeature 'Image Expansion (Menu)', ImageExpand.menu
initFeature 'Reveal Spoilers', RevealSpoilers
initFeature 'Auto-GIF', AutoGIF
initFeature 'Image Hover', ImageHover
initFeature 'Comment Expansion', ExpandComment
initFeature 'Thread Expansion', ExpandThread
initFeature 'Thread Excerpt', ThreadExcerpt
initFeature 'Favicon', Favicon
initFeature 'Unread', Unread
initFeature 'Thread Stats', ThreadStats
initFeature 'Thread Updater', ThreadUpdater
initFeature 'Thread Watcher', ThreadWatcher
initFeature 'Index Navigation', Nav
initFeature 'Keybinds', Keybinds
initFeature 'Dice Checker', Dice
# c.timeEnd 'All initializations'
$.on d, 'AddCallback', Main.addCallback
$.ready Main.initReady
initStyle: ->
$.off d, '4chanMainInit', Main.initStyle
return if !Main.isThisPageLegit() or $.hasClass doc, 'fourchan-x'
# disable the mobile layout
$('link[href*=mobile]', d.head)?.disabled = true
<% if (type === 'crx') { %>
$.addClass doc, 'blink'
<% } else { %>
$.addClass doc, 'gecko'
<% } %>
$.addClass doc, 'fourchan-x'
$.addStyle Main.css
if g.VIEW is 'catalog'
$.addClass doc, $.id('base-css').href.match(/catalog_(\w+)/)[1].replace('_new', '').replace /_+/g, '-'
return
style = 'yotsuba-b'
mainStyleSheet = $ 'link[title=switch]', d.head
styleSheets = $$ 'link[rel="alternate stylesheet"]', d.head
setStyle = ->
$.rmClass doc, style
for styleSheet in styleSheets
if styleSheet.href is mainStyleSheet.href
style = styleSheet.title.toLowerCase().replace('new', '').trim().replace /\s+/g, '-'
break
$.addClass doc, style
setStyle()
return unless mainStyleSheet
new MutationObserver(setStyle).observe mainStyleSheet,
attributes: true
attributeFilter: ['href']
initReady: ->
if d.title is '4chan - 404 Not Found'
if Conf['404 Redirect'] and g.VIEW is 'thread'
href = Redirect.to 'thread',
boardID: g.BOARD.ID
threadID: g.THREADID
postID: +location.hash.match /\d+/ # post number or 0
location.replace href or "/#{g.BOARD}/"
return
unless $.hasClass doc, 'fourchan-x'
# Something might have gone wrong!
Main.initStyle()
if board = $ '.board'
threads = []
posts = []
for threadRoot in $$ '.board > .thread', board
thread = new Thread +threadRoot.id[1..], g.BOARD
threads.push thread
for postRoot in $$ '.thread > .postContainer', threadRoot
try
posts.push new Post postRoot, thread, g.BOARD
catch err
# Skip posts that we failed to parse.
unless errors
errors = []
errors.push
message: "Parsing of Post No.#{postRoot.id.match(/\d+/)} failed. Post will be skipped."
error: err
Main.handleErrors errors if errors
Main.callbackNodes Thread, threads
Main.callbackNodes Post, posts
if $.hasClass d.body, 'fourchan_x'
Main.v2Detected = true
alert '4chan X v2 detected: Disable it or v3 will break.'
try
localStorage.getItem '4chan-settings'
catch err
new Notification 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to properly function.', 30
<% if (type === 'userscript') { %>
el = $.el 'span'
el.style.flex = 'test'
if el.style.flex is 'test'
el.innerHTML = """
Firefox is not correctly set up and some <%= meta.name %> features will be displayed incorrectly.<br>
Follow the instructions of the <a href='<%= meta.page %>' target=_blank>install guide</a> to fix it.
"""
new Notification 'warning', el, 30
<% } %>
$.event '4chanXInitFinished'
Main.checkUpdate()
callbackNodes: (klass, nodes) ->
# get the nodes' length only once
len = nodes.length
for callback in klass::callbacks
# c.profile callback.name
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
# c.profileEnd callback.name
Main.handleErrors errors if errors
addCallback: (e) ->
obj = e.detail
unless typeof obj.callback.name is 'string'
throw new Error "Invalid callback name: #{obj.callback.name}"
switch obj.type
when 'Post'
Klass = Post
when 'Thread'
Klass = Thread
else
return
obj.callback.isAddon = true
Klass::callbacks.push obj.callback
checkUpdate: ->
return unless Conf['Check for Updates'] and Main.isThisPageLegit()
# Check for updates after 7 days since the last update.
# After that, check for updates every day if we still haven't updated.
now = Date.now()
freq = 7 * $.DAY
items =
lastupdate: 0
lastchecked: 0
$.get items, (items) ->
if items.lastupdate > now - freq or items.lastchecked > now - $.DAY
return
$.ajax '<%= meta.page %><%= meta.buildsPath %>version', onload: ->
return unless @status is 200
version = @response
return unless /^\d\.\d+\.\d+$/.test version
if g.VERSION is version
# Don't check for updates too frequently if there wasn't one in a 'long' time.
$.set 'lastupdate', now
return
$.set 'lastchecked', now
# dmichnowicz
# 19 juin 2013 09:39:28
# innerHTML is not allowed in conjunction with external resources: [...]
#
# mayhemydg
# 19 juin 2013 09:43:14
# Will it be good if I make sure to match a version number?
#
# dmichnowicz
# 19 juin 2013 09:46:12
# Yes.
#
# mayhemydg
# 19 juin 2013 13:04:30
# Well actually there is nothing to fix, the following code makes sure this is a
# valid version number and precedes the innerHTML part: [see /^\d\.\d+\.\d+$/.test]
#
# dmichnowicz
# 21 juin 2013 04:52:37
# OK but anyway please remove that line of code.
el = $.el 'span',
textContent: "Update: <%= meta.name %> v#{version} is out, get it "
el.innerHTML += '<a href=<%= meta.page %> target=_blank>here</a>.'
new Notification 'info', el, 120
handleErrors: (errors) ->
unless errors instanceof Array
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 occurred. [<a href=javascript:;>show</a>]"
$.on div.lastElementChild, 'click', ->
[@textContent, logs.hidden] = if @textContent is 'show'
['hide', false]
else
['show', true]
logs = $.el 'div',
hidden: true
for error in errors
$.add logs, Main.parseError error
new Notification 'error', [div, logs], 30
parseError: (data) ->
Main.logError data
message = $.el 'div',
textContent: data.message
error = $.el 'div',
textContent: data.error
[message, error]
errors: []
logError: (data) ->
unless Main.errors.length
$.on window, 'unload', Main.postErrors
c.error data.message, data.error.stack
Main.errors.push data
postErrors: ->
return if Main.v2Detected
errors = Main.errors.filter((d) -> !!d.error.stack).map((d) ->
<% if (type === 'userscript') { %>
# Before:
# someFn@file:///C:/Users/<USER>/AppData/Roaming/Mozilla/Firefox/Profiles/<garbage>.default/gm_scripts/4chan_X/4chan-X.user.js:line_number
# someFn@file:///home/<USER>/.mozilla/firefox/<garbage>.default/gm_scripts/4chan_X/4chan-X.user.js:line_number
# After:
# someFn@4chan-X.user.js:line_number
{name, message, stack} = d.error
stack = stack.replace /file:\/{3}.+\//g, ''
"#{d.message} #{name}: #{message} #{stack}"
<% } else { %>
"#{d.message} #{d.error.stack}"
<% } %>
).join '\n'
return unless errors
$.ajax '<%= meta.page %>errors', null,
sync: true
form: $.formData
n: "<%= meta.name %> v#{g.VERSION}"
t: '<%= type %>'
ua: window.navigator.userAgent
url: window.location.href
e: errors
isThisPageLegit: ->
# 404 error page or similar.
unless 'thisPageIsLegit' of Main
Main.thisPageIsLegit = location.hostname is 'boards.4chan.org' and
!$('link[href*="favicon-status.ico"]', d.head) and
d.title not in ['4chan - Temporarily Offline', '4chan - Error', '504 Gateway Time-out']
Main.thisPageIsLegit
css: """
<%= grunt.file.read('css/style.css') %>
<%= grunt.file.read('css/yotsuba.css') %>
<%= grunt.file.read('css/yotsuba-b.css') %>
<%= grunt.file.read('css/futaba.css') %>
<%= grunt.file.read('css/burichan.css') %>
<%= grunt.file.read('css/tomorrow.css') %>
<%= grunt.file.read('css/photon.css') %>
"""
Main.init()