Compare commits

..

56 commits

Author SHA1 Message Date
92fc2daa9c post.php: fix undefined exif_stripped 2025-03-29 08:55:15 +01:00
d224c0af23 post.php: skip resize only if already stripped 2025-03-29 00:34:37 +01:00
c1c20bdab2 post.php: fix typo 2025-03-29 00:32:56 +01:00
42e850091a config.php: remove minimum_copy_resize 2025-03-29 00:31:29 +01:00
87029580b6 post.php: default minimum_copy_resize to true by removing it 2025-03-29 00:30:59 +01:00
cf74272806 Merge pull request 'Remove functionality' (#114) from remove-tesseract into config
Reviewed-on: leftypol/leftypol#114
2025-03-28 09:06:52 -05:00
336c40b0f7 remove all tesseract traces 2025-03-28 15:05:01 +01:00
81c02be563 style.css: reduce margin between multiple files 2025-03-27 01:23:37 +01:00
fa56876c36 style.css: set minimum file container width and multifile margin 2025-03-27 00:17:17 +01:00
8da10af101 frames.html: break long words to prevent page getting cut 2025-03-26 13:55:04 +01:00
9431536112 frames.html: trim 2025-03-26 13:54:55 +01:00
025f1c4221 news.html: remove spurious stuff from css 2025-03-26 13:40:28 +01:00
501e696891 banners: remove just monika banner
This reverts commit 6bcf22aa7e.
2025-03-23 17:05:20 +01:00
557e43e38f Merge pull request 'Fix #66 Use equilateral triangle as the post menu button' (#113) from 10-post-menu-triangle into config
Reviewed-on: leftypol/leftypol#113
2025-03-17 15:24:36 -05:00
6ee8670401 post-menu.js: use unicode code with variant selector for equilateral triangle 2025-03-17 16:20:49 +01:00
c4d7bc39de Merge pull request 'WIP Port LogDriver from upstream' (#111) from log-driver-port into config
Reviewed-on: leftypol/leftypol#111
2025-03-16 12:37:21 -05:00
b2029d2533 context.php: log error if log_system is erroneous 2025-03-16 18:17:42 +01:00
5b4d1b7f4c pages.php: use LogDriver 2025-03-16 18:10:19 +01:00
665e3d339a post.php: use LogDriver 2025-03-16 18:07:58 +01:00
6be3f4bbff post.php: update LogDriver 2025-03-16 18:07:58 +01:00
8f7db3bdef FileLogDriver.php: flush writes to file 2025-03-16 18:07:58 +01:00
cca8d88d91 config.php: update LogDriver configuration 2025-03-16 18:07:58 +01:00
Zankaria
268bd84128 Refactor the logging system 2025-03-16 18:07:58 +01:00
2c0c003b2c context.php: report deprecation notice on syslog option 2025-03-16 18:07:58 +01:00
fe4813867b context.php: fix log init 2025-03-16 18:07:58 +01:00
6132084b4b context.php: update LogDriver 2025-03-16 18:07:57 +01:00
79523f8251 context.php: initial add LogDriver 2025-03-16 18:07:13 +01:00
707fb62c04 log-driver.php: split up log driver 2025-03-16 18:05:40 +01:00
6f9ea52212 faq: update git repo (again) 2025-02-24 16:14:53 +01:00
9ea7bf5938 Merge pull request 'Fix 108: fix backlink' (#110) from 108-buggy-op-backlink into config
Reviewed-on: leftypol/leftypol#110
2025-02-22 16:37:10 -06:00
c45fbd5ec9 show-backlinks.js: fix backlink 2025-02-22 23:33:48 +01:00
77ae0b101e Merge pull request 'Finish the work of the previous PR' (#107) from ip-note-queries-refactor-2 into config
Reviewed-on: leftypol/leftypol#107
2025-02-21 05:40:27 -06:00
94b5c82517 post.php: pass Context to filters 2025-02-21 12:35:28 +01:00
e753588aeb filters.php: use IpNoteQueries 2025-02-21 12:35:13 +01:00
c3a1960427 filters.php: trim 2025-02-21 12:33:28 +01:00
8754eda6c7 Merge pull request 'refactor IP notes backend' (#106) from ip-note-queries into config
Reviewed-on: leftypol/leftypol#106
2025-02-21 05:01:50 -06:00
b61653230d context.php: provide IpNoteQueries 2025-02-21 11:55:44 +01:00
041958e20f Merge pull request 'Fix #71 fix matrix report url fragment' (#105) from 71-incorrect-fragment into config
Reviewed-on: leftypol/leftypol#105
2025-02-20 15:32:06 -06:00
218dc42aca post.php: fix matrix report url fragment 2025-02-20 22:29:00 +01:00
5d52f6b32a pages.php: use IpNoteQueries 2025-02-18 12:17:06 +01:00
583b50af56 IpNoteQueries.php: add ip notes wrapper 2025-02-18 12:16:52 +01:00
e5ba5feb25 youtube.js: adjust referrer policy 2025-02-17 21:42:55 +01:00
9256c15c46 rules.html: extend rule 10 to extreme fetishes 2025-02-17 18:51:24 +01:00
0819c509fa dark_red.css: increase contrast 2025-02-17 18:01:46 +01:00
9a4ce56d86 dark_red.css: always use Verdana font if available 2025-02-17 17:21:32 +01:00
230dc4760b Merge branch '67-strikethrough-invalid-citations' into 'config'
Resolve "Strikethrough invalid citations"

Closes #67

See merge request leftypol/leftypol!33
2025-02-16 23:43:29 +00:00
c73a893e54 functions.php: strikethrough bad citations 2025-02-17 00:31:59 +01:00
2da6c95aa5 functions.php: remove unused parameter from markup 2025-02-16 23:54:01 +01:00
9a5b79452f pages.php: remove unused code 2025-02-16 23:52:56 +01:00
Zankaria
bf060ce174 UserPostQueries.php: fix paging boundaries 2025-02-14 12:50:15 +01:00
b1989a69b0 flags: add Alunya flag 2025-02-14 09:40:04 +01:00
9746293fed inline-expanding.js: fit expanded images into the screen's height (port of soyjak party feature) 2025-02-13 10:35:01 +01:00
3a44b68c41 Merge branch '68-embed-field-is-not-reset-after-submitting-post' into 'config'
Resolve "Embed field is not reset after submitting post"

Closes #68

See merge request leftypol/leftypol!31
2025-02-12 23:04:22 +00:00
aed49a6188 ajax.js: clear embed 2025-02-13 00:00:20 +01:00
e3252306a5 Merge branch '69-set-style-menu-in-lower-bar' into 'config'
Resolve "Set style menu in lower bar"

Closes #69

See merge request leftypol/leftypol!30
2025-02-12 22:42:11 +00:00
9bb7f0d2f2 general.js: do not move the style-select in the options 2025-02-12 23:30:00 +01:00
30 changed files with 541 additions and 246 deletions

View file

@ -0,0 +1,28 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* Log via the php function error_log.
*/
class ErrorLogLogDriver implements LogDriver {
use LogTrait;
private string $name;
private int $level;
public function __construct(string $name, int $level) {
$this->name = $name;
$this->level = $level;
}
public function log(int $level, string $message): void {
if ($level <= $this->level) {
$lv = $this->levelToString($level);
$line = "{$this->name} $lv: $message";
\error_log($line, 0, null, null);
}
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* Log to a file.
*/
class FileLogDriver implements LogDriver {
use LogTrait;
private string $name;
private int $level;
private mixed $fd;
public function __construct(string $name, int $level, string $file_path) {
/*
* error_log is slow as hell in it's 3rd mode, so use fopen + file locking instead.
* https://grobmeier.solutions/performance-ofnonblocking-write-to-files-via-php-21082009.html
*
* Whatever file appending is atomic is contentious:
* - There are no POSIX guarantees: https://stackoverflow.com/a/7237901
* - But linus suggested they are on linux, on some filesystems: https://web.archive.org/web/20151201111541/http://article.gmane.org/gmane.linux.kernel/43445
* - But it doesn't seem to be always the case: https://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/
*
* So we just use file locking to be sure.
*/
$this->fd = \fopen($file_path, 'a');
if ($this->fd === false) {
throw new \RuntimeException("Unable to open log file at $file_path");
}
$this->name = $name;
$this->level = $level;
// In some cases PHP does not run the destructor.
\register_shutdown_function([$this, 'close']);
}
public function __destruct() {
$this->close();
}
public function log(int $level, string $message): void {
if ($level <= $this->level) {
$lv = $this->levelToString($level);
$line = "{$this->name} $lv: $message\n";
\flock($this->fd, LOCK_EX);
\fwrite($this->fd, $line);
\fflush($this->fd);
\flock($this->fd, LOCK_UN);
}
}
public function close() {
\flock($this->fd, LOCK_UN);
\fclose($this->fd);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
interface LogDriver {
public const EMERG = \LOG_EMERG;
public const ERROR = \LOG_ERR;
public const WARNING = \LOG_WARNING;
public const NOTICE = \LOG_NOTICE;
public const INFO = \LOG_INFO;
public const DEBUG = \LOG_DEBUG;
/**
* Log a message if the level of relevancy is at least the minimum.
*
* @param int $level Message level. Use Log interface constants.
* @param string $message The message to log.
*/
public function log(int $level, string $message): void;
}

View file

@ -0,0 +1,26 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
trait LogTrait {
public static function levelToString(int $level): string {
switch ($level) {
case LogDriver::EMERG:
return 'EMERG';
case LogDriver::ERROR:
return 'ERROR';
case LogDriver::WARNING:
return 'WARNING';
case LogDriver::NOTICE:
return 'NOTICE';
case LogDriver::INFO:
return 'INFO';
case LogDriver::DEBUG:
return 'DEBUG';
default:
throw new \InvalidArgumentException('Not a logging level');
}
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* Log to php's standard error file stream.
*/
class StderrLogDriver implements LogDriver {
use LogTrait;
private string $name;
private int $level;
public function __construct(string $name, int $level) {
$this->name = $name;
$this->level = $level;
}
public function log(int $level, string $message): void {
if ($level <= $this->level) {
$lv = $this->levelToString($level);
\fwrite(\STDERR, "{$this->name} $lv: $message\n");
}
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* Log to syslog.
*/
class SyslogLogDriver implements LogDriver {
private int $level;
public function __construct(string $name, int $level, bool $print_stderr) {
$flags = \LOG_ODELAY;
if ($print_stderr) {
$flags |= \LOG_PERROR;
}
if (!\openlog($name, $flags, \LOG_USER)) {
throw new \RuntimeException('Unable to open syslog');
}
$this->level = $level;
}
public function log(int $level, string $message): void {
if ($level <= $this->level) {
if (isset($_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'])) {
// CGI
\syslog($level, "$message - client: {$_SERVER['REMOTE_ADDR']}, request: \"{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}\"");
} else {
\syslog($level, $message);
}
}
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Vichan\Data;
use Vichan\Data\Driver\CacheDriver;
class IpNoteQueries {
private \PDO $pdo;
private CacheDriver $cache;
public function __construct(\PDO $pdo, CacheDriver $cache) {
$this->pdo = $pdo;
$this->cache = $cache;
}
/**
* Get all the notes relative to an IP.
*
* @param string $ip The IP of the notes. THE STRING IS NOT VALIDATED.
* @return array Returns an array of notes sorted by the most recent. Includes the username of the mods.
*/
public function getByIp(string $ip) {
$ret = $this->cache->get("ip_note_queries_$ip");
if ($ret !== null) {
return $ret;
}
$query = $this->pdo->prepare('SELECT `ip_notes`.*, `username` FROM `ip_notes` LEFT JOIN `mods` ON `mod` = `mods`.`id` WHERE `ip` = :ip ORDER BY `time` DESC');
$query->bindValue(':ip', $ip);
$query->execute();
$ret = $query->fetchAll(\PDO::FETCH_ASSOC);
$this->cache->set("ip_note_queries_$ip", $ret);
return $ret;
}
/**
* Creates a new note relative to the given ip.
*
* @param string $ip The IP of the note. THE STRING IS NOT VALIDATED.
* @param int $mod_id The id of the mod who created the note.
* @param string $body The text of the note.
* @return void
*/
public function add(string $ip, int $mod_id, string $body) {
$query = $this->pdo->prepare('INSERT INTO `ip_notes` (`ip`, `mod`, `time`, `body`) VALUES (:ip, :mod, :time, :body)');
$query->bindValue(':ip', $ip);
$query->bindValue(':mod', $mod_id);
$query->bindValue(':time', time());
$query->bindValue(':body', $body);
$query->execute();
$this->cache->delete("ip_note_queries_$ip");
}
/**
* Delete a note only if it's of a particular IP address.
*
* @param int $id The id of the note.
* @param int $ip The expected IP of the note. THE STRING IS NOT VALIDATED.
* @return bool True if any note was deleted.
*/
public function deleteWhereIp(int $id, string $ip): bool {
$query = $this->pdo->prepare('DELETE FROM `ip_notes` WHERE `ip` = :ip AND `id` = :id');
$query->bindValue(':ip', $ip);
$query->bindValue(':id', $id);
$query->execute();
$any = $query->rowCount() != 0;
if ($any) {
$this->cache->delete("ip_note_queries_$ip");
}
return $any;
}
}

View file

@ -44,19 +44,20 @@ class UserPostQueries {
$posts_count = \count($posts);
// By fetching one extra post bellow and/or above the limit, we know if there are any posts beside the current page.
if ($posts_count === $page_size + 2) {
$has_extra_prev_post = true;
$has_extra_end_post = true;
} elseif ($posts_count === $page_size + 1) {
$has_extra_prev_post = $start_id !== null && $start_id === (int)$posts[0]['id'];
$has_extra_end_post = !$has_extra_prev_post;
} else {
$has_extra_prev_post = false;
$has_extra_end_post = false;
/*
* If the id we start fetching from is also the first id fetched from the DB, then we exclude it from
* the results, noting that we fetched 1 more posts than we needed, and it was before the current page.
* Hence, we have no extra post at the end and no next page.
*/
$has_extra_prev_post = $start_id !== null && $start_id === (int)$posts[0]['id'];
$has_extra_end_post = !$has_extra_prev_post && $posts_count > $page_size;
}
// Since we fetched one post bellow and/or above the limit, we always know if there are any posts after the current page.
// Get the previous cursor, if any.
if ($has_extra_prev_post) {
\array_shift($posts);

View file

@ -63,9 +63,29 @@
// been generated. This keeps the script from querying the database and causing strain when not needed.
$config['has_installed'] = '.installed';
// Use syslog() for logging all error messages and unauthorized login attempts.
// Deprecated, use 'log_system'.
$config['syslog'] = false;
$config['log_system'] = [
/*
* Log all error messages and unauthorized login attempts.
* Can be "syslog", "error_log" (default), "file", or "stderr".
*/
'type' => 'error_log',
// The application name used by the logging system. Defaults to "tinyboard" for backwards compatibility.
'name' => 'tinyboard',
/*
* Only relevant if 'log_system' is set to "syslog". If true, double print the logs also in stderr. Defaults to
* false.
*/
'syslog_stderr' => false,
/*
* Only relevant if "log_system" is set to `file`. Sets the file that vichan will log to. Defaults to
* '/var/log/vichan.log'.
*/
'file_path' => '/var/log/vichan.log',
];
// Use `host` via shell_exec() to lookup hostnames, avoiding query timeouts. May not work on your system.
// Requires safe_mode to be disabled.
$config['dns_system'] = false;
@ -923,10 +943,6 @@
// Location of thumbnail to use for deleted images.
$config['image_deleted'] = 'static/deleted.png';
// When a thumbnailed image is going to be the same (in dimension), just copy the entire file and use
// that as a thumbnail instead of resizing/redrawing.
$config['minimum_copy_resize'] = false;
// Maximum image upload size in bytes.
$config['max_filesize'] = 10 * 1024 * 1024; // 10MB
// Maximum image dimensions.
@ -965,15 +981,6 @@
// Set this to true if you're using Linux and you can execute `md5sum` binary.
$config['gnu_md5'] = false;
// Use Tesseract OCR to retrieve text from images, so you can use it as a spamfilter.
$config['tesseract_ocr'] = false;
// Tesseract parameters
$config['tesseract_params'] = '';
// Tesseract preprocess command
$config['tesseract_preprocess_command'] = 'convert -monochrome %s -';
// Number of posts in a "View Last X Posts" page
$config['noko50_count'] = 50;
// Number of posts a thread needs before it gets a "View Last X Posts" page.

View file

@ -1,8 +1,8 @@
<?php
namespace Vichan;
use Vichan\Data\Driver\CacheDriver;
use Vichan\Data\{ReportQueries, UserPostQueries};
use Vichan\Data\{IpNoteQueries, ReportQueries, UserPostQueries};
use Vichan\Data\Driver\{CacheDriver, ErrorLogLogDriver, FileLogDriver, LogDriver, StderrLogDriver, SyslogLogDriver};
defined('TINYBOARD') or exit;
@ -31,6 +31,34 @@ class Context {
function build_context(array $config): Context {
return new Context([
'config' => $config,
LogDriver::class => function($c) {
$config = $c->get('config');
$name = $config['log_system']['name'];
$level = $config['debug'] ? LogDriver::DEBUG : LogDriver::NOTICE;
$backend = $config['log_system']['type'];
$legacy_syslog = isset($config['syslog']) && $config['syslog'];
// Check 'syslog' for backwards compatibility.
if ($legacy_syslog || $backend === 'syslog') {
$log_driver = new SyslogLogDriver($name, $level, $config['log_system']['syslog_stderr']);
if ($legacy_syslog) {
$log_driver->log(LogDriver::NOTICE, 'The configuration setting \'syslog\' is deprecated. Please use \'log_system\' instead');
}
return $log_driver;
} elseif ($backend === 'file') {
return new FileLogDriver($name, $level, $config['log_system']['file_path']);
} elseif ($backend === 'stderr') {
return new StderrLogDriver($name, $level);
} elseif ($backend === 'error_log') {
return new ErrorLogLogDriver($name, $level);
} else {
$log_driver = new ErrorLogLogDriver($name, $level);
$log_driver->log(LogDriver::ERROR, "Unknown 'log_system' value '$backend', using 'error_log' default");
return $log_driver;
}
},
CacheDriver::class => function($c) {
// Use the global for backwards compatibility.
return \cache::getCache();
@ -48,6 +76,7 @@ function build_context(array $config): Context {
},
UserPostQueries::class => function($c) {
return new UserPostQueries($c->get(\PDO::class));
}
},
IpNoteQueries::class => fn($c) => new IpNoteQueries($c->get(\PDO::class), $c->get(CacheDriver::class)),
]);
}

View file

@ -4,23 +4,26 @@
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
use Vichan\Context;
use Vichan\Data\IpNoteQueries;
defined('TINYBOARD') or exit;
class Filter {
public $flood_check;
private $condition;
private $post;
public function __construct(array $arr) {
foreach ($arr as $key => $value)
$this->$key = $value;
$this->$key = $value;
}
public function match($condition, $match) {
$condition = strtolower($condition);
$post = &$this->post;
switch($condition) {
case 'custom':
if (!is_callable($match))
@ -29,11 +32,11 @@ class Filter {
case 'flood-match':
if (!is_array($match))
error('Filter condition "flood-match" must be an array.');
// Filter out "flood" table entries which do not match this filter.
$flood_check_matched = array();
foreach ($this->flood_check as $flood_post) {
foreach ($match as $flood_match_arg) {
switch ($flood_match_arg) {
@ -69,10 +72,10 @@ class Filter {
}
$flood_check_matched[] = $flood_post;
}
// is there any reason for this assignment?
$this->flood_check = $flood_check_matched;
return !empty($this->flood_check);
case 'flood-time':
foreach ($this->flood_check as $flood_post) {
@ -135,46 +138,42 @@ class Filter {
error('Unknown filter condition: ' . $condition);
}
}
public function action() {
public function action(Context $ctx) {
global $board;
$this->add_note = isset($this->add_note) ? $this->add_note : false;
if ($this->add_note) {
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
$query->bindValue(':mod', -1);
$query->bindValue(':time', time());
$query->bindValue(':body', "Autoban message: ".$this->post['body']);
$query->execute() or error(db_error($query));
}
$note_queries = $ctx->get(IpNoteQueries::class);
$note_queries->add($_SERVER['REMOTE_ADDR'], -1, 'Autoban message: ' . $this->post['body']);
}
if (isset ($this->action)) switch($this->action) {
case 'reject':
error(isset($this->message) ? $this->message : 'Posting blocked by filter.');
case 'ban':
if (!isset($this->reason))
error('The ban action requires a reason.');
$this->expires = isset($this->expires) ? $this->expires : false;
$this->reject = isset($this->reject) ? $this->reject : true;
$this->all_boards = isset($this->all_boards) ? $this->all_boards : false;
Bans::new_ban($_SERVER['REMOTE_ADDR'], $this->reason, $this->expires, $this->all_boards ? false : $board['uri'], -1);
if ($this->reject) {
if (isset($this->message))
error($message);
checkBan($board['uri']);
exit;
}
break;
default:
error('Unknown filter action: ' . $this->action);
}
}
public function check(array $post) {
$this->post = $post;
foreach ($this->condition as $condition => $value) {
@ -184,7 +183,7 @@ class Filter {
} else {
$NOT = false;
}
if ($this->match($condition, $value) == $NOT)
return false;
}
@ -194,11 +193,11 @@ class Filter {
function purge_flood_table() {
global $config;
// Determine how long we need to keep a cache of posts for flood prevention. Unfortunately, it is not
// aware of flood filters in other board configurations. You can solve this problem by settings the
// config variable $config['flood_cache'] (seconds).
if (isset($config['flood_cache'])) {
$max_time = &$config['flood_cache'];
} else {
@ -208,18 +207,18 @@ function purge_flood_table() {
$max_time = max($max_time, $filter['condition']['flood-time']);
}
}
$time = time() - $max_time;
query("DELETE FROM ``flood`` WHERE `time` < $time") or error(db_error());
}
function do_filters(array $post) {
function do_filters(Context $ctx, array $post) {
global $config;
if (!isset($config['filters']) || empty($config['filters']))
return;
foreach ($config['filters'] as $filter) {
if (isset($filter['condition']['flood-match'])) {
$has_flood = true;
@ -232,15 +231,15 @@ function do_filters(array $post) {
} else {
$flood_check = false;
}
foreach ($config['filters'] as $filter_array) {
$filter = new Filter($filter_array);
$filter->flood_check = $flood_check;
if ($filter->check($post)) {
$filter->action();
$filter->action($ctx);
}
}
purge_flood_table();
}

View file

@ -2069,7 +2069,7 @@ function remove_modifiers($body) {
return preg_replace('@<tinyboard ([\w\s]+)>(.+?)</tinyboard>@usm', '', $body);
}
function markup(&$body, $track_cites = false, $op = false) {
function markup(&$body, $track_cites = false) {
global $board, $config, $markup_urls;
$modifiers = extract_modifiers($body);
@ -2168,12 +2168,15 @@ function markup(&$body, $track_cites = false, $op = false) {
link_for(array('id' => $cite, 'thread' => $cited_posts[$cite])) . '#' . $cite . '">' .
'&gt;&gt;' . $cite .
'</a>';
} else {
$replacement = "<s>&gt;&gt;$cite</s>";
}
$body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[3][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0]));
$skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[3][0]) - mb_strlen($matches[0][0]);
$body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[3][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0]));
$skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[3][0]) - mb_strlen($matches[0][0]);
if ($track_cites && $config['track_cites'])
$tracked_cites[] = array($board['uri'], $cite);
if ($track_cites && $config['track_cites']) {
$tracked_cites[] = array($board['uri'], $cite);
}
}
}

View file

@ -3,17 +3,20 @@
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
use Vichan\Context;
use Vichan\Data\{UserPostQueries, ReportQueries};
use Vichan\Data\{IpNoteQueries, UserPostQueries, ReportQueries};
use Vichan\Data\Driver\LogDriver;
use Vichan\Functions\Net;
defined('TINYBOARD') or exit;
function _link_or_copy(string $target, string $link): bool {
if (!link($target, $link)) {
error_log("Failed to link() $target to $link. FAlling back to copy()");
return copy($target, $link);
}
return true;
function _link_or_copy_factory(Context $ctx): callable {
return function(string $target, string $link) use ($ctx) {
if (!\link($target, $link)) {
$ctx->get(LogDriver::class)->log(LogDriver::NOTICE, "Failed to link() $target to $link. FAlling back to copy()");
return \copy($target, $link);
}
return true;
};
}
function mod_page($title, $template, $args, $subtitle = false) {
@ -54,8 +57,7 @@ function mod_login(Context $ctx, $redirect = false) {
if (!isset($_POST['username'], $_POST['password']) || $_POST['username'] == '' || $_POST['password'] == '') {
$args['error'] = $config['error']['invalid'];
} elseif (!login($_POST['username'], $_POST['password'])) {
if ($config['syslog'])
_syslog(LOG_WARNING, 'Unauthorized login attempt!');
$ctx->get(LogDriver::class)->log(LogDriver::INFO, 'Unauthorized login attempt!');
$args['error'] = $config['error']['invalid'];
} else {
@ -851,18 +853,26 @@ function mod_view_thread50(Context $ctx, $boardName, $thread) {
}
function mod_ip_remove_note(Context $ctx, $ip, $id) {
global $config;
$config = $ctx->get('config');
if (!hasPermission($config['mod']['remove_notes']))
error($config['error']['noaccess']);
if (!hasPermission($config['mod']['remove_notes'])) {
error($config['error']['noaccess']);
}
if (filter_var($ip, FILTER_VALIDATE_IP) === false)
if (filter_var($ip, \FILTER_VALIDATE_IP) === false) {
error('Invalid IP address');
}
$query = prepare('DELETE FROM ``ip_notes`` WHERE `ip` = :ip AND `id` = :id');
$query->bindValue(':ip', $ip);
$query->bindValue(':id', $id);
$query->execute() or error(db_error($query));
if (!is_numeric($id)) {
error('Invalid note ID');
}
$queries = $ctx->get(IpNoteQueries::class);
$deleted = $queries->deleteWhereIp((int)$id, $ip);
if (!$deleted) {
error("Note $id does not exist for $ip");
}
modLog("Removed a note for <a href=\"?/user_posts/ip/{$ip}\">{$ip}</a>");
@ -870,14 +880,17 @@ function mod_ip_remove_note(Context $ctx, $ip, $id) {
}
function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) {
global $config, $mod;
global $mod;
$config = $ctx->get('config');
if (filter_var($ip, FILTER_VALIDATE_IP) === false)
if (filter_var($ip, FILTER_VALIDATE_IP) === false) {
error('Invalid IP address');
}
if (isset($_POST['ban_id'], $_POST['unban'])) {
if (!hasPermission($config['mod']['unban']))
if (!hasPermission($config['mod']['unban'])) {
error($config['error']['noaccess']);
}
Bans::delete($_POST['ban_id'], true, $mod['boards']);
@ -890,17 +903,15 @@ function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) {
}
if (isset($_POST['note'])) {
if (!hasPermission($config['mod']['create_notes']))
if (!hasPermission($config['mod']['create_notes'])) {
error($config['error']['noaccess']);
}
$_POST['note'] = escape_markup_modifiers($_POST['note']);
markup($_POST['note']);
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
$query->bindValue(':ip', $ip);
$query->bindValue(':mod', $mod['id']);
$query->bindValue(':time', time());
$query->bindValue(':body', $_POST['note']);
$query->execute() or error(db_error($query));
$note_queries = $ctx->get(IpNoteQueries::class);
$note_queries->add($ip, $mod['id'], $_POST['note']);
Cache::delete("mod_page_ip_view_notes_$ip");
@ -952,15 +963,8 @@ function mod_user_posts_by_ip(Context $ctx, string $ip, string $encoded_cursor =
}
if (hasPermission($config['mod']['view_notes'])) {
$ret = Cache::get("mod_page_ip_view_notes_$ip");
if (!$ret) {
$query = prepare("SELECT ``ip_notes``.*, `username` FROM ``ip_notes`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `ip` = :ip ORDER BY `time` DESC");
$query->bindValue(':ip', $ip);
$query->execute() or error(db_error($query));
$ret = $query->fetchAll(PDO::FETCH_ASSOC);
Cache::set("mod_page_ip_view_notes_$ip", $ret, 900);
}
$args['notes'] = $ret;
$note_queries = $ctx->get(IpNoteQueries::class);
$args['notes'] = $note_queries->getByIp($ip);
}
if (hasPermission($config['mod']['modlog_ip'])) {
@ -1087,12 +1091,6 @@ function mod_user_posts_by_passwd(Context $ctx, string $passwd, string $encoded_
$args['boards'] = $boards;
$args['token'] = make_secure_link_token('ban');
if (empty($encoded_cursor)) {
$args['security_token'] = make_secure_link_token("user_posts/passwd/$passwd");
} else {
$args['security_token'] = make_secure_link_token("user_posts/passwd/$passwd/cursor/$encoded_cursor");
}
mod_page(\sprintf('%s: %s', _('Password'), \htmlspecialchars($passwd)), 'mod/view_passwd.html', $args);
}
@ -1493,8 +1491,9 @@ function mod_move(Context $ctx, $originBoard, $postID) {
if ($targetBoard === $originBoard)
error(_('Target and source board are the same.'));
$_link_or_copy = _link_or_copy_factory($ctx);
// link() if leaving a shadow thread behind; else, rename().
$clone = $shadow ? '_link_or_copy' : 'rename';
$clone = $shadow ? $_link_or_copy : 'rename';
// indicate that the post is a thread
$post['op'] = true;
@ -1788,7 +1787,8 @@ function mod_merge(Context $ctx, $originBoard, $postID) {
$op = $post;
$op['id'] = $newID;
$clone = $shadow ? '_link_or_copy' : 'rename';
$_link_or_copy = _link_or_copy_factory($ctx);
$clone = $shadow ? $_link_or_copy : 'rename';
if ($post['has_file']) {
// copy image
@ -2004,13 +2004,9 @@ function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) {
$autotag .= "/${board}/" . " " . $filehash . " " . $filename ."\r\n";
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
markup($autotag);
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
$query->bindValue(':ip', $ip);
$query->bindValue(':mod', $mod['id']);
$query->bindValue(':time', time());
$query->bindValue(':body', $autotag);
$query->execute() or error(db_error($query));
$note_queries = $ctx->get(IpNoteQueries::class);
$note_queries->add($ip, $mod['id'], $autotag);
modLog("Added a note for <a href=\"?/user_posts/ip/{$ip}\">{$ip}</a>");
}
}
@ -2116,12 +2112,9 @@ function mod_warning_post(Context $ctx, $board, $post, $token = false) {
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
markup($autotag);
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
$query->bindValue(':ip', $ip);
$query->bindValue(':mod', $mod['id']);
$query->bindValue(':time', time());
$query->bindValue(':body', $autotag);
$query->execute() or error(db_error($query));
$note_queries = $ctx->get(IpNoteQueries::class);
$note_queries->add($ip, $mod['id'], $autotag);
modLog("Added a note for <a href=\"?/user_posts/ip/{$ip}\">{$ip}</a>");
}
}
@ -2227,7 +2220,7 @@ function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) {
}
function mod_delete(Context $ctx, $board, $post) {
global $config;
global $config, $mod;
if (!openBoard($board))
error($config['error']['noboard']);
@ -2268,12 +2261,9 @@ function mod_delete(Context $ctx, $board, $post) {
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
markup($autotag);
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
$query->bindValue(':ip', $ip);
$query->bindValue(':mod', $mod['id']);
$query->bindValue(':time', time());
$query->bindValue(':body', $autotag);
$query->execute() or error(db_error($query));
$note_queries = $ctx->get(IpNoteQueries::class);
$note_queries->add($ip, $mod['id'], $autotag);
modLog("Added a note for <a href=\"?/user_posts/ip/{$ip}\">{$ip}</a>");
}
}
@ -2361,7 +2351,7 @@ function mod_spoiler_image(Context $ctx, $board, $post, $file) {
}
function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) {
global $config, $board;
global $config, $board, $mod;
$global = (bool)$global;
@ -2439,12 +2429,9 @@ function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) {
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
markup($autotag);
$query2 = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
$query2->bindValue(':ip', $ip);
$query2->bindValue(':mod', $mod['id']);
$query2->bindValue(':time', time());
$query2->bindValue(':body', $autotag);
$query2->execute() or error(db_error($query2));
$note_queries = $ctx->get(IpNoteQueries::class);
$note_queries->add($ip, $mod['id'], $autotag);
modLog("Added a note for <a href=\"?/user_posts/ip/{$ip}\">{$ip}</a>");
}
}

View file

@ -111,7 +111,7 @@ $(window).ready(function() {
$(form).find('input[type="submit"]').val(submit_txt);
$(form).find('input[type="submit"]').removeAttr('disabled');
$(form).find('input[name="subject"],input[name="file_url"],\
textarea[name="body"],input[type="file"]').val('').change();
textarea[name="body"],input[type="file"],input[name="embed"]').val('').change();
},
cache: false,
contentType: false,
@ -123,7 +123,7 @@ $(window).ready(function() {
$(form).find('input[type="submit"]').val(submit_txt);
$(form).find('input[type="submit"]').removeAttr('disabled');
$(form).find('input[name="subject"],input[name="file_url"],\
textarea[name="body"],input[type="file"]').val('').change();
textarea[name="body"],input[type="file"],input[name="embed"]').val('').change();
} else {
alert(_('An unknown error occured when posting!'));
$(form).find('input[type="submit"]').val(submit_txt);

View file

@ -17,6 +17,10 @@ $(document).ready(function() {
// Default maximum image loads.
const DEFAULT_MAX = 5;
if (localStorage.inline_expand_fit_height !== 'false') {
$('<style id="expand-fit-height-style">.full-image { max-height: ' + window.innerHeight + 'px; }</style>').appendTo($('head'));
}
let inline_expand_post = function() {
let link = this.getElementsByTagName('a');
@ -56,12 +60,12 @@ $(document).ready(function() {
},
add: function(ele) {
ele.deferred = $.Deferred();
ele.deferred.done(function () {
ele.deferred.done(function() {
let $loadstart = $.Deferred();
let thumb = ele.childNodes[0];
let img = ele.childNodes[1];
let onLoadStart = function (img) {
let onLoadStart = function(img) {
if (img.naturalWidth) {
$loadstart.resolve(img, thumb);
} else {
@ -69,15 +73,15 @@ $(document).ready(function() {
}
};
$(img).one('load', function () {
$.when($loadstart).done(function () {
// Once fully loaded, update the waiting queue.
$(img).one('load', function() {
$.when($loadstart).done(function() {
// once fully loaded, update the waiting queue
--loading;
$(ele).data('imageLoading', 'false');
update();
});
});
$loadstart.done(function (img, thumb) {
$loadstart.done(function(img, thumb) {
thumb.style.display = 'none';
img.style.display = '';
});
@ -202,6 +206,8 @@ $(document).ready(function() {
Options.extend_tab('general', '<span id="inline-expand-max">' +
_('Number of simultaneous image downloads (0 to disable): ') +
'<input type="number" step="1" min="0" size="4"></span>');
Options.extend_tab('general', '<label id="inline-expand-fit-height"><input type="checkbox">' + _('Fit expanded images into screen height') + '</label>');
$('#inline-expand-max input')
.css('width', '50px')
.val(localStorage.inline_expand_max || DEFAULT_MAX)
@ -212,6 +218,21 @@ $(document).ready(function() {
localStorage.inline_expand_max = val;
});
$('#inline-expand-fit-height input').on('change', function() {
if (localStorage.inline_expand_fit_height !== 'false') {
localStorage.inline_expand_fit_height = 'false';
$('#expand-fit-height-style').remove();
}
else {
localStorage.inline_expand_fit_height = 'true';
$('<style id="expand-fit-height-style">.full-image { max-height: ' + window.innerHeight + 'px; }</style>').appendTo($('head'));
}
});
if (localStorage.inline_expand_fit_height !== 'false') {
$('#inline-expand-fit-height input').prop('checked', true);
}
}
if (window.jQuery) {

View file

@ -23,20 +23,20 @@ function addBoard(){
add_favorites();
} //This adds the text inside the textbox to favorites, localStorage.favorites and the page
var favorites = localStorage.favorites ? JSON.parse(localStorage.favorites) : [];
var favorites = JSON.parse(localStorage.favorites);
Options.add_tab('fav-tab','star',_("Favorites"));
//Pregenerating list of boards
//Pregenerating list of boards
var favList = $('<div id="sortable" style="cursor: pointer; display: inline-block">');
for(var i=0; i<favorites.length; i++){
favList.append( $('<div>'+favorites[i]+'</div>') );
}
}
//Creating list of minus symbols to remove unwanted boards
var minusList = $('<div id="minusList" style="color: #0000FF; display: inline-block">');
for(var i=0; i<favorites.length; i++){
minusList.append( $('<div data-board="'+i+'" style="cursor: pointer; margin-right: 5px">-</div>').on('click', function(e){removeBoard($(this).data('board'));}) );
}
}
//Help message so people understand how sorting boards works
$("<span>"+_("Drag the boards to sort them.")+"</span><br><br>").appendTo(Options.get_tab('fav-tab').content);
@ -65,7 +65,7 @@ addDiv.appendTo(Options.get_tab('fav-tab').content); //Adding the plus button
favList.sortable(); //Making boards with sortable id use the sortable jquery function
favList.on('sortstop', function() {
favorites = generateList();
favorites = generateList();
localStorage.favorites = JSON.stringify(favorites);
add_favorites();
});

View file

@ -43,9 +43,6 @@ $(function(){
document.location.reload();
}
});
$("#style-select").detach().css({float:"none","margin-bottom":0}).appendTo(tab.content);
});
}();

View file

@ -104,8 +104,10 @@ function buildMenu(e) {
function addButton(post) {
var $ele = $(post);
// Use unicode code with ascii variant selector
// https://stackoverflow.com/questions/37906969/how-to-prevent-ios-from-converting-ascii-into-emoji
$ele.find('input.delete').after(
$('<a>', {href: '#', class: 'post-btn', title: 'Post menu'}).text('►')
$('<a>', {href: '#', class: 'post-btn', title: 'Post menu'}).text('\u{25B6}\u{fe0e}')
);
}

View file

@ -15,7 +15,7 @@
$(document).ready(function() {
let showBackLinks = function() {
let replyId = $(this).attr('id').replace(/^reply_/, '');
let replyId = $(this).attr('id').split('_')[1];
$(this).find('div.body a:not([rel="nofollow"])').each(function() {
let id = $(this).text().match(/^>>(\d+)$/);

View file

@ -22,7 +22,7 @@ $(document).ready(function() {
function makeEmbedNode(embedHost, videoId, width, height) {
return $(`<iframe type="text/html" width="${width}" height="${height}" class="full-image"
src="https://${embedHost}/embed/${videoId}?autoplay=1" allow="fullscreen" frameborder="0"/>`);
src="https://${embedHost}/embed/${videoId}?autoplay=1" allow="fullscreen" frameborder="0" referrerpolicy="strict-origin"/>`);
}
// Adds Options panel item.

View file

@ -5,6 +5,7 @@
use Vichan\Context;
use Vichan\Data\ReportQueries;
use Vichan\Data\Driver\LogDriver;
require_once 'inc/bootstrap.php';
@ -221,7 +222,7 @@ function send_matrix_report(
$end = strlen($post['body_nomarkup']) > $max_msg_len ? ' [...]' : '';
$post_content = mb_substr($post['body_nomarkup'], 0, $max_msg_len) . $end;
$text_body = $reported_post_url . ($post['thread'] ? "#$post_id" : '') . " \nReason:\n" . $report_reason . " \nPost:\n" . $post_content;
$text_body = $reported_post_url . ($post['thread'] ? "#$id" : '') . " \nReason:\n" . $report_reason . " \nPost:\n" . $post_content;
$random_transaction_id = mt_rand();
$json_body = json_encode([
@ -354,7 +355,7 @@ function db_select_ban_appeals($ban_id)
$dropped_post = false;
function handle_nntpchan()
function handle_nntpchan(Context $ctx)
{
global $config;
if ($_SERVER['REMOTE_ADDR'] != $config['nntpchan']['trusted_peer']) {
@ -433,7 +434,7 @@ function handle_nntpchan()
if ($ct == 'text/plain') {
$content = file_get_contents("php://input");
} elseif ($ct == 'multipart/mixed' || $ct == 'multipart/form-data') {
_syslog(LOG_INFO, "MM: Files: " . print_r($GLOBALS, true)); // Debug
$ctx->get(LogDriver::class)->log(LogDriver::DEBUG, 'MM: Files: ' . print_r($GLOBALS, true));
$content = '';
@ -610,8 +611,8 @@ function handle_delete(Context $ctx)
modLog("User at $ip deleted his own post #$id");
}
_syslog(
LOG_INFO,
$ctx->get(LogDriver::class)->log(
LogDriver::INFO,
'Deleted post: ' .
'/' . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['thread'] ? $post['thread'] : $id) . ($post['thread'] ? '#' . $id : '')
);
@ -699,9 +700,7 @@ function handle_report(Context $ctx)
foreach ($report as $id) {
$post = db_select_post_minimal($board['uri'], $id);
if ($post === false) {
if ($config['syslog']) {
_syslog(LOG_INFO, "Failed to report non-existing post #{$id} in {$board['dir']}");
}
$ctx->get(LogDriver::class)->log(LogDriver::INFO, "Failed to report non-existing post #{$id} in {$board['dir']}");
error($config['error']['nopost']);
}
@ -716,13 +715,12 @@ function handle_report(Context $ctx)
error($error);
}
if ($config['syslog'])
_syslog(
LOG_INFO,
'Reported post: ' .
'/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') .
' for "' . $reason . '"'
);
$ctx->get(LogDriver::class)->log(
LogDriver::INFO,
'Reported post: ' .
'/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') .
' for "' . $reason . '"'
);
$report_queries->add($_SERVER['REMOTE_ADDR'], $board['uri'], $id, $reason);
@ -1315,7 +1313,7 @@ function handle_post(Context $ctx)
if (!hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) {
require_once 'inc/filters.php';
do_filters($post);
do_filters($ctx, $post);
}
if ($post['has_file']) {
@ -1404,13 +1402,13 @@ function handle_post(Context $ctx)
$file['thumbwidth'] = $size[0];
$file['thumbheight'] = $size[1];
} elseif (
$config['minimum_copy_resize'] &&
(($config['strip_exif'] && isset($file['exif_stripped']) && $file['exif_stripped']) || !$config['strip_exif']) &&
$image->size->width <= $config['thumb_width'] &&
$image->size->height <= $config['thumb_height'] &&
$file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension'])
) {
// Copy, because there's nothing to resize
coopy($file['tmp_name'], $file['thumb']);
copy($file['tmp_name'], $file['thumb']);
$file['thumbwidth'] = $image->size->width;
$file['thumbheight'] = $image->size->height;
@ -1553,35 +1551,6 @@ function handle_post(Context $ctx)
}
}
if ($config['tesseract_ocr'] && $file['thumb'] != 'file') {
// Let's OCR it!
$fname = $file['tmp_name'];
if ($file['height'] > 500 || $file['width'] > 500) {
$fname = $file['thumb'];
}
if ($fname == 'spoiler') {
// We don't have that much CPU time, do we?
} else {
$tmpname = __DIR__ . "/tmp/tesseract/" . rand(0, 10000000);
// Preprocess command is an ImageMagick b/w quantization
$error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " .
'tesseract stdin ' . escapeshellarg($tmpname) . ' ' . $config['tesseract_params']);
$tmpname .= ".txt";
$value = @file_get_contents($tmpname);
@unlink($tmpname);
if ($value && trim($value)) {
// This one has an effect, that the body is appended to a post body. So you can write a correct
// spamfilter.
$post['body_nomarkup'] .= "<tinyboard ocr image $key>" . htmlspecialchars($value) . "</tinyboard>";
}
}
}
if (!isset($dont_copy_file) || !$dont_copy_file) {
if (isset($file['file_tmp'])) {
if (!@rename($file['tmp_name'], $file['file'])) {
@ -1629,11 +1598,6 @@ function handle_post(Context $ctx)
}
}
// Do filters again if OCRing
if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) {
do_filters($post);
}
if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup']) && !$dropped_post) {
undoImage($post);
if ($config['robot_mute']) {
@ -1783,10 +1747,10 @@ function handle_post(Context $ctx)
buildThread($post['op'] ? $id : $post['thread']);
if ($config['syslog']) {
_syslog(LOG_INFO, 'New post: /' . $board['dir'] . $config['dir']['res'] .
link_for($post) . (!$post['op'] ? '#' . $id : ''));
}
$ctx->get(LogDriver::class)->log(
LogDriver::INFO,
'New post: /' . $board['dir'] . $config['dir']['res'] . link_for($post) . (!$post['op'] ? '#' . $id : '')
);
if (!$post['mod']) {
header('X-Associated-Content: "' . $redirect . '"');
@ -1879,17 +1843,17 @@ function handle_appeal(Context $ctx)
displayBan($ban);
}
$ctx = Vichan\build_context($config);
// Is it a post coming from NNTP? Let's extract it and pretend it's a normal post.
if (isset($_GET['Newsgroups'])) {
if ($config['nntpchan']['enabled']) {
handle_nntpchan();
handle_nntpchan($ctx);
} else {
error("NNTPChan: NNTPChan support is disabled");
}
}
$ctx = Vichan\build_context($config);
if (isset($_POST['delete'])) {
handle_delete($ctx);
} elseif (isset($_POST['report'])) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

BIN
static/flags/alunya.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

View file

@ -5,7 +5,7 @@
/*dark.css has been prepended (2021-11-11) instead of @import'd for performance*/
body {
background: #1E1E1E;
color: #A7A7A7;
color: #C0C0C0;
font-family: Verdana, sans-serif;
font-size: 14px;
}
@ -31,13 +31,13 @@ div.title p {
font-size: 10px;
}
a, a:link, a:visited, .intro a.email span.name {
color: #CCC;
color: #EEE;
text-decoration: none;
font-family: sans-serif;
font-family: Verdana, sans-serif;
}
a:link:hover, a:visited:hover {
color: #fff;
font-family: sans-serif;
font-family: Verdana, sans-serif;
text-decoration: none;
}
a.post_no {
@ -48,11 +48,11 @@ a.post_no:hover {
text-decoration: underline overline;
}
.intro a.post_no {
color: #CCC;
color: #EEE;
}
div.post.reply {
background: #333333;
border: #555555 1px solid;
border: #4f4f4f 1px solid;
@media (max-width: 48em) {
border-left-style: none;
@ -60,7 +60,7 @@ div.post.reply {
}
}
div.post.reply.highlighted {
background: #555;
background: #4f4f4f;
border: transparent 1px solid;
@media (max-width: 48em) {
@ -69,17 +69,17 @@ div.post.reply.highlighted {
}
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #CCCCCC;
color: #EEE;
}
div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover {
color: #32DD72;
}
div.post.inline {
border: #555555 1px solid;
border: #4f4f4f 1px solid;
}
.intro span.subject {
font-size: 12px;
font-family: sans-serif;
font-family: Verdana, sans-serif;
color: #446655;
font-weight: 800;
}
@ -96,16 +96,16 @@ div.post.inline {
}
input[type="text"], textarea, select {
background: #333333;
color: #CCCCCC;
color: #EEE;
border: #666666 1px solid;
padding-left: 5px;
padding-right: -5px;
font-family: sans-serif;
font-family: Verdana, sans-serif;
font-size: 10pt;
}
input[type="password"] {
background: #333333;
color: #CCCCCC;
color: #EEE;
border: #666666 1px solid;
}
form table tr th {
@ -133,10 +133,10 @@ div.banner a {
input[type="submit"] {
background: #333333;
border: #888888 1px solid;
color: #CCCCCC;
color: #EEE;
}
input[type="submit"]:hover {
background: #555555;
background: #4f4f4f;
border: #888888 1px solid;
color: #32DD72;
}
@ -151,7 +151,7 @@ span.trip {
}
div.pages {
background: #1E1E1E;
font-family: sans-serif;
font-family: Verdana, sans-serif;
}
.bar.bottom {
bottom: 0px;
@ -159,7 +159,7 @@ div.pages {
background-color: #1E1E1E;
}
div.pages a.selected {
color: #CCCCCC;
color: #EEE;
}
hr {
height: 1px;
@ -167,7 +167,7 @@ hr {
}
div.boardlist {
text-align: center;
color: #A7A7A7;
color: #C0C0C0;
}
div.ban {
background-color: transparent;
@ -188,7 +188,7 @@ div.boardlist:not(.bottom) {
}
.desktop-style div.boardlist:not(.bottom) {
text-shadow: black 1px 1px 1px, black -1px -1px 1px, black -1px 1px 1px, black 1px -1px 1px;
color: #A7A7A7;
color: #C0C0C0;
background-color: #1E1E1E;
}
div.report {
@ -211,7 +211,7 @@ div.report {
}
.box {
background: #333333;
border-color: #555555;
border-color: #4f4f4f;
color: #C5C8C6;
border-radius: 10px;
}
@ -221,7 +221,7 @@ div.report {
}
table thead th {
background: #333333;
border-color: #555555;
border-color: #4f4f4f;
color: #C5C8C6;
border-radius: 4px;
}
@ -229,11 +229,11 @@ table tbody tr:nth-of-type( even ) {
background-color: #333333;
}
table.board-list-table .board-uri .board-sfw {
color: #CCCCCC;
color: #EEE;
}
tbody.board-list-omitted td {
background: #333333;
border-color: #555555;
border-color: #4f4f4f;
}
table.board-list-table .board-tags .board-cell:hover {
background: #1e1e1e;

View file

@ -380,6 +380,7 @@ form table tr td div.center {
.file {
float: left;
min-width: 100px;
}
.file:not(.multifile) .post-image {
@ -390,6 +391,10 @@ form table tr td div.center {
float: none;
}
.file.multifile {
margin: 0 10px 0 0;
}
.file.multifile > p {
width: 0px;
min-width: 100%;

View file

@ -7,7 +7,7 @@
<div class="ban" id="global">
<h2>
RULES
RULES
</h2>
<p><b>GLOBAL:</b></p>
@ -36,7 +36,7 @@ Opening posts with liberalism or reactionary topics will be treated with far mor
<p>9) Due to derailing, COVID denialism outside the COVID-19 thread will be deleted.</p>
<p>10) All boards except for /siberia/ (and potentially /roulette/) are 'Safe For Work' boards. Pornography should not be posted on them without good reason, and any pornography on these boards should be hidden using the Spoiler Image option. New threads on /siberia/ with pornographic topics should have a Spoiler Image on the opening post.</p>
<p>10) All boards except for /siberia/ (and potentially /roulette/) are 'Safe For Work' boards. Pornography should not be posted on them without good reason, and any pornography on these boards should be hidden using the Spoiler Image option. New threads on /siberia/ with pornographic topics should have a Spoiler Image on the opening post. Some kinds of pornographic content are always banned on every board, including /siberia/: cp/loli/jailbait/anything that could possibly interpreted a child, "feral" furry, zoophilia, murder/gore (photographic) and other suitably extreme fetishes.</p>
<p>11) Posts should, overall, be conductive to an informed and productive discussion. /leftypol/ is not an academic journal, but it also should not be a cesspit of back and forth bickering and pointless insults. Users should attempt to argue for the point they are presenting in an honest and open way and should be receptive to information or arguments that do, in fact, challenge their views.</p>

View file

@ -16,13 +16,13 @@
border-width: 2px;
margin-right: 15px;
}
.introduction {
grid-column: 2 / 9;
grid-row: 1;
width: 100%;
}
.content {
grid-column: 2 / 9;
grid-row: 2;
@ -35,7 +35,7 @@
gap: 20px;
height: 100vh;
}
.modlog {
width: 50%;
text-align: left;
@ -69,7 +69,7 @@
li a.system {
font-weight: bold;
}
@media (max-width:768px) {
body{
display: grid;
@ -78,7 +78,7 @@
height: 100vh;
width: 100%;
}
.introduction {
grid-column: 1;
grid-row: 1;
@ -96,17 +96,18 @@
grid-column: 1;
grid-row: 3;
width: 100%;
word-break: break-all;
}
.modlog {
width: 100%;
text-align: center;
}
table {
table-layout: fixed;
}
table.modlog tr th {
white-space: normal;
word-wrap: break-word;

View file

@ -11,7 +11,7 @@
.home-description {
margin: 20px auto 0 auto;
text-align: center;
max-width: 700px;"
max-width: 700px;
}
</style>
{{ boardlist.top }}

View file

@ -88,7 +88,7 @@
<h2 id="9">
How can I suggest or submit fixes to the site ?
</h2>
<p>There is a /meta/ thread for this, and our <a href="https://gitlab.leftypol.org/leftypol/leftypol/">Gitlab repo</a>.</p>
<p>There is a /meta/ thread for this, and our <a href="https://forgejo.leftypol.org/leftypol/leftypol/">Forgejo repo</a>.</p>
<h2 id="10">
I don't trust Tor exit nodes. Do you have an .onion site ?
</h2>
@ -129,6 +129,10 @@
What are the maximum filesize for attachments ?
</h2>
<p>Maximum file size in megabytes for attachments to a single post is 80MB (e.g. 5 * 16MB), as most boards support uploading 5 attachments by default. Maximum file size in pixels for images is currently set to 20000 by 20000. </p>
<h2 id="16">
Can I have an account on your git instance?
</h2>
<p>Create the account on <a href="https://forgejo.leftypol.org/">Forgejo</a>, then contact the staff via the Tech Team General thread on /meta/ to get your account approved.</p>
</div>

View file