Merge branch 'v3' into Av2

Conflicts:
	LICENSE
	builds/4chan-X.js
	builds/4chan-X.meta.js
	builds/4chan-X.user.js
	builds/crx/manifest.json
	builds/crx/script.js
	package.json
	src/General/Main.coffee
	src/General/css/style.css
	src/General/html/Features/QuickReply.html
	src/Posting/QuickReply.coffee
This commit is contained in:
Zixaphir 2013-05-03 07:17:59 -07:00
commit ee00d5a3aa
42 changed files with 22701 additions and 617 deletions

View File

@ -1,11 +1,75 @@
### 1.1.9 - 2013-05-02
seaweedchan
- Fix boards with previously deleted archives not switching to new archives
ihavenoface:
- 4chan Pass link by the style selector
zixaphir:
- Make Allow False Positives option more efficient
### 1.1.8 - 2013-05-01
seaweedchan:
- Fix QR not clearing on submit with Posting Success Notifications disabled
- New archives for /h/, /v/, and /vg/
### 1.1.7 - 2013-05-01
seaweedchan:
- External image embedding
- Account for time options in youtube links for embedding
- Once again remove /v/ and /vg/ archiving... ;_;
- Add paste.installgentoo.com embedding
- Added `Posting Success Notifications` option to make "Post Successful!" and "_____ uploaded" notifications optional
- Added `Allow False Positives` option under Linkification, giving the user more control over what's linkified.
- Fix URL for update checking
### 1.1.6 - 2013-05-01
seaweedchan:
- Fix Gist links if no username is specificed
MayhemYDG:
- Access it in the `Advanced` tab of the Settings window.
zixaphir:
- Add Gist link titles
### 1.1.5 - 2013-04-30
seaweedchan:
- Fix various embedding issues
- Fix Link Title depending on Embedding
- Added favicons to links that can be embedded
- Add gist embedding
### 1.1.4 - 2013-04-29
seaweedchan:
- Change ESC functionality in QR to autohide if Persistent QR is enabled
- Add /v/ and /vg/ archiving to archive.nihil-ad-rem.net, and make sure Archiver Selection settings actually switch to it
- Add option to toggle between updater and stats fixed in header or floating
MayhemYDG:
- Add nyafuu archiving for /w/
- Add /d/ archive
### 1.1.3 - 2013-04-28
seaweedchan:
- Chrome doesn't get .null, so don't style it
- Fix count when auto update is disabled and set updater text to "Update"
- Remove /v/ and /vg/ redirection. See https://archive.foolz.us/foolz/thread/509388/ for news and how you can donate to bring /v/ and /vg/ archiving back.
- Remove /v/ and /vg/ redirection from Foolz.
- Toggle keybind for header auto-hiding
MayhemYDG:
=======
- Access it in the `QR` tab of the Settings window.
- Updated archive redirection for /h/, /v/ and /vg/.
### 3.2.3 - *2013-04-30*
- Update archive redirection for /c/, /d/, /v/, /vg/, /w/ and /wg/.
- Minor fixes.
### 3.2.2 - *2013-04-27*
>>>>>>> b74e0c92fdf2d755d996cb574dddb3c8d964e91a
- Fix Unread Count taking into account hidden posts.
### 1.1.2 - 2013-04-26
@ -22,7 +86,7 @@ zixaphir:
- Fix preview with favicons and emoji
- Fix NaN error on Thread Updater Interval
- Draggable UI can no longer overlap the Header.
-- Setting the header to Autohide also increases its z-index to overlap other UI
- Setting the header to Autohide also increases its z-index to overlap other UI
### 1.1.1 - 2013-04-26
zixaphir:
@ -35,29 +99,30 @@ MayhemYDG:
seaweedchan:
- Allow thread watcher to load on catalog
### 1.0.10:
### 1.0.10 - 2013-04-23
- Add message pertaining to rewrite
### 1.0.9:
### 1.0.9 - 2013-04-17
ihavenoface:
- Implement Announcement Hiding
seaweedchan:
- Change #options back to inheriting colors from replies
- Fix script breaking when disabling image expansion
### 1.0.8:
### 1.0.8 - 2013-04-15
seaweedchan:
- Redo settings menu styling
- Move Export/Import buttons and dialog
- Update license and use banner.js for license
### 1.0.7:
### 1.0.7 - 2013-04-14
qqueue:
- Relative post dates
MayhemYDG:
- Exporting/importing settings
### 1.0.6
### 1.0.6 - 2013-04-13
seaweedchan:
- Update supported boards for archive redirection and custom navigation
- Point to github.io instead of github.com for pages
@ -65,40 +130,44 @@ seaweedchan:
- Make InstallGentoo default for /g/
- Fix embedding issues
### 1.0.5:
### 1.0.5 - 2013-04-09
seaweedchan:
- Added keybind to toggle Fappe Tyme
- Fix code tag keybind
Zixaphir:
- Add 'yourPost' class to own replies
### 1.0.4:
### 1.0.4 - 2013-04-08
seaweedchan:
- Fix Fappe Tyme
- Re- add label for image expanding
- Move restore button to left side as per RiDeag
### 1.0.3
### 1.0.3 - 2013-03-23
seaweedchan:
- Add ad- blocking CSS into Custom CSS examples
Zixaphir:
- Fix ctrl+s bringing up save dialog
- Fix issues with soundcloud embedding
### 1.0.2:
### 1.0.2 - 2013-03-14
seaweedchan:
- New Rice option: Emoji Position
- New layout for Rice tab
- No more Yotsuba / Yotsuba B in options
### 1.0.1:
### 1.0.1 - 2013-03-14
- New option: Emoji
- New Rice option: Sage Emoji
seaweedchan:
- Prettier error messages
### 1.0.0
### 1.0.0 - 2013-03-13
- Initial release
zixaphir:
- Fix unread post count for filtered posts
- Fix issues when switching from ihavenoface's fork

View File

@ -39,10 +39,11 @@ module.exports = (grunt) ->
]
dest: 'tmp-<%= pkg.type %>/script.coffee'
license:
meta:
options: concatOptions
files:
'LICENSE': 'src/General/meta/banner.js'
'LICENSE': 'src/General/meta/banner.js',
'builds/version': 'src/General/meta/version.js'
crx:
options: concatOptions
@ -86,6 +87,7 @@ module.exports = (grunt) ->
concurrent:
build: [
'concat:meta'
'build-crx'
'build-userjs'
'build-userscript'
@ -153,7 +155,6 @@ module.exports = (grunt) ->
grunt.registerTask 'build', [
'concurrent:build'
'concat:license'
]
grunt.registerTask 'build-crx', [

View File

@ -1,5 +1,5 @@
/*
* appchan x - Version 2.0.0 - 2013-04-28
* appchan x - Version 2.0.0 - 2013-05-03
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE

View File

@ -8,7 +8,7 @@
1. Precise steps to reproduce the problem, with the expected and actual results.
2. Console errors, if any.
3. Browser version.
4. Your exported settings.
4. Your exported settings. If your settings contains sensitive information (e.g. personas), edit the text file manually.
Open your console with:
- `Ctrl + Shift + J` on Chrome.

10166
builds/4chan-X.js Normal file

File diff suppressed because one or more lines are too long

19
builds/4chan-X.meta.js Normal file
View File

@ -0,0 +1,19 @@
// ==UserScript==
// @name 4chan X
// @version 1.1.9
// @namespace 4chan-X
// @description Cross-browser userscript for maximum lurking on 4chan.
// @license MIT; https://github.com/seaweedchan/4chan-x/blob/master/LICENSE
// @match *://api.4chan.org/*
// @match *://boards.4chan.org/*
// @match *://images.4chan.org/*
// @match *://sys.4chan.org/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_openInTab
// @run-at document-start
// @updateURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan_X.meta.js
// @downloadURL https://github.com/seaweedchan/4chan-x/raw/stable/builds/4chan_X.user.js
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAgMAAAAqbBEUAAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAHFJREFUKFOt0LENACEIBdBv4Qju4wgWanEj3D6OcIVMKaitYHEU/jwTCQj8W75kiVCSBvdQ5/AvfVHBin11BgdRq3ysBgfwBDRrj3MCIA+oAQaku/Q1cNctrAmyDl577tOThYt/Y1RBM4DgOHzM0HFTAyLukH/cmRnqAAAAAElFTkSuQmCC
// ==/UserScript==

10189
builds/4chan-X.user.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
builds/version Normal file
View File

@ -0,0 +1 @@
2.0.0

View File

@ -1,30 +1,39 @@
Redirect =
init: ->
$.sync 'archs', @updateArchives
$.sync 'archivers', @updateArchives
updateArchives: ->
$.get 'archivers', {}, ({archivers}) ->
Conf['archivers'] = archivers
imageArchives: do ->
o =
a: "//archive.foolz.us/"
ck: "//fuuka.warosu.org/"
an: "http://archive.heinessen.com/"
cgl: "//rbt.asia/"
c: "//archive.nyafuu.org/"
d: "//loveisover.me/"
hr: "http://archive.4plebs.org/"
u: "//nsfw.foolz.us/"
po: "//archive.thedarkcave.org/"
vg: "http://nth.pensivenonsen.se/"
c: "//archive.nyafuu.org/"
o.gd = o.jp = o.m = o.q = o.tg = o.vp = o.vr = o.wsg = o.a
o.fa = o.lit = o.s4s = o.ck
o.k = o.toy = o.x = o.an
o.g = o.mu = o.cgl
o.w = o.wg = o.c
o.h = o.v = o.d
o.tv = o.hr
return o
image: (boardID, filename) ->
# Do not use g.BOARD, the image url can originate from a cross-quote.
switch boardID
when 'a', 'gd', 'jp', 'm', 'q', 'tg', 'vp', 'vr', 'wsg'
"//archive.foolz.us/#{boardID}/full_image/#{filename}"
when 'u'
"//nsfw.foolz.us/#{boardID}/full_image/#{filename}"
when 'po'
"//archive.thedarkcave.org/#{boardID}/full_image/#{filename}"
when 'hr', 'tv'
"http://archive.4plebs.org/#{boardID}/full_image/#{filename}"
when 'ck', 'fa', 'lit', 's4s'
"//fuuka.warosu.org/#{boardID}/full_image/#{filename}"
when 'cgl', 'g', 'mu', 'w'
"//rbt.asia/#{boardID}/full_image/#{filename}"
when 'an', 'k', 'toy', 'x'
"http://archive.heinessen.com/#{boardID}/full_image/#{filename}"
when 'c'
"//archive.nyafuu.org/#{boardID}/full_image/#{filename}"
# Fuck. Your. Shit.
"#{Redirect.imageArchives[boardID]}#{boardID}/full_image/#{filename}"
post: (boardID, postID) ->
unless Redirect.post[boardID]?
@ -58,6 +67,9 @@ Redirect =
else
null)
unless archive.boards.contains g.BOARD.ID
Conf['archivers'] = archive
archiver:
'Foolz':
base: 'https://archive.foolz.us'
@ -75,9 +87,21 @@ Redirect =
base: 'http://archive.4plebs.org'
boards: ['hr', 'tg', 'tv', 'x']
base: 'foolfuuka'
'NyaFuu':
base: '//archive.nyafuu.org'
boards: ['c', 'w', 'wg']
type: 'foolfuuka'
'LoveIsOver':
base: '//loveisover.me'
boards: ['d', 'h', 'v']
type: 'foolfuuka'
'PensiveNonsen':
base: 'http://nth.pensivenonsen.se'
boards: ['vg']
type: 'foolfuuka'
'Warosu':
base: '//fuuka.warosu.org'
boards: ['cgl', 'ck', 'fa', 'jp', 'lit', 's4s', 'q', 'tg']
boards: ['cgl', 'ck', 'fa', 'jp', 'lit', 's4s', 'q', 'tg', 'vr']
type: 'fuuka'
'InstallGentoo':
base: '//archive.installgentoo.net'
@ -85,7 +109,7 @@ Redirect =
type: 'fuuka'
'RebeccaBlackTech':
base: '//rbt.asia'
boards: ['an', 'cgl', 'g', 'mu', 'w']
boards: ['cgl', 'g', 'mu', 'w']
type: 'fuuka_mail'
'Heinessen':
base: 'http://archive.heinessen.com'
@ -95,10 +119,6 @@ Redirect =
base: '//www.cliché.net/4chan/cgi-board.pl'
boards: ['e']
type: 'fuuka'
'NyaFuu':
base: '//archive.nyafuu.org'
boards: ['c', 'w']
type: 'fuuka'
path: (base, archiver, data) ->
if data.isSearch

View File

@ -19,12 +19,7 @@ ThreadHiding =
hiddenThreads = ThreadHiding.db.get
boardID: g.BOARD.ID
defaultValue: {}
# XXX tmp fix
try
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
catch e
localStorage.setItem "4chan-hide-t-#{g.BOARD}", JSON.stringify {}
return ThreadHiding.syncCatalog()
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
# Add threads that were hidden in the catalog.
for threadID of hiddenThreadsOnCatalog
@ -130,10 +125,10 @@ ThreadHiding =
return if thread.isHidden
{OP} = thread
threadRoot = OP.nodes.root.parentNode
threadRoot.hidden = thread.isHidden = true
thread.isHidden = true
unless makeStub
threadRoot.nextElementSibling.hidden = true # <hr>
threadRoot.hidden = threadRoot.nextElementSibling.hidden = true # <hr>
return
numReplies = 0
@ -154,7 +149,7 @@ ThreadHiding =
$.add thread.stub, a
if Conf['Menu']
$.add thread.stub, [$.tn(' '), Menu.makeButton OP]
$.before threadRoot, thread.stub
$.prepend threadRoot, thread.stub
show: (thread) ->
if thread.stub
@ -162,4 +157,4 @@ ThreadHiding =
delete thread.stub
threadRoot = thread.OP.nodes.root.parentNode
threadRoot.nextElementSibling.hidden =
threadRoot.hidden = thread.isHidden = false
threadRoot.hidden = thread.isHidden = false

View File

@ -79,6 +79,10 @@ Config =
true
'Convert text into links where applicable.'
]
'Allow False Positives': [
false
'Linkify everything, allowing more false positives but reducing missed links'
]
'Embedding': [
true
'Embed supported services.'
@ -89,7 +93,7 @@ Config =
]
'Link Title': [
true
'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud'
'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud, and Github gists'
]
'Filtering':
@ -217,6 +221,10 @@ Config =
true
'Display reply and image count.'
]
'Updater and Stats in Header': [
true,
'Places the thread updater and thread stats in the header instead of floating them.'
]
'Thread Watcher': [
true
'Bookmark threads.'
@ -277,6 +285,10 @@ Config =
true
'Decrease the cooldown time by taking into account upload speed. Disable it if it\'s inaccurate for you.'
]
'Posting Success Notifications': [
true
'Show notifications on successful post creation or file uploading.'
]
'Quote Links':
'Quote Backlinks': [
@ -766,6 +778,11 @@ http://iqdb.org/?url=%TURL
boardnav: '[ toggle-all ] [current-title]'
QR:
'QR.personas': [
'#email:"sage";boards:jp;always'
].join '\n'
time: '%m/%d/%y(%a)%H:%M:%S'
backlink: '>>%id'

View File

@ -8,6 +8,8 @@ Get =
if excerpt.length > 70
excerpt = "#{excerpt[...67]}..."
"/#{thread.board}/ - #{excerpt}"
threadFromRoot: (root) ->
g.threads["#{g.BOARD}.#{root.id[1..]}"]
postFromRoot: (root) ->
link = $ 'a[title="Highlight this post"]', root
boardID = link.pathname.split('/')[1]
@ -226,4 +228,4 @@ Get =
post = new Post Build.post(o, true), thread, board,
isArchived: true
Main.callbackNodes Post, [post]
Get.insert post, root, context
Get.insert post, root, context

View File

@ -135,22 +135,22 @@ Header =
for a in as
if a.textContent is board
a = a.cloneNode true
if /-title/.test t
a.textContent = a.title
else if /-replace/.test t
if $.hasClass a, 'current'
a.textContent = a.title
a.textContent = if /-title/.test(t) or /-replace/.test(t) and $.hasClass a, 'current'
a.title
else if /-full/.test t
a.textContent = "/#{board}/ - #{a.title}"
else if /-(index|catalog|text)/.test t
if m = t.match /-(index|catalog)/
a.setAttribute 'data-only', m[1]
a.href = "//boards.4chan.org/#{board}/"
a.href += 'catalog' if m[1] is 'catalog'
if m = t.match /-text:"(.+)"/
a.textContent = m[1]
else if board is '@'
$.addClass a, 'navSmall'
"/#{board}/ - #{a.title}"
else if m = t.match /-text:"(.+)"/
m[1]
else
a.textContent
if m = t.match /-(index|catalog)/
a.setAttribute 'data-only', m[1]
a.href = "//boards.4chan.org/#{board}/"
a.href += 'catalog' if m[1] is 'catalog'
$.addClass a, 'navSmall' if board is '@'
return a
$.tn t
$.add list, nodes

View File

@ -93,7 +93,7 @@ Main =
'Rice': Rice
'Banner': Banner
'Announcements': GlobalMessage
'Redirection': Redirect
'Archive Redirection': Redirect
'Header': Header
'Catalog Links': CatalogLinks
'Settings': Settings
@ -190,8 +190,19 @@ Main =
$.event '4chanXInitFinished'
Main.checkUpdate()
if styleSelector = $.id 'styleSelector'
passLink = $.el 'a',
textContent: '4chan Pass'
href: 'javascript:;'
$.on passLink, 'click', ->
window.open '//sys.4chan.org/auth',
'This will steal your data.'
'left=0,top=0,width=500,height=255,toolbar=0,resizable=0'
$.before styleSelector.previousSibling, [$.tn '['; passLink, $.tn ']\u00A0\u00A0']
return
$.event '4chanXInitFinished'
Main.checkUpdate()
@ -280,7 +291,7 @@ Main =
$.get items, (items) ->
if items.lastupdate > now - freq or items.lastchecked > now - $.DAY
return
$.ajax '<%= meta.page %><%= meta.buildsPath %>version', onload: ->
$.ajax '<%= meta.repo %>raw/<%= meta.mainBranch %>/<%= meta.buildsPath %>version', onload: ->
return unless @status is 200
version = @response
return unless /^\d\.\d+\.\d+$/.test version

View File

@ -334,10 +334,10 @@ Settings =
section.innerHTML = """
<%= grunt.file.read('src/General/html/Settings/Sauce.html').replace(/>\s+</g, '><').trim() %>
"""
sauce = $ 'textarea', section
ta = $ 'textarea', section
$.get 'sauces', Conf['sauces'], (item) ->
sauce.value = item['sauces']
$.on sauce, 'change', $.cb.value
ta.value = item['sauces']
$.on ta, 'change', $.cb.value
advanced: (section) ->
section.innerHTML = """<%= grunt.file.read('src/General/html/Settings/Advanced.html').replace(/>\s+</g, '><').trim() %>"""
@ -353,6 +353,12 @@ Settings =
'input'
$.on input, event, $.cb.value
# Quick Reply Personas
ta = $ '.personafield', section
$.get 'QR.personas', Conf['QR.personas'], (item) ->
ta.value = item['QR.personas']
$.on ta, 'change', $.cb.value
# Archiver
archiver = $ 'select[name=archiver]', section
toSelect = Redirect.select g.BOARD.ID
@ -985,4 +991,4 @@ Settings =
userThemes = item["userThemes"]
userThemes[@id] = Themes[@id]
$.set 'userThemes', userThemes
$.rm @
$.rm @

941
src/General/css/style.css Normal file
View File

@ -0,0 +1,941 @@
/* General */
.dialog {
box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
border: 1px solid;
display: block;
padding: 0;
}
.captcha-img,
.field {
background-color: #FFF;
border: 1px solid #CCC;
-moz-box-sizing: border-box;
box-sizing: border-box;
color: #333;
font: 13px sans-serif;
outline: none;
transition: color .25s, border-color .25s;
transition: color .25s, border-color .25s;
}
.field::-moz-placeholder,
.field:hover::-moz-placeholder {
color: #AAA !important;
font-size: 13px !important;
opacity: 1.0 !important;
}
.captch-img:hover,
.field:hover {
border-color: #999;
}
.field:hover, .field:focus {
color: #000;
}
.field[disabled] {
background-color: #F2F2F2;
color: #888;
}
.move {
cursor: move;
overflow: hidden;
}
label, .favicon {
cursor: pointer;
}
a[href="javascript:;"] {
text-decoration: none;
}
.warning {
color: red;
}
#boardNavDesktop {
display: none !important;
}
/* 4chan style fixes */
.opContainer, .op {
display: block !important;
overflow: visible !important;
}
[hidden] {
display: none !important;
}
/* fixed, z-index */
#overlay,
#fourchanx-settings,
#qp, #ihover,
#navlinks, .fixed #header-bar,
#watcher,
:root.float #updater,
:root.float #thread-stats,
#qr {
position: fixed;
}
#fourchanx-settings {
z-index: 999;
}
#overlay {
z-index: 900;
}
#notifications {
z-index: 70;
}
#qp, #ihover {
z-index: 60;
}
#menu {
z-index: 50;
}
#navlinks, #updater, #thread-stats {
z-index: 40;
}
.fixed #header-bar.autohide {
z-index: 35;
}
#qr {
z-index: 30;
}
#watcher {
z-index: 20;
}
.fixed #header-bar {
z-index: 10;
}
/* Header */
.fixed.top body {
padding-top: 2em;
}
.fixed.bottom body {
padding-bottom: 2em;
}
.fixed #header-bar {
right: 0;
left: 0;
padding: 3px 4px 4px;
}
.fixed.top #header-bar {
top: 0;
}
.fixed.bottom #header-bar {
bottom: 0;
}
#header-bar {
border-width: 0;
transition: all .1s .05s ease-in-out;
}
.fixed.top #header-bar {
border-bottom-width: 1px;
}
.fixed.bottom #header-bar {
box-shadow: 0 -1px 2px rgba(0, 0, 0, .15);
border-top-width: 1px;
}
.fixed.bottom #header-bar .menu-button i {
border-top: none;
border-bottom: 6px solid;
}
#board-list {
text-align: center;
}
.fixed #header-bar.autohide:not(:hover) {
box-shadow: none;
transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
}
.fixed.top #header-bar.autohide:not(:hover) {
margin-bottom: -1em;
-webkit-transform: translateY(-100%);
transform: translateY(-100%);
}
.fixed.bottom #header-bar.autohide:not(:hover) {
-webkit-transform: translateY(100%);
transform: translateY(100%);
}
#scroll-marker {
left: 0;
right: 0;
height: 10px;
position: absolute;
}
#header-bar #scroll-marker {
display: none;
}
.fixed #header-bar #scroll-marker {
display: block;
}
.fixed.top #header-bar #scroll-marker {
top: 100%;
}
.fixed.bottom #header-bar #scroll-marker {
bottom: 100%;
}
#header-bar a:not(.entry):not(.close) {
text-decoration: none;
padding: 1px;
}
#header-bar input {
margin: 0;
vertical-align: bottom;
}
#shortcuts:empty {
display: none;
}
.brackets-wrap::before {
content: "\\00a0[";
}
.brackets-wrap::after {
content: "]\\00a0";
}
.disabled,
.expand-all-shortcut {
opacity: .45;
}
#shortcuts {
float: right;
}
#navbotright,
#navtopright {
display: none;
}
#toggleMsgBtn {
display: none !important;
}
/* Notifications */
#notifications {
position: fixed;
top: 0;
height: 0;
text-align: center;
right: 0;
left: 0;
transition: all .8s .6s cubic-bezier(.55, .055, .675, .19);
}
.fixed.top #header-bar #notifications {
position: absolute;
top: 100%;
}
.notification {
color: #FFF;
font-weight: 700;
text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
border-radius: 2px;
margin: 1px auto;
width: 500px;
max-width: 100%;
position: relative;
transition: all .25s ease-in-out;
}
.notification.error {
background-color: hsla(0, 100%, 38%, .9);
}
.notification.warning {
background-color: hsla(36, 100%, 38%, .9);
}
.notification.info {
background-color: hsla(200, 100%, 38%, .9);
}
.notification.success {
background-color: hsla(104, 100%, 38%, .9);
}
.notification a {
color: white;
}
.notification > .close {
padding: 6px;
top: 0;
right: 5px;
position: absolute;
}
.message {
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 6px 20px;
max-height: 200px;
width: 100%;
overflow: auto;
}
/* Settings */
:root.fourchan-x body {
-moz-box-sizing: border-box;
box-sizing: border-box;
}
#overlay {
background-color: rgba(0, 0, 0, .5);
top: 0;
left: 0;
height: 100%;
width: 100%;
}
#fourchanx-settings {
-moz-box-sizing: border-box;
box-sizing: border-box;
box-shadow: 0 0 15px rgba(0, 0, 0, .15);
height: 600px;
min-height: 0;
max-height: 100%;
width: 900px;
min-width: 0;
max-width: 100%;
margin: auto;
padding: 3px;
top: 50%;
left: 50%;
-moz-transform: translate(-50%, -50%);
-webkit-transform: translate(-50%, -50%);
-o-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
#fourchanx-settings > nav {
padding: 2px 2px 0;
height: 15px;
}
#fourchanx-settings > nav a {
text-decoration: underline;
}
#fourchanx-settings > nav a.close {
text-decoration: none;
padding: 2px;
}
.section-container {
overflow: auto;
position: absolute;
top: 2.1em;
right: 5px;
bottom: 5px;
left: 5px;
padding-right: 5px;
}
.sections-list {
padding: 0 3px;
float: left;
}
.credits {
float: right;
}
.tab-selected {
font-weight: 700;
}
.section-sauce ul,
.section-advanced ul {
list-style: none;
margin: 0;
}
.section-sauce ul {
padding: 8px;
}
.section-advanced ul {
padding: 0px;
}
.section-sauce li,
.section-advanced li {
padding-left: 4px;
}
.section-main label {
text-decoration: underline;
}
.section-filter ul {
padding: 0;
}
.section-filter li {
margin: 10px 40px;
}
.section-filter textarea {
height: 500px;
}
.section-sauce textarea {
height: 350px;
}
.section-advanced .field[name="boardnav"] {
width: 100%;
}
.section-advanced textarea {
height: 150px;
}
#fourchanx-settings fieldset {
border: 1px solid;
border-radius: 3px;
}
#fourchanx-settings legend {
font-weight: 700;
}
#fourchanx-settings textarea {
font-family: monospace;
min-width: 100%;
max-width: 100%;
}
#fourchanx-settings code {
color: #000;
background-color: #FFF;
padding: 0 2px;
}
.unscroll {
overflow: hidden;
}
/* Announcement Hiding */
:root.hide-announcement #globalMessage {
display: none;
}
a.hide-announcement {
float: left;
}
/* Unread */
#unread-line {
margin: 0;
border-color: rgb(255,0,0);
}
/* Thread Updater */
#updater {
background: none;
border: none;
box-shadow: none;
}
#updater > .move {
padding: 5px 3px 0px;
margin-bottom: -3px;
}
#updater > div:last-child {
text-align: center;
}
#updater input[type=number] {
width: 4em;
}
:root.float #updater {
padding: 0px 3px;
}
.new {
color: limegreen;
}
#update-status.warning, #update-status.new {
margin-right: 5px;
}
#update-timer {
cursor: pointer;
}
/* Thread Watcher */
#watcher {
padding-bottom: 3px;
overflow: hidden;
white-space: nowrap;
}
#watcher:not(:hover) {
max-height: 220px;
}
#watcher > .move {
padding-top: 3px;
}
#watcher > div {
max-width: 200px;
overflow: hidden;
padding-left: 3px;
padding-right: 3px;
text-overflow: ellipsis;
}
#watcher a {
text-decoration: none;
}
/* Thread Stats */
#thread-stats {
background: none;
border: none;
box-shadow: none;
}
:root.float #post-count, :root.float #file-count {
pointer-events: none;
}
:root.float #thread-stats {
padding: 0px 3px;
}
/* Quote */
.deadlink {
text-decoration: none !important;
}
.backlink.deadlink:not(.forwardlink), .quotelink.deadlink:not(.forwardlink) {
text-decoration: underline !important;
}
.inlined {
opacity: .5;
}
#qp input, .forwarded {
display: none;
}
.quotelink.forwardlink,
.backlink.forwardlink {
text-decoration: none;
border-bottom: 1px dashed;
}
.filtered {
text-decoration: underline line-through;
}
.inline {
border: 1px solid;
display: table;
margin: 2px 0;
}
.inline .post {
border: 0 !important;
background-color: transparent !important;
display: table !important;
margin: 0 !important;
padding: 1px 2px !important;
}
#qp > .opContainer::after {
content: '';
clear: both;
display: table;
}
#qp .post {
border: none;
margin: 0;
padding: 2px 2px 5px;
}
#qp img {
max-height: 300px;
max-width: 500px;
max-height: 80vh;
max-width: 50vw;
}
.qphl {
outline: 2px solid rgba(216, 94, 49, .7);
}
.highlight-own .yourPost>.reply {
border-left: 2px solid rgba(221,0,0,.5);
}
/* Quote Threading */
.threadContainer {
margin-left: 20px;
border-left: 1px solid rgba(128,128,128,.3);
}
.threadOP {
clear: both;
}
/* File */
.fileText:hover .fntrunc,
.fileText:not(:hover) .fnfull,
.expanded-image > .post > .file > .fileThumb > img[data-md5],
:not(.expanded-image) > .post > .file > .fileThumb > .full-image {
display: none;
}
.expanding {
opacity: .5;
}
.expanded-image {
clear: both;
}
.expanded-image > .op > .file::after {
content: '';
clear: both;
display: table;
}
:root.fit-width .full-image {
max-width: 100%;
}
:root.gecko.fit-width .full-image,
:root.presto.fit-width .full-image {
width: 100%;
}
#ihover {
-moz-box-sizing: border-box;
box-sizing: border-box;
max-height: 100%;
max-width: 75%;
padding-bottom: 16px;
}
.fappeTyme .thread > .noFile,
.fappeTyme .threadContainer > .noFile {
display: none;
}
/* Index/Reply Navigation */
#navlinks {
font-size: 16px;
top: 25px;
right: 10px;
}
/* Filter */
.opContainer.filter-highlight {
box-shadow: inset 5px 0 rgba(255, 0, 0, .5);
}
.filter-highlight > .reply {
box-shadow: -5px 0 rgba(255, 0, 0, .5);
}
/* Thread & Reply Hiding */
.hide-thread-button,
.hide-reply-button {
float: left;
margin-right: 2px;
}
.stub ~ * {
display: none !important;
}
.stub input {
display: inline-block;
}
/* QR */
:root.hide-original-post-form #postForm,
:root.hide-original-post-form .postingMode,
:root.hide-original-post-form #togglePostForm,
#qr.autohide:not(.has-focus):not(:hover) > form,
.postingMode ~ #qr select,
#file-n-submit:not(.has-file) #qr-filerm {
display: none;
}
#qr select, #dump-button, .remove, .captcha-img {
cursor: pointer;
}
#qr {
z-index: 20;
position: fixed;
padding: 1px;
border: 1px solid transparent;
min-width: 248px;
border-radius: 3px 3px 0 0;
}
#qrtab {
border-radius: 3px 3px 0 0;
}
#qrtab {
margin-bottom: 1px;
}
#qr .close {
float: right;
padding: 0 3px;
}
#qr .warning {
min-height: 1.6em;
vertical-align: middle;
padding: 0 1px;
border-width: 1px;
border-style: solid;
}
.qr-link {
text-align: center;
}
.persona {
width: 248px;
max-width: 100%;
min-width: 100%;
}
#dump-button {
background: linear-gradient(#EEE, #CCC);
border: 1px solid #CCC;
width: 10%;
margin: 0;
font: 13px sans-serif;
padding: 1px 0px 2px;
}
.persona .field:not(#dump) {
width: 95px;
min-width: 30%;
max-width: 30%;
}
#qr textarea.field {
height: 14.8em;
min-height: 9em;
}
#qr.has-captcha textarea.field {
height: 9em;
}
input.field.tripped:not(:hover):not(:focus) {
color: transparent !important; text-shadow: none !important;
}
#qr textarea {
resize: both;
}
.captcha-img {
margin: 0px;
text-align: center;
background-image: #fff;
font-size: 0px;
min-height: 59px;
min-width: 302px;
}
.captcha-input {
width: 100%;
margin: 1px 0 0;
}
.field {
-moz-box-sizing: border-box;
margin: 0px;
padding: 2px 4px 3px;
}
#qr textarea {
min-width: 100%;
}
#qr [type='submit'] {
width: 25%;
vertical-align: top;
}
/* Fake File Input */
#qr-filename,
.has-file #qr-no-file {
display: none;
}
#qr-no-file,
.has-file #qr-filename {
display: block;
padding: 0px 4px;
margin-bottom: 2px;
overflow: hidden;
text-overflow: ellipsis;
}
#qr-no-file {
color: #AAA;
}
#qr-filename-container {
-moz-box-sizing: border-box;
display: inline-block;
position: relative;
width: 100px;
min-width: 74.6%;
max-width: 74.6%;
margin-right: 0.4%;
margin-top: 1px;
overflow: hidden;
padding: 2px 1px 0;
height: 22px;
}
#qr-filename-container:hover {
cursor: text;
}
#qr-filerm {
position: relative;
right: 14px;
bottom: 6px;
margin-right: -8px;
z-index: 2;
}
#file-n-submit {
height: 23px;
}
#qr input[type=file] {
display: none;
}
/* Thread Select / Spoiler Label */
#qr select {
float: right;
}
/* Dumping UI */
.dump #dump-list-container {
display: block;
}
#dump-list-container {
display: none;
position: relative;
overflow-y: hidden;
margin-top: 1px;
}
#dump-list {
overflow-x: auto;
overflow-y: hidden;
white-space: pre;
width: 248px;
max-width: 100%;
min-width: 100%;
}
#dump-list:hover {
overflow-x: auto;
}
.qr-preview {
-moz-box-sizing: border-box;
counter-increment: thumbnails;
cursor: move;
display: inline-block;
height: 90px;
width: 90px;
padding: 2px;
opacity: .5;
overflow: hidden;
position: relative;
text-shadow: 0 1px 1px #000;
-moz-transition: opacity .25s ease-in-out;
vertical-align: top;
}
.qr-preview:hover,
.qr-preview:focus {
opacity: .9;
}
.qr-preview::before {
content: counter(thumbnails);
color: #fff;
position: absolute;
top: 3px;
right: 3px;
text-shadow: 0 0 3px #000, 0 0 8px #000;
}
.qr-preview#selected {
opacity: 1;
}
.qr-preview.drag {
box-shadow: 0 0 10px rgba(0,0,0,.5);
}
.qr-preview.over {
border-color: #fff;
}
.qr-preview > span {
color: #fff;
}
.remove {
background: none;
color: #e00;
font-weight: 700;
padding: 3px;
}
a:only-of-type > .remove {
display: none;
}
.remove:hover::after {
content: " Remove";
}
.qr-preview > label {
background: rgba(0,0,0,.5);
color: #fff;
right: 0; bottom: 0; left: 0;
position: absolute;
text-align: center;
}
.qr-preview > label > input {
margin: 0;
}
#add-post {
cursor: pointer;
font-size: 2em;
position: absolute;
top: 50%;
right: 10px;
-moz-transform: translateY(-50%);
}
.textarea {
position: relative;
}
#char-count {
color: #000;
background: hsla(0, 0%, 100%, .5);
font-size: 8pt;
position: absolute;
bottom: 1px;
right: 1px;
pointer-events: none;
}
/* Menu */
.menu-button {
display: inline-block;
position: relative;
cursor: pointer;
}
.menu-button i {
border-top: 6px solid;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
display: inline-block;
margin: 2px;
vertical-align: middle;
}
#menu {
position: fixed;
outline: none;
}
.entry {
border-bottom: 1px solid rgba(0,0,0,.25);
cursor: pointer;
display: block;
outline: none;
padding: 3px 7px;
position: relative;
text-decoration: none;
white-space: nowrap;
}
.left>.entry.has-submenu {
padding-right: 17px !important;
}
.entry:last-child {
border-bottom: 0;
}
.has-submenu::after {
content: "";
border-left: .5em solid;
border-top: .3em solid transparent;
border-bottom: .3em solid transparent;
display: inline-block;
margin: .3em;
position: absolute;
right: 3px;
}
.left .has-submenu::after {
border-left: 0;
border-right: .5em solid;
}
.submenu {
display: none;
position: absolute;
left: 100%;
top: -1px;
}
.focused .submenu {
display: block;
}
.imp-exp-result {
position: absolute;
text-align: center;
margin: auto;
right: 0px;
left: 0px;
width: 200px;
}
.export, .import {
cursor: pointer;
text-decoration: none !important;
}
/* Link Title Favicons */
.linkify.YouTube {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/youtube.png", {encoding: "base64"}) %>') center left no-repeat!important;
padding-left: 18px;
}
.linkify.Vimeo {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/vimeo.png", {encoding: "base64"}) %>') center left no-repeat!important;
padding-left: 18px;
}
.linkify.SoundCloud {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/soundcloud.png", {encoding: "base64"}) %>') center left no-repeat!important;
padding-left: 18px;
}
.linkify.audio {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/audio.png", {encoding: "base64"}) %>') center left no-repeat!important;
padding-left: 18px;
}
.linkify.LiveLeak {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/liveleak.png", {encoding: "base64"}) %>') center left no-repeat!important;
padding-left: 18px;
}
.linkify.Vocaroo {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/vocaroo.png", {encoding: "base64"}) %>') center left no-repeat!important;
padding-left: 18px;
}
.linkify.pastebin {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/pastebin.png", {encoding: "base64"}) %>') center left no-repeat!important;
padding-left: 18px;
}
.linkify.gist {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/gist.png", {encoding: "base64"}) %>') center left no-repeat!important;
padding-left: 18px;
}
.linkify.image {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/image.png", {encoding: "base64"}) %>') center left no-repeat!important;
padding-left: 18px;
}
.linkify.InstallGentoo {
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/installgentoo.png", {encoding: "base64"}) %>') center left no-repeat!important;
padding-left: 18px;
}

View File

@ -5,12 +5,12 @@
<form>
<div class=persona>
<input id=dump-button type=button title='Dump list' value=+ tabindex=0>
<input name=name data-name=name title=Name placeholder=Name class=field size=1 tabindex=10>
<input name=email data-name=email title=E-mail placeholder=E-mail class=field size=1 tabindex=20>
<input name=sub data-name=sub title=Subject placeholder=Subject class=field size=1 tabindex=30>
<input name=name data-name=name list="list-name" placeholder=Name class=field size=1 tabindex=10>
<input name=email data-name=email list="list-email" placeholder=E-mail class=field size=1 tabindex=20>
<input name=sub data-name=sub list="list-sub" placeholder=Subject class=field size=1 tabindex=30>
</div>
<div class=textarea>
<textarea data-name=com title=Comment placeholder=Comment class=field tabindex=40></textarea>
<textarea data-name=com placeholder=Comment class=field tabindex=40></textarea>
<span id=char-count></span>
</div>
<div id=dump-list-container>
@ -35,3 +35,6 @@
<input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=90>Spoiler?
</label>
</form>
<datalist id="list-name"></datalist>
<datalist id="list-email"></datalist>
<datalist id="list-sub"></datalist>

View File

@ -54,6 +54,23 @@
<div>Resolution: <code>%r</code> (Displays 'PDF' for PDF files)</div>
</fieldset>
<fieldset>
<legend>Quick Reply Personas <span class="warning" #{if Conf['Quick Reply'] then 'hidden' else ''}>is disabled.</span></legend>
<textarea class=personafield name="QR.personas" class="field" spellcheck="false"></textarea>
<p>
One item per line.<br>
Items will be added in the relevant input's auto-completion list.<br>
Password items will always be used, since there is no password input.<br>
Lines starting with a <code>#</code> will be ignored.
</p>
<ul>You can use these settings with each item, separate them with semicolons:
<li>Possible items are: <code>name</code>, <code>email</code>, <code>subject</code> and <code>password</code>.</li>
<li>Wrap values of items with quotes, like this: <code>email:"sage"</code>.</li>
<li>Force values as defaults with the <code>always</code> keyword, for example: <code>email:"sage";always</code>.</li>
<li>Select specific boards for an item, separated with commas, for example: <code>email:"sage";boards:jp;always</code>.</li>
</ul>
</fieldset>
<fieldset>
<legend>Unread Favicon <span class=warning #{if Conf['Unread Favicon'] then 'hidden' else ''}>is disabled.</span></legend>
<div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 863 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

View File

@ -16,14 +16,14 @@ Array::contains = (object) ->
Array::indexOf = (object) ->
i = @length
while i--
break if @[i] is object
return i if @[i] is object
return i
Array::pushArrays = ->
args = arguments
for arg in args
@push.apply @, arg
return
return @
Array::remove = (object) ->
if (index = @indexOf object) > -1
@ -133,7 +133,8 @@ $.x = (path, root) ->
$.X = (path, root) ->
root or= d.body
d.evaluate path, root, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null
# XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE === 6
d.evaluate path, root, null, 6, null
$.addClass = (el, className) ->
el.classList.add className
@ -148,7 +149,7 @@ $.hasClass = (el, className) ->
el.classList.contains className
$.rm = do ->
if 'remove' of Element.prototype
if 'remove' of Element::
(el) -> el.remove()
else
(el) -> el.parentNode?.removeChild el
@ -373,7 +374,7 @@ $.set = do ->
items = {}
localItems = {}
catch err
c.error err
c.error err.stack
(key, val) ->
if typeof key is 'string'

View File

@ -0,0 +1 @@
<%= version %>

View File

@ -2,6 +2,26 @@ Linkify =
init: ->
return if g.VIEW is 'catalog' or not Conf['Linkify']
@regString = if Conf['Allow False Positives']
///(
\b(
[a-z]+://
|
[a-z]{3,}\.[-a-z0-9]+\.[a-z]+
|
[-a-z0-9]+\.[a-z]
|
[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+
|
[a-z]{3,}:[a-z0-9?]
|
[a-z0-9._%+-:]+@[a-z0-9.-]+\.[a-z0-9]
)
[^\s'"]+
)///gi
else
/(((magnet|mailto)\:|(www\.)|(news|(ht|f)tp(s?))\:\/\/){1}\S+)/gi
if Conf['Comment Expansion']
ExpandComment.callbacks.push @node
@ -9,23 +29,6 @@ Linkify =
name: 'Linkify'
cb: @node
regString: ///(
\b(
[a-z]+://
|
[a-z]{3,}\.[-a-z0-9]+\.[a-z]+
|
[-a-z0-9]+\.[a-z]
|
[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+
|
[a-z]{3,}:[a-z0-9?]
|
[a-z0-9._%+-:]+@[a-z0-9.-]+\.[a-z0-9]
)
[^\s'"]+
)///gi
cypher: $.el 'div'
node: ->
@ -44,6 +47,7 @@ Linkify =
data = node.data
# Test for valid links
continue unless node.parentNode and Linkify.regString.test data
Linkify.regString.lastIndex = 0
@ -125,21 +129,17 @@ Linkify =
textContent: @getAttribute("data-title") or url
@textContent = '(embed)'
$.addClass el, "#{@getAttribute 'data-service'}"
# Embed
else
# We create an element to embed
el = (type = Linkify.types[@getAttribute("data-service")]).el.call @
# Set style values.
if style = type.style
el.style.cssText = style
el.style.cssText = if style = type.style
style
else
items =
'embedWidth': Config['embedWidth']
'embedHeight': Config['embedHeight']
$.get items, (items) ->
el.style.cssText = "border: 0; width: #{items['embedWidth']}px; height: #{items['embedHeight']}px"
"border: 0; width: 640px; height: 390px"
@textContent = '(unembed)'
@ -148,10 +148,10 @@ Linkify =
types:
YouTube:
regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*).*/
regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*)\??(t\=.*)?/
el: ->
$.el 'iframe',
src: "//www.youtube.com/embed/#{@name}"
src: "//www.youtube.com/embed/#{@name}#{if @option then '#' + @option else ''}"
title:
api: -> "https://gdata.youtube.com/feeds/api/videos/#{@name}?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode"
text: -> JSON.parse(@responseText).entry.title.$t
@ -186,33 +186,63 @@ Linkify =
preload: 'auto'
src: @name
image:
regExp: /(http|www).*\.(gif|png|jpg|jpeg|bmp)$/
style: 'border: 0; width: auto; height: auto;'
el: ->
$.el 'div',
innerHTML: "<a target=_blank href='#{@getAttribute 'data-originalURL'}'><img src='#{@getAttribute 'data-originalURL'}'></a>"
SoundCloud:
regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/
regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/
style: 'height: auto; width: 500px; display: inline-block;'
el: ->
div = $.el 'div',
className: "soundcloud"
name: "soundcloud"
name: "soundcloud"
$.ajax(
"//soundcloud.com/oembed?show_artwork=false&&maxwidth=500px&show_comments=false&format=json&url=#{@getAttribute 'data-originalURL'}&color=#{Style.colorToHex Themes[Conf['theme']]['Background Color']}"
"//soundcloud.com/oembed?show_artwork=false&&maxwidth=500px&show_comments=false&format=json&url=https://www.soundcloud.com/#{@name}"
div: div
onloadend: ->
@div.innerHTML = JSON.parse(@responseText).html
false)
div
title:
api: -> "//soundcloud.com/oembed?show_artwork=false&&maxwidth=500px&show_comments=false&format=json&url=https://www.soundcloud.com/#{@name}"
text: -> JSON.parse(@responseText).title
pastebin:
regExp: /.*(?:pastebin.com\/)([^#\&\?]*).*/
regExp: /.*(?:pastebin.com\/(?!u\/))([^#\&\?]*).*/
el: ->
div = $.el 'iframe',
src: "http://pastebin.com/embed_iframe.php?i=#{@name}"
gist:
regExp: /.*(?:gist.github.com.*\/)([^\/][^\/]*)$/
el: ->
div = $.el 'iframe',
# Github doesn't allow embedding straight from the site, so we use an external site to bypass that.
src: "http://www.purplegene.com/script?url=https://gist.github.com/#{@name}.js"
title:
api: -> "https://api.github.com/gists/#{@name}"
text: ->
response = JSON.parse(@responseText).files
return file for file of response when response.hasOwnProperty file
InstallGentoo:
regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/
el: ->
$.el 'iframe',
src: "http://paste.installgentoo.com/view/embed/#{@name}"
embedder: (a) ->
return [a] unless Conf['Embedding']
return [a] unless Conf['Link Title']
titles = {}
callbacks = ->
a.textContent = switch @status
when 200, 304
title = "[#{embed.getAttribute 'data-service'}] #{service.text.call @}"
title = "#{service.text.call @}"
embed.setAttribute 'data-title', title
titles[embed.name] = [title, Date.now()]
$.set 'CachedTitles', titles
@ -229,19 +259,23 @@ Linkify =
embed = $.el 'a',
name: (a.name = match[1])
option: match[2]
className: 'embedder'
href: 'javascript:;'
textContent: '(embed)'
embed.setAttribute 'data-service', key
embed.setAttribute 'data-originalURL', a.href
$.addClass a, "#{embed.getAttribute 'data-service'}"
$.on embed, 'click', Linkify.toggle
unless Conf['Embedding']
embed.hidden = true
if Conf['Link Title'] and (service = type.title)
$.get 'CachedTitles', {}, (item) ->
titles = item['CachedTitles']
if title = titles[match[1]]
a.textContent = title[0]
embed.setAttribute 'data-title', title[0]

View File

@ -46,18 +46,12 @@ DeleteLink =
$.off @, 'click', DeleteLink.delete
@textContent = "Deleting #{@textContent}..."
pwd =
if m = d.cookie.match /4chan_pass=([^;]+)/
decodeURIComponent m[1]
else
$.id('delPassword').value
fileOnly = $.hasClass @, 'delete-file'
form =
mode: 'usrdel'
onlyimgdel: fileOnly
pwd: pwd
pwd: QR.persona.getPassword()
form[post.ID] = 'delete'
link = @
@ -106,4 +100,4 @@ DeleteLink =
delete DeleteLink.cooldown.counting
return
setTimeout DeleteLink.cooldown.count, 1000, post, seconds - 1, length, node
node.textContent = "Delete (#{seconds})"
node.textContent = "Delete (#{seconds})"

View File

@ -12,7 +12,7 @@ ExpandComment =
cb: @node
node: ->
if a = $ '.abbr > a', @nodes.comment
if a = $ '.abbr > a:not([onclick])', @nodes.comment
$.on a, 'click', ExpandComment.cb
callbacks: []
@ -69,4 +69,4 @@ ExpandComment =
for callback in ExpandComment.callbacks
callback.call post
return
return

View File

@ -41,7 +41,10 @@ Keybinds =
for notification in notifications
$('.close', notification).click()
else if QR.nodes
QR.close()
if Conf['Persistent QR']
QR.hide()
else
QR.close()
when Conf['Spoiler tags']
return if target.nodeName isnt 'TEXTAREA'
Keybinds.tags 'spoiler', target

View File

@ -42,7 +42,9 @@ Nav =
else
headRect = Header.bar.getBoundingClientRect()
topMargin = headRect.top + headRect.height
threads = $$ '.thread:not([hidden])'
threads = $$('.thread').filter (thread) ->
thread = Get.threadFromRoot thread
!(thread.isHidden and !thread.stub)
for thread, i in threads
rect = thread.getBoundingClientRect()
if rect.bottom > topMargin # not scrolled past
@ -60,4 +62,4 @@ Nav =
i += delta
top = threads[i]?.getBoundingClientRect().top - topMargin
window.scrollBy 0, top
window.scrollBy 0, top

View File

@ -1,14 +1,20 @@
ThreadStats =
init: ->
return if g.VIEW isnt 'thread' or !Conf['Thread Stats']
@dialog = sc = $.el 'span',
innerHTML: "<span id=post-count>0</span> / <span id=file-count>0</span></div>"
id: 'thread-stats'
if Conf['Updater and Stats in Header']
@dialog = sc = $.el 'span',
innerHTML: "<span id=post-count>0</span> / <span id=file-count>0</span>"
id: 'thread-stats'
Header.addShortcut sc
else
@dialog = sc = UI.dialog 'thread-stats', 'bottom: 0px; right: 0px;',
"<div class=move><span id=post-count>0</span> / <span id=file-count>0</span></div>"
$.ready =>
$.add d.body, sc
@postCountEl = $ '#post-count', sc
@fileCountEl = $ '#file-count', sc
Header.addShortcut sc
Thread::callbacks.push
name: 'Thread Stats'

View File

@ -3,20 +3,28 @@ ThreadUpdater =
return if g.VIEW isnt 'thread' or !Conf['Thread Updater']
checked = if Conf['Auto Update'] then 'checked' else ''
@dialog = sc = $.el 'span',
innerHTML: "<span id=update-status></span><span id=update-timer title='Update now'></span>"
id: 'updater'
@timer = $ '#update-timer', sc
if Conf['Updater and Stats in Header']
@dialog = sc = $.el 'span',
innerHTML: "<span id=update-status></span><span id=update-timer title='Update now'></span>"
id: 'updater'
Header.addShortcut sc
else
@dialog = sc = UI.dialog 'updater', 'bottom: 0px; left: 0px;',
"<div class=move></div><span id=update-status></span><span id=update-timer title='Update now'></span>"
$.addClass doc, 'float'
$.ready =>
$.addClass doc, 'float'
$.add d.body, sc
@checkPostCount = 0
@timer = $ '#update-timer', sc
@status = $ '#update-status', sc
$.on @timer, 'click', ThreadUpdater.update
$.on @status, 'click', ThreadUpdater.update
@checkPostCount = 0
Header.addShortcut sc
subEntries = []
for name, conf of Config.updater.checkbox
checked = if Conf[name] then 'checked' else ''

View File

@ -39,6 +39,7 @@ ThreadWatcher =
for id, props of watched[board]
x = $.el 'a',
textContent: '×'
className: 'close'
href: 'javascript:;'
$.on x, 'click', ThreadWatcher.cb.x
link = $.el 'a', props

View File

@ -149,6 +149,73 @@ QR =
value
status.disabled = disabled or false
persona:
pwd: ''
always: {}
init: ->
QR.persona.getPassword()
$.get 'QR.personas', Conf['QR.personas'], ({'QR.personas': personas}) ->
types =
name: []
email: []
sub: []
for item in personas.split '\n'
QR.persona.parseItem item.trim(), types
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
[match, type, val] = match
# Don't mix up item settings with val.
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 ',')
return
if type is 'password'
QR.persona.pwd = val
return
type = 'sub' if type is 'subject'
if /always/i.test item
QR.persona.always[type] = val
unless val in types[type]
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=([^;]+)/
decodeURIComponent m[1]
else if input = $.id 'postPassword'
input.value
else
# If we're in a closed thread, #postPassword isn't available.
# And since #delPassword.value is only filled on window.onload
# 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 =
name: post.name
email: if /^sage$/.test post.email then persona.email else post.email
sub: if Conf['Remember Subject'] then post.sub else undefined
$.set 'QR.persona', persona
cooldown:
init: ->
return unless Conf['Cooldown']
@ -428,18 +495,27 @@ QR =
prev.spoiler
else
false
$.get 'QR.persona', {}, (item) =>
persona = item['QR.persona']
@name = if prev
QR.persona.get (persona) =>
@name = if 'name' of QR.persona.always
QR.persona.always.name
else if prev
prev.name
else
persona.name
@email = if prev and !/^sage$/.test prev.email
@email = if 'email' of QR.persona.always
QR.persona.always.email
else if prev and !/^sage$/.test prev.email
prev.email
else
persona.email
if Conf['Remember Subject']
@sub = if prev then prev.sub else persona.sub
@sub = if 'sub' of QR.persona.always
QR.persona.always.sub
else if Conf['Remember Subject']
if prev then prev.sub else persona.sub
else
''
@load() if QR.selected is @ # load persona
@select() if select
@unlock()
@ -878,8 +954,9 @@ QR =
return if e.button isnt 0
$.set 'QR Size', @style.cssText
<% } %>
new QR.post true
QR.persona.init()
new QR.post true
QR.status()
QR.cooldown.init()
QR.captcha.init()
@ -970,7 +1047,7 @@ QR =
spoiler: post.spoiler
textonly: textOnly
mode: 'regist'
pwd: if m = d.cookie.match(/4chan_pass=([^;]+)/) then decodeURIComponent m[1] else $.id('postPassword').value
pwd: QR.persona.pwd
recaptcha_challenge_field: challenge
recaptcha_response_field: response
@ -1068,18 +1145,15 @@ QR =
QR.status()
QR.error err
return
h1 = $ 'h1', tmpDoc
QR.cleanNotifications()
QR.notifications.push new Notification 'success', h1.textContent, 5
h1 = $ 'h1', tmpDoc
if Conf['Posting Success Notifications']
QR.notifications.push new Notification 'success', h1.textContent, 5
$.get 'QR.persona', {}, (item) ->
persona = item['QR.persona']
persona =
name: post.name
email: if /^sage$/.test post.email then persona.email else post.email
sub: if Conf['Remember Subject'] then post.sub else null
$.set 'QR.persona', persona
QR.persona.set post
[_, threadID, postID] = h1.nextSibling.textContent.match /thread:(\d+),no:(\d+)/
postID = +postID