Merge remote-tracking branch 'upstream/master'

This commit is contained in:
7185 2014-12-22 01:23:10 +01:00
commit 5c2d5a9494
928 changed files with 2121 additions and 317 deletions

View File

@ -1,6 +1,7 @@
<?php
include "inc/functions.php";
require_once "inc/functions.php";
header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
$dir = "static/404/";
@ -35,8 +36,8 @@ $page = <<<EOT
var faves = JSON.parse(localStorage.favorites);
$.each(faves, function(k, v) {
if (window.location.pathname === '/' + v + '/') {
faves.pop(v);
if ((window.location.pathname === '/' + v + '/') || (window.location.pathname === '/' + v)) {
faves.splice(k, 1);
localStorage.favorites = JSON.stringify(faves);
alert('As /' + v + '/ no longer exists, it has been removed from your favorites.');

140
README.md
View File

@ -3,145 +3,23 @@
About
------------
8chan is a free light-weight, fast, highly configurable and user-friendly
imageboard software package. It is written in PHP and has few dependencies.
8chan is a fork of vichan, with the difference that 8chan is geared towards allowing users to create their own boards.
8chan is a fork of [vichan](https://github.com/vichan-devel/vichan), which is a fork of [Tinyboard](http://tinyboard.org/), a great imageboard package, actively
building on it and adding a lot of features and other improvements.
Most things (other than installation) that apply to upstream vichan also apply to 8chan. See their readme for a detailed FAQ: https://github.com/vichan-devel/vichan/blob/master/README.md
Support and announcements: https://int.vichan.net/devel/
If you are not interested in letting your users make their own boards, install vichan instead of 8chan.
Requirements
------------
1. PHP >= 5.3
2. MySQL/MariaDB server
3. [mbstring](http://www.php.net/manual/en/mbstring.installation.php)
4. [PHP GD](http://www.php.net/manual/en/intro.image.php)
5. [PHP PDO](http://www.php.net/manual/en/intro.pdo.php)
We try to make sure vichan is compatible with all major web servers and
operating systems. vichan does not include an Apache ```.htaccess``` file nor does
it need one.
### Recommended
1. MySQL/MariaDB server >= 5.5.3
2. ImageMagick (command-line ImageMagick or GraphicsMagick preferred).
3. [APC (Alternative PHP Cache)](http://php.net/manual/en/book.apc.php),
[XCache](http://xcache.lighttpd.net/) or
[Memcached](http://www.php.net/manual/en/intro.memcached.php)
Contributing
------------
You can contribute to vichan by:
* Developing patches/improvements/translations and using GitHub to submit pull requests
* Providing feedback and suggestions
* Writing/editing documentation
If you need help developing a patch, please join our IRC channel.
Installation
-------------
1. Download and extract Tinyboard to your web directory or get the latest
development version with:
git clone git://github.com/ctrlcctrlv/8chan.git
2. Copy secrets.example.php to secrets.php and edit it.
3. Navigate to ```install.php``` in your web browser and follow the
prompts.
4. vichan should now be installed. Log in to ```mod.php``` with the
default username and password combination: **admin / password**.
Please remember to change the administrator account password.
See also: [Configuration Basics](http://tinyboard.org/docs/?p=Config).
Upgrade
-------
To upgrade from any version of Tinyboard or vichan:
Either run ```git pull``` to update your files, if you used git, or
backup your ```inc/instance-config.php```, replace all your files in place
(don't remove boards etc.), then put ```inc/instance-config.php``` back and
finally run ```install.php```.
Support
--------
vichan is still beta software -- there are bound to be bugs. If you find a
bug, please report it.
If you need assistance with installing, configuring, or using vichan, you may
find support from a variety of sources:
* If you're unsure about how to enable or configure certain features, make
sure you have read the comments in ```inc/config.php```.
* Check out an [official vichan board](http://int.vichan.net/devel/).
* You can join vichan's IRC channel for support
[irc.6irc.net #vichan-devel](irc://irc.6irc.net/vichan-devel)
### Tinyboard support
8chan is based on a Tinyboard, so both engines have very much in common. These
links may be helpful for you as well:
* Tinyboard documentation can be found [here](http://tinyboard.org/docs/).
* You can join Tinyboard's IRC channel for support and general queries:
[irc.datnode.net #tinyboard](irc://irc.datnode.net/tinyboard).
* You may find help at [tinyboard.org](http://tinyboard.org/#help).
Donations
---------
Do you like our work? You can motivate us financially to do better ;)
* Bitcoin: [![tip for next commit](http://tip4commit.com/projects/708.svg)](http://tip4commit.com/projects/708)
You can also ask us to develop some feature specially for you <3. Join our IRC
channel and ask for a quote (there are a few of us, who work with the codebase
and are skilled enough to develop such features pretty quickly).
CLI tools
-----------------
There are a few command line interface tools, based on Tinyboard-Tools. These need
to be launched from a Unix shell account (SSH, or something). They are located in a ```tools/```
directory.
You actually don't need these tools for your imageboard functioning, they are aimed
at the power users. You won't be able to run these from shared hosting accounts
(i.e. all free web servers).
Localisation
------------
Want to have vichan/8chan in your language? You can contribute your translations at this URL:
https://www.transifex.com/projects/p/tinyboard-vichan-devel/
Oekaki
------
vichan makes use of [wPaint](https://github.com/websanova/wPaint) for oekaki. After you pull the repository, however, you will need to download wPaint separately using git's `submodule` feature. Use the following commands:
Because I cannot be bothered to maintain `install.php`, the install process is as such:
```
git submodule init
git submodule update
mysql -uroot 8chan < install.sql
echo '8chan' > .installed
```
To enable oekaki, add all the scripts listed in `js/wpaint.js` to your `instance-config.php`.
Here's my install script as of 11/14/2014 for the 8chan servers which run Ubuntu 14.04:
WebM support
------------
Read `inc/lib/webm/README.md` for information about enabling webm.
Static Pages
------------
Some pages like `/faq.html` need to be pre-generated:
```
$ php faq.php > faq.html
apt-get install graphicsmagick gifsicle php5-fpm mysql-client php5-mysql php5-cli php-pear php5-apcu; add-apt-repository ppa:jon-severinsson/ffmpeg; add-apt-repository ppa:nginx/stable; apt-get update; apt-get install nginx ffmpeg; pear install Net_DNS2
```
vichan API
----------
vichan provides by default a 4chan-compatible JSON API. For documentation on this, see:
https://github.com/vichan-devel/vichan-API/ .
License
--------
See [LICENSE.md](http://github.com/vichan-devel/vichan/blob/master/LICENSE.md).
Have fun!

View File

@ -1,7 +0,0 @@
<?php
require_once 'inc/functions.php';
checkBan();
print "<!doctype html><html><head><meta charset='utf-8'><title>"._("Banned?")."</title></head><body>";
print "<h1>"._("You are not banned.")."</h1>";
print "</body></html>";
?>

View File

@ -8,7 +8,7 @@ header("Expires: 0");
function get_custom_banner(&$b) {
# Validate the board name
if (!(isset($b) && preg_match('/^[a-z0-9]{1,10}$/', $b)))
if (!(isset($b) && preg_match('/^[a-z0-9+]{1,30}$/', $b)))
return null;
# Check if directory exists

View File

@ -10,10 +10,17 @@ if (php_sapi_name() == 'fpm-fcgi' && !$admin) {
error('Cannot be run directly.');
}
$boards = listBoards();
$all_tags = array();
$total_posts_hour = 0;
$total_posts = 0;
function to_tag($str) {
$str = trim($str);
$str = strtolower($str);
$str = str_replace(['_', ' '], '-', $str);
return $str;
}
foreach ($boards as $i => $board) {
//$query = prepare(sprintf("SELECT (SELECT MAX(id) from ``posts_%s``) AS max, (SELECT MAX(id) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) < DATE_SUB(NOW(), INTERVAL 1 HOUR)) AS oldmax, (SELECT MAX(id) from ``posts_%s``) AS max_d, (SELECT MAX(id) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time) < DATE_SUB(NOW(), INTERVAL 1 DAY)) AS oldmax_d, (SELECT count(id) FROM ``posts_%s``) AS count;", $board['uri'], $board['uri'], $board['uri'], $board['uri'], $board['uri']));
@ -28,6 +35,23 @@ SELECT MAX(id) max, (SELECT COUNT(*) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time)
$query->execute() or error(db_error($query));
$r = $query->fetch(PDO::FETCH_ASSOC);
$tquery = prepare("SELECT `tag` FROM ``board_tags`` WHERE `uri` = :uri");
$tquery->execute([":uri" => $board['uri']]) or error(db_error($tquery));
$r2 = $tquery->fetchAll(PDO::FETCH_ASSOC);
$tags = array();
if ($r2) {
foreach ($r2 as $ii => $t) {
$tag=to_tag($t['tag']);
$tags[] = $tag;
if (!isset($all_tags[$tag])) {
$all_tags[$tag] = (int)$r['uniq_ip'];
} else {
$all_tags[$tag] += $r['uniq_ip'];
}
}
}
$pph = $r['pph'];
$ppd = $r['ppd'];
@ -38,6 +62,7 @@ SELECT MAX(id) max, (SELECT COUNT(*) FROM ``posts_%s`` WHERE FROM_UNIXTIME(time)
$boards[$i]['ppd'] = $ppd;
$boards[$i]['max'] = $r['max'];
$boards[$i]['uniq_ip'] = $r['uniq_ip'];
$boards[$i]['tags'] = $tags;
}
usort($boards,
@ -86,14 +111,18 @@ foreach ($boards as $i => &$board) {
$n_boards = sizeof($boards);
$t_boards = $hidden_boards_total + $n_boards;
$boards = array_values($boards);
arsort($all_tags);
$config['additional_javascript'] = array('js/jquery.min.js', 'js/jquery.tablesorter.min.js');
$body = Element("8chan/boards.html", array("config" => $config, "n_boards" => $n_boards, "t_boards" => $t_boards, "hidden_boards_total" => $hidden_boards_total, "total_posts" => $total_posts, "total_posts_hour" => $total_posts_hour, "boards" => $boards, "last_update" => date('r'), "uptime_p" => shell_exec('uptime -p')));
$body = Element("8chan/boards-tags.html", array("config" => $config, "n_boards" => $n_boards, "t_boards" => $t_boards, "hidden_boards_total" => $hidden_boards_total, "total_posts" => $total_posts, "total_posts_hour" => $total_posts_hour, "boards" => $boards, "last_update" => date('r'), "uptime_p" => shell_exec('uptime -p'), 'tags' => $all_tags));
$html = Element("page.html", array("config" => $config, "body" => $body, "title" => "Boards on &infin;chan"));
if ($admin) {
echo $html;
} else {
file_write("boards.json", json_encode($boards));
file_write("tags.json", json_encode($all_tags));
foreach ($boards as $i => $b) {
if (in_array($b['uri'], $config['no_top_bar_boards'])) {
unset($boards[$i]);

View File

@ -1,5 +1,61 @@
<?php
include 'inc/functions.php';
if (php_sapi_name() == 'fpm-fcgi') {
error('Cannot be run directly.');
}
function last_activity($board) {
// last post
$query = prepare(sprintf("SELECT MAX(time) AS time FROM posts_%s", $board));
$query->execute();
$row = $query->fetch();
$ago = (new DateTime)->sub(new DateInterval('P1W'));
$mod_ago = (new DateTime)->sub(new DateInterval('P2W'));
error('Automatic claiming is no longer available. To claim a board, send your request to admin@8chan.co along with the IP you used to post on that board.');
$last_activity_date = new DateTime();
$last_mod_date = new DateTime();
$last_activity_date->setTimestamp($row['time']);
$query = query("SELECT id, username FROM mods WHERE boards = '$board' AND type = 20");
$mods = $query->fetchAll();
if ($mods) {
$mod = $mods[0]['id'];
$query = query("SELECT MAX(time) AS time FROM modlogs WHERE `mod` = $mod");
$a = $query->fetchAll(PDO::FETCH_COLUMN);
if ($a[0]) {
$last_mod_date->setTimestamp($a[0]);
if (!$row['time'])
$last_activity_date->setTimestamp($a[0]);
} else {// no one ever logged in, try board creation time
$query = query("SELECT UNIX_TIMESTAMP(time) AS time FROM board_create WHERE uri = '$board'");
$crt = $query->fetchAll(PDO::FETCH_COLUMN);
$last_activity_date->setTimestamp($crt[0]);
$last_mod_date = false;
}
}
if ($mods and ($last_activity_date < $ago or ($last_mod_date and $last_mod_date < $mod_ago))) {
return array($last_activity_date, $last_mod_date, $mods);
}
else {
return false;
}
}
$q = query("SELECT uri FROM boards");
$boards = $q->fetchAll(PDO::FETCH_COLUMN);
$delete = array();
foreach($boards as $board) {
$last_activity = last_activity($board);
if ($last_activity) {
list($last_activity_date, $last_mod_date, $mods) = $last_activity;
$last_mod_f = $last_mod_date ? $last_mod_date->format('Y-m-d H:i:s') : '<em>never</em>';
$last_activity_f = $last_activity_date ? $last_activity_date->format('Y-m-d H:i:s') : '<em>never</em>';
$delete[] = array('board' => $board, 'last_activity_date' => $last_activity_f, 'last_mod' => $last_mod_date, 'last_mod_f' => $last_mod_f);
}
}
$body = Element("8chan/claim.html", array("config" => $config, "delete" => $delete));
echo Element("page.html", array("config" => $config, "body" => $body, "title" => _("Claim"), "subtitle" => _("Take deserted boards back from their owners")));

View File

@ -8,8 +8,8 @@ $protected = array('burgers', 'cow', 'wilno', 'cute', 'yoga');
$q = query("SELECT uri FROM boards");
$boards = $q->fetchAll(PDO::FETCH_COLUMN);
$now = new DateTime();
$ago = (new DateTime)->sub(new DateInterval('P3D'));
$mod_ago = (new DateTime)->sub(new DateInterval('P7D'));
$ago = (new DateTime)->sub(new DateInterval('P7D'));
$mod_ago = (new DateTime)->sub(new DateInterval('P14D'));
// Find out the last activity for our board
$delete = array();

27
faq.php
View File

@ -26,6 +26,7 @@ $body = <<<EOT
<li>Do not post, request, or link to any content illegal in the United States of America. Do not create boards with the sole purpose of posting or spreading such content.</li>
</ul>
<p>Other than that, you are free to institute whatever rules you want on your board.</p>
<p><a href="https://8chan.co/obscenity.html">More information about US obscenity laws and how they relate to 8chan boards</a></p>
<h2>How do I add more volunteers?</h2>
<p>You may do this in your board settings, click on "Edit board volunteers".
<h2>How do I manage my board?</h2>
@ -34,9 +35,9 @@ $body = <<<EOT
<p>The admin can be reached at <tt>admin at 8chan dot co</tt>.</p>
<h2>Help! My board has been deleted!</h2>
<p>Were you inactive for longer than one week? Were there no posts on the board for 72 hours?</p>
<p>As of November 13th, 2014, board expiration no longer occurs.</p>
<p>If either of those is true, the board was deleted automatically. You are free to recreate it. I cannot restore it, so don't bother emailing me about it.</p>
<p>You still may lose access to your board, however, if you fail to log in for two weeks or it receives no posts for a week. See <a href="/claim.html">here</a> for a list of boards that are available for reclaiming.</p>
<h2>How do I post as a volunteer on my board?</h2>
<p>Make sure you are using the volunteer interface to view your board. The URL of your browser should be <a href="https://8chan.co/mod.php?/yourboard"><tt>https://8chan.co/mod.php?/yourboard</tt></a>.</p>
@ -47,7 +48,7 @@ $body = <<<EOT
<p>If they are doing something illegal, email me.</p>
<h2>Can you give me X board?</h2>
<p>If the owner of the board is inactive or the board is broken due to bad CSS, sure. Send me an email.</p>
<p>If the owner of the board is inactive or the board is broken due to bad CSS, sure. Send me an email. You can see a list of boards that qualify for being taken over <a href="/claim.html">here</a>.</p>
<h2>Can you add some new feature?</h2>
<p>Open a <a href="https://github.com/ctrlcctrlv/8chan/issues">Github issue</a>. Better yet, write it yourself and open a pull request.
@ -65,9 +66,9 @@ $body = <<<EOT
</ul>
<h2>How are featured boards chosen?</h2>
<p>Top fifteen boards excluding /meta/, /b/ and /int/.</p>
<p>Top fifteen boards excluding /meta/, /b/ and /news+/.</p>
<h2>Who owns /meta/, /b/, and /int/?</h2>
<h2>Who owns /meta/ and /b/?</h2>
<p>No one, so they are <em>de facto</em> property of the administration.</p>
<h2>Where's the mobile app?</h2>
@ -76,12 +77,22 @@ $body = <<<EOT
<p>I don't provide support for this app, ask the developer of it if you have a problem with it.</p>
<h2>Where's the archive?</h2>
<p>There isn't one yet and there will never be an official archive.</p>
<p><s>There isn't one yet and there will never be an official archive.</s></p>
<p>Given that archives are inevitable and will be created anyway via <a href="https://archive.today">archive.today</a>, Google cache, and anyone who installs Asagi, I'm softening my stance on this. Currently, 8archive.moe provides our archive, and I may set up an official one. <strong>All archives officially partnered with us will be opt-in by our board owners, not opt-out. Archives who archive boards that have not opted in will be considered pirate archives, and legal action may be taken.</strong></p>
<h2>I got an email from an @8chan.co email address, is that you?</h2>
<p>8chan.co uses <a href="https://cock.li">cock.li</a> to manage our domain's email. cock.li allows anyone to create an email account @8chan.co.</p>
<p>That said, we have quite a few official 8chan.co email addresses. They are:</p>
<ul>
<li>admin at 8chan dot co</li>
<li>dmca at 8chan dot co</li>
<li>claim at 8chan dot co</li>
</ul>
<h2>How do I donate?</h2>
<p>Donations can be sent to 1NpQaXqmCBji6gfX8UgaQEmEstvVY7U32C (Bitcoin) or LUPgSCJt3iGeJXUETVhmnbQ89Riaq1yjZm (Litecoin). PayPal is also accepted @ fredrick.brennan1@gmail.com .</p>
<p>Donations can be sent to 1NpQaXqmCBji6gfX8UgaQEmEstvVY7U32C (Bitcoin) or LUPgSCJt3iGeJXUETVhmnbQ89Riaq1yjZm (Litecoin).</p>
<p>I am also a big fan of Monero (XMR). You can send XMR to our <a href="http://openalias.org">OpenAlias</a> in the simplewallet client, or simply send to 49dBJhGhYFxJEfydS6hH6GRyg1W4cDgupdNVtw7j1WtcUY7xPXwNLw6fUVay644viaCcEhMFG1Z7SjjxRXEFDdNWJdvH9kS.</p>
<p>You may also donate monthly via Patreon at <a href="http://www.patreon.com/user?u=162165">http://www.patreon.com/user?u=162165</a>.
<h2>Are you really a cripple?</h2>
<p>Yes.</p>

View File

@ -20,6 +20,9 @@
$config['mod']['noticeboard_post'] = GLOBALVOLUNTEER;
$config['mod']['search'] = GLOBALVOLUNTEER;
$config['mod']['clean_global'] = GLOBALVOLUNTEER;
$config['mod']['view_notes'] = DISABLED;
$config['mod']['create_notes'] = DISABLED;
$config['mod']['edit_config'] = DISABLED;
$config['mod']['debug_recent'] = ADMIN;
$config['mod']['debug_antispam'] = ADMIN;
$config['mod']['noticeboard_post'] = ADMIN;
@ -29,6 +32,7 @@
$config['mod']['edit_flags'] = MOD;
$config['mod']['edit_settings'] = MOD;
$config['mod']['edit_volunteers'] = MOD;
$config['mod']['edit_tags'] = MOD;
$config['mod']['clean'] = BOARDVOLUNTEER;
// new perms
@ -49,6 +53,87 @@
$config['mod']['ban_appeals'] = BOARDVOLUNTEER;
$config['mod']['view_ban_appeals'] = BOARDVOLUNTEER;
$config['mod']['view_ban'] = BOARDVOLUNTEER;
$config['mod']['reassign_board'] = ADMIN;
$config['mod']['custom_pages']['/tags/(\%b)'] = function ($b) {
global $board, $config;
if (!openBoard($b))
error("Could not open board!");
if (!hasPermission($config['mod']['edit_tags'], $b))
error($config['error']['noaccess']);
if (isset($_POST['tags'])) {
if (sizeof($_POST['tags']) > 5)
error(_('Too many tags.'));
$delete = prepare('DELETE FROM ``board_tags`` WHERE uri = :uri');
$delete->bindValue(':uri', $b);
$delete->execute();
foreach ($_POST['tags'] as $i => $tag) {
if ($tag) {
if (strlen($tag) > 255)
continue;
$insert = prepare('INSERT INTO ``board_tags``(uri, tag) VALUES (:uri, :tag)');
$insert->bindValue(':uri', $b);
$insert->bindValue(':tag', utf8tohtml($tag));
$insert->execute();
}
}
$update = prepare('UPDATE ``boards`` SET sfw = :sfw WHERE uri = :uri');
$update->bindValue(':uri', $b);
$update->bindValue(':sfw', isset($_POST['sfw']));
$update->execute();
}
$query = prepare('SELECT * FROM ``board_tags`` WHERE uri = :uri');
$query->bindValue(':uri', $b);
$query->execute();
$tags = $query->fetchAll();
$query = prepare('SELECT `sfw` FROM ``boards`` WHERE uri = :uri');
$query->bindValue(':uri', $b);
$query->execute();
$sfw = $query->fetchColumn();
mod_page(_('Edit tags'), 'mod/tags.html', array('board'=>$board,'token'=>make_secure_link_token('reassign/'.$board['uri']), 'tags'=>$tags, 'sfw'=>$sfw));
};
$config['mod']['custom_pages']['/reassign/(\%b)'] = function($b) {
global $board, $config;
if (!openBoard($b))
error("Could not open board!");
if (!hasPermission($config['mod']['reassign_board'], $b))
error($config['error']['noaccess']);
$query = query("SELECT id, username FROM mods WHERE boards = '$b' AND type = 20");
$mods = $query->fetchAll();
if (!$mods) {
error('No mods?');
}
$password = base64_encode(openssl_random_pseudo_bytes(9));
$salt = generate_salt();
$hashed = hash('sha256', $salt . sha1($password));
$query = prepare('UPDATE ``mods`` SET `password` = :hashed, `salt` = :salt WHERE BINARY username = :mod');
$query->bindValue(':hashed', $hashed);
$query->bindValue(':salt', $salt);
$query->bindValue(':mod', $mods[0]['username']);
$query->execute();
$body = "Thanks for your interest in this board. Kindly find the username and password below. You can login at 8chan.co/mod.php.<br>Username: {$mods[0]['username']}<br>Password: {$password}<br>Thanks for using 8chan.co!";
mod_page(_('Edit reassign'), 'blank.html', array('board'=>$board,'token'=>make_secure_link_token('reassign/'.$board['uri']),'body'=>$body));
};
$config['mod']['custom_pages']['/volunteers/(\%b)'] = function($b) {
global $board, $config, $pdo;
@ -71,14 +156,16 @@
error(sprintf($config['error']['required'], 'username'));
if ($_POST['password'] == '')
error(sprintf($config['error']['required'], 'password'));
if (!preg_match('/^[a-zA-Z0-9._]{1,30}$/', $_POST['username']))
error(_('Invalid username'));
if ($count > 10) {
error(_('Too many board volunteers!'));
}
foreach ($volunteers as $i => $v) {
if ($_POST['username'] == $v['username']) {
error(_('Refusing to create a volunteer with the same username as an existing one.'));
if (strtolower($_POST['username']) == strtolower($v['username'])) {
error(_('Refusing to create a volunteer with the same username as an existing one.'));
}
}
@ -175,7 +262,7 @@
error($config['error']['invalidimg']);
}
if ($size[0] > 20 or $size[0] < 11 or $size[1] != 11){
if ($size[0] > 20 or $size[0] < 11 or $size[1] > 16 or $size[1] < 11){
error(_('Image wrong size!'));
}
if (sizeof($banners) > 256) {
@ -317,6 +404,9 @@ FLAGS;
$code_tags = isset($_POST['code_tags']) ? '$config[\'additional_javascript\'][] = \'js/code_tags/run_prettify.js\';$config[\'markup\'][] = array("/\[code\](.+?)\[\/code\]/ms", "<code><pre class=\'prettyprint\' style=\'display:inline-block\'>\$1</pre></code>");' : '';
$katex = isset($_POST['katex']) ? '$config[\'katex\'] = true;$config[\'additional_javascript\'][] = \'js/katex/katex.min.js\'; $config[\'markup\'][] = array("/\[tex\](.+?)\[\/tex\]/ms", "<span class=\'tex\'>\$1</span>"); $config[\'additional_javascript\'][] = \'js/katex-enable.js\';' : '';
$user_flags = isset($_POST['user_flags']) ? "if (file_exists('$b/flags.php')) { include 'flags.php'; }\n" : '';
$captcha = isset($_POST['captcha']) ? 'true' : 'false';
$force_subject_op = isset($_POST['force_subject_op']) ? 'true' : 'false';
$oekaki_js = <<<OEKAKI
\$config['additional_javascript'][] = 'js/jquery-ui.custom.min.js';
@ -392,6 +482,8 @@ OEKAKI;
\$config['blotter'] = base64_decode('$blotter');
\$config['stylesheets']['Custom'] = 'board/$b.css';
\$config['default_stylesheet'] = array('Custom', \$config['stylesheets']['Custom']);
\$config['captcha']['enabled'] = $captcha;
\$config['force_subject_op'] = $force_subject_op;
$code_tags $katex $oekaki $replace $multiimage $allow_flash $allow_pdf $user_flags
if (\$config['disable_images'])
\$config['max_pages'] = 10000;
@ -400,19 +492,54 @@ $locale
$add_to_config
EOT;
// Clean up our CSS...no more expression() or off-site URLs.
$clean_css = preg_replace('/expression\s*\(/', '', $_POST['css']);
// URL matcher from SO:
$match_urls = '(?xi)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))';
$matched = array();
preg_match_all("#$match_urls#im", $clean_css, $matched);
$allowed_urls = array('https://i.imgur.com/', 'https://media.8chan.co/', 'https://a.pomf.se/', 'https://fonts.googleapis.com/', 'http://8ch.net/');
$error = false;
if (isset($matched[0])) {
foreach ($matched[0] as $i => $v) {
$error = true;
foreach ($allowed_urls as $ii => $url) {
if (strpos($v, $url) === 0) {
$error = false;
break;
}
}
}
}
if ($error) {
error(_('Off-site links are not allowed in board stylesheets!'));
}
$query = query('SELECT `uri`, `title`, `subtitle` FROM ``boards`` WHERE `8archive` = TRUE');
file_write('8archive.json', json_encode($query->fetchAll(PDO::FETCH_ASSOC)));
file_write($b.'/config.php', $config_file);
file_write('stylesheets/board/'.$b.'.css', $_POST['css']);
file_write('stylesheets/board/'.$b.'.css', $clean_css);
file_write($b.'/rules.html', Element('page.html', array('title'=>'Rules', 'subtitle'=>'', 'config'=>$config, 'body'=>'<div class="ban">'.purify($_POST['rules']).'</div>')));
file_write($b.'/rules.txt', $_POST['rules']);
$_config = $config;
openBoard($b);
// Faster than openBoard and bypasses cache...we're trusting the PHP output
// to be safe enough to run with every request, we can eval it here.
eval(str_replace('flags.php', "$b/flags.php", preg_replace('/^\<\?php$/m', '', $config_file)));
// be smarter about rebuilds...only some changes really require us to rebuild all threads
if ($_config['blotter'] != $config['blotter'] || $_config['field_disable_name'] != $config['field_disable_name'] || $_config['show_sages'] != $config['show_sages']) {
if ($_config['captcha']['enabled'] != $config['captcha']['enabled']
|| $_config['captcha']['extra'] != $config['captcha']['extra']
|| $_config['blotter'] != $config['blotter']
|| $_config['field_disable_name'] != $config['field_disable_name']
|| $_config['show_sages'] != (isset($config['show_sages']) && $config['show_sages'])) {
buildIndex();
$query = query(sprintf("SELECT `id` FROM ``posts_%s`` WHERE `thread` IS NULL", $b)) or error(db_error());
while ($post = $query->fetch(PDO::FETCH_ASSOC)) {

View File

@ -218,14 +218,8 @@ class Bans {
}
unset($ban['type']);
if ($filter_ips || ($board_access !== false && !in_array($ban['board'], $board_access))) {
@list($ban['mask'], $subnet) = explode("/", $ban['mask']);
$ban['mask'] = preg_split("/[\.:]/", $ban['mask']);
$ban['mask'] = array_slice($ban['mask'], 0, 2);
$ban['mask'] = implode(".", $ban['mask']);
$ban['mask'] .= ".x.x";
if (isset ($subnet)) {
$ban['mask'] .= "/$subnet";
}
$ban['mask'] = @less_ip($ban['mask']);
$ban['masked'] = true;
}

View File

@ -272,6 +272,8 @@
'embed',
'recaptcha_challenge_field',
'recaptcha_response_field',
'captcha_cookie',
'captcha_text',
'spoiler',
'page',
'file_url',
@ -300,6 +302,19 @@
$config['recaptcha_public'] = '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f';
$config['recaptcha_private'] = '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_';
$config['captcha'] = array();
// Enable custom captcha provider
$config['captcha']['enabled'] = false;
// Custom captcha provider path
$config['captcha']['provider_get'] = 'http://8chan.vichan.net/captcha/entrypoint.php';
$config['captcha']['provider_check'] = 'http://8chan.vichan.net/captcha/entrypoint.php';
// Custom captcha extra field (eg. charset)
$config['captcha']['extra'] = 'abcdefghijklmnopqrstuvwxyz';
/*
* Custom filters detect certain posts and reject/ban accordingly. They are made up of a condition and an
* action (for when ALL conditions are met). As every single post has to be put through each filter,
@ -602,6 +617,17 @@
// How many ban appeals can be made for a single ban?
$config['ban_appeals_max'] = 1;
// Blacklisted board names. Default values to protect existing folders in the core codebase.
$config['banned_boards'] = array(
'.git',
'inc',
'js',
'static',
'stylesheets',
'templates',
'tools'
);
// Show moderator name on ban page.
$config['show_modname'] = false;
@ -727,7 +753,6 @@
// Allowed image file extensions.
$config['allowed_ext'][] = 'jpg';
$config['allowed_ext'][] = 'jpeg';
$config['allowed_ext'][] = 'bmp';
$config['allowed_ext'][] = 'gif';
$config['allowed_ext'][] = 'png';
// $config['allowed_ext'][] = 'svg';
@ -745,6 +770,7 @@
$config['file_icons']['default'] = 'file.png';
$config['file_icons']['zip'] = 'zip.png';
$config['file_icons']['webm'] = 'video.png';
$config['file_icons']['mp4'] = 'video.png';
// Example: Custom thumbnail for certain file extension.
// $config['file_icons']['extension'] = 'some_file.png';
@ -858,7 +884,7 @@
$config['thread_subject_in_title'] = false;
// Additional lines added to the footer of all pages.
$config['footer'][] = _('All trademarks, copyrights, comments, and images on this page are owned by and are the responsibility of their respective parties.');
// $config['footer'][] = _('All trademarks, copyrights, comments, and images on this page are owned by and are the responsibility of their respective parties.');
// Characters used to generate a random password (with Javascript).
$config['genpassword_chars'] = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+';
@ -1057,6 +1083,7 @@
$config['error']['youaremuted'] = _('You are muted! Expires in %d seconds.');
$config['error']['dnsbl'] = _('Your IP address is listed in %s.');
$config['error']['toomanylinks'] = _('Too many links; flood detected.');
$config['error']['notenoughlinks'] = _('OPs are required to have at least %d links on this board.');
$config['error']['toomanycites'] = _('Too many cites; post discarded.');
$config['error']['toomanycross'] = _('Too many cross-board links; post discarded.');
$config['error']['nodelete'] = _('You didn\'t select anything to delete.');
@ -1326,8 +1353,8 @@
// Capcode permissions.
$config['mod']['capcode'] = array(
// JANITOR => array('Janitor'),
MOD => array('Mod'),
ADMIN => true
MOD => array('Mod'),
ADMIN => true
);
// Example: Allow mods to post with "## Moderator" as well
@ -1410,7 +1437,7 @@
$config['mod']['view_banlist'] = MOD;
// View the username of the mod who made a ban
$config['mod']['view_banstaff'] = MOD;
// If the moderator doesn't fit the $config['mod']['view_banstaff''] (previous) permission, show him just
// If the moderator doesn't fit the $config['mod']['view_banstaff'] (previous) permission, show him just
// a "?" instead. Otherwise, it will be "Mod" or "Admin".
$config['mod']['view_banquestionmark'] = false;
// Show expired bans in the ban list (they are kept in cache until the culprit returns)
@ -1654,10 +1681,17 @@
// Regex for board URIs. Don't add "`" character or any Unicode that MySQL can't handle. 58 characters
// is the absolute maximum, because MySQL cannot handle table names greater than 64 characters.
$config['board_regex'] = '[0-9a-zA-Z$_\x{0080}-\x{FFFF}]{1,58}';
$config['board_regex'] = '[0-9a-zA-Z\+$_\x{0080}-\x{FFFF}]{1,58}';
// Youtube.js embed HTML code
$config['youtube_js_html'] = '<div class="video-container" data-video="$1" data-params="&$2&$3">'.
'<a href="$0" target="_blank" class="file">'.
'<img style="width:360px;height:270px;" src="//img.youtube.com/vi/$1/0.jpg" class="post-image"/>'.
'</a></div>';
// Use read.php?
// read.php is a file that dynamically displays pages to users instead of the build on demand system in use in Tinyboard since 2010.
//
// read.php is basically a watered down mod.php -- if coupled with caching, it improves performance and allows for easier replication
// across machines.
$config['use_read_php'] = false;

View File

@ -406,9 +406,13 @@ class Post {
}
public function getClean( ) {
global $board;
global $board, $config;
if( !isset( $this->clean ) ) {
if ($config['cache']['enabled'] && $this->clean = cache::get("post_clean_{$board['uri']}_{$this->id}")) {
return $this->clean;
}
$query = prepare("SELECT * FROM `post_clean` WHERE `post_id` = :post AND `board_id` = :board");
$query->bindValue( ':board', $board['uri'] );
$query->bindValue( ':post', $this->id );
@ -424,6 +428,8 @@ class Post {
'clean_local_mod_id' => null,
'clean_global_mod_id' => null,
);
cache::set("post_clean_{$board['uri']}_{$this->id}", $this->clean);
}
}

View File

@ -29,14 +29,16 @@ mb_internal_encoding('UTF-8');
loadConfig();
function init_locale($locale, $error='error') {
if (_setlocale(LC_ALL, $locale) === false) {
$error('The specified locale (' . $locale . ') does not exist on your platform!');
}
if ($locale === 'en')
$locale = 'en_US.utf8';
if (extension_loaded('gettext')) {
setlocale(LC_ALL, $locale);
bindtextdomain('tinyboard', './inc/locale');
bind_textdomain_codeset('tinyboard', 'UTF-8');
textdomain('tinyboard');
} else {
_setlocale(LC_ALL, $locale);
_bindtextdomain('tinyboard', './inc/locale');
_bind_textdomain_codeset('tinyboard', 'UTF-8');
_textdomain('tinyboard');
@ -430,7 +432,8 @@ function setupBoard($array) {
$board = array(
'uri' => $array['uri'],
'title' => $array['title'],
'subtitle' => $array['subtitle']
'subtitle' => $array['subtitle'],
'indexed' => $array['indexed']
);
// older versions
@ -680,7 +683,7 @@ function listBoards($just_uri = false, $indexed_only = false) {
return $boards;
if (!$just_uri) {
$query = query("SELECT ``boards``.`uri` uri, ``boards``.`title` title, ``boards``.`subtitle` subtitle, ``board_create``.`time` time, ``boards``.`indexed` indexed FROM ``boards``" . ( $indexed_only ? " WHERE `indexed` = 1 " : "" ) . "LEFT JOIN ``board_create`` ON ``boards``.`uri` = ``board_create``.`uri` ORDER BY ``boards``.`uri`") or error(db_error());
$query = query("SELECT ``boards``.`uri` uri, ``boards``.`title` title, ``boards``.`subtitle` subtitle, ``board_create``.`time` time, ``boards``.`indexed` indexed, ``boards``.`sfw` sfw FROM ``boards``" . ( $indexed_only ? " WHERE `indexed` = 1 " : "" ) . "LEFT JOIN ``board_create`` ON ``boards``.`uri` = ``board_create``.`uri` ORDER BY ``boards``.`uri`") or error(db_error());
$boards = $query->fetchAll(PDO::FETCH_ASSOC);
} else {
$boards = array();
@ -1091,9 +1094,9 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
if (!$post['thread']) {
// Delete thread HTML page
file_unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['id']));
file_unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $post['id']));
file_unlink($board['dir'] . $config['dir']['res'] . sprintf('%d.json', $post['id']));
@file_unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['id']));
@file_unlink($board['dir'] . $config['dir']['res'] . sprintf($config['file_page50'], $post['id']));
@file_unlink($board['dir'] . $config['dir']['res'] . sprintf('%d.json', $post['id']));
$antispam_query = prepare('DELETE FROM ``antispam`` WHERE `board` = :board AND `thread` = :thread');
$antispam_query->bindValue(':board', $board['uri']);
@ -1106,9 +1109,9 @@ function deletePost($id, $error_if_doesnt_exist=true, $rebuild_after=true) {
if ($post['files']) {
// Delete file
foreach (json_decode($post['files']) as $i => $f) {
if ($f->file !== 'deleted') {
file_unlink($config['dir']['img_root'] . $board['dir'] . $config['dir']['img'] . $f->file);
file_unlink($config['dir']['img_root'] . $board['dir'] . $config['dir']['thumb'] . $f->thumb);
if (isset($f->file, $f->thumb) && $f->file !== 'deleted') {
@file_unlink($config['dir']['img_root'] . $board['dir'] . $config['dir']['img'] . $f->file);
@file_unlink($config['dir']['img_root'] . $board['dir'] . $config['dir']['thumb'] . $f->thumb);
}
}
}
@ -1329,23 +1332,26 @@ function getPages($mod=false) {
// Stolen with permission from PlainIB (by Frank Usrs)
function make_comment_hex($str) {
global $config;
// remove cross-board citations
// the numbers don't matter
$str = preg_replace('!>>>/[A-Za-z0-9]+/!', '', $str);
$str = preg_replace("!>>>/[A-Za-z0-9]+/!", '', $str);
if (function_exists('iconv')) {
// remove diacritics and other noise
// FIXME: this removes cyrillic entirely
$oldstr = $str;
$str = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
if (!$str) $str = $oldstr;
if ($config['robot_enable']) {
if (function_exists('iconv')) {
// remove diacritics and other noise
// FIXME: this removes cyrillic entirely
$oldstr = $str;
$str = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
if (!$str) $str = $oldstr;
}
$str = strtolower($str);
// strip all non-alphabet characters
$str = preg_replace('/[^a-z]/', '', $str);
}
$str = strtolower($str);
// strip all non-alphabet characters
$str = preg_replace('/[^a-z]/', '', $str);
return md5($str);
}
@ -1701,7 +1707,7 @@ function extract_modifiers($body) {
return $modifiers;
}
function markup(&$body, $track_cites = false) {
function markup(&$body, $track_cites = false, $op = false) {
global $board, $config, $markup_urls;
$modifiers = extract_modifiers($body);
@ -1739,6 +1745,9 @@ function markup(&$body, $track_cites = false) {
if ($num_links > $config['max_links'])
error($config['error']['toomanylinks']);
if ($num_links < $config['min_links'] && $op)
error(sprintf($config['error']['notenoughlinks'], $config['min_links']));
}
if ($config['markup_repair_tidy'])
@ -2427,6 +2436,8 @@ function diceRoller($post) {
}
function less_ip($ip) {
global $config;
$ipv6 = (strstr($ip, ':') !== false);
$has_range = (strstr($ip, '/') !== false);
@ -2446,7 +2457,15 @@ function less_ip($ip) {
}
$final = inet_ntop($in_addr & $mask);
return str_replace(array(':0', '.0'), array(':x', '.x'), $final) . (isset($range) ? '/'.$range : '');
$masked = str_replace(array(':0', '.0'), array(':x', '.x'), $final);
if ($config['hash_masked_ip']) {
$masked = substr(sha1(sha1($masked) . $config['secure_trip_salt']), 0, 10);
}
$masked .= (isset($range) ? '/'.$range : '');
return $masked;
}
function less_hostmask($hostmask) {

View File

@ -61,6 +61,7 @@
$config['spoiler_images'] = true;
$config['image_reject_repost'] = true;
$config['allowed_ext_files'][] = 'webm';
$config['allowed_ext_files'][] = 'mp4';
$config['webm']['use_ffmpeg'] = true;
$config['webm']['allow_audio'] = true;
$config['webm']['max_length'] = 60 * 15;
@ -73,7 +74,7 @@
$config['mod']['capcode'][MOD] = array('Board Owner');
$config['mod']['capcode'][GLOBALVOLUNTEER] = array('Global Volunteer');
$config['custom_capcode']['Admin'] = array(
'<span class="capcode" style="color:blue;font-weight:bold"> <i class="fa fa-wheelchair"></i> %s</span>',
'<span class="capcode" title="This post is written by the global 8chan.co administrator."> <i class="fa fa-wheelchair" style="color:blue;"></i> <span style="color:red">8chan.co Administrator</span></span>',
);
//$config['mod']['view_banlist'] = GLOBALVOLUNTEER;
$config['mod']['recent_reports'] = 65535;
@ -90,7 +91,9 @@
//$config['default_stylesheet'] = array('Notsuba', 'notsuba.css');
$config['additional_javascript'][] = 'js/jquery.min.js';
$config['additional_javascript'][] = 'js/jquery.mixitup.min.js';
$config['additional_javascript'][] = 'js/jquery-ui.custom.min.js';
$config['additional_javascript'][] = 'js/catalog.js';
$config['additional_javascript'][] = 'js/captcha.js';
$config['additional_javascript'][] = 'js/jquery.tablesorter.min.js';
$config['additional_javascript'][] = 'js/options.js';
$config['additional_javascript'][] = 'js/style-select.js';
@ -135,6 +138,9 @@
$config['additional_javascript'][] = 'js/youtube.js';
$config['additional_javascript'][] = 'js/comment-toolbar.js';
$config['additional_javascript'][] = 'js/catalog-search.js';
$config['additional_javascript'][] = 'js/thread-stats.js';
$config['additional_javascript'][] = 'js/quote-selection.js';
$config['additional_javascript'][] = 'js/twemoji/twemoji.js';
//$config['font_awesome_css'] = '/netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css';
@ -146,16 +152,18 @@
$config['markup'][] = array("/\[spoiler\](.+?)\[\/spoiler\]/", "<span class=\"spoiler\">\$1</span>");
$config['markup'][] = array("/~~(.+?)~~/", "<s>\$1</s>");
$config['markup'][] = array("/__(.+?)__/", "<u>\$1</u>");
$config['markup'][] = array("/###([^\s']+)###/", "<a href='/boards.html#\$1'>###\$1###</a>");
$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-ban" title="Public ban list"></i>' => '/bans.html', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-ban" title="Public ban list"></i>' => '/bans.html', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta', 'news+'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
//$config['boards'] = array(array('<i class="fa fa-home" title="Home"></i>' => '/', '<i class="fa fa-tags" title="Boards"></i>' => '/boards.html', '<i class="fa fa-question" title="FAQ"></i>' => '/faq.html', '<i class="fa fa-random" title="Random"></i>' => '/random.php', '<i class="fa fa-plus" title="New board"></i>' => '/create.php', '<i class="fa fa-search" title="Search"></i>' => '/search.php', '<i class="fa fa-cog" title="Manage board"></i>' => '/mod.php', '<i class="fa fa-quote-right" title="Chat"></i>' => 'https://qchat.rizon.net/?channels=#8chan'), array('b', 'meta', 'int'), array('v', 'a', 'tg', 'fit', 'pol', 'tech', 'mu', 'co', 'sp', 'boards'), array('<i class="fa fa-twitter" title="Twitter"></i>'=>'https://twitter.com/infinitechan'));
$config['footer'][] = 'All posts on 8chan.co are the responsibility of the individual poster and not the administration of 8chan.co, pursuant to 47 U.S.C. &sect; 230.';
$config['footer'][] = 'We have not been served any secret court orders and are not under any gag orders.';
$config['footer'][] = 'Contribute to 8chan.co development at <a href="https://github.com/ctrlcctrlv/8chan">github</a>';
$config['footer'][] = 'To make a DMCA request or report illegal content, please email <a href="mailto:admin@8chan.co">admin@8chan.co</a> or use the "Global Report" functionality on every page.';
$config['footer'][] = 'To make a DMCA request or report illegal content, please email <a href="mailto:admin@8chan.co">admin@8chan.co</a>.';
$config['search']['enable'] = true;
//$config['debug'] = true;
$config['syslog'] = true;
$config['wordfilters'][] = array('\rule', ''); // 'true' means it's a regular expression
@ -189,6 +197,10 @@
);
$config['gzip_static'] = false;
$config['hash_masked_ip'] = true;
$config['force_subject_op'] = false;
$config['min_links'] = 0;
$config['min_body'] = 0;
// 8chan specific mod pages
require '8chan-mod-pages.php';

View File

@ -30,8 +30,17 @@ function is_valid_webm($ffprobe_out) {
if (empty($ffprobe_out))
return array('code' => 1, 'msg' => $config['error']['genwebmerror']);
if ($ffprobe_out['format']['format_name'] != 'matroska,webm')
return array('code' => 2, 'msg' => $config['error']['invalidwebm']);
$extension = pathinfo($ffprobe_out['format']['filename'], PATHINFO_EXTENSION);
if ($extension === 'webm') {
if ($ffprobe_out['format']['format_name'] != 'matroska,webm')
return array('code' => 2, 'msg' => $config['error']['invalidwebm']);
} elseif ($extension === 'mp4') {
if ($ffprobe_out['streams'][0]['codec_name'] != 'h264' && $ffprobe_out['streams'][1]['codec_name'] != 'aac')
return array('code' => 2, 'msg' => $config['error']['invalidwebm']);
} else {
return array('code' => 1, 'msg' => $config['error']['genwebmerror']);
}
if ((count($ffprobe_out['streams']) > 1) && (!$config['webm']['allow_audio']))
return array('code' => 3, 'msg' => $config['error']['webmhasaudio']);
@ -49,11 +58,11 @@ function make_webm_thumbnail($filename, $thumbnail, $width, $height) {
$filename = escapeshellarg($filename);
$thumbnail = escapeshellarg($thumbnail); // Should be safe by default but you
// can never be too safe.
$ffmpeg = $config['webm']['ffmpeg_path'];
$ret = 0;
$ffmpeg_out = array();
exec("$ffmpeg -strict -2 -i $filename -v quiet -ss 00:00:00 -an -vframes 1 -f mjpeg -vf scale=$width:$height $thumbnail 2>&1", $ffmpeg_out, $ret);
exec("$ffmpeg -strict -2 -i $filename -v quiet -ss 00:00:00 -an -vframes 1 -f mjpeg -vf scale=$width:$height $thumbnail 2>&1");
return count($ffmpeg_out);
return $ret;
}

View File

@ -5,7 +5,7 @@
function postHandler($post) {
global $board, $config;
if ($post->has_file) foreach ($post->files as &$file) if ($file->extension == 'webm') {
if ($post->has_file) foreach ($post->files as &$file) if ($file->extension == 'webm' || $file->extension == 'mp4') {
if ($config['webm']['use_ffmpeg']) {
require_once dirname(__FILE__) . '/ffmpeg.php';
$webminfo = get_webm_info($file->file_path);
@ -21,7 +21,7 @@ function postHandler($post) {
$file = set_thumbnail_dimensions($post, $file);
$tn_path = $board['dir'] . $config['dir']['thumb'] . $file->file_id . '.jpg';
if(false == make_webm_thumbnail($file->file_path, $tn_path, $file->thumbwidth, $file->thumbheight)) {
if(0 == make_webm_thumbnail($file->file_path, $tn_path, $file->thumbwidth, $file->thumbheight)) {
$file->thumb = $file->file_id . '.jpg';
}
else {

View File

@ -689,7 +689,7 @@ msgstr ""
msgid "You seem to have mistyped the verification."
msgstr ""
#. Moderator errors
#. Volunteer errors
#: ../../../../inc/config.php:906 ../../../../inc/config.php:1011
#: ../../../../inc/config.php:1002 ../../../../inc/config.php:1004
#: ../../../../inc/config.php:1006 ../../../../inc/config.php:1022
@ -879,7 +879,7 @@ msgstr ""
#: ../../../../inc/mod/pages.php:671 ../../../../inc/mod/pages.php:698
#: ../../../../templates/cache/72/7e/271125664718133518fd942f20fb724224e100f8a0d47cb0b52f895ac12f.php:300
#: ../../../../templates/cache/73/f8/5e3142a8a6f8d7e40422ff577e83b0dedf55a7cb9bc7082839b24f653545.php:75
msgid "Moderation log"
msgid "Volunteer log"
msgstr ""
#. line 104
@ -3489,7 +3489,7 @@ msgid "There are no active posts."
msgstr ""
#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:49
msgid "Show only bans from boards I moderate"
msgid "Show only bans from boards I volunteer on"
msgstr ""
#: ../../../../templates/cache/ba/55/2553cc018aecf7d29a62331aec4bedc71b646817c7e4c4e7d1a885263676.php:55

View File

@ -495,7 +495,15 @@ function mod_new_board() {
if (openBoard($_POST['uri'])) {
error(sprintf($config['error']['boardexists'], $board['url']));
}
foreach ($config['banned_boards'] as $i => $w) {
if ($w[0] !== '/') {
if (strpos($_POST['uri'],$w) !== false)
error(_("Cannot create board with banned word $w"));
} else {
if (preg_match($w,$_POST['uri']))
error(_("Cannot create board matching banned pattern $w"));
}
}
$query = prepare('INSERT INTO ``boards`` (``uri``, ``title``, ``subtitle``) VALUES (:uri, :title, :subtitle)');
$query->bindValue(':uri', $_POST['uri']);
$query->bindValue(':title', $_POST['title']);
@ -625,7 +633,7 @@ function mod_news($page_no = 1) {
rebuildThemes('news');
header('Location: ?/news#' . $pdo->lastInsertId(), true, $config['redirect_http']);
header('Location: ?/edit_news#' . $pdo->lastInsertId(), true, $config['redirect_http']);
}
$query = prepare("SELECT * FROM ``news`` ORDER BY `id` DESC LIMIT :offset, :limit");
@ -638,14 +646,14 @@ function mod_news($page_no = 1) {
error($config['error']['404']);
foreach ($news as &$entry) {
$entry['delete_token'] = make_secure_link_token('news/delete/' . $entry['id']);
$entry['delete_token'] = make_secure_link_token('edit_news/delete/' . $entry['id']);
}
$query = prepare("SELECT COUNT(*) FROM ``news``");
$query->execute() or error(db_error($query));
$count = $query->fetchColumn();
mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('news')));
mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('edit_news')));
}
function mod_news_delete($id) {
@ -660,7 +668,7 @@ function mod_news_delete($id) {
modLog('Deleted a news entry');
header('Location: ?/news', true, $config['redirect_http']);
header('Location: ?/edit_news', true, $config['redirect_http']);
}
function mod_log($page_no = 1) {
@ -1591,12 +1599,13 @@ function mod_edit_post($board, $edit_raw_html, $postID) {
error($config['error']['404']);
if (isset($_POST['name'], $_POST['email'], $_POST['subject'], $_POST['body'])) {
$trip = isset($_POST['remove_trip']) ? ' `trip` = NULL,' : '';
if ($edit_raw_html)
$query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body` = :body, `body_nomarkup` = :body_nomarkup, `edited_at` = NOW() WHERE `id` = :id', $board));
$query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name,'. $trip .' `email` = :email, `subject` = :subject, `body` = :body, `body_nomarkup` = :body_nomarkup, `edited_at` = NOW() WHERE `id` = :id', $board));
else
$query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name, `email` = :email, `subject` = :subject, `body_nomarkup` = :body, `edited_at` = NOW() WHERE `id` = :id', $board));
$query = prepare(sprintf('UPDATE ``posts_%s`` SET `name` = :name,'. $trip .' `email` = :email, `subject` = :subject, `body_nomarkup` = :body, `edited_at` = NOW() WHERE `id` = :id', $board));
$query->bindValue(':id', $postID);
$query->bindValue('name', $_POST['name']);
$query->bindValue(':name', $_POST['name'] ? $_POST['name'] : $config['anonymous']);
$query->bindValue(':email', $_POST['email']);
$query->bindValue(':subject', $_POST['subject']);
$query->bindValue(':body', $_POST['body']);
@ -2987,6 +2996,9 @@ function mod_report_clean( $global_reports, $board, $unclean, $post, $global, $l
$log_action = ($unclean ? "Closed" : "Re-opened" );
$log_scope = ($local && $global ? "local and global" : ($local ? "local" : "global" ) );
modLog( "{$log_action} reports for post #{$post} in {$log_scope}.", $board);
if ($config['cache']['enabled']) {
cache::delete("post_clean_{$board}_{$post}");
}
rebuildPost( $post );
}

View File

@ -67,6 +67,8 @@ CREATE TABLE IF NOT EXISTS `boards` (
`subtitle` tinytext,
`indexed` boolean default true,
`public_bans` boolean default true,
`8archive` boolean default false,
`sfw` boolean default false,
PRIMARY KEY (`uri`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
@ -75,13 +77,6 @@ CREATE TABLE IF NOT EXISTS `board_create` (
`uri` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `boards`
--
INSERT INTO `boards` VALUES
('b', 'Random', NULL);
-- --------------------------------------------------------
--
@ -227,6 +222,7 @@ CREATE TABLE IF NOT EXISTS `reports` (
`board` varchar(58) CHARACTER SET utf8 DEFAULT NULL,
`post` int(11) NOT NULL,
`reason` text NOT NULL,
`local` tinyint(1) NOT NULL DEFAULT '0',
`global` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ;
@ -322,6 +318,19 @@ CREATE TABLE `post_clean` (
UNIQUE KEY `clean_id_UNIQUE` (`clean_id`)
);
-- --------------------------------------------------------
--
-- Table structure for table `board_tags`
--
CREATE TABLE `board_tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uri` varchar(30) DEFAULT NULL,
`tag` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

View File

@ -55,7 +55,7 @@ $(window).ready(function() {
}
return xhr;
},
success: function(post_response) {
success: function(post_response, textStatus, xhr) {
if (post_response.error) {
if (post_response.banned) {
// You are banned. Must post the form normally so the user can see the ban message.
@ -109,23 +109,15 @@ $(window).ready(function() {
$(form).find('input[type="submit"]').val(_('Posted...'));
$(document).trigger("ajax_after_post", post_response);
} else {
console.log(xhr);
alert(_('An unknown error occured when posting!'));
$(form).find('input[type="submit"]').val(submit_txt);
$(form).find('input[type="submit"]').removeAttr('disabled');
}
},
error: function(xhr, status, er) {
// An error occured
do_not_ajax = true;
$(form).find('input[type="submit"]').each(function() {
var $replacement = $('<input type="hidden">');
$replacement.attr('name', $(this).attr('name'));
$replacement.val(submit_txt);
$(this)
.after($replacement)
.replaceWith($('<input type="button">').val(submit_txt));
});
$(form).submit();
console.log(xhr);
alert(_('The server returned an error or truncated response -- your post was probably still submitted. If it wasn\'t, 8chan.co might be experiencing issues right now -- please try your post again later.'));
},
data: formData,
cache: false,

44
js/captcha.js Normal file
View File

@ -0,0 +1,44 @@
var tout;
function redo_events(provider, extra) {
$('.captcha .captcha_text, textarea[id="body"]').off("focus").one("focus", function() { actually_load_captcha(provider, extra); });
}
function actually_load_captcha(provider, extra) {
$('.captcha .captcha_text, textarea[id="body"]').off("focus");
if (tout !== undefined) {
clearTimeout(tout);
}
$.getJSON(provider, {mode: 'get', extra: extra}, function(json) {
$(".captcha .captcha_cookie").val(json.cookie);
$(".captcha .captcha_html").html(json.captchahtml);
setTimeout(function() {
redo_events(provider, extra);
}, json.expires_in * 1000);
});
}
function load_captcha(provider, extra) {
$(function() {
$(".captcha>td").html("<input class='captcha_text' type='text' name='captcha_text' size='25' maxlength='6' autocomplete='off'>"+
"<input class='captcha_cookie' name='captcha_cookie' type='hidden'>"+
"<div class='captcha_html'></div>");
$("#quick-reply .captcha .captcha_text").prop("placeholder", _("Verification"));
$(".captcha .captcha_html").on("click", function() { actually_load_captcha(provider, extra); });
$(document).on("ajax_after_post", function() { actually_load_captcha(provider, extra); });
redo_events(provider, extra);
$(window).on("quick-reply", function() {
redo_events(provider, extra);
$("#quick-reply .captcha .captcha_html").html($("form:not(#quick-reply) .captcha .captcha_html").html());
$("#quick-reply .captcha .captcha_cookie").val($("form:not(#quick-reply) .captcha .captcha_cookie").html());
$("#quick-reply .captcha .captcha_html").on("click", function() { actually_load_captcha(provider, extra); });
});
});
}

35
js/disable-styles.js Normal file
View File

@ -0,0 +1,35 @@
/* Adds a checkbox in the General options tab to disable and enable board style sheets. */
$(document).ready(function () {
var disableStyles = localStorage['disablestylesheet'] ? true : false;
/* only search for and disable board stylesheets if the user is on a page that uses them */
if(active_page == 'ukko' || active_page == 'thread' || active_page == 'index' || active_page == 'catalog')
{
var i = 0
while(i<document.styleSheets.length) {
var protAndHost = window.location.protocol + '//' + window.location.host
if(document.styleSheets[i].href == protAndHost + $('link[id="stylesheet"]').attr('href'))
{
var sheet = i
document.styleSheets[sheet].disabled = disableStyles
break
}
i++
}
}
/* add the option on all pages so that the user doesn't need to goto a board to toggle it */
if (window.Options && Options.get_tab('general')){
Options.extend_tab('general','<label id=\'disablestyle\'><input type=\'checkbox\' />' + ' Disable board specific style sheets' + '</label>')
$('#disablestyle').find('input').prop('checked', disableStyles)
}
$('#disablestyle').on('change', function() {
if(disableStyles) {
delete localStorage.disablestylesheet
} else {
localStorage.disablestylesheet = true
}
disableStyles =! disableStyles
if(active_page == 'ukko' || active_page == 'thread' || active_page == 'index' || active_page == 'catalog') document.styleSheets[sheet].disabled = disableStyles
})
})

View File

@ -26,6 +26,9 @@ $(function() {
var content = $(data).find('#'+url.split('#')[1]).parent().parent().find(".body").first().html();
body.html(content);
var post = $(body).parents('.post');
$(document).trigger('new_post', post);
}
});
});

View File

@ -204,13 +204,13 @@ function setupVideo(thumb, url) {
function setupVideosIn(element) {
var thumbs = element.querySelectorAll("a.file");
for (var i = 0; i < thumbs.length; i++) {
if (/\.webm$/.test(thumbs[i].pathname)) {
if (/(\.webm)|(\.mp4)$/.test(thumbs[i].pathname)) {
setupVideo(thumbs[i], thumbs[i].href);
} else {
var m = thumbs[i].search.match(/\bv=([^&]*)/);
if (m != null) {
var url = decodeURIComponent(m[1]);
if (/\.webm$/.test(url)) setupVideo(thumbs[i], url);
if (/(\.webm)|(\.mp4)$/.test(url)) setupVideo(thumbs[i], url);
}
}
}

View File

@ -56,7 +56,6 @@ if (active_page == 'thread' || active_page == 'index' || active_page == 'catalog
$(document).ready(function(){
var favorites = JSON.parse(localStorage.favorites);
var is_board_favorite = ~$.inArray(board_name, favorites);
console.log(is_board_favorite);
$('header>h1').append('<a id="favorite-star" href="#" data-active="'+(is_board_favorite ? 'true' : 'false')+'" style="color: '+(is_board_favorite ? 'yellow' : 'grey')+'; text-decoration:none">\u2605</span>');
add_favorites();

View File

@ -19,7 +19,7 @@ onready(function(){
for (var i = 0; i < link.length; i++) {
if (typeof link[i] == "object" && link[i].childNodes && typeof link[i].childNodes[0] !== 'undefined' && link[i].childNodes[0].src && link[i].childNodes[0].className.match(/post-image/) && !link[i].className.match(/file/)) {
link[i].onclick = function(e) {
var img, post_body, still_open;
var img, post_body, still_open, canvas;
var thumb = this.childNodes[0];
var padding = 5;
var boardlist = $('.boardlist')[0];
@ -43,8 +43,10 @@ onready(function(){
this.dataset.expanded = 'true';
if (thumb.tagName === 'CANVAS') {
this.removeChild(thumb);
thumb.style.display = 'block';
canvas = thumb;
thumb = thumb.nextSibling;
this.removeChild(canvas);
canvas.style.display = 'block';
}
thumb.style.opacity = '0.4';
@ -72,10 +74,10 @@ onready(function(){
if (still_open > 1) {
if (e.target.getBoundingClientRect().top - padding < 0)
$('body').scrollTop($(e.target).parent().parent().offset().top - padding);
$(document).scrollTop($(e.target).parent().parent().offset().top - padding);
} else {
if (post_body[0].getBoundingClientRect().top - padding < 0)
$('body').scrollTop(post_body.offset().top - padding);
$(document).scrollTop(post_body.offset().top - padding);
}
}

4
js/jquery.tablesorter.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,8 @@
*/
function unanimate_gif(e) {
if ($(e).closest('.thread').children('.thread-hidden').length > 0) return;
if (active_page === "catalog")
var c = $('<canvas class="thread-image"></canvas>');
else

View File

@ -73,6 +73,7 @@ onready(function(){
.css('font-style', 'normal')
.css('z-index', '100')
.css('left', '0')
.css('margin-left', '')
.addClass('reply').addClass('post')
.appendTo(link.closest('div.post'))

View File

@ -87,7 +87,7 @@
-webkit-box-sizing:border-box;\
-moz-box-sizing: border-box;\
font-size: 10pt;\
resize: vertical;\
resize: both;\
}\
#quick-reply input, #quick-reply select, #quick-reply textarea {\
margin: 0 0 1px 0;\
@ -149,14 +149,14 @@
.removeAttr('size')
.attr('placeholder', $th.clone().children().remove().end().text());
}
// Move anti-spam nonsense and remove <th>
$th.contents().filter(function() {
return this.nodeType == 3; // Node.TEXT_NODE
}).remove();
$th.contents().appendTo($dummyStuff);
$th.remove();
if ($td.find('input[name="password"]').length) {
// Hide password field
$(this).hide();
@ -280,7 +280,7 @@
$postForm.find('textarea[name="body"]').removeAttr('id').removeAttr('cols').attr('placeholder', _('Comment'));
$postForm.find('textarea:not([name="body"]),input[type="hidden"]').removeAttr('id').appendTo($dummyStuff);
$postForm.find('textarea:not([name="body"]),input[type="hidden"]:not(.captcha_cookie)').removeAttr('id').appendTo($dummyStuff);
$postForm.find('br').remove();
$postForm.find('table').prepend('<tr><th colspan="2">\

View File

@ -15,7 +15,7 @@
onready(function(){
var showBackLinks = function() {
var reply_id = $(this).attr('id').replace(/^reply_/, '');
var reply_id = $(this).attr('id').replace(/^(reply|op)_/, '');
$(this).find('div.body a:not([rel="nofollow"])').each(function() {
var id, post, $mentioned;
@ -26,6 +26,9 @@ onready(function(){
return;
$post = $('#reply_' + id);
if($post.length == 0)
$post = $('#op_' + id);
if($post.length == 0)
return;
@ -38,7 +41,8 @@ onready(function(){
var $link = $('<a class="mentioned-' + reply_id + '" onclick="highlightReply(\'' + reply_id + '\');" href="#' + reply_id + '">&gt;&gt;' +
reply_id + '</a>');
$link.appendTo($mentioned)
$link.appendTo($mentioned);
$link.after(" ");
if (window.init_hover) {
$link.each(init_hover);

View File

@ -1,4 +1,4 @@
/*
/*
* thread-stats.js
* - Adds statistics of the thread below the posts area
* - Shows ID post count beside each postID on hover
@ -8,8 +8,9 @@
* $config['additional_javascript'][] = 'js/thread-stats.js';
*/
if (active_page == 'thread') {
$(document).ready(function(){
//check if page uses unique ID
var IDsupport = ($('.poster_id').length > 0);
var IDsupport = ($('.poster_id').length > 0);
var thread_id = (document.location.pathname + document.location.search).split('/');
thread_id = thread_id[thread_id.length -1].split('+')[0].split('.')[0];
@ -49,9 +50,12 @@ if (active_page == 'thread') {
ids[opID] = 0;
}
ids[opID]++;
var cur = op.find('>.intro >.poster_id');
cur.find('+.posts_by_id').remove();
cur.after('<span class="posts_by_id"> ('+ ids[cur.text()] +')</span>');
replies.each(function(){
var cur = $(this).find('> .intro > .poster_id');
cur.find('+ .posts_by_id').remove();
cur = $(this).find('>.intro >.poster_id');
cur.find('+.posts_by_id').remove();
cur.after('<span class="posts_by_id"> ('+ ids[cur.text()] +')</span>');
});
var size = function(obj) {
@ -64,7 +68,7 @@ if (active_page == 'thread') {
$('#thread_stats_uids').text(size(ids));
}
$.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').success(function(data){
var found, page = 'Pruned or Deleted';
var found, page = '???';
for (var i=0;data[i];i++){
var threads = data[i].threads;
for (var j=0; threads[j]; j++){
@ -78,13 +82,14 @@ if (active_page == 'thread') {
}
$('#thread_stats_page').text(page);
if (!found) $('#thread_stats_page').css('color','red');
else $('#thread_stats_page').css('color','');
});
}
// load the current page the thread is on.
// uses ajax call so it gets loaded on a delay (depending on network resources available)
var thread_stats_page_timer = setInterval(function(){
$.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').success(function(data){
var found, page = 'Pruned or Deleted';
var found, page = '???';
for (var i=0;data[i];i++){
var threads = data[i].threads;
for (var j=0; threads[j]; j++){
@ -98,12 +103,12 @@ if (active_page == 'thread') {
}
$('#thread_stats_page').text(page);
if (!found) $('#thread_stats_page').css('color','red');
else $('#thread_stats_page').css('color','');
});
},30000);
$(document).ready(function(){
$('body').append('<style>.posts_by_id{display:none;}.poster_id:hover+.posts_by_id{display:initial}</style>');
update_thread_stats();
$('#update_thread').click(update_thread_stats);
$(document).on('new_post',update_thread_stats);
});
}
});
}

View File

@ -147,15 +147,16 @@ $(document).ready(function(){
}
//Append the watchlist toggle button.
$('.boardlist').append('<span>[ <a id="watchlist-toggle">watchlist</a> ]</span>');
$('.boardlist').append('<span>[ <a class="watchlist-toggle" href="#">watchlist</a> ]</span>');
//Append a watch thread button after every OP.
$('.op>.intro').append('<a class="watchThread">[Watch Thread]</a>');
$('.op>.intro').append('<a class="watchThread" href="#">[Watch Thread]</a>');
//Draw the watchlist, hidden.
watchlist.render();
//Show or hide the watchlist.
$('#watchlist-toggle').on('click', function(e) {
$('.watchlist-toggle').on('click', function(e) {
e.preventDefault();
//if ctrl+click, reset the watchlist.
if (e.ctrlKey) {
watchlist.render(true);
@ -169,7 +170,8 @@ $(document).ready(function(){
//Trigger the watchlist add function.
//The selector is passed as an argument in case the page is not a thread.
$('.watchThread').on('click', function() {
$('.watchThread').on('click', function(e) {
e.preventDefault();
watchlist.add(this).render();
});

View File

@ -11,14 +11,28 @@
*
*/
if (active_page == 'thread' || active_page == 'ukko' || active_page == 'index')
$(function() {
if (window.Options && Options.get_tab('general')) {
var selector = '#treeview-global>input';
Options.extend_tab("general", "<label id='treeview-global'><input type='checkbox' /> "+_('Use tree view by default')+"</label>");
$(selector).on('change', function() {
if (localStorage.treeview === 'true') {
localStorage.treeview = 'false';
} else {
localStorage.treeview = 'true';
}
});
if (localStorage.treeview === 'true') {
$(selector).attr('checked', 'checked');
}
}
});
if (active_page == 'thread')
$(function() {
$('hr:first').before('<div id="treeview" style="text-align:right"><a class="unimportant" href="javascript:void(0)"></a></div>');
$('div#treeview a')
.text(_('Tree view'))
.click(function(e) {
e.preventDefault();
var treeview = function(enable) {
if (enable === true) {
$('.post.reply').each(function(){
var references = [];
$(this).find('.body a').each(function(){
@ -26,7 +40,6 @@ $(function() {
references.push(parseInt($(this).html().replace('&gt;&gt;', '')));
}
});
var maxref = references.reduce(function(a,b) { return a > b ? a : b; }, 0);
var parent_post = $("#reply_"+maxref);
@ -39,7 +52,24 @@ $(function() {
post.detach().css("margin-left", margin).insertAfter(parent_post.next());
br.detach().insertAfter(post);
});
});
} else {
$('.post.reply').sort(function(a,b) {
return parseInt(a.id.replace('reply_', '')) - parseInt(b.id.replace('reply_', ''));
}).each(function () {
var post = $(this);
var br = post.next();
post.detach().css('margin-left', '').appendTo('.thread');
br.detach().insertAfter(post);
});
}
}
$('hr:first').before('<div class="unimportant" style="text-align:right"><label for="treeview"><input type="checkbox" id="treeview"> '+_('Tree view')+'</label></div>');
$('input#treeview').on('change', function(e) { treeview($(this).is(':checked')); });
if (localStorage.treeview === 'true') {
treeview(true);
$('input#treeview').attr('checked', true);
}
});

BIN
js/twemoji/16x16/1f004.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

BIN
js/twemoji/16x16/1f0cf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

BIN
js/twemoji/16x16/1f170.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

BIN
js/twemoji/16x16/1f171.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

BIN
js/twemoji/16x16/1f17e.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

BIN
js/twemoji/16x16/1f17f.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

BIN
js/twemoji/16x16/1f18e.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

BIN
js/twemoji/16x16/1f191.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

BIN
js/twemoji/16x16/1f192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

BIN
js/twemoji/16x16/1f193.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

BIN
js/twemoji/16x16/1f194.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

BIN
js/twemoji/16x16/1f195.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

BIN
js/twemoji/16x16/1f196.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

BIN
js/twemoji/16x16/1f197.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

BIN
js/twemoji/16x16/1f198.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

BIN
js/twemoji/16x16/1f199.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

BIN
js/twemoji/16x16/1f19a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

BIN
js/twemoji/16x16/1f1e6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

BIN
js/twemoji/16x16/1f1e7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

BIN
js/twemoji/16x16/1f1e8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

BIN
js/twemoji/16x16/1f1e9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

BIN
js/twemoji/16x16/1f1ea.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 B

BIN
js/twemoji/16x16/1f1eb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

BIN
js/twemoji/16x16/1f1ec.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

BIN
js/twemoji/16x16/1f1ed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

BIN
js/twemoji/16x16/1f1ee.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

BIN
js/twemoji/16x16/1f1ef.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

BIN
js/twemoji/16x16/1f1f0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

BIN
js/twemoji/16x16/1f1f1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

BIN
js/twemoji/16x16/1f1f2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

BIN
js/twemoji/16x16/1f1f3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

BIN
js/twemoji/16x16/1f1f4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

BIN
js/twemoji/16x16/1f1f5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

BIN
js/twemoji/16x16/1f1f6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

BIN
js/twemoji/16x16/1f1f7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

BIN
js/twemoji/16x16/1f1f8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

BIN
js/twemoji/16x16/1f1f9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

BIN
js/twemoji/16x16/1f1fa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

BIN
js/twemoji/16x16/1f1fb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

BIN
js/twemoji/16x16/1f1fc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

BIN
js/twemoji/16x16/1f1fd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

BIN
js/twemoji/16x16/1f1fe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

BIN
js/twemoji/16x16/1f1ff.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

BIN
js/twemoji/16x16/1f201.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 B

BIN
js/twemoji/16x16/1f202.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

BIN
js/twemoji/16x16/1f21a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

BIN
js/twemoji/16x16/1f22f.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

BIN
js/twemoji/16x16/1f232.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

BIN
js/twemoji/16x16/1f233.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

BIN
js/twemoji/16x16/1f234.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

BIN
js/twemoji/16x16/1f235.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

BIN
js/twemoji/16x16/1f236.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

BIN
js/twemoji/16x16/1f237.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

BIN
js/twemoji/16x16/1f238.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

BIN
js/twemoji/16x16/1f239.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

BIN
js/twemoji/16x16/1f23a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Some files were not shown because too many files have changed in this diff Show More