355 lines
10 KiB
CoffeeScript
355 lines
10 KiB
CoffeeScript
Style =
|
|
init: ->
|
|
@setup()
|
|
$.asap (-> d.body), @asapInit
|
|
$.on window, "resize", Style.padding
|
|
$.on doc, '4chanXInitFinished', @readyInit
|
|
|
|
asapInit: ->
|
|
<% if (type === 'crx') { %>
|
|
$.addClass doc, 'webkit'
|
|
$.addClass doc, 'blink'
|
|
<% } else { %>
|
|
$.addClass doc, 'gecko'
|
|
<% } %>
|
|
$.addClass doc, 'fourchan-x'
|
|
$.addClass doc, 'appchan-x'
|
|
$.addClass doc, g.VIEW
|
|
|
|
$.add d.body, Style.svg
|
|
|
|
for title, cat of Config.style
|
|
for name, setting of cat
|
|
continue if !Conf[name] or setting[2] is 'text'
|
|
hyphenated = "#{name}#{if setting[2] then " #{Conf[name]}" else ""}".toLowerCase().replace(/^4/, 'four').replace /\s+/g, '-'
|
|
$.addClass doc, hyphenated
|
|
|
|
MascotTools.init()
|
|
|
|
if g.VIEW is 'index'
|
|
$.asap (-> $ '.mPagelist'), ->
|
|
|
|
prev = $ ".pagelist > .prev"
|
|
prevA = $.el 'a',
|
|
textContent: '<'
|
|
|
|
next = $ ".pagelist > .next"
|
|
nextA = $.el 'a',
|
|
textContent: '>'
|
|
|
|
if (prevAction = prev.firstElementChild).nodeName is 'FORM'
|
|
prevA.href = 'javascript:;'
|
|
$.on prevA, 'click', ->
|
|
prevAction.firstElementChild.click()
|
|
|
|
if (nextAction = next.firstElementChild).nodeName is 'FORM'
|
|
nextA.href = 'javascript:;'
|
|
$.on nextA, 'click', ->
|
|
nextAction.firstElementChild.click()
|
|
|
|
$.add prev, prevA
|
|
$.add next, nextA
|
|
|
|
readyInit: ->
|
|
Style.padding()
|
|
Style.iconPositions()
|
|
if exLink = $ "#navtopright .exlinksOptionsLink", d.body
|
|
$.on exLink, "click", ->
|
|
setTimeout Rice.nodes, 100
|
|
|
|
setup: ->
|
|
theme = Themes[Conf['theme']] or Themes['Yotsuba B']
|
|
Style.svg = $.el 'div',
|
|
id: 'svg_filters'
|
|
$.extend Style,
|
|
layoutCSS: $.addStyle Style.layout, 'layout'
|
|
themeCSS: $.addStyle Style.theme(theme), 'theme'
|
|
emojiCSS: $.addStyle Emoji.css(), 'emoji'
|
|
dynamicCSS: $.addStyle Style.dynamic(), 'dynamic'
|
|
icons: $.addStyle "", 'icons'
|
|
paddingSheet: $.addStyle "", 'padding'
|
|
mascot: $.addStyle "", 'mascotSheet'
|
|
|
|
# Non-customizable
|
|
$.addStyle JSColor.css(), 'jsColor'
|
|
|
|
if d.head
|
|
@remStyle()
|
|
unless Style.headCount
|
|
return @cleanup()
|
|
@observe()
|
|
|
|
observe: ->
|
|
if window.MutationObserver
|
|
Style.observer = new MutationObserver onMutationObserver = @wrapper
|
|
Style.observer.observe d,
|
|
childList: true
|
|
subtree: true
|
|
else
|
|
$.on d, 'DOMNodeInserted', @wrapper
|
|
|
|
wrapper: ->
|
|
if d.head
|
|
Style.remStyle()
|
|
|
|
if not Style.headCount or d.readyState is 'complete'
|
|
if Style.observer
|
|
Style.observer.disconnect()
|
|
else
|
|
$.off d, 'DOMNodeInserted', Style.wrapper
|
|
Style.cleanup()
|
|
|
|
cleanup: ->
|
|
delete Style.observe
|
|
delete Style.wrapper
|
|
delete Style.remStyle
|
|
delete Style.headCount
|
|
delete Style.cleanup
|
|
|
|
addStyle: ->
|
|
Style.dynamicCSS.textContent = Style.dynamic()
|
|
Style.iconPositions()
|
|
Style.padding()
|
|
|
|
headCount: 12
|
|
|
|
remStyle: ->
|
|
nodes = d.head.children
|
|
i = nodes.length
|
|
while i--
|
|
return unless Style.headCount
|
|
node = nodes[i]
|
|
if (node.nodeName is 'STYLE' and !node.id) or ("#{node.rel}".contains('stylesheet') and node.href[..3] isnt 'data')
|
|
Style.headCount--
|
|
$.rm node
|
|
return
|
|
|
|
matrix: (foreground, background) ->
|
|
fgHex = Style.colorToHex foreground
|
|
fg = {
|
|
r: parseInt(fgHex.substr(0, 2), 16) / 255
|
|
g: parseInt(fgHex.substr(2, 2), 16) / 255
|
|
b: parseInt(fgHex.substr(4, 2), 16) / 255
|
|
}
|
|
if background
|
|
bgHex = Style.colorToHex background
|
|
|
|
bg = {
|
|
r: parseInt(bgHex.substr(0, 2), 16) / 255
|
|
g: parseInt(bgHex.substr(2, 2), 16) / 255
|
|
b: parseInt(bgHex.substr(4, 2), 16) / 255
|
|
}
|
|
|
|
[fg, bg or fg]
|
|
|
|
filter: ([fg, bg]) ->
|
|
"#{bg.r} #{-fg.r} 0 0 #{fg.r} #{bg.g} #{-fg.g} 0 0 #{fg.g} #{bg.b} #{-fg.b} 0 0 #{fg.b}"
|
|
|
|
silhouette: ([fg]) ->
|
|
"0 0 0 0 #{fg.r} 0 0 0 0 #{fg.g} 0 0 0 0 #{fg.b}"
|
|
|
|
layout: """<%= grunt.file.read('src/General/css/layout.css') %>"""
|
|
|
|
dynamic: ->
|
|
_conf = Conf
|
|
|
|
sidebarLocation = if _conf["Sidebar Location"] is "left"
|
|
["left", "right"]
|
|
else
|
|
["right", "left" ]
|
|
|
|
if _conf['editMode'] is "theme"
|
|
editSpace = {}
|
|
editSpace[sidebarLocation[1]] = 300
|
|
editSpace[sidebarLocation[0]] = 0
|
|
else
|
|
editSpace =
|
|
left: 0
|
|
right: 0
|
|
|
|
"""<%= grunt.file.read('src/General/css/dynamic.css') %>"""
|
|
|
|
theme: (theme) ->
|
|
bgColor = new Style.color(Style.colorToHex(backgroundC = theme["Background Color"]) or 'aaaaaa')
|
|
replybg = new Style.color Style.colorToHex theme["Reply Background"]
|
|
|
|
Style.lightTheme = bgColor.isLight()
|
|
|
|
Style.svg.innerHTML = """
|
|
<svg xmlns='http://www.w3.org/2000/svg' height=0><filter id='captcha-filter' color-interpolation-filters='sRGB'><feColorMatrix values='#{Style.filter Style.matrix theme["Text"], theme["Input Background"]} 0 0 0 1 0' /></filter></svg>
|
|
<svg xmlns='http://www.w3.org/2000/svg' height=0><filter id='mascot-filter' color-interpolation-filters='sRGB'><feColorMatrix values='#{Style.silhouette Style.matrix theme["Reply Background"]} 0 0 0 1 0' /></filter></svg>
|
|
<svg xmlns='http://www.w3.org/2000/svg' height=0><filter id="grayscale"><feColorMatrix id="color" type="saturate" values="0" /></filter></svg>
|
|
<svg xmlns='http://www.w3.org/2000/svg' height=0><filter id="icons-filter" color-interpolation-filters='sRGB'><feColorMatrix values='-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0' /></filter></svg>
|
|
"""
|
|
|
|
"""<%= grunt.file.read('src/General/css/theme.css') %>""" + <%= grunt.file.read('src/General/css/themeoptions.css') %>
|
|
|
|
iconPositions: ->
|
|
css = """<%= grunt.file.read('src/General/css/icons.base.css') %>"""
|
|
_conf = Conf
|
|
i = 0
|
|
align = _conf['Sidebar Location']
|
|
sidebar = {
|
|
minimal: 20
|
|
hide: 2
|
|
normal: 252
|
|
large: 303
|
|
}[_conf['Sidebar']]
|
|
|
|
notCatalog = g.VIEW isnt 'catalog'
|
|
notEither = notCatalog and g.BOARD isnt 'f'
|
|
|
|
aligner = (first, checks) ->
|
|
# Create a position to hold values
|
|
position = [first]
|
|
|
|
# Check which elements we actually have. Some are easy, because the script creates them so we'd know they're here
|
|
# Some are hard, like 4sight, which we have no way of knowing if available without looking for it.
|
|
for enabled in checks
|
|
position.push(
|
|
if enabled
|
|
first += 19
|
|
else
|
|
first
|
|
)
|
|
|
|
position
|
|
|
|
if _conf["Icon Orientation"] is "horizontal"
|
|
|
|
position = aligner(
|
|
2
|
|
[
|
|
true
|
|
_conf['Slideout Navigation'] isnt 'hide'
|
|
_conf['Announcements'] is 'slideout' and (psa = $ '#globalMessage', d.body) and !psa.hidden
|
|
_conf['Thread Watcher'] and _conf['Slideout Watcher']
|
|
$ '#navtopright .exlinksOptionsLink', d.body
|
|
notEither and _conf['Image Expansion']
|
|
notEither
|
|
g.VIEW is 'thread'
|
|
notEither and _conf['Fappe Tyme']
|
|
navlinks = ((g.VIEW isnt 'thread' and _conf['Index Navigation']) or (g.VIEW is 'thread' and _conf['Reply Navigation'])) and notCatalog
|
|
navlinks
|
|
]
|
|
)
|
|
|
|
iconOffset =
|
|
position[position.length - 1] - (if _conf['4chan SS Navigation']
|
|
0
|
|
else
|
|
sidebar + parseInt(_conf["Right Thread Padding"], 10))
|
|
|
|
if iconOffset < 0 then iconOffset = 0
|
|
|
|
css += """<%= grunt.file.read('src/General/css/icons.horz.css') %>"""
|
|
|
|
else
|
|
|
|
position = aligner(
|
|
2
|
|
[
|
|
notEither and _conf['Image Expansion']
|
|
true
|
|
_conf['Slideout Navigation'] isnt 'hide'
|
|
_conf['Announcements'] is 'slideout' and (psa = $ '#globalMessage', d.body) and !psa.hidden
|
|
_conf['Thread Watcher'] and _conf['Slideout Watcher']
|
|
$ '#navtopright .exlinksOptionsLink', d.body
|
|
notEither
|
|
g.VIEW is 'thread'
|
|
notEither and _conf['Fappe Tyme']
|
|
navlinks = ((g.VIEW isnt 'thread' and _conf['Index Navigation']) or (g.VIEW is 'thread' and _conf['Reply Navigation'])) and notCatalog
|
|
navlinks
|
|
]
|
|
)
|
|
|
|
iconOffset = (
|
|
20 + (
|
|
if g.VIEW is 'thread' and _conf['Updater Position'] is 'top'
|
|
100
|
|
else
|
|
0
|
|
)
|
|
) - (
|
|
if _conf['4chan SS Navigation']
|
|
0
|
|
else
|
|
sidebar + parseInt _conf[align.capitalize() + " Thread Padding"], 10
|
|
)
|
|
|
|
if iconOffset < 0 then iconOffset = 0
|
|
|
|
css += """<%= grunt.file.read('src/General/css/icons.vert.css') %>"""
|
|
|
|
Style.icons.textContent = css
|
|
|
|
padding: ->
|
|
Style.padding.nav = Header.bar
|
|
Style.padding.pages = $ '.pagelist', d.body
|
|
css = """<%= grunt.file.read('src/General/css/padding.nav.css') %>"""
|
|
|
|
if Style.padding.pages
|
|
css += """<%= grunt.file.read('src/General/css/padding.pages.css') %>"""
|
|
|
|
Style.paddingSheet.textContent = css
|
|
|
|
color: (hex) ->
|
|
@hex = "#" + hex
|
|
|
|
@calc_rgb = (hex) ->
|
|
hex = parseInt hex, 16
|
|
[ # 0xRRGGBB to [R, G, B]
|
|
(hex >> 16) & 0xFF
|
|
(hex >> 8) & 0xFF
|
|
hex & 0xFF
|
|
]
|
|
|
|
@private_rgb = @calc_rgb(hex)
|
|
|
|
@rgb = @private_rgb.join ","
|
|
|
|
@isLight = ->
|
|
rgb = @private_rgb
|
|
return (rgb[0] + rgb[1] + rgb[2]) >= 400
|
|
|
|
@shiftRGB = (shift, smart) ->
|
|
minmax = (base) ->
|
|
Math.min Math.max(base, 0), 255
|
|
rgb = @private_rgb.slice 0
|
|
shift = if smart
|
|
(
|
|
if @isLight rgb
|
|
-1
|
|
else
|
|
1
|
|
) * Math.abs shift
|
|
else
|
|
shift
|
|
|
|
return [
|
|
minmax rgb[0] + shift
|
|
minmax rgb[1] + shift
|
|
minmax rgb[2] + shift
|
|
].join ","
|
|
|
|
@hover = @shiftRGB 16, true
|
|
|
|
colorToHex: (color) ->
|
|
if color.substr(0, 1) is '#'
|
|
return color.slice 1, color.length
|
|
|
|
if digits = color.match /(.*?)rgba?\((\d+), ?(\d+), ?(\d+)(.*?)\)/
|
|
# [R, G, B] to 0xRRGGBB
|
|
hex = (
|
|
(parseInt(digits[2], 10) << 16) |
|
|
(parseInt(digits[3], 10) << 8) |
|
|
(parseInt(digits[4], 10))
|
|
).toString 16
|
|
|
|
while hex.length < 6
|
|
hex = "0#{hex}"
|
|
|
|
hex
|
|
|
|
else
|
|
false |