Merge branch 'v3'

Conflicts:
	CHANGELOG.md
	LICENSE
	builds/appchan-x.user.js
	builds/crx/script.js
	src/General/Config.coffee
	src/General/css/style.css
	src/Miscellaneous/AnnouncementHiding.coffee
	src/Miscellaneous/InfiniScroll.coffee
	src/Posting/QR.coffee
This commit is contained in:
Zixaphir 2014-03-13 18:12:44 -07:00
commit e459069ae6
24 changed files with 1934 additions and 437 deletions

View File

@ -1,3 +1,9 @@
**Mayhem**:
- Bugfixes
**Zixaphir**:
- Bugfixes
### v2.9.3
*2014-03-11*
@ -69,7 +75,7 @@
<li> Filter (hiding, highlighting)
<li> Thread Hiding
<li> Linkify
<li> Auto-GIF
<li> Thumbnail Replacemenu
<li> Image Hover
</ul>
- Support for the official catalog will be removed in the future, once the catalog mode for the index is deemed satisfactory.
@ -86,7 +92,10 @@
**Vampiricwulf**
- Flash embedding and other Flash features.
**Zixaphir**
**Zixaphir**
- Update Custom Navigation legend to reflect index mode changes.
- JSON Navigation now works for backlinks (when Quote Inlining is disabled) and backlink hashlinks.
- JSON Navigation (Index, Catalog) performance improvements.
- Added a nifty bread-crumb for the JSON Navigation.
- Many spiffy performance, state awareness, and sanity improvements to JSON Navigation.
- Bugfixes.

View File

@ -1,5 +1,5 @@
/*
* appchan x - Version 2.9.3 - 2014-03-11
* appchan x - Version 2.9.3 - 2014-03-13
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,26 +0,0 @@
<input type="search" id="index-search" class="field" placeholder="Search">
<a id="index-search-clear" class="fa fa-times-circle" href="javascript:;"></a>
&nbsp;
<time id="index-last-refresh" title="Last index refresh">...</time>
<span id="hidden-label" hidden>&nbsp;&mdash; <span id="hidden-count"></span> <span id="hidden-toggle">[<a href="javascript:;">Show</a>]</span></span>
<span style="flex:1"></span>
<select id="index-mode" name="Index Mode">
<option disabled>Index Mode</option>
<option value="paged">Paged</option>
<option value="infinite">Infinite Scrolling</option>
<option value="all pages">All threads</option>
<option value="catalog">Catalog</option>
</select>
<select id="index-sort" name="Index Sort">
<option disabled>Index Sort</option>
<option value="bump">Bump order</option>
<option value="lastreply">Last reply</option>
<option value="birth">Creation date</option>
<option value="replycount">Reply count</option>
<option value="filecount">File count</option>
</select>
<select id="index-size" name="Index Size">
<option disabled>Image Size</option>
<option value="small">Small</option>
<option value="large">Large</option>
</select>

View File

@ -1,4 +0,0 @@
<button class="export">Export Settings</button>
<button class="import">Import Settings</button>
<button class="reset">Reset Settings</button>
<input type="file" hidden>

View File

@ -1,15 +0,0 @@
<div id="fourchanx-settings" class="dialog">
<nav>
<div class="sections-list"></div>
<div class="credits">
<a href="<%= meta.page %>" target="_blank"><%= meta.name %></a>
&nbsp;|&nbsp;
<a href="<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md" target="_blank">#{g.VERSION}</a>
&nbsp;|&nbsp;
<a href="<%= meta.repo %>blob/<%= meta.mainBranch %>/CONTRIBUTING.md#reporting-bugs-and-suggestions" target="_blank">Issues</a>
&nbsp;|&nbsp;
<a href="javascript:;" class="close fa fa-times" title="Close"></a>
</div>
</nav>
<section></section>
</div>

View File

@ -1,35 +0,0 @@
<div>
<input type="checkbox" id="autohide" title="Auto-hide">
<select data-name="thread" title="Create a new thread / Reply">
<option value="new">New thread</option>
</select>
<span class="move"></span>
<a href="javascript:;" class="close fa fa-times" title="Close"></a>
</div>
<form>
<div class="persona">
<input type="button" id="dump-button" title="Dump list" value="+">
<input data-name="name" name="name" list="list-name" placeholder="Name" class="field">
<input data-name="email" name="email" list="list-email" placeholder="E-mail" class="field">
<input data-name="sub" name="sub" list="list-sub" placeholder="Subject" class="field">
</div>
<div id="dump-list"></div>
<a href="javascript:;" id="add-post" class="fa fa-plus" title="Add a post"></a>
<div class="textarea">
<textarea data-name="com" placeholder="Comment" class="field"></textarea>
<span id="char-count"></span>
</div>
<div id="file-n-submit">
<input type="file" hidden multiple>
<input type="submit">
<input type="button" id="qr-file-button" value="Choose files">
<span id="qr-no-file">No selected file</span>
<input id="qr-filename" data-name="filename" spellcheck="false">
<span id="qr-filesize"></span>
<a href="javascript:;" id="qr-filerm" class="fa fa-times-circle" title="Remove file"></a>
<input type="checkbox" id="qr-file-spoiler" title="Spoiler image">
</div>
</form>
<datalist id="list-name"></datalist>
<datalist id="list-email"></datalist>
<datalist id="list-sub"></datalist>

View File

@ -114,6 +114,6 @@
"https": true,
"withCredentials": true,
"software": "foolfuuka",
"boards": ["a", "biz", "co", "d", "diy", "gd", "jp", "m", "mlp", "s4s", "sci", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"],
"boards": ["a", "biz", "co", "d", "diy", "gd", "jp", "m", "s4s", "sci", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"],
"files": ["a", "biz", "d", "diy", "gd", "jp", "m", "s4s", "sci", "tg", "u", "vg", "vp", "vr", "wsg"]
}]

View File

@ -30,13 +30,13 @@
"font-awesome": "~4.0.3",
"grunt": "~0.4.2",
"grunt-bump": "~0.0.13",
"grunt-concurrent": "~0.4.3",
"grunt-concurrent": "~0.5.0",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-coffee": "~0.10.0",
"grunt-contrib-compress": "~0.7.0",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-copy": "~0.5.0",
"grunt-contrib-watch": "~0.5.3",
"grunt-contrib-watch": "~0.6.0",
"grunt-shell": "~0.6.4",
"load-grunt-tasks": "~0.4.0"
},

View File

@ -148,6 +148,7 @@ PostHiding =
el: $.el 'a', href: 'javascript:;'
order: 20
open: (post) ->
return false if post.isReply
@el.textContent = if post.isHidden
'Unhide thread'
else

View File

@ -181,7 +181,7 @@ Build =
''
if isOP and g.VIEW is 'index'
pageNum = Index.liveThreadIDs.indexOf(postID) // Index.threadsNumPerPage
pageNum = Index.liveThreadData.keys.indexOf("#{postID}") // Index.threadsNumPerPage
pageIcon = " <span class=page-num title='This thread is on page #{pageNum} in the original index.'>Page #{pageNum}</span>"
replyLink = " &nbsp; <span>[<a href='/#{boardID}/res/#{threadID}' class=replylink>Reply</a>]</span>"
else
@ -237,11 +237,11 @@ Build =
catalogThread: (thread) ->
{staticPath, gifIcon} = Build
data = Index.liveThreadData[Index.liveThreadIDs.indexOf thread.ID]
data = Index.liveThreadData[thread.ID]
postCount = data.replies + 1
fileCount = data.images + !!data.ext
pageCount = Index.liveThreadIDs.indexOf(thread.ID) // Index.threadsNumPerPage
pageCount = Index.liveThreadData.keys.indexOf("#{thread.ID}") // Index.threadsNumPerPage
subject = if thread.OP.info.subject
"<div class='subject'>#{thread.OP.info.subject}</div>"

View File

@ -13,10 +13,6 @@ Config =
false
'Link to external catalog instead of the internal one.'
]
'Announcement Hiding': [
true
'Add button to hide 4chan announcements.'
]
'Desktop Notifications': [
false
'Enables desktop notifications across various <%= meta.name %> features.'

View File

@ -1,5 +1,6 @@
Header =
init: ->
@menu = new UI.Menu 'header'
menuButton = $.el 'span',
@ -245,25 +246,13 @@ Header =
setBarVisibility: (hide) ->
Header.headerToggler.checked = hide
$.event 'CloseMenu'
(if hide then $.addClass else $.rmClass) Header.bar, 'autohide'
(if hide then $.addClass else $.rmClass) doc, 'autohide'
toggleBarVisibility: ->
hide = if @nodeName is 'INPUT'
@checked
else
!$.hasClass Header.bar, 'autohide'
# set checked status if called from keybind
@checked = hide
$.set 'Header auto-hide', Conf['Header auto-hide'] = hide
toggleBarVisibility: (e) ->
hide = @checked
Conf['Header auto-hide'] = hide
$.set 'Header auto-hide', hide
Header.setBarVisibility hide
message = "The header bar will #{if hide
'automatically hide itself.'
else
'remain visible.'}"
new Notice 'info', message, 2
setHideBarOnScroll: (hide) ->
Header.scrollHeaderToggler.checked = hide

View File

@ -96,7 +96,7 @@ Index =
@searchInput = $ '#index-search', @navLinks
@searchTest()
@searchTest true
@hideLabel = $ '#hidden-label', @navLinks
@selectMode = $ '#index-mode', @navLinks
@ -153,8 +153,8 @@ Index =
$.after $.id('delform'), Index.pagelist
$.rmClass doc, 'index-loading'
scroll: $.debounce 100, ->
return if Index.req or Conf['Index Mode'] isnt 'infinite' or (doc.scrollTop <= doc.scrollHeight - (300 + window.innerHeight)) or g.VIEW is 'thread'
scroll: ->
return if Index.req or Conf['Index Mode'] isnt 'infinite' or (window.scrollY <= doc.scrollHeight - (300 + window.innerHeight)) or g.VIEW is 'thread'
Index.pageNum = (Index.pageNum or Index.getCurrentPage()) + 1 # Avoid having to pushState to keep track of the current page
return Index.endNotice() if Index.pageNum >= Index.pagesNum
@ -270,11 +270,15 @@ Index =
{hash} = window.location
window.location = './' + hash
searchTest: ->
return unless hash = window.location.hash
return unless match = hash.match /s=([\w]+)/
searchTest: (init) ->
return false unless hash = window.location.hash
return false unless match = hash.match /s=([\w]+)/
@searchInput.value = match[1]
$.on d, '4chanXInitFinished', @onSearchInput
if init
$.on d, '4chanXInitFinished', Index.onSearchInput
else
Index.onSearchInput()
return true
setupNavLinks: ->
for el in $$ '.navLinks.desktop > a'
@ -410,7 +414,7 @@ Index =
Index.pageNav pageNum
pageNav: (pageNum) ->
return if Index.currentPage is pageNum
return if Index.currentPage is pageNum and not Index.root.parentElement
history.pushState null, '', if pageNum is 0 then './' else pageNum
Index.pageLoad pageNum
@ -475,7 +479,7 @@ Index =
updateHideLabel: ->
hiddenCount = 0
for threadID, thread of g.BOARD.threads when thread.isHidden
hiddenCount++ if thread.ID in Index.liveThreadIDs
hiddenCount++ if thread.ID in Index.liveThreadData.keys
unless hiddenCount
Index.hideLabel.hidden = true
Index.cb.toggleHiddenThreads() if Index.showHiddenThreads
@ -496,6 +500,10 @@ Index =
delete Index.pageNum
Index.req?.abort()
Index.notice?.close()
{sortedThreads} = Index
if sortedThreads
board = sortedThreads[0].board.ID
# This notice only displays if Index Refresh is taking too long
now = Date.now()
@ -507,11 +515,11 @@ Index =
pageNum = null if typeof pageNum isnt 'number' # event
onload = (e) -> Index.load e, pageNum
Index.req = $.ajax "//a.4cdn.org/#{g.BOARD}/catalog.json",
Index.req = $.ajax "//a.4cdn.org/#{g.BOARD.ID}/catalog.json",
onabort: onload
onloadend: onload
,
whenModified: Index.board is "#{g.BOARD}"
whenModified: board is g.BOARD.ID
$.addClass Index.button, 'fa-spin'
load: (e, pageNum) ->
@ -539,13 +547,11 @@ Index =
Navigate.title()
Index.board = "#{g.BOARD}"
try
if req.status is 200
Index.parse req.response, pageNum
else if req.status is 304 and pageNum?
Index.pageNav pageNum
else if req.status is 304
Index.pageNav pageNum or 0
catch err
c.error "Index failure: #{err.message}", err.stack
# network error or non-JSON content for example.
@ -574,26 +580,25 @@ Index =
parseThreadList: (pages) ->
Index.threadsNumPerPage = pages[0].threads.length
live = []
live = new SimpleDict()
i = 0
while page = pages[i++]
live = live.concat page.threads
j = 0
{threads} = page
while thread = threads[j++]
live.push thread.no, thread
data = []
i = 0
while thread = live[i++]
data.push thread.no
Index.liveThreadData = live
Index.liveThreadIDs = data
Index.liveThreadData = live
g.BOARD.threads.forEach (thread) ->
thread.collect() unless thread.ID in Index.liveThreadIDs
thread.collect() unless thread.ID in Index.liveThreadData.keys
buildThreads: ->
threads = []
posts = []
for threadData, i in Index.liveThreadData
errors = null
Index.liveThreadData.forEach (threadData) ->
threadRoot = Build.thread g.BOARD, threadData
if thread = g.BOARD.threads[threadData.no]
thread.setPage i // Index.threadsNumPerPage
@ -601,52 +606,48 @@ Index =
thread.setCount 'file', threadData.images + !!threadData.ext, threadData.imagelimit
thread.setStatus 'Sticky', !!threadData.sticky
thread.setStatus 'Closed', !!threadData.closed
else
thread = new Thread threadData.no, g.BOARD
threads.push thread
continue if thread.ID of thread.posts
return if thread.ID of thread.posts
try
posts.push new Post $('.opContainer', threadRoot), thread, g.BOARD
catch err
# Skip posts that we failed to parse.
errors = [] unless errors
errors.push
message: "Parsing of Thread No.#{thread} failed. Thread will be skipped."
error: err
Main.handleErrors errors if errors
Main.handleErrors errors if errors
Main.callbackNodes Thread, threads
Main.callbackNodes Post, posts
Index.updateHideLabel()
$.event 'IndexRefresh'
buildHRs: (threadRoots) ->
nodes = []
i = 0
while node = threadRoots[i++]
nodes.push node, $.el 'hr'
nodes
buildReplies: (threads) ->
buildReplies: (thread) ->
return unless Conf['Show Replies']
posts = []
for thread in threads
i = Index.liveThreadIDs.indexOf thread.ID
continue unless lastReplies = Index.liveThreadData[i].last_replies
nodes = []
for data in lastReplies
if post = thread.posts[data.no]
nodes.push post.nodes.root
continue
nodes.push node = Build.postFromObject data, thread.board.ID
try
posts.push new Post node, thread, thread.board
catch err
# Skip posts that we failed to parse.
errors = [] unless errors
errors.push
message: "Parsing of Post No.#{data.no} failed. Post will be skipped."
error: err
return unless lastReplies = Index.liveThreadData[thread.ID].last_replies
nodes = []
for data in lastReplies
if post = thread.posts[data.no]
nodes.push post.nodes.root
continue
nodes.push node = Build.postFromObject data, thread.board.ID
try
posts.push new Post node, thread, thread.board
catch err
# Skip posts that we failed to parse.
errors = [] unless errors
errors.push
message: "Parsing of Post No.#{data.no} failed. Post will be skipped."
error: err
$.add thread.OP.nodes.root.parentNode, nodes
Main.handleErrors errors if errors
@ -679,11 +680,14 @@ Index =
sortedThreads = []
sortedThreadIDs = []
liveData = []
Index.liveThreadData.forEach (data) -> liveData.push data
{
'bump': ->
sortedThreadIDs = Index.liveThreadIDs
sortedThreadIDs = Index.liveThreadData.keys
'lastreply': ->
liveData = [Index.liveThreadData...].sort (a, b) ->
liveData.sort (a, b) ->
[..., a] = a.last_replies if 'last_replies' of a
[..., b] = b.last_replies if 'last_replies' of b
b.no - a.no
@ -692,15 +696,17 @@ Index =
sortedThreadIDs.push data.no
return
'birth': ->
sortedThreadIDs = [Index.liveThreadIDs...].sort (a, b) -> b - a
sortedThreadIDs = [Index.liveThreadData.keys...].sort (a, b) -> b - a
'replycount': ->
liveData = [Index.liveThreadData...].sort((a, b) -> b.replies - a.replies)
liveData.sort (a, b) -> b.replies - a.replies
i = 0
while data = liveData[i++]
sortedThreadIDs.push data.no
return
'filecount': ->
liveData = [Index.liveThreadData...].sort((a, b) -> b.images - a.images)
liveData = []
Index.liveThreadData.forEach (data) -> liveData.push data
liveData.sort (a, b) -> b.images - a.images
i = 0
while data = liveData[i++]
sortedThreadIDs.push data.no
@ -730,6 +736,7 @@ Index =
return
buildIndex: (infinite) ->
{sortedThreads} = Index
switch Conf['Index Mode']
when 'paged', 'infinite'
pageNum = Index.getCurrentPage()
@ -738,21 +745,30 @@ Index =
Index.pageNav Index.getMaxPageNum()
return
threadsPerPage = Index.getThreadsNumPerPage()
threads = Index.sortedThreads[threadsPerPage * pageNum ... threadsPerPage * (pageNum + 1)]
nodes = []
nodes.push thread.OP.nodes.root.parentNode for thread in threads
Index.buildReplies threads
nodes = Index.buildHRs nodes
nodes = []
threads = []
i = threadsPerPage * pageNum
max = i + threadsPerPage
while i < max and thread = sortedThreads[i++]
threads.push thread
nodes.push thread.OP.nodes.root.parentNode, $.el 'hr'
Index.buildReplies thread
Index.buildPagelist()
Index.setPage()
when 'catalog'
nodes = Index.buildCatalogViews()
Index.sizeCatalogViews nodes
else
nodes = []
nodes.push thread.OP.nodes.root.parentNode for thread in Index.sortedThreads
Index.buildReplies Index.sortedThreads
nodes = Index.buildHRs nodes
i = 0
while thread = sortedThreads[i++]
nodes.push thread.OP.nodes.root.parentNode, $.el 'hr'
Index.buildReplies thread
$.rmAll Index.root unless infinite
$.add Index.root, nodes
$.event 'IndexBuild', nodes

View File

@ -11,7 +11,7 @@ Navigate =
@title = -> return
@el = $.el 'div',
@el = $.el 'span',
id: 'breadCrumb'
Thread.callbacks.push
@ -165,14 +165,16 @@ Navigate =
navigate: (e) ->
return if @hostname isnt 'boards.4chan.org' or window.location.hostname is 'rs.4chan.org'
return if e and (e.shiftKey or e.ctrlKey or (e.type is 'click' and e.button isnt 0)) # Not simply a left click
if e
if e.shiftKey or e.ctrlKey or (e.type is 'click' and e.button isnt 0) # Not simply a left click
return
if @pathname is Navigate.path
if g.VIEW is 'thread'
ThreadUpdater.update()
else
Index.update()
unless Index.searchTest()
Index.update()
e.preventDefault()
return

View File

@ -77,11 +77,19 @@ body > hr {
display: none;
}
/* Index Features */
#index-menu {
display: flex;
}
:root.thread #index-menu,
:root.index-loading .navLinks,
:root.index-loading .board,
:root.index-loading .pagelist,
.index #returnlink {
:root.thread .pagelist {
display: none;
}
:root:not(.catalog-mode) #index-size,
:root:not(.catalog-mode) #index-size + .selectrice,
.index:not(.catalog-mode) #returnlink {
display: none;
}
#index-menu .selectrice {
@ -107,23 +115,6 @@ body > hr {
#index-search:not([data-searching]) + #index-search-clear {
display: none;
}
.index #returnlink,
.index #bottomlink,
.thread #index-last-refresh,
.thread #index-search-clear,
.thread #index-search {
display: none;
}
#returnlink::before,
#bottomlink::before,
#index-last-refresh::before {
content: '[';
}
#returnlink::after,
#bottomlink::after,
#index-last-refresh::after {
content: ']';
}
.catalog-mode .board {
text-align: center;
}

1553
src/General/css/style.css Executable file

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,10 @@
<span class=brackets-wrap id=returnlink><a href=.././>Return</a></span>
<span class=brackets-wrap id=bottomlink><a href="#bottom">Bottom</a></span>
<span id="index-menu">
<input type="search" id="index-search" class="field" placeholder="Search">
<a id="index-search-clear" class="fa fa-times-circle" href="javascript:;"></a>
&nbsp;
<time id="index-last-refresh" title="Last index refresh">...</time>
<span id="hidden-label" hidden>&nbsp;&mdash; <span id="hidden-count"></span> <span id="hidden-toggle">[<a href="javascript:;">Show</a>]</span></span>
<span style='flex: 1'></span>
<select id="index-mode" name="Index Mode">
<option disabled>Index Mode</option>
<option value="paged">Paged</option>
@ -26,4 +25,6 @@
<option value="small">Small</option>
<option value="large">Large</option>
</select>
</span>
</span>
<span class=brackets-wrap id=returnlink><a href=.././>Return</a></span>
<span class=brackets-wrap id=bottomlink><a href="#bottom">Bottom</a></span>

View File

@ -23,6 +23,8 @@ Linkify =
return
return unless Linkify.regString.test @info.comment
test = /[^\s'"]+/g
space = /[\s'"]/

View File

@ -35,4 +35,5 @@ Menu =
clone
toggle: (e) ->
Menu.menu.toggle e, @, Get.postFromNode @
fullID = $.x('ancestor::*[@data-full-i-d]', @).dataset.fullID
Menu.menu.toggle e, @, g.posts[fullID]

View File

@ -1,6 +1,5 @@
PSAHiding =
init: ->
return unless Conf['Announcement Hiding']
$.addClass doc, 'hide-announcement'
$.on d, '4chanXInitFinished', @setup

View File

@ -40,7 +40,7 @@ ThreadStats =
if Conf['Updater and Stats in Header']
Header.rmShortcut @dialog
else
$.rm d.body, sc
$.rm @dialog
clearTimeout @timeout # a possible race condition might be that this won't clear in time, but the resulting error will prevent issues anyways.

View File

@ -606,6 +606,7 @@ QR =
flagsInput: ->
{nodes} = QR
return unless nodes
if nodes.flag
$.rm nodes.flag
delete nodes.flag