diff --git a/.gitignore b/.gitignore
index 868b74e84..7820bd9be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
builds/
node_modules/
tmp-crx/
-tmp-userjs/
tmp-userscript/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a1164bc88..735b0841c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -11,9 +11,8 @@ Reporting bugs:
4. Your exported settings. If your settings contains sensible information (e.g. personas), edit the text file manually.
Open your console with:
-- `Ctrl + Shift + J` on Chrome.
+- `Ctrl + Shift + J` on Chrome and Opera.
- `Ctrl + Shift + K` on Firefox.
-- `Ctrl + Shift + O` on Opera.
Respect these guidelines:
- Describe the issue clearly, put some effort into it. A one-liner isn't a good enough description.
diff --git a/Gruntfile.js b/Gruntfile.js
index 9bf70810d..69100ad0f 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -56,15 +56,6 @@ module.exports = function(grunt) {
]
}
},
- userjs: {
- options: concatOptions,
- src: [
- 'src/Meta/metadata.js',
- 'src/Meta/banner.js',
- 'tmp-<%= pkg.type %>/script.js'
- ],
- dest: 'builds/<%= pkg.name %>.js'
- },
userscript: {
options: concatOptions,
files: {
@@ -102,7 +93,7 @@ module.exports = function(grunt) {
}
},
concurrent: {
- build: ['build-crx', 'build-userjs', 'build-userscript']
+ build: ['build-crx', 'build-userscript']
},
bump: {
options: {
@@ -161,7 +152,6 @@ module.exports = function(grunt) {
clean: {
builds: 'builds',
tmpcrx: 'tmp-crx',
- tmpuserjs: 'tmp-userjs',
tmpuserscript: 'tmp-userscript'
}
});
@@ -193,13 +183,6 @@ module.exports = function(grunt) {
'copy:crx',
'clean:tmpcrx'
]);
- grunt.registerTask('build-userjs', [
- 'set-build:userjs',
- 'concat:coffee',
- 'coffee:script',
- 'concat:userjs',
- 'clean:tmpuserjs'
- ]);
grunt.registerTask('build-userscript', [
'set-build:userscript',
'concat:coffee',
diff --git a/css/style.css b/css/style.css
index ce398bc5e..28dfb5bed 100644
--- a/css/style.css
+++ b/css/style.css
@@ -278,10 +278,8 @@ a[href="javascript:;"] {
box-sizing: border-box;
box-shadow: 0 0 15px rgba(0, 0, 0, .15);
height: 600px;
- min-height: 0;
max-height: 100%;
width: 900px;
- min-width: 0;
max-width: 100%;
margin: auto;
padding: 3px;
@@ -468,7 +466,8 @@ a.hide-announcement {
.deadlink {
text-decoration: none !important;
}
-.backlink.deadlink:not(.forwardlink), .quotelink.deadlink:not(.forwardlink) {
+.backlink.deadlink:not(.forwardlink),
+.quotelink.deadlink:not(.forwardlink) {
text-decoration: underline !important;
}
.inlined {
@@ -508,8 +507,6 @@ a.hide-announcement {
padding: 2px 2px 5px;
}
#qp img {
- max-height: 300px;
- max-width: 500px;
max-height: 80vh;
max-width: 50vw;
}
@@ -541,8 +538,7 @@ a.hide-announcement {
:root.fit-width .full-image {
max-width: 100%;
}
-:root.gecko.fit-width .full-image,
-:root.presto.fit-width .full-image {
+:root.gecko.fit-width .full-image {
width: 100%;
}
#ihover {
@@ -616,9 +612,6 @@ a.hide-announcement {
color: #000;
background-color: #F7F7F7;
}
-.presto #qr select {
- height: 1em;
-}
#qr .close {
padding: 0 3px;
}
@@ -649,13 +642,15 @@ a.hide-announcement {
outline: none;
width: 30px;
}
-#dump-button:hover, #dump-button:focus {
+#dump-button:hover,
+#dump-button:focus {
background: linear-gradient(#FFF, #DDD);
}
-#dump-button:active, .dump #dump-button:not(:hover):not(:focus) {
+#dump-button:active,
+.dump #dump-button:not(:hover):not(:focus) {
background: linear-gradient(#CCC, #DDD);
}
-.gecko #dump-button {
+:root.gecko #dump-button {
padding: 0;
}
#qr:not(.dump) #dump-list-container {
@@ -670,7 +665,10 @@ a.hide-announcement {
}
#dump-list {
counter-reset: qrpreviews;
- top: 0; right: 0; bottom: 0; left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
overflow: hidden;
position: absolute;
white-space: nowrap;
@@ -696,8 +694,10 @@ a.hide-announcement {
box-sizing: border-box;
cursor: move;
display: inline-block;
- height: 92px; width: 92px;
- margin: 4px; padding: 2px;
+ height: 92px;
+ width: 92px;
+ margin: 4px;
+ padding: 2px;
opacity: .6;
outline: none;
overflow: hidden;
@@ -707,7 +707,8 @@ a.hide-announcement {
vertical-align: top;
white-space: pre;
}
-.qr-preview:hover, .qr-preview:focus {
+.qr-preview:hover,
+.qr-preview:focus {
opacity: .9;
color: #FFF !important;
}
@@ -720,7 +721,8 @@ a.hide-announcement {
font-weight: 700;
text-shadow: 0 0 3px #000, 0 0 5px #000;
position: absolute;
- top: 3px; right: 3px;
+ top: 3px;
+ right: 3px;
}
.qr-preview.drag {
border-color: red;
@@ -740,7 +742,9 @@ a.hide-announcement {
}
.qr-preview > label {
background: rgba(0, 0, 0, .5);
- right: 0; bottom: 0; left: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
position: absolute;
text-align: center;
}
@@ -756,7 +760,8 @@ a.hide-announcement {
line-height: 1;
text-align: center;
position: absolute;
- right: 0; bottom: 0;
+ right: 0;
+ bottom: 0;
z-index: 1;
}
#qr textarea {
@@ -822,7 +827,10 @@ a.hide-announcement {
}
#qr-filename {
position: absolute;
- top: 0; right: 0; bottom: 0; left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
@@ -838,10 +846,6 @@ a.hide-announcement {
-webkit-order: 1;
order: 1;
}
-#qr input[type='file'] {
- position: absolute;
- visibility: hidden;
-}
/* Menu */
.menu-button {
diff --git a/html/Posting/QR.html b/html/Posting/QR.html
index b02ef3b5f..b34fb36b3 100644
--- a/html/Posting/QR.html
+++ b/html/Posting/QR.html
@@ -31,7 +31,7 @@
×
-
+
diff --git a/lib/$.coffee b/lib/$.coffee
index 40b283713..de39560c9 100644
--- a/lib/$.coffee
+++ b/lib/$.coffee
@@ -208,26 +208,19 @@ $.bytesToString = (size) ->
# Round to an integer otherwise.
Math.round size
"#{size} #{['B', 'KB', 'MB', 'GB'][unit]}"
+$.item = (key, val) ->
+ item = {}
+ item[key] = val
+ item
$.syncing = {}
-$.sync = do ->
<% if (type === 'crx') { %>
+$.sync = do ->
chrome.storage.onChanged.addListener (changes) ->
for key of changes
if cb = $.syncing[key]
cb changes[key].newValue
return
(key, cb) -> $.syncing[key] = cb
-<% } else { %>
- $.on window, 'storage', (e) ->
- if cb = $.syncing[e.key]
- cb JSON.parse e.newValue
- (key, cb) -> $.syncing[g.NAMESPACE + key] = cb
-<% } %>
-$.item = (key, val) ->
- item = {}
- item[key] = val
- item
-<% if (type === 'crx') { %>
$.localKeys = [
# filters
'name',
@@ -300,52 +293,13 @@ $.set = do ->
else
$.extend items, key
set()
-<% } else if (type === 'userjs') { %>
-do ->
- # http://www.opera.com/docs/userjs/specs/#scriptstorage
- # http://www.opera.com/docs/userjs/using/#securepages
- # The scriptStorage object is available only during
- # the main User JavaScript thread, being therefore
- # accessible only in the main body of the user script.
- # To access the storage object later, keep a reference
- # to the object.
- {scriptStorage} = opera
- $.delete = (keys) ->
- unless keys instanceof Array
- keys = [keys]
- for key in keys
- key = g.NAMESPACE + key
- localStorage.removeItem key
- delete scriptStorage[key]
- return
- $.get = (key, val, cb) ->
- if typeof cb is 'function'
- items = $.item key, val
- else
- items = key
- cb = val
- $.queueTask ->
- for key of items
- if val = scriptStorage[g.NAMESPACE + key]
- items[key] = JSON.parse val
- cb items
- $.set = do ->
- set = (key, val) ->
- key = g.NAMESPACE + key
- val = JSON.stringify val
- if key of $.syncing
- # for `storage` events
- localStorage.setItem key, val
- scriptStorage[key] = val
- (keys, val) ->
- if typeof keys is 'string'
- set keys, val
- return
- for key, val of keys
- set key, val
- return
<% } else { %>
- # http://wiki.greasespot.net/Main_Page
+# http://wiki.greasespot.net/Main_Page
+$.sync = do ->
+ $.on window, 'storage', (e) ->
+ if cb = $.syncing[e.key]
+ cb JSON.parse e.newValue
+ (key, cb) -> $.syncing[g.NAMESPACE + key] = cb
$.delete = (keys) ->
unless keys instanceof Array
keys = [keys]
diff --git a/src/General/Globals.coffee b/src/General/Globals.coffee
index ad48281e4..d5d09eaa6 100644
--- a/src/General/Globals.coffee
+++ b/src/General/Globals.coffee
@@ -1,9 +1,3 @@
-<% if (type === 'userjs') { %>
-# Opera doesn't support the @match metadata key,
-# return 4chan X here if we're not on 4chan.
-return unless /^(boards|images|sys)\.4chan\.org$/.test location.hostname
-<% } %>
-
Conf = {}
c = console
d = document
diff --git a/src/General/Main.coffee b/src/General/Main.coffee
index b023d704f..09fe39241 100644
--- a/src/General/Main.coffee
+++ b/src/General/Main.coffee
@@ -127,8 +127,6 @@ Main =
<% if (type === 'crx') { %>
$.addClass doc, 'webkit'
$.addClass doc, 'blink'
- <% } else if (type === 'userjs') { %>
- $.addClass doc, 'presto'
<% } else { %>
$.addClass doc, 'gecko'
<% } %>
@@ -151,13 +149,9 @@ Main =
$.addClass doc, style
setStyle()
return unless mainStyleSheet
- if window.MutationObserver
- observer = new MutationObserver setStyle
- observer.observe mainStyleSheet,
- attributes: true
- attributeFilter: ['href']
- else
- $.on mainStyleSheet, 'DOMAttrModified', setStyle
+ new MutationObserver(setStyle).observe mainStyleSheet,
+ attributes: true
+ attributeFilter: ['href']
initReady: ->
if d.title is '4chan - 404 Not Found'
@@ -252,12 +246,10 @@ Main =
checkUpdate: ->
return unless Conf['Check for Updates'] and Main.isThisPageLegit()
- # Check for updates after:
- # - 6 hours since the last update on Opera because it lacks auto-updating.
- # - 7 days since the last update on Chrome/Firefox.
+ # 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 = <% if (type === 'userjs') { %>6 * $.HOUR<% } else { %>7 * $.DAY<% } %>
+ freq = 7 * $.DAY
items =
lastupdate: 0
lastchecked: 0
diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee
index 18d91fcc1..777cceea8 100644
--- a/src/Images/ImageExpand.coffee
+++ b/src/Images/ImageExpand.coffee
@@ -52,19 +52,6 @@ ImageExpand =
return
setFitness: ->
(if @checked then $.addClass else $.rmClass) doc, @name.toLowerCase().replace /\s+/g, '-'
-<% if (type === 'userjs') { %>
-# XXX Opera doesn't support CSS vh.
- return unless @name is 'Fit height'
- if @checked
- $.on window, 'resize', ImageExpand.resize
- unless ImageExpand.style
- ImageExpand.style = $.addStyle null
- ImageExpand.resize()
- else
- $.off window, 'resize', ImageExpand.resize
- resize: ->
- ImageExpand.style.textContent = ":root.fit-height .full-image {max-height:#{doc.clientHeight}px}"
-<% } %>
toggle: (post) ->
{thumb} = post.file
diff --git a/src/Meta/manifest.json b/src/Meta/manifest.json
index b9793bda3..fb8cfba4e 100644
--- a/src/Meta/manifest.json
+++ b/src/Meta/manifest.json
@@ -15,7 +15,8 @@
"run_at": "document_start"
}],
"homepage_url": "<%= meta.page %>",
- "minimum_chrome_version": "26",
+ "minimum_chrome_version": "27",
+ "minimum_opera_version": "15",
"permissions": [
"storage"
]
diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee
index 0c3f5317c..7dab2983a 100644
--- a/src/Monitoring/ThreadUpdater.coffee
+++ b/src/Monitoring/ThreadUpdater.coffee
@@ -114,8 +114,7 @@ ThreadUpdater =
By sending the `If-Modified-Since` header we get a proper status code, and no response.
This saves bandwidth for both the user and the servers and avoid unnecessary computation.
###
- # XXX 304 -> 0 in Opera
- [text, klass] = if req.status in [0, 304]
+ [text, klass] = if req.status is 304
[null, null]
else
["#{req.statusText} (#{req.status})", 'warning']
diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee
index 6ad07f03e..1e0b63fd6 100644
--- a/src/Monitoring/Unread.coffee
+++ b/src/Monitoring/Unread.coffee
@@ -184,9 +184,7 @@ Unread =
else
Favicon.default
- <% if (type !== 'crx') { %>
+ <% if (type === 'userscript') { %>
# `favicon.href = href` doesn't work on Firefox.
- # `favicon.href = href` isn't enough on Opera.
- # Opera won't always update the favicon if the href didn't change.
$.add d.head, Favicon.el
<% } %>
diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee
index fb5bce620..981053704 100644
--- a/src/Posting/QR.coffee
+++ b/src/Posting/QR.coffee
@@ -330,7 +330,6 @@ QR =
post = Get.postFromNode @
text = ">>#{post}\n"
if (s = sel.toString().trim()) and post is Get.postFromNode sel.anchorNode
- # XXX Opera doesn't retain `\n`s?
s = s.replace /\n/g, '\n>'
text += ">#{s}\n"
@@ -501,7 +500,6 @@ QR =
else if @ is QR.selected
(QR.posts[index-1] or QR.posts[index+1]).select()
QR.posts.splice index, 1
- return unless window.URL
URL.revokeObjectURL @URL
lock: (lock=true) ->
@isLocked = lock
@@ -558,25 +556,14 @@ QR =
@filename = "#{file.name} (#{$.bytesToString file.size})"
@nodes.el.title = @filename
@nodes.label.hidden = false if QR.spoiler
- URL.revokeObjectURL @URL if window.URL
+ URL.revokeObjectURL @URL
@showFileData()
unless /^image/.test file.type
@nodes.el.style.backgroundImage = null
return
@setThumbnail()
- setThumbnail: (fileURL) ->
- # XXX Opera does not support blob URL
+ setThumbnail: ->
# Create a redimensioned thumbnail.
- unless window.URL
- unless fileURL
- reader = new FileReader()
- reader.onload = (e) =>
- @setThumbnail e.target.result
- reader.readAsDataURL @file
- return
- else
- fileURL = URL.createObjectURL @file
-
img = $.el 'img'
img.onload = =>
@@ -588,7 +575,7 @@ QR =
s *= 3 if @file.type is 'image/gif' # let them animate
{height, width} = img
if height < s or width < s
- @URL = fileURL if window.URL
+ @URL = fileURL
@nodes.el.style.backgroundImage = "url(#{@URL})"
return
if height <= width
@@ -601,10 +588,6 @@ QR =
cv.height = img.height = height
cv.width = img.width = width
cv.getContext('2d').drawImage img, 0, 0, width, height
- unless window.URL
- @nodes.el.style.backgroundImage = "url(#{cv.toDataURL()})"
- delete @URL
- return
URL.revokeObjectURL fileURL
applyBlob = (blob) =>
@URL = URL.createObjectURL blob
@@ -622,6 +605,7 @@ QR =
applyBlob new Blob [ui8a], type: 'image/png'
+ fileURL = URL.createObjectURL @file
img.src = fileURL
rmFile: ->
delete @file
@@ -630,7 +614,6 @@ QR =
@nodes.el.style.backgroundImage = null
@nodes.label.hidden = true if QR.spoiler
@showFileData()
- return unless window.URL
URL.revokeObjectURL @URL
showFileData: ->
if @file
@@ -652,22 +635,17 @@ QR =
QR.nodes.com.value = @com
@nodes.span.textContent = @com
reader.readAsText file
- dragStart: ->
- $.addClass @, 'drag'
- dragEnd: ->
- $.rmClass @, 'drag'
- dragEnter: ->
- $.addClass @, 'over'
- dragLeave: ->
- $.rmClass @, 'over'
+ dragStart: -> $.addClass @, 'drag'
+ dragEnd: -> $.rmClass @, 'drag'
+ dragEnter: -> $.addClass @, 'over'
+ dragLeave: -> $.rmClass @, 'over'
dragOver: (e) ->
e.preventDefault()
e.dataTransfer.dropEffect = 'move'
drop: ->
- el = $ '.drag', @parentNode
- $.rmClass el, 'drag' # Opera doesn't fire dragEnd if we drop it on something else
- $.rmClass @, 'over'
+ $.rmClass @, 'over'
return unless @draggable
+ el = $ '.drag', @parentNode
index = (el) -> [el.parentNode.children...].indexOf el
oldIndex = index el
newIndex = index @
@@ -700,12 +678,8 @@ QR =
img: imgContainer.firstChild
input: input
- if window.MutationObserver
- observer = new MutationObserver @load.bind @
- observer.observe @nodes.challenge,
- childList: true
- else
- $.on @nodes.challenge, 'DOMNodeInserted', @load.bind @
+ new MutationObserver(@load.bind @).observe @nodes.challenge,
+ childList: true
$.on imgContainer, 'click', @reload.bind @
$.on input, 'keydown', @keydown.bind @
@@ -836,10 +810,6 @@ QR =
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog.
QR.mimeTypes.push ''
nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value
- <% if (type !== 'userjs') { %>
- # Opera's accept attribute is fucked up
- nodes.fileInput.accept = "text/*, #{mimeTypes}"
- <% } %>
QR.spoiler = !!$ 'input[name=spoiler]'
nodes.spoiler.hidden = !QR.spoiler
@@ -1018,11 +988,6 @@ QR =
QR.status()
response: ->
- <% if (type === 'userjs') { %>
- # The upload.onload callback is not called
- # or at least not in time with Opera.
- QR.req.upload.onload()
- <% } %>
{req} = QR
delete QR.req
diff --git a/src/Quotelinks/QuotePreview.coffee b/src/Quotelinks/QuotePreview.coffee
index fcd0bfd98..ab21dfa78 100644
--- a/src/Quotelinks/QuotePreview.coffee
+++ b/src/Quotelinks/QuotePreview.coffee
@@ -28,21 +28,6 @@ QuotePreview =
cb: QuotePreview.mouseout
asapTest: -> qp.firstElementChild
- <% if (type === 'userjs') { %>
- # XXX Opera workaround for "no mouseout fired" bug.
- # Remove it once Opera uses Blink.
- root = @
- workaround = (e) ->
- if @ is root
- e.stopPropagation()
- return
- $.event 'mouseout', null, root
- $.off d, 'mousemove', workaround
- $.off root, 'mousemove', workaround
- $.on d, 'mousemove', workaround
- $.on root, 'mousemove', workaround
- <% } %>
-
return unless origin = g.posts["#{boardID}.#{postID}"]
if Conf['Quote Highlighting']