diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f04c17b6..8ebce0a1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ +detharonil +- Support for %Y in time formatting +- More future-proof %y + MayhemYDG: - Fix whitespaces not being preserved in code tags in /g/. +zixaphir: +- Fix custom CSS + ### 1.1.11 - 2013-05-04 seaweedchan: - Add `Highlight Posts Quoting You` option diff --git a/LICENSE b/LICENSE index ae78c588e..571834053 100644 --- a/LICENSE +++ b/LICENSE @@ -59,6 +59,7 @@ * WakiMiko * btmcsweeney * AppleBloom +* detharonil * * All the people who've taken the time to write bug reports. * diff --git a/builds/4chan-X.js b/builds/4chan-X.js index 986996419..58097eed0 100644 --- a/builds/4chan-X.js +++ b/builds/4chan-X.js @@ -78,6 +78,7 @@ * WakiMiko * btmcsweeney * AppleBloom +* detharonil * * All the people who've taken the time to write bug reports. * @@ -111,8 +112,7 @@ __slice = [].slice, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; Config = { main: { @@ -2542,6 +2542,7 @@ } }); $.on(root, endEvents, o.hoverend); + $.on(d, 'keydown', o.hoverend); return $.on(root, 'mousemove', o.hover); }; hover = function(e) { @@ -2558,9 +2559,13 @@ style.left = left; return style.right = right; }; - hoverend = function() { + hoverend = function(e) { + if (e.type === 'keydown' && e.keyCode !== 13) { + return; + } $.rm(this.el); $.off(this.root, this.endEvents, this.hoverend); + $.off(d, 'keydown', this.hoverend); $.off(this.root, 'mousemove', this.hover); if (this.cb) { return this.cb.call(this); @@ -4678,7 +4683,7 @@ }); }, parseItem: function(item, types) { - var boards, match, type, val, _ref, _ref1, _ref2; + var boards, match, type, val, _ref, _ref1; if (item[0] === '#') { return; @@ -4689,7 +4694,7 @@ _ref = match, match = _ref[0], type = _ref[1], val = _ref[2]; item = item.replace(match, ''); boards = ((_ref1 = item.match(/boards:([^;]+)/i)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; - if (boards !== 'global' && !(_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) >= 0)) { + if (boards !== 'global' && !((boards.split(',')).contains(g.BOARD.ID))) { return; } if (type === 'password') { @@ -4702,7 +4707,7 @@ if (/always/i.test(item)) { QR.persona.always[type] = val; } - if (__indexOf.call(types[type], val) < 0) { + if (!types[type].contains(val)) { return types[type].push(val); } }, @@ -9111,7 +9116,10 @@ return Time.zeroPad(this.getSeconds()); }, y: function() { - return this.getFullYear() - 2000; + return this.getFullYear() % 100; + }, + Y: function() { + return this.getFullYear(); } } }; @@ -9539,7 +9547,7 @@ advanced: function(section) { var archiver, event, input, inputs, items, name, ta, toSelect, _i, _j, _len, _len1, _ref; - section.innerHTML = "
Archiver\nSelect an Archiver for this board:\n
Custom Board Navigation
In the following, board can translate to a board ID (a, b, etc...), the current board (current), or the Status/Twitter link (status, @).
\n For example:
[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]
\n will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
\n if you are on /g/.\n
Board link: board
Title link: board-title
Board link (Replace with title when on that board): board-replace
Full text link: board-full
Custom text link: board-text:\"VIP Board\"
Index-only link: board-index
Catalog-only link: board-catalog
Combinations are possible: board-index-text:\"VIP Index\"
Full board list toggle: toggle-all
Time Formatting is disabled.
:
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Quick Reply Personas is disabled.

\n One item per line.
\n Items will be added in the relevant input's auto-completion list.
\n Password items will always be used, since there is no password input.
\n Lines starting with a # will be ignored.\n

Unread Favicon is disabled.
Emoji is disabled.
\n Sage Icon:
\n Position:
Thread Updater is disabled.
\n Interval:
"; + section.innerHTML = "
Archiver\nSelect an Archiver for this board:\n
Custom Board Navigation
In the following, board can translate to a board ID (a, b, etc...), the current board (current), or the Status/Twitter link (status, @).
\n For example:
[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]
\n will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
\n if you are on /g/.\n
Board link: board
Title link: board-title
Board link (Replace with title when on that board): board-replace
Full text link: board-full
Custom text link: board-text:\"VIP Board\"
Index-only link: board-index
Catalog-only link: board-catalog
Combinations are possible: board-index-text:\"VIP Index\"
Full board list toggle: toggle-all
Time Formatting is disabled.
:
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y, %Y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Quick Reply Personas is disabled.

\n One item per line.
\n Items will be added in the relevant input's auto-completion list.
\n Password items will always be used, since there is no password input.
\n Lines starting with a # will be ignored.\n

Unread Favicon is disabled.
Emoji is disabled.
\n Sage Icon:
\n Position:
Thread Updater is disabled.
\n Interval:
"; items = {}; inputs = {}; _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'sageEmoji', 'emojiPos', 'usercss']; @@ -9580,11 +9588,14 @@ for (key in items) { val = items[key]; - if (['usercss', 'emojiPos', 'archiver'].contains(key)) { + if (['emojiPos', 'archiver'].contains(key)) { continue; } input = inputs[key]; input.value = val; + if (key === 'usercss') { + continue; + } $.on(input, event, Settings[key]); Settings[key].call(input); } diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index 5ef83d34f..b71a3da7e 100644 --- a/builds/4chan-X.user.js +++ b/builds/4chan-X.user.js @@ -78,6 +78,7 @@ * WakiMiko * btmcsweeney * AppleBloom +* detharonil * * All the people who've taken the time to write bug reports. * @@ -111,8 +112,7 @@ __slice = [].slice, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; Config = { main: { @@ -2538,6 +2538,7 @@ } }); $.on(root, endEvents, o.hoverend); + $.on(d, 'keydown', o.hoverend); return $.on(root, 'mousemove', o.hover); }; hover = function(e) { @@ -2554,9 +2555,13 @@ style.left = left; return style.right = right; }; - hoverend = function() { + hoverend = function(e) { + if (e.type === 'keydown' && e.keyCode !== 13) { + return; + } $.rm(this.el); $.off(this.root, this.endEvents, this.hoverend); + $.off(d, 'keydown', this.hoverend); $.off(this.root, 'mousemove', this.hover); if (this.cb) { return this.cb.call(this); @@ -4674,7 +4679,7 @@ }); }, parseItem: function(item, types) { - var boards, match, type, val, _ref, _ref1, _ref2; + var boards, match, type, val, _ref, _ref1; if (item[0] === '#') { return; @@ -4685,7 +4690,7 @@ _ref = match, match = _ref[0], type = _ref[1], val = _ref[2]; item = item.replace(match, ''); boards = ((_ref1 = item.match(/boards:([^;]+)/i)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; - if (boards !== 'global' && !(_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) >= 0)) { + if (boards !== 'global' && !((boards.split(',')).contains(g.BOARD.ID))) { return; } if (type === 'password') { @@ -4698,7 +4703,7 @@ if (/always/i.test(item)) { QR.persona.always[type] = val; } - if (__indexOf.call(types[type], val) < 0) { + if (!types[type].contains(val)) { return types[type].push(val); } }, @@ -9132,7 +9137,10 @@ return Time.zeroPad(this.getSeconds()); }, y: function() { - return this.getFullYear() - 2000; + return this.getFullYear() % 100; + }, + Y: function() { + return this.getFullYear(); } } }; @@ -9562,7 +9570,7 @@ advanced: function(section) { var archiver, event, input, inputs, items, name, ta, toSelect, _i, _j, _len, _len1, _ref; - section.innerHTML = "
Archiver\nSelect an Archiver for this board:\n
Custom Board Navigation
In the following, board can translate to a board ID (a, b, etc...), the current board (current), or the Status/Twitter link (status, @).
\n For example:
[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]
\n will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
\n if you are on /g/.\n
Board link: board
Title link: board-title
Board link (Replace with title when on that board): board-replace
Full text link: board-full
Custom text link: board-text:\"VIP Board\"
Index-only link: board-index
Catalog-only link: board-catalog
Combinations are possible: board-index-text:\"VIP Index\"
Full board list toggle: toggle-all
Time Formatting is disabled.
:
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Quick Reply Personas is disabled.

\n One item per line.
\n Items will be added in the relevant input's auto-completion list.
\n Password items will always be used, since there is no password input.
\n Lines starting with a # will be ignored.\n

Unread Favicon is disabled.
Emoji is disabled.
\n Sage Icon:
\n Position:
Thread Updater is disabled.
\n Interval:
"; + section.innerHTML = "
Archiver\nSelect an Archiver for this board:\n
Custom Board Navigation
In the following, board can translate to a board ID (a, b, etc...), the current board (current), or the Status/Twitter link (status, @).
\n For example:
[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]
\n will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
\n if you are on /g/.\n
Board link: board
Title link: board-title
Board link (Replace with title when on that board): board-replace
Full text link: board-full
Custom text link: board-text:\"VIP Board\"
Index-only link: board-index
Catalog-only link: board-catalog
Combinations are possible: board-index-text:\"VIP Index\"
Full board list toggle: toggle-all
Time Formatting is disabled.
:
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y, %Y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Quick Reply Personas is disabled.

\n One item per line.
\n Items will be added in the relevant input's auto-completion list.
\n Password items will always be used, since there is no password input.
\n Lines starting with a # will be ignored.\n

Unread Favicon is disabled.
Emoji is disabled.
\n Sage Icon:
\n Position:
Thread Updater is disabled.
\n Interval:
"; items = {}; inputs = {}; _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'sageEmoji', 'emojiPos', 'usercss']; @@ -9603,11 +9611,14 @@ for (key in items) { val = items[key]; - if (['usercss', 'emojiPos', 'archiver'].contains(key)) { + if (['emojiPos', 'archiver'].contains(key)) { continue; } input = inputs[key]; input.value = val; + if (key === 'usercss') { + continue; + } $.on(input, event, Settings[key]); Settings[key].call(input); } diff --git a/builds/crx/script.js b/builds/crx/script.js index 3c9c9921f..c4a74b303 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -59,6 +59,7 @@ * WakiMiko * btmcsweeney * AppleBloom +* detharonil * * All the people who've taken the time to write bug reports. * @@ -2540,6 +2541,7 @@ } }); $.on(root, endEvents, o.hoverend); + $.on(d, 'keydown', o.hoverend); return $.on(root, 'mousemove', o.hover); }; hover = function(e) { @@ -2556,9 +2558,13 @@ style.left = left; return style.right = right; }; - hoverend = function() { + hoverend = function(e) { + if (e.type === 'keydown' && e.keyCode !== 13) { + return; + } $.rm(this.el); $.off(this.root, this.endEvents, this.hoverend); + $.off(d, 'keydown', this.hoverend); $.off(this.root, 'mousemove', this.hover); if (this.cb) { return this.cb.call(this); @@ -4677,7 +4683,7 @@ }); }, parseItem: function(item, types) { - var boards, match, type, val, _ref, _ref1, _ref2; + var boards, match, type, val, _ref, _ref1; if (item[0] === '#') { return; @@ -4688,7 +4694,7 @@ _ref = match, match = _ref[0], type = _ref[1], val = _ref[2]; item = item.replace(match, ''); boards = ((_ref1 = item.match(/boards:([^;]+)/i)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; - if (boards !== 'global' && !(_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) >= 0)) { + if (boards !== 'global' && !((boards.split(',')).contains(g.BOARD.ID))) { return; } if (type === 'password') { @@ -4701,7 +4707,7 @@ if (/always/i.test(item)) { QR.persona.always[type] = val; } - if (__indexOf.call(types[type], val) < 0) { + if (!types[type].contains(val)) { return types[type].push(val); } }, @@ -9115,7 +9121,10 @@ return Time.zeroPad(this.getSeconds()); }, y: function() { - return this.getFullYear() - 2000; + return this.getFullYear() % 100; + }, + Y: function() { + return this.getFullYear(); } } }; @@ -9543,7 +9552,7 @@ advanced: function(section) { var archiver, event, input, inputs, items, name, ta, toSelect, _i, _j, _len, _len1, _ref; - section.innerHTML = "
Archiver\nSelect an Archiver for this board:\n
Custom Board Navigation
In the following, board can translate to a board ID (a, b, etc...), the current board (current), or the Status/Twitter link (status, @).
\n For example:
[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]
\n will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
\n if you are on /g/.\n
Board link: board
Title link: board-title
Board link (Replace with title when on that board): board-replace
Full text link: board-full
Custom text link: board-text:\"VIP Board\"
Index-only link: board-index
Catalog-only link: board-catalog
Combinations are possible: board-index-text:\"VIP Index\"
Full board list toggle: toggle-all
Time Formatting is disabled.
:
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Quick Reply Personas is disabled.

\n One item per line.
\n Items will be added in the relevant input's auto-completion list.
\n Password items will always be used, since there is no password input.
\n Lines starting with a # will be ignored.\n

Unread Favicon is disabled.
Emoji is disabled.
\n Sage Icon:
\n Position:
Thread Updater is disabled.
\n Interval:
"; + section.innerHTML = "
Archiver\nSelect an Archiver for this board:\n
Custom Board Navigation
In the following, board can translate to a board ID (a, b, etc...), the current board (current), or the Status/Twitter link (status, @).
\n For example:
[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [t-text:\"Piracy\"]
\n will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
\n if you are on /g/.\n
Board link: board
Title link: board-title
Board link (Replace with title when on that board): board-replace
Full text link: board-full
Custom text link: board-text:\"VIP Board\"
Index-only link: board-index
Catalog-only link: board-catalog
Combinations are possible: board-index-text:\"VIP Index\"
Full board list toggle: toggle-all
Time Formatting is disabled.
:
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y, %Y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Quick Reply Personas is disabled.

\n One item per line.
\n Items will be added in the relevant input's auto-completion list.
\n Password items will always be used, since there is no password input.
\n Lines starting with a # will be ignored.\n

Unread Favicon is disabled.
Emoji is disabled.
\n Sage Icon:
\n Position:
Thread Updater is disabled.
\n Interval:
"; items = {}; inputs = {}; _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'sageEmoji', 'emojiPos', 'usercss']; @@ -9584,11 +9593,14 @@ for (key in items) { val = items[key]; - if (['usercss', 'emojiPos', 'archiver'].contains(key)) { + if (['emojiPos', 'archiver'].contains(key)) { continue; } input = inputs[key]; input.value = val; + if (key === 'usercss') { + continue; + } $.on(input, event, Settings[key]); Settings[key].call(input); } diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 30a817903..7cba6b4ee 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -353,9 +353,10 @@ Settings = $.get items, (items) -> for key, val of items - continue if ['usercss', 'emojiPos', 'archiver'].contains key + continue if ['emojiPos', 'archiver'].contains key input = inputs[key] input.value = val + continue if key is 'usercss' $.on input, event, Settings[key] Settings[key].call input return diff --git a/src/General/UI.coffee b/src/General/UI.coffee index c97791439..aab109ead 100644 --- a/src/General/UI.coffee +++ b/src/General/UI.coffee @@ -203,7 +203,6 @@ UI = do -> @parseEntry subEntry return - dragstart = (e) -> return if e.type is 'mousedown' and e.button isnt 0 # not LMB # prevent text selection @@ -245,11 +244,13 @@ UI = do -> o.up = dragend.bind o $.on d, 'mousemove', o.move $.on d, 'mouseup', o.up + touchmove = (e) -> for touch in e.changedTouches if touch.identifier is @identifier drag.call @, touch return + drag = (e) -> {clientX, clientY} = e @@ -284,11 +285,13 @@ UI = do -> style.right = right style.top = top style.bottom = bottom + touchend = (e) -> for touch in e.changedTouches if touch.identifier is @identifier dragend.call @ return + dragend = -> if @isTouching $.off d, 'touchmove', @move @@ -318,7 +321,9 @@ UI = do -> o.hover o.latestEvent if el.parentNode $.on root, endEvents, o.hoverend + $.on d, 'keydown', o.hoverend $.on root, 'mousemove', o.hover + hover = (e) -> @latestEvent = e height = @el.offsetHeight @@ -341,9 +346,12 @@ UI = do -> style.top = top + 'px' style.left = left style.right = right - hoverend = -> + + hoverend = (e) -> + return if e.type is 'keydown' and e.keyCode isnt 13 $.rm @el $.off @root, @endEvents, @hoverend + $.off d, 'keydown', @hoverend $.off @root, 'mousemove', @hover @cb.call @ if @cb diff --git a/src/General/html/Settings/Advanced.html b/src/General/html/Settings/Advanced.html index 0adb18834..0f41d75ed 100644 --- a/src/General/html/Settings/Advanced.html +++ b/src/General/html/Settings/Advanced.html @@ -31,7 +31,7 @@
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
-
Year: %y
+
Year: %y, %Y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
diff --git a/src/General/meta/banner.js b/src/General/meta/banner.js index ef02c4782..ec6d6aa2b 100644 --- a/src/General/meta/banner.js +++ b/src/General/meta/banner.js @@ -59,6 +59,7 @@ * WakiMiko * btmcsweeney * AppleBloom +* detharonil * * All the people who've taken the time to write bug reports. * diff --git a/src/Miscellaneous/Time.coffee b/src/Miscellaneous/Time.coffee index 78bc8d85e..c772792cc 100644 --- a/src/Miscellaneous/Time.coffee +++ b/src/Miscellaneous/Time.coffee @@ -56,4 +56,5 @@ Time = p: -> if @getHours() < 12 then 'AM' else 'PM' P: -> if @getHours() < 12 then 'am' else 'pm' S: -> Time.zeroPad @getSeconds() - y: -> @getFullYear() - 2000 \ No newline at end of file + y: -> @getFullYear() % 100 + Y: -> @getFullYear() \ No newline at end of file diff --git a/src/Posting/QuickReply.coffee b/src/Posting/QuickReply.coffee index b83ceda5e..490ffcd21 100644 --- a/src/Posting/QuickReply.coffee +++ b/src/Posting/QuickReply.coffee @@ -68,7 +68,6 @@ QR = else QR.status() - node: -> $.on $('a[title="Quote this post"]', @nodes.info), 'click', QR.quote @@ -172,6 +171,7 @@ QR = for type, arr of types QR.persona.loadPersonas type, arr return + parseItem: (item, types) -> return if item[0] is '#' return unless match = item.match /(name|email|subject|password):"(.*)"/i @@ -181,7 +181,7 @@ QR = item = item.replace match, '' boards = item.match(/boards:([^;]+)/i)?[1].toLowerCase() or 'global' - if boards isnt 'global' and not (g.BOARD.ID in boards.split ',') + if boards isnt 'global' and not ((boards.split ',').contains g.BOARD.ID) return if type is 'password' @@ -193,14 +193,16 @@ QR = if /always/i.test item QR.persona.always[type] = val - unless val in types[type] + unless types[type].contains val types[type].push val + loadPersonas: (type, arr) -> list = $ "#list-#{type}", QR.nodes.el for val in arr $.add list, $.el 'option', textContent: val return + getPassword: -> unless QR.persona.pwd QR.persona.pwd = if m = d.cookie.match /4chan_pass=([^;]+)/ @@ -213,9 +215,11 @@ QR = # we'd rather use #postPassword when we can. $.id('delPassword').value return QR.persona.pwd + get: (cb) -> $.get 'QR.persona', {}, ({'QR.persona': persona}) -> cb persona + set: (post) -> $.get 'QR.persona', {}, ({'QR.persona': persona}) -> persona = @@ -242,17 +246,20 @@ QR = QR.cooldown.cooldowns = item["cooldown.#{board}"] QR.cooldown.start() $.sync "cooldown.#{board}", QR.cooldown.sync + start: -> return unless Conf['Cooldown'] return if QR.cooldown.isCounting QR.cooldown.isCounting = true QR.cooldown.count() + sync: (cooldowns) -> # Add each cooldowns, don't overwrite everything in case we # still need to prune one in the current tab to auto-post. for id of cooldowns QR.cooldown.cooldowns[id] = cooldowns[id] QR.cooldown.start() + set: (data) -> return unless Conf['Cooldown'] {req, post, isReply, delay} = data @@ -282,12 +289,14 @@ QR = QR.cooldown.cooldowns[start] = cooldown $.set "cooldown.#{g.BOARD}", QR.cooldown.cooldowns QR.cooldown.start() + unset: (id) -> delete QR.cooldown.cooldowns[id] if Object.keys(QR.cooldown.cooldowns).length $.set "cooldown.#{g.BOARD}", QR.cooldown.cooldowns else $.delete "cooldown.#{g.BOARD}" + count: -> unless Object.keys(QR.cooldown.cooldowns).length $.delete "#{g.BOARD}.cooldown" @@ -393,9 +402,11 @@ QR = toggle = if e.type is 'dragstart' then $.off else $.on toggle d, 'dragover', QR.dragOver toggle d, 'drop', QR.dropFile + dragOver: (e) -> e.preventDefault() e.dataTransfer.dropEffect = 'copy' # cursor feedback + dropFile: (e) -> # Let it only handle files from the desktop. return unless e.dataTransfer.files.length @@ -403,6 +414,7 @@ QR = QR.open() QR.fileInput e.dataTransfer.files $.addClass QR.nodes.el, 'dump' + paste: (e) -> files = [] for item in e.clipboardData.items @@ -414,9 +426,11 @@ QR = return unless files.length QR.open() QR.fileInput files + openFileInput: (e) -> return if e.keyCode and e.keyCode isnt 32 QR.nodes.fileInput.click() + fileInput: (files) -> if @ instanceof Element #or files instanceof Event # file input files = [@files...] @@ -454,6 +468,7 @@ QR = $.addClass QR.nodes.el, 'dump' posts: [] + post: class constructor: (select) -> el = $.el 'a', @@ -521,6 +536,7 @@ QR = @load() if QR.selected is @ # load persona @select() if select @unlock() + rm: -> $.rm @nodes.el index = QR.posts.indexOf @ @@ -531,6 +547,7 @@ QR = QR.posts.splice index, 1 return unless window.URL URL.revokeObjectURL @URL + lock: (lock=true) -> @isLocked = lock return unless @ is QR.selected @@ -541,8 +558,10 @@ QR = (if lock then $.off else $.on) QR.nodes.filename.parentNode, 'click', QR.openFileInput @nodes.spoiler.disabled = lock @nodes.el.draggable = !lock + unlock: -> @lock false + select: -> if QR.selected QR.selected.nodes.el.id = null @@ -556,12 +575,14 @@ QR = @nodes.el.parentNode.scrollLeft += rectEl.left + rectEl.width/2 - rectList.left - rectList.width/2 @load() $.event 'QRPostSelection', @ + load: -> # Load this post's values. for name in ['thread', 'name', 'email', 'sub', 'com'] QR.nodes[name].value = @[name] or null @showFileData() QR.characterCount() + save: (input) -> if input.type is 'checkbox' @spoiler = input.checked @@ -575,6 +596,7 @@ QR = # during the last 5 seconds of the cooldown. if QR.cooldown.auto and @ is QR.posts[0] and 0 < QR.cooldown.seconds <= 5 QR.cooldown.auto = false + forceSave: -> return unless @ is QR.selected # Do this in case people use extensions @@ -582,6 +604,7 @@ QR = for name in ['thread', 'name', 'email', 'sub', 'com', 'spoiler'] @save QR.nodes[name] return + setFile: (@file) -> @filename = "#{file.name} (#{$.bytesToString file.size})" @nodes.el.title = @filename @@ -592,6 +615,7 @@ QR = @nodes.el.style.backgroundImage = null return @setThumbnail() + setThumbnail: (fileURL) -> # XXX Opera does not support blob URL # Create a redimensioned thumbnail. @@ -651,6 +675,7 @@ QR = applyBlob new Blob [ui8a], type: 'image/png' img.src = fileURL + rmFile: -> delete @file delete @filename @@ -660,6 +685,7 @@ QR = @showFileData() return unless window.URL URL.revokeObjectURL @URL + showFileData: -> if @file QR.nodes.filename.textContent = @filename @@ -668,6 +694,7 @@ QR = $.addClass QR.nodes.fileSubmit, 'has-file' else $.rmClass QR.nodes.fileSubmit, 'has-file' + pasteText: (file) -> reader = new FileReader() reader.onload = (e) => @@ -680,17 +707,23 @@ 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' + 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 @@ -708,6 +741,7 @@ QR = return if d.cookie.indexOf('pass_enabled=1') >= 0 return unless @isEnabled = !!$.id 'captchaFormPart' $.asap (-> $.id 'recaptcha_challenge_field_holder'), @ready.bind @ + ready: -> setLifetime = (e) => @lifetime = e.detail $.on window, 'captcha:timeout', setLifetime @@ -754,8 +788,10 @@ QR = $.addClass QR.nodes.el, 'has-captcha' $.after QR.nodes.com.parentNode, [imgContainer, input] + sync: (@captchas) -> QR.captcha.count() + getOne: -> @clear() if captcha = @captchas.shift() @@ -771,6 +807,7 @@ QR = # If there's only one word, duplicate it. response = "#{response} #{response}" unless /\s/.test response {challenge, response} + save: -> return unless response = @nodes.input.value.trim() @captchas.push @@ -780,6 +817,7 @@ QR = @count() @reload() $.set 'captchas', @captchas + clear: -> now = Date.now() for captcha, i in @captchas @@ -788,6 +826,7 @@ QR = @captchas = @captchas[i..] @count() $.set 'captchas', @captchas + load: -> return unless @nodes.challenge.firstChild # -1 minute to give upload some time. @@ -797,6 +836,7 @@ QR = @nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}" @nodes.input.value = null @clear() + count: -> count = @captchas.length @nodes.input.placeholder = switch count @@ -806,12 +846,14 @@ QR = 'Verification (1 cached captcha)' else "Verification (#{count} cached captchas)" - @nodes.input.alt = count # For XTRM RICE. + @nodes.input.alt = count + reload: (focus) -> # the 't' argument prevents the input from being focused $.globalEval 'Recaptcha.reload("t")' # Focus if we meant to. @nodes.input.focus() if focus + keydown: (e) -> if e.keyCode is 8 and not @nodes.input.value @reload() @@ -935,6 +977,7 @@ QR = $.event 'QRDialogCreation', null, dialog preSubmitHooks: [] + submit: (e) -> e?.preventDefault()