Add SFW overboard and its catalog option
This commit is contained in:
parent
ffff01f986
commit
24fcb96404
|
@ -76,6 +76,13 @@
|
||||||
'default' => false,
|
'default' => false,
|
||||||
'comment' => 'Enable catalog for the Rand theme. This requires the Rand theme to be enabled.'
|
'comment' => 'Enable catalog for the Rand theme. This requires the Rand theme to be enabled.'
|
||||||
);
|
);
|
||||||
|
$theme['config'][] = Array(
|
||||||
|
'title' => 'Enable SFW overboard catalog',
|
||||||
|
'name' => 'enable_sfwoverboard',
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'default' => false,
|
||||||
|
'comment' => 'Enable catalog for the sfwoverboard theme. This requires the sfwoverboard theme to be enabled.'
|
||||||
|
);
|
||||||
$theme['config'][] = Array(
|
$theme['config'][] = Array(
|
||||||
'title' => 'Use tooltipster',
|
'title' => 'Use tooltipster',
|
||||||
'name' => 'use_tooltipster',
|
'name' => 'use_tooltipster',
|
||||||
|
|
|
@ -92,6 +92,14 @@
|
||||||
{
|
{
|
||||||
$b->buildRand();
|
$b->buildRand();
|
||||||
}
|
}
|
||||||
|
// FIXME: Check that sfwoverboard is actually enabled
|
||||||
|
if ($settings['enable_sfwoverboard'] && (
|
||||||
|
$action === 'all' || $action === 'post' ||
|
||||||
|
$action === 'post-thread' || $action === 'post-delete' || $action === 'rebuild'))
|
||||||
|
{
|
||||||
|
$b->buildsfwoverboard();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap functions in a class so they don't interfere with normal Tinyboard operations
|
// Wrap functions in a class so they don't interfere with normal Tinyboard operations
|
||||||
|
@ -297,7 +305,43 @@
|
||||||
$recent_posts = $this->generateRecentPosts($threads);
|
$recent_posts = $this->generateRecentPosts($threads);
|
||||||
|
|
||||||
$this->saveForBoard($randSettings['uri'], $recent_posts,
|
$this->saveForBoard($randSettings['uri'], $recent_posts,
|
||||||
$config['root'] . $randSettings['uri']);
|
$config['root'] . $randSettings['uri'], true);
|
||||||
|
}
|
||||||
|
public function buildsfwoverboard() {
|
||||||
|
global $config;
|
||||||
|
print_err("Catalog.buildsfwoverboard");
|
||||||
|
|
||||||
|
$sfwoverboardSettings = themeSettings('sfwoverboard');
|
||||||
|
$queries = array();
|
||||||
|
$threads = array();
|
||||||
|
|
||||||
|
$exclusions = explode(' ', $sfwoverboardSettings['exclude']);
|
||||||
|
$boards = array_diff(listBoards(true), $exclusions);
|
||||||
|
|
||||||
|
foreach ($boards as $b) {
|
||||||
|
if (array_key_exists($b, $this->threadsCache)) {
|
||||||
|
$threads = array_merge($threads, $this->threadsCache[$b]);
|
||||||
|
} else {
|
||||||
|
$queries[] = $this->buildThreadsQuery($b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch threads from boards that haven't beenp processed yet
|
||||||
|
if (!empty($queries)) {
|
||||||
|
$sql = implode(' UNION ALL ', $queries);
|
||||||
|
$res = query($sql) or error(db_error());
|
||||||
|
$threads = array_merge($threads, $res->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort in bump order
|
||||||
|
usort($threads, function($a, $b) {
|
||||||
|
return strcmp($b['bump'], $a['bump']);
|
||||||
|
});
|
||||||
|
// Generate data for the template
|
||||||
|
$recent_posts = $this->generateRecentPosts($threads);
|
||||||
|
|
||||||
|
$this->saveForBoard($sfwoverboardSettings['uri'], $recent_posts,
|
||||||
|
$config['root'] . $sfwoverboardSettings['uri'], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Basic theme properties
|
||||||
|
$theme = array(
|
||||||
|
'name' => 'SFW Overboard',
|
||||||
|
// Description (you can use Tinyboard markup here)
|
||||||
|
'description' => 'An additional overboard, intended to list a different set of boards.',
|
||||||
|
'version' => 'v0.1',
|
||||||
|
// Unique function name for building and installing whatever's necessary
|
||||||
|
'build_function' => 'sfwoverboard_build',
|
||||||
|
'install_callback' => 'sfwoverboard_install',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Theme configuration
|
||||||
|
$theme['config'] = array(
|
||||||
|
array(
|
||||||
|
'title' => 'Board name',
|
||||||
|
'name' => 'title',
|
||||||
|
'type' => 'text',
|
||||||
|
'default' => 'SFW Overboard',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => 'Board URI',
|
||||||
|
'name' => 'uri',
|
||||||
|
'type' => 'text',
|
||||||
|
'default' => '.',
|
||||||
|
'comment' => '("mixed", for example)',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => 'Subtitle',
|
||||||
|
'name' => 'subtitle',
|
||||||
|
'type' => 'text',
|
||||||
|
'comment' => '(%s = thread limit, for example "%s coolest threads")',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => 'Excluded boards',
|
||||||
|
'name' => 'exclude',
|
||||||
|
'type' => 'text',
|
||||||
|
'comment' => '(space seperated)',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => 'Number of threads',
|
||||||
|
'name' => 'thread_limit',
|
||||||
|
'type' => 'text',
|
||||||
|
'default' => '15',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!function_exists('sfwoverboard_install')) {
|
||||||
|
function sfwoverboard_install($settings) {
|
||||||
|
if (!file_exists($settings['uri'])) {
|
||||||
|
@mkdir($settings['uri'], 0777) or error("Couldn't create {$settings['uri']}. Check permissions.", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
$(document).ready(function() {
|
||||||
|
var cachedPages = [],
|
||||||
|
loading = false,
|
||||||
|
timer = null;
|
||||||
|
|
||||||
|
// Load data from HTML5 localStorage
|
||||||
|
var hiddenBoards = JSON.parse(localStorage.getItem('hiddenboards'));
|
||||||
|
|
||||||
|
var storeHiddenBoards = function() {
|
||||||
|
localStorage.setItem('hiddenboards', JSON.stringify(hiddenBoards));
|
||||||
|
};
|
||||||
|
|
||||||
|
// No board are hidden by default
|
||||||
|
if (!hiddenBoards) {
|
||||||
|
hiddenBoards = {};
|
||||||
|
storeHiddenBoards();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide threads from the same board and remember for next time
|
||||||
|
var onHideClick = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var board = $(this).parent().next().data('board'),
|
||||||
|
threads = $('[data-board="'+board+'"]:not([data-cached="yes"])'),
|
||||||
|
btns = threads.prev().find('.threads-toggle'),
|
||||||
|
hrs = btns.next();
|
||||||
|
|
||||||
|
if (hiddenBoards[board]) {
|
||||||
|
threads.show();
|
||||||
|
btns.find('.threads-toggle').html(_('(hide threads from this board)'));
|
||||||
|
hrs.hide();
|
||||||
|
} else {
|
||||||
|
threads.hide();
|
||||||
|
btns.html(_('(show threads from this board)'));
|
||||||
|
hrs.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
hiddenBoards[board] = !hiddenBoards[board];
|
||||||
|
storeHiddenBoards();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add a hiding link and horizontal separator to each thread
|
||||||
|
var addHideButton = function() {
|
||||||
|
var board = $(this).next().data('board'),
|
||||||
|
// Create the link and separator
|
||||||
|
button = $('<a href="#" class="unimportant threads-toggle"></a>')
|
||||||
|
.click(onHideClick),
|
||||||
|
myHr = $('<hr />');
|
||||||
|
|
||||||
|
// Insert them after the board name
|
||||||
|
$(this).append(' ').append(button).append(myHr);
|
||||||
|
|
||||||
|
if (hiddenBoards[board]) {
|
||||||
|
button.html(_('(show threads from this board)'));
|
||||||
|
$(this).next().hide();
|
||||||
|
} else {
|
||||||
|
button.html(_('(hide threads from this board)'));
|
||||||
|
myHr.hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$('h2').each(addHideButton);
|
||||||
|
|
||||||
|
var appendThread = function(elem, data) {
|
||||||
|
var boardLink = $('<h2><a href="' + modRoot + data.board + '/">/' +
|
||||||
|
data.board + '/</a></h2>');
|
||||||
|
|
||||||
|
// Push the thread after the currently last one
|
||||||
|
$('div[id*="thread_"]').last()
|
||||||
|
.after(elem.data('board', data.board)
|
||||||
|
.data('cached', 'no')
|
||||||
|
.show());
|
||||||
|
// Add the obligatory board link
|
||||||
|
boardLink.insertBefore(elem);
|
||||||
|
// Set up the hiding link
|
||||||
|
addHideButton.call(boardLink);
|
||||||
|
// Trigger an event to let the world know that we have a new thread aboard
|
||||||
|
$(document).trigger('new_post', elem);
|
||||||
|
};
|
||||||
|
|
||||||
|
var attemptLoadNext = function() {
|
||||||
|
if (!ukko_overflow.length) {
|
||||||
|
$('.pages').show().html(_('No more threads to display'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var viewHeight = $(window).scrollTop() + $(window).height(),
|
||||||
|
pageHeight = $(document).height();
|
||||||
|
// Keep loading deferred threads as long as we're close to the bottom of the
|
||||||
|
// page and there are threads remaining
|
||||||
|
while(viewHeight + 1000 > pageHeight && !loading && ukko_overflow.length > 0) {
|
||||||
|
// Take the first unloaded post
|
||||||
|
var post = ukko_overflow.shift(),
|
||||||
|
page = modRoot + post.board + '/' + post.page;
|
||||||
|
|
||||||
|
var thread = $('div#thread_' + post.id + '[data-board="' + post.board + '"]');
|
||||||
|
// Check that the thread hasn't been inserted yet
|
||||||
|
if (thread.length && thread.data('cached') !== 'yes') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we've already downloaded the index page on which this thread
|
||||||
|
// is located
|
||||||
|
if ($.inArray(page, cachedPages) !== -1) {
|
||||||
|
if (thread.length) {
|
||||||
|
appendThread(thread, post);
|
||||||
|
}
|
||||||
|
// Otherwise just load the page and cache its threads
|
||||||
|
} else {
|
||||||
|
// Make sure that no other thread does the job that we're about to do
|
||||||
|
loading = true;
|
||||||
|
$('.pages').show().html(_('Loading…'));
|
||||||
|
|
||||||
|
// Retrieve the page from the server
|
||||||
|
$.get(page, function(data) {
|
||||||
|
cachedPages.push(page);
|
||||||
|
|
||||||
|
// Cache each retrieved thread
|
||||||
|
$(data).find('div[id*="thread_"]').each(function() {
|
||||||
|
var thread_id = $(this).attr('id').replace('thread_', '');
|
||||||
|
|
||||||
|
// Check that this thread hasn't already been loaded somehow
|
||||||
|
if ($('div#thread_' + thread_id + '[data-board="' +
|
||||||
|
post.board + '"]').length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the freshly loaded threads somewhere at the top
|
||||||
|
// of the page for now
|
||||||
|
$('form[name="postcontrols"]')
|
||||||
|
.prepend($(this).hide()
|
||||||
|
.data('cached', 'yes')
|
||||||
|
.data('data-board', post.board));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find the current thread in the newly retrieved ones
|
||||||
|
thread = $('div#thread_' + post.id + '[data-board="' +
|
||||||
|
post.board + '"][data-cached="yes"]');
|
||||||
|
|
||||||
|
if (thread.length) {
|
||||||
|
appendThread(thread, post);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the lock
|
||||||
|
loading = false;
|
||||||
|
$('.pages').hide().html('');
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(timer);
|
||||||
|
// Check again in one second
|
||||||
|
timer = setTimeout(attemptLoadNext, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
attemptLoadNext();
|
||||||
|
});
|
|
@ -0,0 +1,223 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require 'info.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the board's HTML and move it and its JavaScript in place, whence
|
||||||
|
* it's served
|
||||||
|
*/
|
||||||
|
function sfwoverboard_build($action, $settings) {
|
||||||
|
global $config;
|
||||||
|
|
||||||
|
if ($action !== 'all' && $action !== 'post' && $action !== 'post-thread' &&
|
||||||
|
$action !== 'post-delete')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($config['smart_build']) {
|
||||||
|
file_unlink($settings['uri'] . '/index.html');
|
||||||
|
} else {
|
||||||
|
$sfwoverboard = new sfwoverboard($settings);
|
||||||
|
|
||||||
|
// Copy the generated board HTML to its place
|
||||||
|
file_write($settings['uri'] . '/index.html', $sfwoverboard->build());
|
||||||
|
file_write($settings['uri'] . '/semirand.js',
|
||||||
|
Element('themes/sfwoverboard/semirand.js', array()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulation of the theme's internals
|
||||||
|
*/
|
||||||
|
class sfwoverboard {
|
||||||
|
private $settings;
|
||||||
|
|
||||||
|
function __construct($settings) {
|
||||||
|
$this->settings = $this->parseSettings($settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse and validate configuration parameters passed from the UI
|
||||||
|
*/
|
||||||
|
private function parseSettings($settings) {
|
||||||
|
if (!is_numeric($settings['thread_limit']))
|
||||||
|
{
|
||||||
|
error('Invalid configuration parameters.', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings['exclude'] = explode(' ', $settings['exclude']);
|
||||||
|
$settings['thread_limit'] = intval($settings['thread_limit']);
|
||||||
|
|
||||||
|
if ($settings['thread_limit'] < 1)
|
||||||
|
{
|
||||||
|
error('Invalid configuration parameters.', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain list of all threads from all non-excluded boards
|
||||||
|
*/
|
||||||
|
private function fetchThreads() {
|
||||||
|
$query = '';
|
||||||
|
$boards = listBoards(true);
|
||||||
|
|
||||||
|
foreach ($boards as $b) {
|
||||||
|
if (in_array($b, $this->settings['exclude']))
|
||||||
|
continue;
|
||||||
|
// Threads are those posts that have no parent thread
|
||||||
|
$query .= "SELECT *, '$b' AS `board` FROM ``posts_$b`` " .
|
||||||
|
"WHERE `thread` IS NULL UNION ALL ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = preg_replace('/UNION ALL $/', 'ORDER BY `bump` DESC', $query);
|
||||||
|
$result = query($query) or error(db_error());
|
||||||
|
|
||||||
|
return $result->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all replies to a given thread
|
||||||
|
*/
|
||||||
|
private function fetchReplies($board, $thread_id, $preview_count) {
|
||||||
|
$query = prepare("SELECT * FROM (SELECT * FROM ``posts_$board`` WHERE `thread` = :id ORDER BY `time` DESC LIMIT :limit) as
|
||||||
|
t ORDER BY t.time ASC");
|
||||||
|
$query->bindValue(':id', $thread_id, PDO::PARAM_INT);
|
||||||
|
$query->bindValue(':limit', $preview_count, PDO::PARAM_INT);
|
||||||
|
$query->execute() or error(db_error($query));
|
||||||
|
|
||||||
|
return $query->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve count of images and posts in a thread
|
||||||
|
*/
|
||||||
|
private function fetchThreadCount($board, $thread_id, $preview_count) {
|
||||||
|
$query = prepare("SELECT SUM(t.num_files) as file_count, COUNT(t.id) as post_count FROM (SELECT * FROM ``posts_$board`` WHERE `thread` = :id ORDER BY `time` DESC LIMIT :offset , 18446744073709551615) as t;");
|
||||||
|
$query->bindValue(':id', $thread_id, PDO::PARAM_INT);
|
||||||
|
$query->bindValue(':offset', $preview_count, PDO::PARAM_INT);
|
||||||
|
$query->execute() or error(db_error($query));
|
||||||
|
|
||||||
|
return $query->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the HTML of a single thread in the catalog
|
||||||
|
*/
|
||||||
|
private function buildOne($post, $mod = false) {
|
||||||
|
global $config;
|
||||||
|
|
||||||
|
openBoard($post['board']);
|
||||||
|
$thread = new Thread($post, $mod ? '?/' : $config['root'], $mod);
|
||||||
|
// Number of replies to a thread that are displayed beneath it
|
||||||
|
$preview_count = $post['sticky'] ? $config['threads_preview_sticky'] :
|
||||||
|
$config['threads_preview'];
|
||||||
|
$replies = $this->fetchReplies($post['board'], $post['id'], $preview_count);
|
||||||
|
|
||||||
|
$disp_replies = $replies;
|
||||||
|
foreach ($disp_replies as $reply) {
|
||||||
|
// Append the reply to the thread as it's being built
|
||||||
|
$thread->add(new Post($reply, $mod ? '?/' : $config['root'], $mod));
|
||||||
|
}
|
||||||
|
|
||||||
|
$threadCount = $this->fetchThreadCount($post['board'], $post['id'], $preview_count);
|
||||||
|
$thread->omitted = $threadCount['post_count'];
|
||||||
|
$thread->omitted_images = $threadCount['file_count'];
|
||||||
|
|
||||||
|
// Board name and link
|
||||||
|
$html = '<h2><a href="' . $config['root'] . $post['board'] . '/">/' .
|
||||||
|
$post['board'] . '/</a></h2>';
|
||||||
|
// The thread itself
|
||||||
|
$html .= $thread->build(true);
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the required information and generate the HTML
|
||||||
|
*/
|
||||||
|
public function build($mod = false) {
|
||||||
|
if (!isset($this->settings)) {
|
||||||
|
error('Theme is not configured properly.');
|
||||||
|
}
|
||||||
|
|
||||||
|
global $config;
|
||||||
|
|
||||||
|
$html = '';
|
||||||
|
$overflow = array();
|
||||||
|
|
||||||
|
// Fetch threads from all boards and chomp the first 'n' posts, depending
|
||||||
|
// on the setting
|
||||||
|
$threads = $this->fetchThreads();
|
||||||
|
$total_count = count($threads);
|
||||||
|
// Top threads displayed on load
|
||||||
|
$top_threads = array_splice($threads, 0, $this->settings['thread_limit']);
|
||||||
|
// Number of processed threads by board
|
||||||
|
$counts = array();
|
||||||
|
|
||||||
|
// Output threads up to the specified limit
|
||||||
|
foreach ($top_threads as $post) {
|
||||||
|
if (array_key_exists($post['board'], $counts)) {
|
||||||
|
++$counts[$post['board']];
|
||||||
|
} else {
|
||||||
|
$counts[$post['board']] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= $this->buildOne($post, $mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($threads as $post) {
|
||||||
|
if (array_key_exists($post['board'], $counts)) {
|
||||||
|
++$counts[$post['board']];
|
||||||
|
} else {
|
||||||
|
$counts[$post['board']] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$page = 'index';
|
||||||
|
$board_page = floor($counts[$post['board']] / $config['threads_per_page']);
|
||||||
|
if ($board_page > 0) {
|
||||||
|
$page = $board_page + 1;
|
||||||
|
}
|
||||||
|
$overflow[] = array(
|
||||||
|
'id' => $post['id'],
|
||||||
|
'board' => $post['board'],
|
||||||
|
'page' => $page . '.html'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '<script>var ukko_overflow = ' . json_encode($overflow) . '</script>';
|
||||||
|
$html .= '<script type="text/javascript" src="/'.$this->settings['uri'].'/semirand.js"></script>';
|
||||||
|
|
||||||
|
return Element('index.html', array(
|
||||||
|
'config' => $config,
|
||||||
|
'board' => array(
|
||||||
|
'dir' => $this->settings['uri'] . "/",
|
||||||
|
'url' => $this->settings['uri'],
|
||||||
|
'title' => $this->settings['title'],
|
||||||
|
'subtitle' => str_replace('%s', $this->settings['thread_limit'],
|
||||||
|
strval(min($this->settings['subtitle'], $total_count))),
|
||||||
|
),
|
||||||
|
'no_post_form' => true,
|
||||||
|
'body' => $html,
|
||||||
|
'mod' => $mod,
|
||||||
|
'boardlist' => createBoardlist($mod),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!function_exists('array_column')) {
|
||||||
|
/**
|
||||||
|
* Pick out values from subarrays by given key
|
||||||
|
*/
|
||||||
|
function array_column($array, $key) {
|
||||||
|
$result = [];
|
||||||
|
foreach ($array as $val) {
|
||||||
|
$result[] = $val[$key];
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Loading…
Reference in New Issue