diff --git a/CHANGELOG.md b/CHANGELOG.md index f727a71bb..9a2f7b7d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,72 @@ +## 3.18.0 - *2014-02-15* + +- Added `Image Size` setting for the catalog. +- Added `Open threads in a new tab` setting for the catalog. +- Added `board-mode:"type"` and `board-sort:"type"` parameters to custom board navigation. +- Added OP name/date tooltip in the catalog. +- Added a keybind to cycle through index sort types, `Ctrl+x` by default. +- Added keybindings for index modes, `Ctrl+{1,2,3}` by default. + +### 3.17.1 - *2014-02-10* + +- `Index Mode` and `Index Sort` have been moved out of the header's menu into the index page. +- Minor captcha fixes. + +## 3.17.0 - *2014-02-09* + +- Fixed captcha loading in the QR. +- New setting: `Quote Markers`, enabled by default + - This merges `Mark Quotes of You`, `Mark OP Quotes` and `Mark Cross-thread Quotes` into one feature. + - Backlinks now also get these markers. + - Multiple markers are now more compact, for example `>>123 (You/OP)` instead of `>>123 (You) (OP)`. +- New setting: `Image Hover in Catalog` + - Like `Image Hover`, but for the catalog only. + +### 3.16.5 - *2014-02-07* + +- Added `Archive link` to the Custom Board Navigation Rice +- Added a setting to configure the number of threads per page for the paged mode of the index. +- Dropped support for the official catalog. + +### 3.16.4 - *2014-02-04* + +- Firefox release only: fix catalog layout alignment. + +### 3.16.3 - *2014-02-04* + +- Firefox release only: fix catalog layout. + +### 3.16.2 - *2014-02-04* + +- More index navigation improvements: + - Threads in catalog mode have the usual menu. + - When in catalog mode, the menu now also allows to pin/unpin threads. +- Minor bug fixes. + +### 3.16.1 - *2014-02-01* + +- More index navigation improvements: + - The index will now display how many threads are hidden. + - When in catalog mode, you can toggle between hidden/non-hidden threads. +- Fixed a bug which prevented QR cooldowns from being pruned from storage. + - On Chrome, the storage could reach the quota and prevent 4chan X from saving data like QR name/mail or auto-watch for example. + +## 3.16.0 - *2014-01-30* + +- More index navigation improvements: + - New index mode: `catalog`
+ ![catalog mode](img/changelog/3.16.0/0.png) + - When in catalog mode, use `Shift+Click` to hide, and `Alt+Click` to pin threads. + - Existing features affect the catalog mode such as: + + - Support for the official catalog will be removed in the future, once the catalog mode for the index is deemed satisfactory. +- Added `Original filename` variable to Sauce panel. - Added a `Reset Settings` button in the settings. ### 3.15.2 - *2014-01-22* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3478e5082..f57b3dad0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,14 +4,14 @@ Reporting bugs: 1. Make sure both your **browser** and **4chan X** are up to date.
Only **Chrome**, **Firefox** and **Opera** are supported.
- **SRWare Iron**, **Firefox ESR**, **Pale Moon**, **Waterfox**, and other derivatives are not supported, use them at your own risk. + **SRWare Iron**, **Firefox ESR**, **Pale Moon**, **Waterfox**, and other derivatives are not supported; use them at your own risk. 2. Look at the list of [known problems and solutions](https://github.com/MayhemYDG/4chan-x/wiki/FAQ#known-problems). 3. Disable your other extensions & scripts to identify conflicts. 4. If your issue persists, open a [new issue](https://github.com/MayhemYDG/4chan-x/issues) with the following information: 1. Precise steps to reproduce the problem, with the expected and actual results. 2. [Console errors](https://github.com/MayhemYDG/4chan-x/wiki/FAQ#console-errors), if any. 3. 4chan X version, browser variant, browser version, and Greasemonkey version if you are using it. - 4. Your exported settings. If your settings contains sensible information (e.g. personas), edit the text file manually. + 4. Your exported settings. If your settings contain sensitive information (e.g. personas), edit the text file manually. 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.coffee b/Gruntfile.coffee index c97e8e155..64a932158 100644 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -40,6 +40,7 @@ module.exports = (grunt) -> # <--| 'src/General/Board.coffee' 'src/General/Thread.coffee' + 'src/General/CatalogThread.coffee' 'src/General/Post.coffee' 'src/General/Clone.coffee' 'src/General/DataBoard.coffee' diff --git a/css/burichan.css b/css/burichan.css index ac1dc8ba7..6d3958aea 100644 --- a/css/burichan.css +++ b/css/burichan.css @@ -56,3 +56,6 @@ :root.burichan .focused.entry { background: rgba(255, 255, 255, .33); } +:root.burichan .thumb > .menu-button > i { + background: #EEF2FF; +} diff --git a/css/futaba.css b/css/futaba.css index 0f21afb04..9eccd158a 100644 --- a/css/futaba.css +++ b/css/futaba.css @@ -56,3 +56,6 @@ :root.futaba .focused.entry { background: rgba(255, 255, 255, .33); } +:root.futaba .thumb > .menu-button > i { + background: #FFE; +} diff --git a/css/photon.css b/css/photon.css index 2580b0cf2..1f1be5401 100644 --- a/css/photon.css +++ b/css/photon.css @@ -56,3 +56,6 @@ :root.photon .focused.entry { background: rgba(255, 255, 255, .33); } +:root.photon .thumb > .menu-button > i { + background: #EEE; +} diff --git a/css/style.css b/css/style.css index f697b45e2..475ae1166 100644 --- a/css/style.css +++ b/css/style.css @@ -288,16 +288,8 @@ a[href="javascript:;"] { .tab-selected { font-weight: 700; } -.section-container { +#fourchanx-settings > section { flex: 1; - position: relative; -} -.section-container > section { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; overflow: auto; } .section-sauce ul, @@ -375,9 +367,15 @@ a[href="javascript:;"] { /* Index */ :root.index-loading .navLinks, :root.index-loading .board, -:root.index-loading .pagelist { +:root.index-loading .pagelist, +:root:not(.catalog-mode) #hidden-toggle, +:root:not(.catalog-mode) #index-size { display: none; } +#nav-links { + display: flex; + align-items: baseline; +} #index-search { padding-right: 1.5em; width: 100px; @@ -389,7 +387,9 @@ a[href="javascript:;"] { } #index-search-clear { color: gray; - margin-left: -1.25em; + position: relative; + left: -1.25em; + width: 0; } <% if (type === 'crx') { %> /* ``::-webkit-*'' selectors break selector lists on Firefox. */ @@ -401,6 +401,110 @@ a[href="javascript:;"] { .summary { text-decoration: none; } +.catalog-mode .board { + text-align: center; +} +.catalog-thread { + display: inline-flex; + text-align: left; + flex-direction: column; + align-items: center; + margin: 0 2px 5px; + word-break: break-word; + vertical-align: top; +} +.catalog-small .catalog-thread { + width: 165px; + max-height: 320px; +} +.catalog-large .catalog-thread { + width: 270px; + max-height: 410px; +} +.thumb { + flex-shrink: 0; + position: relative; + background-size: 100% 100%; + background-repeat: no-repeat; + background-position: center; + border-radius: 2px; + box-shadow: 0 0 5px rgba(0, 0, 0, .25); +} +.thumb:not(.deleted-file):not(.no-file) { + min-width: 30px; + min-height: 30px; +} +.thumb.spoiler-file { + background-size: 100px; + width: 100px; + height: 100px; +} +.thumb.deleted-file { + background-size: 127px 13px; + width: 127px; + height: 13px; + padding: 20px 11px; +} +.thumb.no-file { + background-size: 77px 13px; + width: 77px; + height: 13px; + padding: 20px 36px; +} +.thread-icons > img { + width: 1em; + height: 1em; + margin: 0; + vertical-align: text-top; +} +.thumb:not(:hover):not(:focus) > .menu-button:not(.open):not(:focus) > i { + display: none; +} +.thumb > .menu-button { + position: absolute; + top: 0; + right: 0; +} +.thumb > .menu-button > i { + width: 1em; + height: 1em; + padding: 1px; + border-radius: 0 2px 0 2px; + font-size: 14px; + text-align: center; +<% if (type === 'userscript') { %> + line-height: normal; +<% } %> +} +.thread-stats { + flex-shrink: 0; + cursor: help; + font-size: 10px; + font-weight: 700; + margin-top: 2px; +} +.catalog-thread > .subject { + flex-shrink: 0; + font-weight: 700; + line-height: 1; + text-align: center; +} +.catalog-thread > .comment { + flex-shrink: 1; + align-self: stretch; + overflow: hidden; + text-align: center; +} +.thread-info { + position: fixed; + background-color: inherit; + padding: 2px; + border-radius: 2px; + box-shadow: 0 0 5px rgba(0, 0, 0, .25); +} +.thread-info .post { + margin: 0; +} /* Announcement Hiding */ :root.hide-announcement #globalMessage, @@ -428,7 +532,7 @@ a.hide-announcement { #updater > div:last-child { text-align: center; } -#updater input[type=number] { +#updater input[type="number"] { width: 4em; } #updater:not(:hover) > div:not(.move) { @@ -597,6 +701,10 @@ a.hide-announcement { .filter-highlight > .reply { box-shadow: -5px 0 rgba(255, 0, 0, .5); } +.pinned .thumb, +.filter-highlight .thumb { + border: 2px solid rgba(255, 0, 0, .5); +} /* Thread & Reply Hiding */ .hide-thread-button, @@ -655,6 +763,7 @@ a.hide-announcement { } .persona .field { flex: 1; + width: 0; } .persona .field:hover, .persona .field:focus { @@ -679,28 +788,24 @@ a.hide-announcement { :root.gecko #dump-button { padding: 0; } -#qr:not(.dump) #dump-list-container { +#qr:not(.dump) #dump-list, +#qr:not(.dump) #add-post { display: none; } -#dump-list-container { - height: 100px; +#dump-list { + counter-reset: qrpreviews; + width: 0; + min-width: 100%; + overflow: hidden; + white-space: nowrap; position: relative; -webkit-user-select: none; -moz-user-select: none; user-select: none; } -#dump-list { - counter-reset: qrpreviews; - top: 0; - right: 0; - bottom: 0; - left: 0; - overflow: hidden; - position: absolute; - white-space: nowrap; -} #dump-list:hover { - bottom: -12px; + padding-bottom: 12px; + margin-bottom: -12px; overflow-x: auto; z-index: 1; } @@ -777,13 +882,11 @@ a.remove { vertical-align: bottom; } #add-post { + align-self: flex-end; font-size: 20px; - height: 20px; - width: 20px; + width: 1em; + margin-top: -1em; text-align: center; - position: absolute; - right: 0; - bottom: 0; z-index: 1; } #qr textarea { @@ -819,23 +922,11 @@ a.remove { height: 57px; width: 300px; } -#file-n-submit-container { - position: relative; -} #file-n-submit { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; display: flex; align-items: center; } -#file-n-submit-container input[type='file'] { - /* Keep it to set an appropriate height to the container. */ - visibility: hidden; -} -#file-n-submit-container input { +#file-n-submit input { margin: 0; } #file-n-submit input[type='submit'] { @@ -852,8 +943,9 @@ a.remove { #qr-no-file, #qr-filename, #qr-filesize, +#qr-filerm, #qr-file-spoiler { - margin: 0 2px !important; + margin: 0 1px !important; } #qr-no-file { cursor: default; @@ -864,17 +956,19 @@ a.remove { -moz-appearance: none; appearance: none; background: none; - border: none !important; + border: none; color: inherit; font: inherit; flex: 1; + width: 0; + padding: 0; text-overflow: ellipsis; } #qr-filesize { font-size: .8em; } #qr-filesize::before { - content: " ("; + content: "("; } #qr-filesize::after { content: ")"; @@ -884,14 +978,6 @@ a.remove { .menu-button { position: relative; } -.menu-button i:not(.fa-bars) { - border-top: 6px solid; - border-right: 4px solid transparent; - border-left: 4px solid transparent; - display: inline-block; - margin: 2px; - vertical-align: middle; -} @media screen and (resolution: 1dppx) { .fa-bars { font-size: 14px; @@ -970,8 +1056,10 @@ a.remove { margin: 0; } -/* colored uid */ - +/* Other */ +.linkified { + word-break: break-all; +} .posteruid.painted { padding: 0 5px; border-radius: 1em; diff --git a/css/tomorrow.css b/css/tomorrow.css index e14ed417a..453c18589 100644 --- a/css/tomorrow.css +++ b/css/tomorrow.css @@ -56,3 +56,6 @@ :root.tomorrow .focused.entry { background: rgba(0, 0, 0, .33); } +:root.tomorrow .thumb > .menu-button > i { + background: #1D1F21; +} diff --git a/css/yotsuba-b.css b/css/yotsuba-b.css index a39555c47..8dadd440a 100644 --- a/css/yotsuba-b.css +++ b/css/yotsuba-b.css @@ -56,3 +56,6 @@ :root.yotsuba-b .focused.entry { background: rgba(255, 255, 255, .33); } +:root.yotsuba-b .thumb > .menu-button > i { + background: #EEF2FF; +} diff --git a/css/yotsuba.css b/css/yotsuba.css index 5bb2e187b..80731f3f1 100644 --- a/css/yotsuba.css +++ b/css/yotsuba.css @@ -56,3 +56,6 @@ :root.yotsuba .focused.entry { background: rgba(255, 255, 255, .33); } +:root.yotsuba .thumb > .menu-button > i { + background: #FFE; +} diff --git a/html/General/Index-navlinks.html b/html/General/Index-navlinks.html index 7caf98685..7ce89c93c 100644 --- a/html/General/Index-navlinks.html +++ b/html/General/Index-navlinks.html @@ -1,4 +1,25 @@ -[Catalog]  -[ +  + + + + + + diff --git a/html/General/Index-pagelist.html b/html/General/Index-pagelist.html index fd5f4020b..a025c7caf 100644 --- a/html/General/Index-pagelist.html +++ b/html/General/Index-pagelist.html @@ -10,5 +10,5 @@ diff --git a/html/General/Settings-section-Rice.html b/html/General/Settings-section-Rice.html index 65ca4ffd7..64535d93f 100644 --- a/html/General/Settings-section-Rice.html +++ b/html/General/Settings-section-Rice.html @@ -3,13 +3,14 @@
In the following, board can translate to a board ID (a, b, etc...), the current board (current), or the Twitter link (@).
Board link: board
+
Archive link: board-archive
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"
+
Index mode: board-mode:"type" where type is paged, all threads or catalog
+
Index sort: board-sort:"type" where type is bump order, last reply, creation date, reply count or file count
+
Combinations are possible: board-text:"VIP Catalog"-mode:"catalog"-sort:"creation date"
Full board list toggle: toggle-all
@@ -32,7 +33,7 @@
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
diff --git a/html/General/Settings-section-Sauce.html b/html/General/Settings-section-Sauce.html index b31017946..c630955c4 100644 --- a/html/General/Settings-section-Sauce.html +++ b/html/General/Settings-section-Sauce.html @@ -5,6 +5,7 @@
  • %TURL: Thumbnail URL.
  • %URL: Full image URL.
  • %MD5: MD5 hash.
  • +
  • %name: Original file name.
  • %board: Current board.
  • diff --git a/html/General/Settings.html b/html/General/Settings.html index 4af4c7feb..f4de786a9 100644 --- a/html/General/Settings.html +++ b/html/General/Settings.html @@ -12,7 +12,5 @@
    -
    -
    -
    +
    diff --git a/html/General/Thread-catalog-view.html b/html/General/Thread-catalog-view.html new file mode 100644 index 000000000..798de0cb4 --- /dev/null +++ b/html/General/Thread-catalog-view.html @@ -0,0 +1,7 @@ + +
    + #{postCount} / #{fileCount} / #{pageCount} + +
    +#{subject} +
    #{comment}
    diff --git a/html/Posting/QR.html b/html/Posting/QR.html index 6593cb38b..3b683ef2c 100644 --- a/html/Posting/QR.html +++ b/html/Posting/QR.html @@ -9,29 +9,25 @@
    - - - -
    -
    -
    - + + +
    +
    +
    -
    - -
    - - - No selected file - - - - -
    +
    + + + + No selected file + + + +
    diff --git a/img/changelog/3.16.0/0.png b/img/changelog/3.16.0/0.png new file mode 100644 index 000000000..c7fe125f4 Binary files /dev/null and b/img/changelog/3.16.0/0.png differ diff --git a/json/archives.json b/json/archives.json index a19808354..0a8e29d4e 100644 --- a/json/archives.json +++ b/json/archives.json @@ -5,8 +5,8 @@ "http": true, "https": true, "software": "foolfuuka", - "boards": ["a", "co", "gd", "jp", "m", "sp", "tg", "tv", "v", "vg", "vp", "vr", "wsg"], - "files": ["a", "gd", "jp", "m", "tg", "vg", "vp", "vr", "wsg"] + "boards": ["a", "biz", "co", "gd", "jp", "m", "sp", "tg", "tv", "v", "vg", "vp", "vr", "wsg"], + "files": ["a", "biz", "gd", "jp", "m", "tg", "vg", "vp", "vr", "wsg"] }, { "uid": 1, "name": "NSFW Foolz", @@ -86,8 +86,8 @@ "http": false, "https": true, "software": "fuuka", - "boards": ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"], - "files": ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"] + "boards": ["3", "biz", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"], + "files": ["3", "biz", "cgl", "ck", "fa", "ic", "jp", "lit", "tg", "vr"] }, { "uid": 15, "name": "fgts", @@ -95,8 +95,8 @@ "http": true, "https": true, "software": "foolfuuka", - "boards": ["soc"], - "files": ["soc"] + "boards": ["r", "soc"], + "files": ["r", "soc"] }, { "uid": 16, "name": "maware", @@ -123,6 +123,6 @@ "https": true, "withCredentials": true, "software": "foolfuuka", - "boards": ["a", "co", "gd", "jp", "m", "s4s", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], - "files": ["a", "gd", "jp", "m", "s4s", "tg", "u", "vg", "vp", "vr", "wsg"] + "boards": ["a", "biz", "co", "d", "gd", "jp", "m", "mlp", "s4s", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], + "files": ["a", "biz", "d", "gd", "jp", "m", "s4s", "tg", "u", "vg", "vp", "vr", "wsg"] }] diff --git a/lib/$.coffee b/lib/$.coffee index c50412cbf..939c1577e 100644 --- a/lib/$.coffee +++ b/lib/$.coffee @@ -99,17 +99,11 @@ $.rmClass = (el, className...) -> el.classList.remove className... $.hasClass = (el, className) -> el.classList.contains className -$.rm = do -> - if 'remove' of Element.prototype - (el) -> el.remove() - else - (el) -> el.parentNode?.removeChild el +$.rm = (el) -> + el.remove() $.rmAll = (root) -> - # jsperf.com/emptify-element - for node in [root.childNodes...] - # HTMLSelectElement.remove !== Element.remove - root.removeChild node - return + # https://gist.github.com/MayhemYDG/8646194 + root.textContent = null $.tn = (s) -> d.createTextNode s $.nodes = (nodes) -> @@ -234,81 +228,91 @@ $.localKeys = [ 'usercss' ] # https://developer.chrome.com/extensions/storage.html -$.delete = (keys) -> - chrome.storage.sync.remove keys -$.get = (key, val, cb) -> - if typeof cb is 'function' - items = $.item key, val - else - items = key - cb = val - - localItems = null - syncItems = null - for key, val of items - if key in $.localKeys - (localItems or= {})[key] = val - else - (syncItems or= {})[key] = val - - count = 0 - done = (item) -> - if chrome.runtime.lastError - c.error chrome.runtime.lastError.message - $.extend items, item - cb items unless --count - - if localItems - count++ - chrome.storage.local.get localItems, done - if syncItems - count++ - chrome.storage.sync.get syncItems, done -$.set = do -> +do -> items = - sync: {} local: {} - timeout = {} + sync: {} + $.delete = (keys) -> + if typeof keys is 'string' + keys = [keys] + for key in keys + delete items.local[key] + delete items.sync[key] + chrome.storage.sync.remove keys + + $.get = (key, val, cb) -> + if typeof cb is 'function' + data = $.item key, val + else + data = key + cb = val + + localItems = null + syncItems = null + for key, val of data + if key in $.localKeys + (localItems or= {})[key] = val + else + (syncItems or= {})[key] = val + + count = 0 + done = (result) -> + if chrome.runtime.lastError + c.error chrome.runtime.lastError.message + $.extend data, result + cb data unless --count + + if localItems + count++ + chrome.storage.local.get localItems, done + if syncItems + count++ + chrome.storage.sync.get syncItems, done + + timeout = {} setArea = (area) -> data = items[area] - return if !Object.keys(data).length or timeout[area] - items[area] = {} + return if !Object.keys(data).length or timeout[area] > Date.now() chrome.storage[area].set data, -> if chrome.runtime.lastError c.error chrome.runtime.lastError.message for key, val of data when key not of items[area] + if area is 'sync' and chrome.storage.sync.QUOTA_BYTES_PER_ITEM < JSON.stringify(val).length + key.length + c.error chrome.runtime.lastError.message, key, val + continue items[area][key] = val - timeout[area] = setTimeout setArea, $.MINUTE, area + setTimeout setArea, $.MINUTE, area + timeout[area] = Date.now() + $.MINUTE return delete timeout[area] + items[area] = {} - setAll = $.debounce $.SECOND, -> - for key in $.localKeys - if key of items.sync - items.local[key] = items.sync[key] - delete items.sync[key] - try - setArea 'local' - setArea 'sync' - catch err - c.error err.stack + setSync = $.debounce $.SECOND, -> + setArea 'sync' - (key, val) -> + $.set = (key, val) -> if typeof key is 'string' items.sync[key] = val else $.extend items.sync, key - setAll() -$.clear = (cb) -> - count = 2 - done = -> - if chrome.runtime.lastError - c.error chrome.runtime.lastError.message - return - cb?() unless --count - chrome.storage.local.clear done - chrome.storage.sync.clear done + for key in $.localKeys when key of items.sync + items.local[key] = items.sync[key] + delete items.sync[key] + setArea 'local' + setSync() + + $.clear = (cb) -> + items.local = {} + items.sync = {} + count = 2 + done = -> + if chrome.runtime.lastError + c.error chrome.runtime.lastError.message + return + cb?() unless --count + chrome.storage.local.clear done + chrome.storage.sync.clear done <% } else { %> # http://wiki.greasespot.net/Main_Page $.sync = do -> diff --git a/package.json b/package.json index 6babef4e9..737696770 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "4chan-X", - "version": "3.15.2", + "version": "3.18.0", "description": "Cross-browser extension for productive lurking on 4chan.", "meta": { "name": "4chan X", @@ -26,13 +26,13 @@ "grunt-bump": "~0.0.13", "grunt-concurrent": "~0.4.3", "grunt-contrib-clean": "~0.5.0", - "grunt-contrib-coffee": "~0.8.2", + "grunt-contrib-coffee": "~0.10.0", "grunt-contrib-compress": "~0.6.0", "grunt-contrib-concat": "~0.3.0", "grunt-contrib-copy": "~0.5.0", "grunt-contrib-watch": "~0.5.3", "grunt-shell": "~0.6.4", - "load-grunt-tasks": "~0.2.1" + "load-grunt-tasks": "~0.3.0" }, "repository": { "type": "git", diff --git a/src/Archive/Redirect.coffee b/src/Archive/Redirect.coffee index 67cf6d575..25a773fc8 100644 --- a/src/Archive/Redirect.coffee +++ b/src/Archive/Redirect.coffee @@ -45,7 +45,7 @@ Redirect = cb?() to: (dest, data) -> - archive = (if dest is 'search' then Redirect.data.thread else Redirect.data[dest])[data.boardID] + archive = (if dest in ['search', 'board'] then Redirect.data.thread else Redirect.data[dest])[data.boardID] return '' unless archive Redirect[dest] archive, data @@ -80,6 +80,9 @@ Redirect = file: (archive, {boardID, filename}) -> "#{Redirect.protocol archive}#{archive.domain}/#{boardID}/full_image/#{filename}" + board: (archive, {boardID}) -> + "#{Redirect.protocol archive}#{archive.domain}/#{boardID}/" + search: (archive, {boardID, type, value}) -> type = if type is 'name' 'username' diff --git a/src/Filtering/Filter.coffee b/src/Filtering/Filter.coffee index eee0dfae9..62abf61c3 100644 --- a/src/Filtering/Filter.coffee +++ b/src/Filtering/Filter.coffee @@ -1,7 +1,7 @@ Filter = filters: {} init: -> - return if g.VIEW is 'catalog' or !Conf['Filter'] + return if !Conf['Filter'] for key of Config.filter @filters[key] = [] @@ -110,6 +110,8 @@ Filter = # Highlight $.addClass @nodes.root, result.class + unless @highlights and result.class in @highlights + (@highlights or= []).push result.class if !@isReply and result.top @thread.isOnTop = true @@ -164,7 +166,7 @@ Filter = menu: init: -> - return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Filter'] + return if !Conf['Menu'] or !Conf['Filter'] div = $.el 'div', textContent: 'Filter' diff --git a/src/Filtering/PostHiding.coffee b/src/Filtering/PostHiding.coffee index 122a7c8c4..ed621d194 100644 --- a/src/Filtering/PostHiding.coffee +++ b/src/Filtering/PostHiding.coffee @@ -1,6 +1,6 @@ PostHiding = init: -> - return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Reply Hiding Link'] + return if !Conf['Reply Hiding'] and !Conf['Reply Hiding Link'] @db = new DataBoard 'hiddenPosts' Post.callbacks.push @@ -20,7 +20,7 @@ PostHiding = menu: init: -> - return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding Link'] + return if !Conf['Menu'] or !Conf['Reply Hiding Link'] # Hide div = $.el 'div', diff --git a/src/Filtering/Recursive.coffee b/src/Filtering/Recursive.coffee index da5496ceb..51d7a406b 100644 --- a/src/Filtering/Recursive.coffee +++ b/src/Filtering/Recursive.coffee @@ -1,8 +1,6 @@ Recursive = recursives: {} init: -> - return if g.VIEW is 'catalog' - Post.callbacks.push name: 'Recursive' cb: @node diff --git a/src/Filtering/ThreadHiding.coffee b/src/Filtering/ThreadHiding.coffee index 6d5635201..b1daef857 100644 --- a/src/Filtering/ThreadHiding.coffee +++ b/src/Filtering/ThreadHiding.coffee @@ -1,10 +1,9 @@ ThreadHiding = init: -> - return if g.VIEW isnt 'index' or !Conf['Thread Hiding'] and !Conf['Thread Hiding Link'] + return if g.VIEW isnt 'index' @db = new DataBoard 'hiddenThreads' - @syncCatalog() - $.on d, 'IndexBuild', @onIndexBuild + $.on d, 'IndexRefresh', @onIndexRefresh Thread.callbacks.push name: 'Thread Hiding' cb: @node @@ -15,61 +14,17 @@ ThreadHiding = return unless Conf['Thread Hiding'] $.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide' - onIndexBuild: ({detail: nodes}) -> - for root, i in nodes by 2 + onIndexRefresh: -> + for root, i in Index.nodes by 2 thread = Get.threadFromRoot root continue unless thread.isHidden unless thread.stub - nodes[i + 1].hidden = true + Index.nodes[i + 1].hidden = true else unless root.contains thread.stub # When we come back to a page, the stub is already there. ThreadHiding.makeStub thread, root return - syncCatalog: -> - # Sync hidden threads from the catalog into the index. - hiddenThreads = ThreadHiding.db.get - boardID: g.BOARD.ID - defaultValue: {} - hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {} - - # Add threads that were hidden in the catalog. - for threadID of hiddenThreadsOnCatalog - unless threadID of hiddenThreads - hiddenThreads[threadID] = {} - - # Remove threads that were un-hidden in the catalog. - for threadID of hiddenThreads - unless threadID of hiddenThreadsOnCatalog - delete hiddenThreads[threadID] - - if (ThreadHiding.db.data.lastChecked or 0) > Date.now() - $.MINUTE - # Was cleaned just now. - ThreadHiding.cleanCatalog hiddenThreadsOnCatalog - - unless Object.keys(hiddenThreads).length - ThreadHiding.db.delete boardID: g.BOARD.ID - return - ThreadHiding.db.set - boardID: g.BOARD.ID - val: hiddenThreads - - cleanCatalog: (hiddenThreadsOnCatalog) -> - # We need to clean hidden threads on the catalog ourselves, - # otherwise if we don't visit the catalog regularly - # it will pollute the localStorage and our data. - $.cache "//a.4cdn.org/#{g.BOARD}/threads.json", -> - return unless @status is 200 - threads = {} - for page in @response - for thread in page.threads - if thread.no of hiddenThreadsOnCatalog - threads[thread.no] = hiddenThreadsOnCatalog[thread.no] - if Object.keys(threads).length - localStorage.setItem "4chan-hide-t-#{g.BOARD}", JSON.stringify threads - else - localStorage.removeItem "4chan-hide-t-#{g.BOARD}" - menu: init: -> return if g.VIEW isnt 'index' or !Conf['Menu'] or !Conf['Thread Hiding Link'] @@ -91,7 +46,7 @@ ThreadHiding = el: div order: 20 open: ({thread, isReply}) -> - if isReply or thread.isHidden + if isReply or thread.isHidden or Conf['Index Mode'] is 'catalog' return false ThreadHiding.menu.thread = thread true @@ -130,19 +85,15 @@ ThreadHiding = $.prepend root, thread.stub saveHiddenState: (thread, makeStub) -> - hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {} if thread.isHidden ThreadHiding.db.set boardID: thread.board.ID threadID: thread.ID val: {makeStub} - hiddenThreadsOnCatalog[thread] = true else ThreadHiding.db.delete boardID: thread.board.ID threadID: thread.ID - delete hiddenThreadsOnCatalog[thread] - localStorage.setItem "4chan-hide-t-#{g.BOARD}", JSON.stringify hiddenThreadsOnCatalog toggle: (thread) -> unless thread instanceof Thread @@ -157,6 +108,7 @@ ThreadHiding = return if thread.isHidden threadRoot = thread.OP.nodes.root.parentNode thread.isHidden = true + Index.updateHideLabel() unless makeStub threadRoot.hidden = threadRoot.nextElementSibling.hidden = true #
    @@ -171,3 +123,4 @@ ThreadHiding = threadRoot = thread.OP.nodes.root.parentNode threadRoot.nextElementSibling.hidden = threadRoot.hidden = thread.isHidden = false + Index.updateHideLabel() diff --git a/src/General/Build.coffee b/src/General/Build.coffee index 6fa1278b8..5cb9d1dca 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -110,18 +110,18 @@ Build = flag = unless flagCode '' else if boardID is 'pol' - " #{flagCode}" + " " else " " if file?.isDeleted fileHTML = if isOP "
    " + - "File deleted." + + "" + "
    " else "
    " + - "File deleted." + + "" + "
    " else if file fileSize = $.bytesToString file.size @@ -167,16 +167,16 @@ Build = fileHTML = '' sticky = if isSticky - " Sticky" + " " else '' closed = if isClosed - " Closed" + " " else '' if isOP and g.VIEW is 'index' - pageNum = Math.floor Index.liveThreadIDs.indexOf(postID) / Index.threadsNumPerPage + pageNum = Index.liveThreadIDs.indexOf(postID) // Index.threadsNumPerPage pageIcon = " Page #{pageNum}" replyLink = "   [Reply]" else @@ -254,9 +254,74 @@ Build = [posts, files] = if Conf['Show Replies'] [data.omitted_posts, data.omitted_images] else - # XXX data.images is not accurate. - [data.replies, data.omitted_images + data.last_replies.filter((data) -> !!data.ext).length] + [data.replies, data.images] nodes.push Build.summary board.ID, data.no, posts, files $.add root, nodes root + catalogThread: (thread) -> + {staticPath, gifIcon} = Build + data = Index.liveThreadData[Index.liveThreadIDs.indexOf thread.ID] + + postCount = data.replies + 1 + fileCount = data.images + !!data.ext + pageCount = Index.liveThreadIDs.indexOf(thread.ID) // Index.threadsNumPerPage + + subject = if thread.OP.info.subject + "
    #{thread.OP.info.subject}
    " + else + '' + comment = thread.OP.nodes.comment.innerHTML.replace /(
    \s*){2,}/g, '
    ' + + root = $.el 'div', + className: 'catalog-thread' + innerHTML: <%= importHTML('General/Thread-catalog-view') %> + + root.dataset.fullID = thread.fullID + $.addClass root, 'pinned' if thread.isPinned + $.addClass root, thread.OP.highlights... if thread.OP.highlights + + thumb = root.firstElementChild + if data.spoiler and !Conf['Reveal Spoilers'] + src = "#{staticPath}spoiler" + if spoilerRange = Build.spoilerRange[thread.board] + # Randomize the spoiler image. + src += "-#{thread.board}" + Math.floor 1 + spoilerRange * Math.random() + src += '.png' + $.addClass thumb, 'spoiler-file' + else if data.filedeleted + src = "#{staticPath}filedeleted-res#{gifIcon}" + $.addClass thumb, 'deleted-file' + else if thread.OP.file + src = thread.OP.file.thumbURL + thumb.dataset.width = data.tn_w + thumb.dataset.height = data.tn_h + else + src = "#{staticPath}nofile.png" + $.addClass thumb, 'no-file' + thumb.style.backgroundImage = "url(#{src})" + if Conf['Open threads in a new tab'] + thumb.target = '_blank' + + for quotelink in $$ '.quotelink', root.lastElementChild + $.replace quotelink, [quotelink.childNodes...] + for pp in $$ '.prettyprint', root.lastElementChild + $.replace pp, $.tn pp.textContent + + if thread.isSticky + $.add $('.thread-icons', root), $.el 'img', + src: "#{staticPath}sticky#{gifIcon}" + className: 'stickyIcon' + title: 'Sticky' + if thread.isClosed + $.add $('.thread-icons', root), $.el 'img', + src: "#{staticPath}closed#{gifIcon}" + className: 'closedIcon' + title: 'Closed' + + if data.bumplimit + $.addClass $('.post-count', root), 'warning' + if data.imagelimit + $.addClass $('.file-count', root), 'warning' + + root diff --git a/src/General/CatalogThread.coffee b/src/General/CatalogThread.coffee new file mode 100644 index 000000000..a7bebe7bc --- /dev/null +++ b/src/General/CatalogThread.coffee @@ -0,0 +1,15 @@ +class CatalogThread + @callbacks = [] + toString: -> @ID + + constructor: (root, @thread) -> + @ID = @thread.ID + @board = @thread.board + @nodes = + root: root + thumb: $ '.thumb', root + icons: $ '.thread-icons', root + postCount: $ '.post-count', root + fileCount: $ '.file-count', root + pageCount: $ '.page-count', root + @thread.catalogView = @ diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 14b665337..426c17b37 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -6,6 +6,7 @@ Config = 'Announcement Hiding': [true, 'Add button to hide 4chan announcements.'] '404 Redirect': [true, 'Redirect dead threads and images.'] 'Keybinds': [true, 'Bind actions to keyboard shortcuts.'] + 'Linkify': [true, 'Convert text links into hyperlinks.'] 'Time Formatting': [true, 'Localize and format timestamps.'] 'Relative Post Dates': [false, 'Display dates like "3 minutes ago". Tooltip shows the timestamp.'] 'File Info Formatting': [true, 'Reformat the file information.'] @@ -24,11 +25,9 @@ Config = 'Auto-GIF': [false, 'Animate GIF thumbnails (disabled on /gif/, /wsg/).'] 'Image Expansion': [true, 'Expand images inline.'] 'Image Hover': [false, 'Show a floating expanded image on hover.'] + 'Image Hover in Catalog': [false, 'Show a floating expanded image on hover in the catalog.'] 'Sauce': [true, 'Add sauce links to images.'] 'Reveal Spoilers': [false, 'Reveal spoiler thumbnails.'] - 'Linkification': - 'Linkify': [true, 'Convert text links into hyperlinks.'] - 'Clean Links': [true, 'Remove spoiler and code tags commonly used to bypass blocked links.'] 'Menu': 'Menu': [true, 'Add a drop-down menu to posts.'] 'Report Link': [true, 'Add a report link to the menu.'] @@ -62,7 +61,6 @@ Config = 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.'] 'Hide Original Post Form': [true, 'Hide the normal post form.'] 'Cooldown': [true, 'Indicate the remaining time before posting again.'] - 'Cooldown Prediction': [true, 'Decrease the cooldown time by taking into account upload speed. Disable it if it\'s inaccurate for you.'] <% if (type === 'crx') { %> 'Tab to Choose Files First': [false, 'Tab to the file input before the submit button.'] <% } %> @@ -74,9 +72,7 @@ Config = 'Quote Previewing': [true, 'Show quoted post on hover.'] 'Quote Highlighting': [true, 'Highlight the previewed post.'] 'Resurrect Quotes': [true, 'Link dead quotes to the archives.'] - 'Mark Quotes of You': [true, 'Add \'(You)\' to quotes linking to your posts.'] - 'Mark OP Quotes': [true, 'Add \'(OP)\' to OP quotes.'] - 'Mark Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes.'] + 'Quote Markers': [true, 'Add "(You)", "(OP)", "(Cross-thread)", "(Dead)" markers to quote links.'] imageExpansion: 'Fit width': [true, ''] 'Fit height': [false, ''] @@ -141,7 +137,11 @@ Config = 'Custom CSS': false Index: 'Index Mode': 'paged' + 'Previous Index Mode': 'paged' 'Index Sort': 'bump' + 'Index Size': 'small' + 'Threads per Page': 0 + 'Open threads in a new tab': false 'Show Replies': true 'Anchor Hidden Threads': true 'Refreshed Navigation': false @@ -149,7 +149,6 @@ Config = 'Header auto-hide': false 'Header auto-hide on scroll': false 'Bottom header': false - 'Header catalog links': false 'Top Board List': false 'Bottom Board List': false 'Custom Board Navigation': true @@ -188,6 +187,10 @@ Config = 'Next page': ['Right', 'Jump to the next page.'] 'Previous page': ['Left', 'Jump to the previous page.'] 'Search form': ['Ctrl+Alt+s', 'Focus the search field on the board index.'] + 'Paged mode': ['Ctrl+1', 'Sets the index mode to paged.'] + 'All pages mode': ['Ctrl+2', 'Sets the index mode to all threads.'] + 'Catalog mode': ['Ctrl+3', 'Sets the index mode to catalog.'] + 'Cycle sort type': ['Ctrl+x', 'Cycle through index sort types.'] # Thread Navigation 'Next thread': ['Down', 'See next thread.'] 'Previous thread': ['Up', 'See previous thread.'] diff --git a/src/General/DataBoard.coffee b/src/General/DataBoard.coffee index aba14d68d..d82ff1b65 100644 --- a/src/General/DataBoard.coffee +++ b/src/General/DataBoard.coffee @@ -1,5 +1,5 @@ class DataBoard - @keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads'] + @keys = ['pinnedThreads', 'hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads'] constructor: (@key, sync, dontClean) -> @data = Conf[key] @@ -58,30 +58,31 @@ class DataBoard val or defaultValue clean: -> - for boardID, val of @data.boards - @deleteIfEmpty {boardID} - now = Date.now() - if (@data.lastChecked or 0) < now - 2 * $.HOUR - @data.lastChecked = now - for boardID of @data.boards - @ajaxClean boardID + return if (@data.lastChecked or 0) > now - 2 * $.HOUR + for boardID of @data.boards + @deleteIfEmpty {boardID} + @ajaxClean boardID if boardID of @data.boards + + @data.lastChecked = now @save() ajaxClean: (boardID) -> $.cache "//a.4cdn.org/#{boardID}/threads.json", (e) => if e.target.status isnt 200 - @delete boardID if e.target.status is 404 + @delete {boardID} if e.target.status is 404 return board = @data.boards[boardID] threads = {} for page in e.target.response - for thread in page.threads - if thread.no of board - threads[thread.no] = board[thread.no] - @data.boards[boardID] = threads - @deleteIfEmpty {boardID} - @save() + for thread in page.threads when thread.no of board + threads[thread.no] = board[thread.no] + count = Object.keys(threads).length + return if count is Object.keys(board).length # Nothing changed. + if count + @set {boardID, val: threads} + else + @delete {boardID} onSync: (data) => @data = data or boards: {} diff --git a/src/General/Get.coffee b/src/General/Get.coffee index 9fe328a13..0490b8403 100644 --- a/src/General/Get.coffee +++ b/src/General/Get.coffee @@ -188,7 +188,7 @@ Get = comment = bq.innerHTML # greentext - .replace(/(^|>)(>[^<$]*)(<|$)/g, '$1$2$3') + .replace /(^|>)(>[^<$]*)(<|$)/g, '$1$2$3' # quotes .replace /((>){2}(>\/[a-z\d]+\/)?\d+)/g, '$1' @@ -233,5 +233,6 @@ Get = thread = g.threads["#{boardID}.#{threadID}"] or new Thread threadID, board post = new Post Build.post(o, true), thread, board, {isArchived: true} + $('.page-num', post.nodes.info).hidden = true Main.callbackNodes Post, [post] Get.insert post, root, context diff --git a/src/General/Header.coffee b/src/General/Header.coffee index e97fcdf0f..74eb9648a 100644 --- a/src/General/Header.coffee +++ b/src/General/Header.coffee @@ -25,8 +25,6 @@ Header = innerHTML: ' Auto-hide header on scroll' barPositionToggler = $.el 'label', innerHTML: ' Bottom header' - catalogToggler = $.el 'label', - innerHTML: ' Use catalog board links' topBoardToggler = $.el 'label', innerHTML: ' Top original board list' botBoardToggler = $.el 'label', @@ -40,7 +38,6 @@ Header = @headerToggler = headerToggler.firstElementChild @scrollHeaderToggler = scrollHeaderToggler.firstElementChild @barPositionToggler = barPositionToggler.firstElementChild - @catalogToggler = catalogToggler.firstElementChild @topBoardToggler = topBoardToggler.firstElementChild @botBoardToggler = botBoardToggler.firstElementChild @customNavToggler = customNavToggler.firstElementChild @@ -48,7 +45,6 @@ Header = $.on @headerToggler, 'change', @toggleBarVisibility $.on @scrollHeaderToggler, 'change', @toggleHideBarOnScroll $.on @barPositionToggler, 'change', @toggleBarPosition - $.on @catalogToggler, 'change', @toggleCatalogLinks $.on @topBoardToggler, 'change', @toggleOriginalBoardList $.on @botBoardToggler, 'change', @toggleOriginalBoardList $.on @customNavToggler, 'change', @toggleCustomNav @@ -74,7 +70,6 @@ Header = {el: headerToggler} {el: scrollHeaderToggler} {el: barPositionToggler} - {el: catalogToggler} {el: topBoardToggler} {el: botBoardToggler} {el: customNavToggler} @@ -92,9 +87,6 @@ Header = if a = $ "a[href*='/#{g.BOARD}/']", $.id 'boardNavDesktopFoot' a.className = 'current' - Header.setCatalogLinks Conf['Header catalog links'] - $.sync 'Header catalog links', Header.setCatalogLinks - @enableDesktopNotifications() setBoardList: -> @@ -120,10 +112,12 @@ Header = list = $ '#custom-board-list', Header.bar $.rmAll list return unless text - as = $$ '#full-board-list a[title]', Header.bar - nodes = text.match(/[\w@]+(-(all|title|replace|full|index|catalog|text:"[^"]+"))*|[^\w@]+/g).map (t) -> + as = $$ '.boardList a[title]', Header.bar + re = /[\w@]+(-(all|title|replace|full|archive|(mode|sort|text):"[^"]+"))*|[^\w@]+/g + nodes = text.match(re).map (t) -> if /^[^\w@]/.test t return $.tn t + if /^toggle-all/.test t a = $.el 'a', className: 'show-board-list-button' @@ -131,31 +125,47 @@ Header = href: 'javascript:;' $.on a, 'click', Header.toggleBoardList return a - board = if /^current/.test t - g.BOARD.ID + + boardID = t.split('-')[0] + boardID = g.BOARD.ID if boardID is 'current' + for a in as when a.textContent is boardID + a = a.cloneNode() + break + return $.tn boardID if a.parentNode # Not a clone. + + a.textContent = if /-title/.test(t) or /-replace/.test(t) and boardID is g.BOARD.ID + a.title + else if /-full/.test t + "/#{boardID}/ - #{a.title}" + else if m = t.match /-text:"([^"]+)"/ + m[1] else - t.match(/^[^-]+/)[0] - for a in as - if a.textContent is board - a = a.cloneNode true + boardID - a.textContent = if /-title/.test(t) or /-replace/.test(t) and $.hasClass a, 'current' - a.title - else if /-full/.test t - "/#{board}/ - #{a.title}" - else if m = t.match /-text:"(.+)"/ - m[1] - else - a.textContent + if /-archive/.test t + if href = Redirect.to 'board', {boardID} + a.href = href + else + return a.firstChild # Its text node. - if m = t.match /-(index|catalog)/ - a.dataset.only = m[1] - a.href = "//boards.4chan.org/#{board}/" - a.href += 'catalog' if m[1] is 'catalog' + if m = t.match /-mode:"([^"]+)"/ + type = m[1].toLowerCase() + a.dataset.indexMode = switch type + when 'all threads' then 'all pages' + when 'paged', 'catalog' then type + else 'paged' + if m = t.match /-sort:"([^"]+)"/ + type = m[1].toLowerCase() + a.dataset.indexSort = switch type + when 'bump order' then 'bump' + when 'last reply' then 'lastreply' + when 'creation date' then 'birth' + when 'reply count' then 'replycount' + when 'file count' then 'filecount' + else 'bump' - $.addClass a, 'navSmall' if board is '@' - return a - $.tn t + $.addClass a, 'navSmall' if boardID is '@' + a $.add list, nodes toggleBoardList: -> @@ -219,21 +229,6 @@ Header = $.cb.checked.call @ Header.setBarPosition @checked - setCatalogLinks: (useCatalog) -> - Header.catalogToggler.checked = useCatalog - as = $$ [ - '#board-list a' - '#boardNavDesktop a' - '#boardNavDesktopFoot a' - ].join ', ' - path = if useCatalog then 'catalog' else '' - for a in as when a.hostname is 'boards.4chan.org' and not a.dataset.only - a.pathname = "/#{a.pathname.split('/')[1]}/#{path}" - return - toggleCatalogLinks: -> - $.cb.checked.call @ - Header.setCatalogLinks @checked - setTopBoardList: (show) -> Header.topBoardToggler.checked = show if show diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 79a935099..2e794b116 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -1,6 +1,18 @@ Index = + showHiddenThreads: false init: -> - return if g.VIEW isnt 'index' or g.BOARD.ID is 'f' + if g.VIEW isnt 'index' + $.ready @setupNavLinks + return + return if g.BOARD.ID is 'f' + + @db = new DataBoard 'pinnedThreads' + Thread.callbacks.push + name: 'Thread Pinning' + cb: @threadNode + CatalogThread.callbacks.push + name: 'Catalog Features' + cb: @catalogNode @button = $.el 'a', className: 'index-refresh-shortcut fa fa-refresh' @@ -9,33 +21,20 @@ Index = $.on @button, 'click', @update Header.addShortcut @button, 1 - modeEntry = - el: $.el 'span', textContent: 'Index mode' + threadNumEntry = + el: $.el 'span', textContent: 'Threads per page' subEntries: [ - { el: $.el 'label', innerHTML: ' Paged' } - { el: $.el 'label', innerHTML: ' All threads' } + { el: $.el 'label', innerHTML: '', title: 'Use 0 for default value' } ] - for label in modeEntry.subEntries - input = label.el.firstChild - input.checked = Conf['Index Mode'] is input.value - $.on input, 'change', $.cb.value - $.on input, 'change', @cb.mode - - sortEntry = - el: $.el 'span', textContent: 'Sort by' - subEntries: [ - { el: $.el 'label', innerHTML: ' Bump order' } - { el: $.el 'label', innerHTML: ' Last reply' } - { el: $.el 'label', innerHTML: ' Creation date' } - { el: $.el 'label', innerHTML: ' Reply count' } - { el: $.el 'label', innerHTML: ' File count' } - ] - for label in sortEntry.subEntries - input = label.el.firstChild - input.checked = Conf['Index Sort'] is input.value - $.on input, 'change', $.cb.value - $.on input, 'change', @cb.sort + threadsNumInput = threadNumEntry.subEntries[0].el.firstChild + threadsNumInput.value = Conf['Threads per Page'] + $.on threadsNumInput, 'change', $.cb.value + $.on threadsNumInput, 'change', @cb.threadsNum + targetEntry = + el: $.el 'label', + innerHTML: ' Open threads in a new tab' + title: 'Catalog-only setting.' repliesEntry = el: $.el 'label', innerHTML: ' Show replies' @@ -47,12 +46,14 @@ Index = el: $.el 'label', innerHTML: ' Refreshed navigation' title: 'Refresh index when navigating through pages.' - for label in [repliesEntry, anchorEntry, refNavEntry] + for label in [targetEntry, repliesEntry, anchorEntry, refNavEntry] input = label.el.firstChild {name} = input input.checked = Conf[name] $.on input, 'change', $.cb.checked switch name + when 'Open threads in a new tab' + $.on input, 'change', @cb.target when 'Show Replies' $.on input, 'change', @cb.replies when 'Anchor Hidden Threads' @@ -63,24 +64,41 @@ Index = el: $.el 'span', textContent: 'Index Navigation' order: 90 - subEntries: [modeEntry, sortEntry, repliesEntry, anchorEntry, refNavEntry] + subEntries: [threadNumEntry, targetEntry, repliesEntry, anchorEntry, refNavEntry] $.addClass doc, 'index-loading' @update() + + @navLinks = $.el 'div', + id: 'nav-links' + innerHTML: <%= importHTML('General/Index-navlinks') %> + @searchInput = $ '#index-search', @navLinks + @hideLabel = $ '#hidden-label', @navLinks + @selectMode = $ '#index-mode', @navLinks + @selectSort = $ '#index-sort', @navLinks + @selectSize = $ '#index-size', @navLinks + $.on @searchInput, 'input', @onSearchInput + $.on $('#index-search-clear', @navLinks), 'click', @clearSearch + $.on $('#hidden-toggle a', @navLinks), 'click', @cb.toggleHiddenThreads + for select in [@selectMode, @selectSort, @selectSize] + select.value = Conf[select.name] + $.on select, 'change', $.cb.value + $.on @selectMode, 'change', @cb.mode + $.on @selectSort, 'change', @cb.sort + $.on @selectSize, 'change', @cb.size + @root = $.el 'div', className: 'board' @pagelist = $.el 'div', className: 'pagelist' hidden: true innerHTML: <%= importHTML('General/Index-pagelist') %> - @navLinks = $.el 'div', - className: 'navLinks' - innerHTML: <%= importHTML('General/Index-navlinks') %> - @searchInput = $ '#index-search', @navLinks @currentPage = @getCurrentPage() $.on window, 'popstate', @cb.popstate $.on @pagelist, 'click', @cb.pageNav - $.on @searchInput, 'input', @onSearchInput - $.on $('#index-search-clear', @navLinks), 'click', @clearSearch + $.on $('#custom-board-list', Header.bar), 'click', @cb.headerNav + + @cb.toggleCatalogMode() + $.asap (-> $('.board', doc) or d.readyState isnt 'loading'), -> board = $ '.board' $.replace board, Index.root @@ -95,18 +113,192 @@ Index = for navLink in $$ '.navLinks' $.rm navLink - $.after $.x('child::form/preceding-sibling::hr[1]'), Index.navLinks + $.before $.id('delform'), [Index.navLinks, $.x 'child::form/preceding-sibling::hr[1]'] $.rmClass doc, 'index-loading' $.asap (-> $('.pagelist') or d.readyState isnt 'loading'), -> $.replace $('.pagelist'), Index.pagelist + menu: + init: -> + return if g.VIEW isnt 'index' or !Conf['Menu'] or g.BOARD.ID is 'f' + + $.event 'AddMenuEntry', + type: 'post' + el: $.el 'a', href: 'javascript:;' + order: 5 + open: ({thread}) -> + return false if Conf['Index Mode'] isnt 'catalog' + @el.textContent = if thread.isHidden + 'Unhide thread' + else + 'Hide thread' + $.off @el, 'click', @cb if @cb + @cb = -> + $.event 'CloseMenu' + Index.toggleHide thread + $.on @el, 'click', @cb + true + + $.event 'AddMenuEntry', + type: 'post' + el: $.el 'a', href: 'javascript:;' + order: 6 + open: ({thread}) -> + return false if Conf['Index Mode'] isnt 'catalog' + @el.textContent = if thread.isPinned + 'Unpin thread' + else + 'Pin thread' + $.off @el, 'click', @cb if @cb + @cb = -> + $.event 'CloseMenu' + Index.togglePin thread + $.on @el, 'click', @cb + true + + threadNode: -> + return unless Index.db.get {boardID: @board.ID, threadID: @ID} + @pin() + catalogNode: -> + $.on @nodes.thumb, 'click', Index.onClick + return if Conf['Image Hover in Catalog'] + $.on @nodes.thumb, 'mouseover', Index.onOver + onClick: (e) -> + return if e.button isnt 0 + thread = g.threads[@parentNode.dataset.fullID] + if e.shiftKey + Index.toggleHide thread + else if e.altKey + Index.togglePin thread + else + return + e.preventDefault() + onOver: (e) -> + # 4chan's less than stellar CSS forces us to include a .post and .postInfo + # in order to have proper styling for the .nameBlock's content. + {nodes} = g.threads[@parentNode.dataset.fullID].OP + el = $.el 'div', + innerHTML: '