mirror of
https://github.com/vichan-devel/vichan.git
synced 2025-01-19 09:27:24 +01:00
This commit is contained in:
commit
c648448a75
@ -85,13 +85,13 @@ class Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function translateFile($file, $post, &$apiPost) {
|
private function translateFile($file, &$apiPost) {
|
||||||
$this->translateFields($this->fileFields, $file, $apiPost);
|
$this->translateFields($this->fileFields, $file, $apiPost);
|
||||||
$apiPost['filename'] = @substr($file->name, 0, strrpos($file->name, '.'));
|
$apiPost['filename'] = @substr($file->name, 0, strrpos($file->name, '.'));
|
||||||
$dotPos = strrpos($file->file, '.');
|
$dotPos = strrpos($file->file, '.');
|
||||||
$apiPost['ext'] = substr($file->file, $dotPos);
|
$apiPost['ext'] = substr($file->file, $dotPos);
|
||||||
$apiPost['tim'] = substr($file->file, 0, $dotPos);
|
$apiPost['tim'] = substr($file->file, 0, $dotPos);
|
||||||
$apiPost['md5'] = base64_encode(hex2bin($post->filehash));
|
$apiPost['md5'] = base64_encode(hex2bin($file->hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function translatePost($post, $threadsPage = false) {
|
private function translatePost($post, $threadsPage = false) {
|
||||||
@ -116,17 +116,16 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle files
|
// Handle files
|
||||||
// Note: 4chan only supports one file, so only the first file is taken into account for 4chan-compatible API.
|
|
||||||
if (isset($post->files) && $post->files && !$threadsPage) {
|
if (isset($post->files) && $post->files && !$threadsPage) {
|
||||||
$file = $post->files[0];
|
$file = $post->files[0];
|
||||||
$this->translateFile($file, $post, $apiPost);
|
$this->translateFile($file, $apiPost);
|
||||||
if (sizeof($post->files) > 1) {
|
if (sizeof($post->files) > 1) {
|
||||||
$extra_files = array();
|
$extra_files = array();
|
||||||
foreach ($post->files as $i => $f) {
|
foreach ($post->files as $i => $f) {
|
||||||
if ($i == 0) continue;
|
if ($i == 0) continue;
|
||||||
|
|
||||||
$extra_file = array();
|
$extra_file = array();
|
||||||
$this->translateFile($f, $post, $extra_file);
|
$this->translateFile($f, $extra_file);
|
||||||
|
|
||||||
$extra_files[] = $extra_file;
|
$extra_files[] = $extra_file;
|
||||||
}
|
}
|
||||||
|
@ -999,8 +999,8 @@
|
|||||||
// It's very important that you match the entire input (with ^ and $) or things will not work correctly.
|
// It's very important that you match the entire input (with ^ and $) or things will not work correctly.
|
||||||
$config['embedding'] = array(
|
$config['embedding'] = array(
|
||||||
array(
|
array(
|
||||||
'/^https?:\/\/(\w+\.)?youtube\.com\/watch\?v=([a-zA-Z0-9\-_]{10,11})(&.+)?$/i',
|
'/^https?:\/\/(?:\w+\.)?(?:youtube\.com\/watch\?|youtu\.be\/)(?:(?:&?v=)?([a-zA-Z0-9\-_]{10,11})\??|&?(start=\d*)|&?(end=\d*)|(?:&?[^&]+))*$/i',
|
||||||
'<iframe style="float: left;margin: 10px 20px;" width="%%tb_width%%" height="%%tb_height%%" frameborder="0" id="ytplayer" src="http://www.youtube.com/embed/$2"></iframe>'
|
'<iframe style="float: left;margin: 10px 20px;" width="%%tb_width%%" height="%%tb_height%%" frameborder="0" id="ytplayer" src="http://youtube.com/embed/$1?$2$3"></iframe>'
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'/^https?:\/\/(\w+\.)?vimeo\.com\/(\d{2,10})(\?.+)?$/i',
|
'/^https?:\/\/(\w+\.)?vimeo\.com\/(\d{2,10})(\?.+)?$/i',
|
||||||
@ -1657,7 +1657,7 @@
|
|||||||
$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
|
// Youtube.js embed HTML code
|
||||||
$config['youtube_js_html'] = '<div class="video-container" data-video="$2">'.
|
$config['youtube_js_html'] = '<div class="video-container" data-video="$1" data-params="&$2&$3">'.
|
||||||
'<a href="$0" target="_blank" class="file">'.
|
'<a href="$0" target="_blank" class="file">'.
|
||||||
'<img style="width:360px;height:270px;" src="//img.youtube.com/vi/$2/0.jpg" class="post-image"/>'.
|
'<img style="width:360px;height:270px;" src="//img.youtube.com/vi/$1/0.jpg" class="post-image"/>'.
|
||||||
'</a></div>';
|
'</a></div>';
|
||||||
|
@ -354,8 +354,13 @@ class Post {
|
|||||||
$this->{$key} = $value;
|
$this->{$key} = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($this->files) && $this->files)
|
if (isset($this->files) && $this->files) {
|
||||||
$this->files = json_decode($this->files);
|
$this->files = json_decode($this->files);
|
||||||
|
// Compatibility for posts before individual file hashing
|
||||||
|
foreach ($this->files as &$file)
|
||||||
|
if (!isset($file->hash))
|
||||||
|
$file->hash = $this->filehash;
|
||||||
|
}
|
||||||
|
|
||||||
$this->subject = utf8tohtml($this->subject);
|
$this->subject = utf8tohtml($this->subject);
|
||||||
$this->name = utf8tohtml($this->name);
|
$this->name = utf8tohtml($this->name);
|
||||||
|
@ -162,7 +162,7 @@
|
|||||||
|
|
||||||
$config['embedding'] = array(
|
$config['embedding'] = array(
|
||||||
array(
|
array(
|
||||||
'/^https?:\/\/(\w+\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9\-_]{10,11})(&.+)?$/i',
|
'/^https?:\/\/(?:\w+\.)?(?:youtube\.com\/watch\?|youtu\.be\/)(?:(?:&?v=)?([a-zA-Z0-9\-_]{10,11})\??|&?(start=\d*)|&?(end=\d*)|(?:&?[^&]+))*$/i',
|
||||||
$config['youtube_js_html']
|
$config['youtube_js_html']
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
|
@ -29,6 +29,9 @@ function load_twig() {
|
|||||||
));
|
));
|
||||||
$twig->addExtension(new Twig_Extensions_Extension_Tinyboard());
|
$twig->addExtension(new Twig_Extensions_Extension_Tinyboard());
|
||||||
$twig->addExtension(new Twig_Extensions_Extension_I18n());
|
$twig->addExtension(new Twig_Extensions_Extension_I18n());
|
||||||
|
|
||||||
|
$twig->addFilter(new Twig_SimpleFilter('hex2bin', 'hex2bin'));
|
||||||
|
$twig->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function Element($templateFile, array $options) {
|
function Element($templateFile, array $options) {
|
||||||
|
82
js/catalog-search.js
Normal file
82
js/catalog-search.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* catalog-search.js
|
||||||
|
* - Search and filters threads when on catalog view
|
||||||
|
* - Optional shortcuts 's' and 'esc' to open and close the search.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* $config['additional_javascript'][] = 'js/jquery.min.js';
|
||||||
|
* $config['additional_javascript'][] = 'js/comment-toolbar.js';
|
||||||
|
*/
|
||||||
|
if (active_page == 'catalog') {
|
||||||
|
$(document).ready(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// 'true' = enable shortcuts
|
||||||
|
var useKeybinds = true;
|
||||||
|
|
||||||
|
// trigger the search 400ms after last keystroke
|
||||||
|
var delay = 400;
|
||||||
|
var timeoutHandle;
|
||||||
|
|
||||||
|
//search and hide none matching threads
|
||||||
|
function filter(search_term) {
|
||||||
|
$('.replies').each(function () {
|
||||||
|
var subject = $(this).children('.intro').text().toLowerCase();
|
||||||
|
var comment = $(this).clone().children().remove(':lt(2)').end().text().trim().toLowerCase();
|
||||||
|
search_term = search_term.toLowerCase();
|
||||||
|
|
||||||
|
if (subject.indexOf(search_term) == -1 && comment.indexOf(search_term) == -1) {
|
||||||
|
$(this).parents('div[id="Grid"]>.mix').css('display', 'none');
|
||||||
|
} else {
|
||||||
|
$(this).parents('div[id="Grid"]>.mix').css('display', 'inline-block');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchToggle() {
|
||||||
|
var button = $('#catalog_search_button')[0];
|
||||||
|
|
||||||
|
if (!button.dataset.expanded) {
|
||||||
|
button.dataset.expanded = '1';
|
||||||
|
button.innerText = 'Close';
|
||||||
|
$('.catalog_search').append(' <input id="search_field" style="border: inset 1px;">');
|
||||||
|
$('#search_field').focus();
|
||||||
|
} else {
|
||||||
|
delete button.dataset.expanded;
|
||||||
|
button.innerText = 'Search';
|
||||||
|
$('.catalog_search').children().last().remove();
|
||||||
|
$('div[id="Grid"]>.mix').each(function () { $(this).css('display', 'inline-block'); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.threads').before('<span class="catalog_search">[<a id="catalog_search_button" style="text-decoration:none; cursor:pointer;"></a>]</span>');
|
||||||
|
$('#catalog_search_button').text('Search');
|
||||||
|
|
||||||
|
$('#catalog_search_button').on('click', searchToggle);
|
||||||
|
$('.catalog_search').on('keyup', 'input#search_field', function (e) {
|
||||||
|
window.clearTimeout(timeoutHandle);
|
||||||
|
timeoutHandle = window.setTimeout(filter, 400, e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (useKeybinds) {
|
||||||
|
// 's'
|
||||||
|
$('body').on('keydown', function (e) {
|
||||||
|
if (e.which === 83 && e.target.tagName === 'BODY' && !(e.ctrlKey || e.altKey || e.shiftKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
if ($('#search_field').length !== 0) {
|
||||||
|
$('#search_field').focus();
|
||||||
|
} else {
|
||||||
|
searchToggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 'esc'
|
||||||
|
$('.catalog_search').on('keydown', 'input#search_field', function (e) {
|
||||||
|
if (e.which === 27 && !(e.ctrlKey || e.altKey || e.shiftKey)) {
|
||||||
|
window.clearTimeout(timeoutHandle);
|
||||||
|
searchToggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -8,253 +8,376 @@
|
|||||||
* $config['additional_javascript'][] = 'js/comment-toolbar.js';
|
* $config['additional_javascript'][] = 'js/comment-toolbar.js';
|
||||||
*/
|
*/
|
||||||
if (active_page == 'thread' || active_page == 'index') {
|
if (active_page == 'thread' || active_page == 'index') {
|
||||||
$(document).ready(function () {
|
var formatText = (function($){
|
||||||
'use strict';
|
"use strict";
|
||||||
var formats = {
|
var self = {};
|
||||||
bold: {
|
self.rules = {
|
||||||
displayText: 'B',
|
spoiler: {
|
||||||
altText: 'bold',
|
text: 'Spoiler',
|
||||||
styleCSS: 'font-weight: bold;',
|
key: 's',
|
||||||
options: {
|
multiline: false,
|
||||||
prefix: "'''",
|
exclusiveline: false,
|
||||||
suffix: "'''"
|
prefix:'**',
|
||||||
},
|
suffix:'**'
|
||||||
edit: function (box, options) {
|
|
||||||
wrapSelection(box, options);
|
|
||||||
},
|
|
||||||
shortcutKey: 'b'
|
|
||||||
},
|
},
|
||||||
italics: {
|
italics: {
|
||||||
displayText: 'i',
|
text: 'Italics',
|
||||||
altText: 'italics',
|
key: 'i',
|
||||||
styleCSS: 'font-style: italic;',
|
multiline: false,
|
||||||
options: {
|
exclusiveline: false,
|
||||||
prefix: "''",
|
prefix: "''",
|
||||||
suffix: "''"
|
suffix: "''"
|
||||||
},
|
},
|
||||||
edit: function (box, options) {
|
bold: {
|
||||||
wrapSelection(box, options);
|
text: 'Bold',
|
||||||
|
key: 'b',
|
||||||
|
multiline: false,
|
||||||
|
exclusiveline: false,
|
||||||
|
prefix: "'''",
|
||||||
|
suffix: "'''"
|
||||||
},
|
},
|
||||||
shortcutKey: 'i'
|
underline: {
|
||||||
},
|
text: 'Underline',
|
||||||
under: {
|
key: 'u',
|
||||||
displayText: 'U',
|
multiline: false,
|
||||||
altText: 'underline',
|
exclusiveline: false,
|
||||||
styleCSS: 'text-decoration: underline;',
|
|
||||||
options: {
|
|
||||||
prefix:'__',
|
prefix:'__',
|
||||||
suffix:'__'
|
suffix:'__'
|
||||||
},
|
},
|
||||||
edit: function (box, options) {
|
|
||||||
wrapSelection(box, options);
|
|
||||||
},
|
|
||||||
shortcutKey: 'u'
|
|
||||||
},
|
|
||||||
spoiler: {
|
|
||||||
displayText: 'spoiler',
|
|
||||||
altText: 'mark as spoiler',
|
|
||||||
styleCSS: '',
|
|
||||||
options: {
|
|
||||||
prefix: '[spoiler]',
|
|
||||||
suffix: '[/spoiler]'
|
|
||||||
},
|
|
||||||
edit: function (box, options) {
|
|
||||||
wrapSelection(box, options);
|
|
||||||
},
|
|
||||||
shortcutKey: 's'
|
|
||||||
},
|
|
||||||
code: {
|
code: {
|
||||||
displayText: 'code',
|
text: 'Code',
|
||||||
altText: "code formatting",
|
key: 'f',
|
||||||
styleCSS: 'font-family: "Courier New", Courier, monospace;',
|
multiline: true,
|
||||||
options: {
|
exclusiveline: false,
|
||||||
prefix: '[code]',
|
prefix: '[code]',
|
||||||
suffix: '[/code]',
|
suffix: '[/code]'
|
||||||
multiline: true
|
|
||||||
},
|
|
||||||
edit: function (box, options) {
|
|
||||||
wrapSelection(box, options);
|
|
||||||
},
|
|
||||||
shortcutKey: 'd'
|
|
||||||
},
|
},
|
||||||
strike: {
|
strike: {
|
||||||
displayText: 'strike',
|
text: 'Strike',
|
||||||
altText: 'strikethrough',
|
key: 'd',
|
||||||
styleCSS: 'text-decoration: line-through;',
|
multiline:false,
|
||||||
options: {
|
exclusiveline:false,
|
||||||
prefix:'~~',
|
prefix:'~~',
|
||||||
suffix:'~~'
|
suffix:'~~'
|
||||||
},
|
},
|
||||||
edit: function (box, options) {
|
|
||||||
wrapSelection(box, options);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
heading: {
|
heading: {
|
||||||
displayText: 'heading',
|
text: 'Heading',
|
||||||
altText: 'redtext',
|
key: 'r',
|
||||||
styleCSS: 'color: #AF0A0F; font-weight: bold;',
|
multiline:false,
|
||||||
options: {
|
exclusiveline:true,
|
||||||
prefix:'==',
|
prefix:'==',
|
||||||
suffix: '==',
|
suffix:'=='
|
||||||
exclusiveLine: true
|
|
||||||
},
|
|
||||||
edit: function (box, options) {
|
|
||||||
wrapSelection(box, options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var key, name, altText, ele;
|
self.toolbar_wrap = function(node) {
|
||||||
var strBuilder = [];
|
var parent = $(node).parents('form[name="post"]');
|
||||||
var subStr = '';
|
self.wrap(parent.find('#body')[0],'textarea[name="body"]', parent.find('.format-text > select')[0].value, false);
|
||||||
var styleRules = '';
|
};
|
||||||
|
|
||||||
//not exactly mine
|
self.wrap = function(ref, target, option, expandedwrap) {
|
||||||
var wrapSelection = function (box, options) {
|
// clean and validate arguments
|
||||||
if (box == null) {
|
if (ref == null) return;
|
||||||
return;
|
var settings = {multiline: false, exclusiveline: false, prefix:'', suffix: null};
|
||||||
|
$.extend(settings,JSON.parse(localStorage.formatText_rules)[option]);
|
||||||
|
|
||||||
|
// resolve targets into array of proper node elements
|
||||||
|
// yea, this is overly verbose, oh well.
|
||||||
|
var res = [];
|
||||||
|
if (target instanceof Array) {
|
||||||
|
for (var indexa in target) {
|
||||||
|
if (target.hasOwnProperty(indexa)) {
|
||||||
|
if (typeof target[indexa] == 'string') {
|
||||||
|
var nodes = $(target[indexa]);
|
||||||
|
for (var indexb in nodes) {
|
||||||
|
if (indexa.hasOwnProperty(indexb)) res.push(nodes[indexb]);
|
||||||
}
|
}
|
||||||
var prefix = options.prefix;
|
} else {
|
||||||
var suffix = options.suffix;
|
res.push(target[indexa]);
|
||||||
var multiline = options.multiline || false;
|
}
|
||||||
var exclusiveLine = options.exclusiveLine || false;
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof target == 'string') {
|
||||||
|
var nodes = $(target);
|
||||||
|
for (var index in nodes) {
|
||||||
|
if (nodes.hasOwnProperty(index)) res.push(nodes[index]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target = res;
|
||||||
//record scroll top to restore it later.
|
//record scroll top to restore it later.
|
||||||
var scrollTop = box.scrollTop;
|
var scrollTop = ref.scrollTop;
|
||||||
var selectionStart = box.selectionStart;
|
|
||||||
var selectionEnd = box.selectionEnd;
|
|
||||||
var text = box.value;
|
|
||||||
var beforeSelection = text.substring(0, selectionStart);
|
|
||||||
var selectedText = text.substring(selectionStart, selectionEnd);
|
|
||||||
var afterSelection = text.substring(selectionEnd);
|
|
||||||
|
|
||||||
|
//We will restore the selection later, so record the current selection
|
||||||
|
var selectionStart = ref.selectionStart;
|
||||||
|
var selectionEnd = ref.selectionEnd;
|
||||||
|
|
||||||
|
var text = ref.value;
|
||||||
|
var before = text.substring(0, selectionStart);
|
||||||
|
var selected = text.substring(selectionStart, selectionEnd);
|
||||||
|
var after = text.substring(selectionEnd);
|
||||||
|
var whiteSpace = [" ","\t"];
|
||||||
var breakSpace = ["\r","\n"];
|
var breakSpace = ["\r","\n"];
|
||||||
var trailingSpace = "";
|
var cursor;
|
||||||
var cursor = selectedText.length - 1;
|
|
||||||
|
|
||||||
//remove trailing space
|
// handles multiline selections on formatting that doesn't support spanning over multiple lines
|
||||||
while (cursor > 0 && selectedText[cursor] === " ") {
|
if (!settings.multiline) selected = selected.replace(/(\r|\n|\r\n)/g,settings.suffix +"$1"+ settings.prefix);
|
||||||
trailingSpace += " ";
|
|
||||||
cursor--;
|
|
||||||
}
|
|
||||||
selectedText = selectedText.substring(0, cursor + 1);
|
|
||||||
|
|
||||||
if (!multiline)
|
// handles formatting that requires it to be on it's own line OR if the user wishes to expand the wrap to the nearest linebreak
|
||||||
selectedText = selectedText.replace(/(\r|\n|\r\n)/g, suffix +"$1"+ prefix);
|
if (settings.exclusiveline || expandedwrap) {
|
||||||
|
|
||||||
if (exclusiveLine) {
|
|
||||||
// buffer the begining of the selection until a linebreak
|
// buffer the begining of the selection until a linebreak
|
||||||
cursor = beforeSelection.length -1;
|
cursor = before.length -1;
|
||||||
while (cursor >= 0 && breakSpace.indexOf(beforeSelection.charAt(cursor)) == -1) {
|
while (cursor >= 0 && breakSpace.indexOf(before.charAt(cursor)) == -1) {
|
||||||
cursor--;
|
cursor--;
|
||||||
}
|
}
|
||||||
selectedText = beforeSelection.substring(cursor +1) + selectedText;
|
selected = before.substring(cursor +1) + selected;
|
||||||
beforeSelection = beforeSelection.substring(0, cursor +1);
|
before = before.substring(0, cursor +1);
|
||||||
|
|
||||||
// buffer the end of the selection until a linebreak
|
// buffer the end of the selection until a linebreak
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
while (cursor < afterSelection.length && breakSpace.indexOf(afterSelection.charAt(cursor)) == -1) {
|
while (cursor < after.length && breakSpace.indexOf(after.charAt(cursor)) == -1) {
|
||||||
cursor++;
|
cursor++;
|
||||||
}
|
}
|
||||||
selectedText += afterSelection.substring(0, cursor);
|
selected += after.substring(0, cursor);
|
||||||
afterSelection = afterSelection.substring(cursor);
|
after = after.substring(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
box.value = beforeSelection + prefix + selectedText + suffix + trailingSpace + afterSelection;
|
// set values
|
||||||
|
var res = before + settings.prefix + selected + settings.suffix + after;
|
||||||
|
$(target).val(res);
|
||||||
|
|
||||||
box.selectionEnd = beforeSelection.length + prefix.length + selectedText.length;
|
// restore the selection area and scroll of the reference
|
||||||
|
ref.selectionEnd = before.length + settings.prefix.length + selected.length;
|
||||||
if (selectionStart === selectionEnd) {
|
if (selectionStart === selectionEnd) {
|
||||||
box.selectionStart = box.selectionEnd;
|
ref.selectionStart = ref.selectionEnd;
|
||||||
} else {
|
} else {
|
||||||
box.selectionStart = beforeSelection.length + prefix.length;
|
ref.selectionStart = before.length + settings.prefix.length;
|
||||||
}
|
}
|
||||||
box.scrollTop = scrollTop;
|
ref.scrollTop = scrollTop;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Generate the HTML for the toolbar
|
self.build_toolbars = function(){
|
||||||
*/
|
if (localStorage.formatText_toolbar == 'true'){
|
||||||
for (ele in formats) {
|
// remove existing toolbars
|
||||||
if (formats.hasOwnProperty(ele) && formats[ele].displayText != null) {
|
if ($('.format-text').length > 0) $('.format-text').remove();
|
||||||
name = formats[ele].displayText;
|
|
||||||
altText = formats[ele].altText || '';
|
|
||||||
key = formats[ele].shortcutKey;
|
|
||||||
|
|
||||||
//add tooltip text
|
// Place toolbar above each textarea input
|
||||||
if (altText) {
|
var name, options = '', rules = JSON.parse(localStorage.formatText_rules);
|
||||||
if (key) {
|
for (var index in rules) {
|
||||||
altText += ' (ctrl+'+ key +')';
|
if (!rules.hasOwnProperty(index)) continue;
|
||||||
|
name = rules[index].text;
|
||||||
|
|
||||||
|
//add hint if key exists
|
||||||
|
if (rules[index].key) {
|
||||||
|
name += ' (CTRL + '+ rules[index].key.toUpperCase() +')';
|
||||||
}
|
}
|
||||||
altText = 'title="'+ altText +'"';
|
options += '<option value="'+ index +'">'+ name +'</option>';
|
||||||
|
}
|
||||||
|
$('[name="body"]').before('<div class="format-text"><a href="javascript:;" onclick="formatText.toolbar_wrap(this);">Wrap</a><select>'+ options +'</select></div>');
|
||||||
|
$('body').append('<style>#quick-reply .format-text>a{width:15%;display:inline-block;text-align:center;}#quick-reply .format-text>select{width:85%;};</style>');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.add_rule = function(rule, index){
|
||||||
|
if (rule === undefined) rule = {
|
||||||
|
text: 'New Rule',
|
||||||
|
key: '',
|
||||||
|
multiline:false,
|
||||||
|
exclusiveline:false,
|
||||||
|
prefix:'',
|
||||||
|
suffix:''
|
||||||
}
|
}
|
||||||
|
|
||||||
subStr = '<a href="javascript:void(0)" '+ altText +' id="tf-'+ ele +'">'+ name +'</a>';
|
// generate an id for the rule
|
||||||
strBuilder.push(subStr);
|
if (index === undefined) {
|
||||||
|
var rules = JSON.parse(localStorage.formatText_rules);
|
||||||
|
while (rules[index] || index === undefined) {
|
||||||
|
index = ''
|
||||||
|
index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1);
|
||||||
|
index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1);
|
||||||
|
index +='abcdefghijklmnopqrstuvwxyz'.substr(Math.floor(Math.random()*26),1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (window.Options && Options.get_tab('formatting')){
|
||||||
|
var html = $('<div class="format_rule" name="'+ index +'"></div>').html('\
|
||||||
|
<input type="text" name="text" class="format_option" size="10" value=\"'+ rule.text.replace(/"/g, '"') +'\">\
|
||||||
|
<input type="checkbox" name="multiline" class="format_option" '+ (rule.multiline ? 'checked' : '') +'>\
|
||||||
|
<input type="checkbox" name="exclusiveline" class="format_option" '+ (rule.exclusiveline ? 'checked' : '') +'>\
|
||||||
|
<input type="text" name="prefix" class="format_option" size="8" value=\"'+ (rule.prefix ? rule.prefix.replace(/"/g, '"') : '') +'\">\
|
||||||
|
<input type="text" name="suffix" class="format_option" size="8" value=\"'+ (rule.suffix ? rule.suffix.replace(/"/g, '"') : '') +'\">\
|
||||||
|
<input type="text" name="key" class="format_option" size="2" maxlength="1" value=\"'+ rule.key +'\">\
|
||||||
|
<input type="button" value="X" onclick="if(confirm(\'Do you wish to remove the '+ rule.text +' formatting rule?\'))$(this).parent().remove();">\
|
||||||
|
');
|
||||||
|
|
||||||
|
if ($('.format_rule').length > 0) {
|
||||||
|
$('.format_rule').last().after(html);
|
||||||
} else {
|
} else {
|
||||||
continue;
|
Options.extend_tab('formatting', html);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$( 'textarea[name="body"]' ).before( '<div class="tf-toolbar"></div>' );
|
self.save_rules = function(){
|
||||||
$( '.tf-toolbar' ).html( strBuilder.join(' | ') );
|
var rule, newrules = {}, rules = $('.format_rule');
|
||||||
|
for (var index=0;rules[index];index++) {
|
||||||
|
rule = $(rules[index]);
|
||||||
|
newrules[rule.attr('name')] = {
|
||||||
|
text: rule.find('[name="text"]').val(),
|
||||||
|
key: rule.find('[name="key"]').val(),
|
||||||
|
prefix: rule.find('[name="prefix"]').val(),
|
||||||
|
suffix: rule.find('[name="suffix"]').val(),
|
||||||
|
multiline: rule.find('[name="multiline"]').is(':checked'),
|
||||||
|
exclusiveline: rule.find('[name="exclusiveline"]').is(':checked')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
localStorage.formatText_rules = JSON.stringify(newrules);
|
||||||
|
self.build_toolbars();
|
||||||
|
};
|
||||||
|
|
||||||
/* Sets the CSS style
|
self.reset_rules = function(to_default) {
|
||||||
*/
|
$('.format_rule').remove();
|
||||||
styleRules = '\n/* generated by 8chan Formatting Tools */'+
|
var rules;
|
||||||
'\n.tf-toolbar {padding: 0px 5px 1px 5px;}'+
|
if (to_default) rules = self.rules;
|
||||||
'\n.tf-toolbar :link {text-decoration: none;}';
|
else rules = JSON.parse(localStorage.formatText_rules);
|
||||||
for (ele in formats) {
|
for (var index in rules){
|
||||||
if (formats.hasOwnProperty(ele) && formats[ele].styleCSS) {
|
if (!rules.hasOwnProperty(index)) continue;
|
||||||
styleRules += ' \n#tf-' + ele + ' {' + formats[ele].styleCSS + '}';
|
self.add_rule(rules[index], index);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
//add CSS rule to user's custom CSS if it exist
|
|
||||||
if ($( '.user-css' ).length !== 0) {
|
// setup default rules for customizing
|
||||||
$( '.user-css' ).append( styleRules );
|
if (!localStorage.formatText_rules) localStorage.formatText_rules = JSON.stringify(self.rules);
|
||||||
|
|
||||||
|
// setup code to be ran when page is ready (work around for main.js compilation).
|
||||||
|
$(document).ready(function(){
|
||||||
|
// Add settings to Options panel general tab
|
||||||
|
if (window.Options && Options.get_tab('general')) {
|
||||||
|
var s1 = '#formatText_keybinds>input', s2 = '#formatText_toolbar>input', e = 'change';
|
||||||
|
Options.extend_tab('general', '\
|
||||||
|
<fieldset>\
|
||||||
|
<legend>Formatting Options</legend>\
|
||||||
|
<label id="formatText_keybinds"><input type="checkbox">' + _('Enable formatting keybinds') + '</label>\
|
||||||
|
<label id="formatText_toolbar"><input type="checkbox">' + _('Show formatting toolbar') + '</label>\
|
||||||
|
</fieldset>\
|
||||||
|
');
|
||||||
} else {
|
} else {
|
||||||
$( 'body' ).append( '<style>'+ styleRules +'\n</style>' );
|
var s1 = '#formatText_keybinds', s2 = '#formatText_toolbar', e = 'click';
|
||||||
|
$('hr:first').before('<div id="formatText_keybinds" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Enable formatting keybinds') +'</a></div>');
|
||||||
|
$('hr:first').before('<div id="formatText_toolbar" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+ _('Show formatting toolbar') +'</a></div>');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Attach event listeners
|
// add the tab for customizing the format settings
|
||||||
*/
|
if (window.Options && !Options.get_tab('formatting')) {
|
||||||
$( 'body' ).on( 'keydown', 'textarea[name="body"]', {formats: formats}, function (e) {
|
Options.add_tab('formatting', 'angle-right', 'Customize Formatting');
|
||||||
//shortcuts
|
Options.extend_tab('formatting', '\
|
||||||
if (e.ctrlKey) {
|
<style>\
|
||||||
var ch = String.fromCharCode(e.which).toLowerCase();
|
.format_option{\
|
||||||
var box = e.target;
|
margin-right:5px;\
|
||||||
var formats = e.data.formats;
|
overflow:initial;\
|
||||||
for (var ele in formats) {
|
font-size:15px;\
|
||||||
if (formats.hasOwnProperty(ele) && (ch === formats[ele].shortcutKey)) {
|
}\
|
||||||
formats[ele].edit(box, formats[ele].options);
|
.format_option[type="text"]{\
|
||||||
|
text-align:center;\
|
||||||
|
padding-bottom: 2px;\
|
||||||
|
padding-top: 2px;\
|
||||||
|
}\
|
||||||
|
.format_option:last-child{\
|
||||||
|
margin-right:0;\
|
||||||
|
}\
|
||||||
|
fieldset{\
|
||||||
|
margin-top:5px;\
|
||||||
|
}\
|
||||||
|
</style>\
|
||||||
|
');
|
||||||
|
|
||||||
|
// Data control row
|
||||||
|
Options.extend_tab('formatting', '\
|
||||||
|
<button onclick="formatText.add_rule();">Add Rule</button>\
|
||||||
|
<button onclick="formatText.save_rules();">Save Rules</button>\
|
||||||
|
<button onclick="formatText.reset_rules(false);">Revert</button>\
|
||||||
|
<button onclick="formatText.reset_rules(true);">Reset to Default</button>\
|
||||||
|
');
|
||||||
|
|
||||||
|
// Descriptor row
|
||||||
|
Options.extend_tab('formatting', '\
|
||||||
|
<span class="format_option" style="margin-left:25px;">Name</span>\
|
||||||
|
<span class="format_option" style="margin-left:45px;" title="Multi-line: Allow formatted area to contain linebreaks.">ML</span>\
|
||||||
|
<span class="format_option" style="margin-left:0px;" title="Exclusive-line: Require formatted area to start after and end before a linebreak.">EL</span>\
|
||||||
|
<span class="format_option" style="margin-left:25px;" title="Text injected at the start of a format area.">Prefix</span>\
|
||||||
|
<span class="format_option" style="margin-left:60px;" title="Text injected at the end of a format area.">Suffix</span>\
|
||||||
|
<span class="format_option" style="margin-left:40px;" title="Optional keybind value to allow keyboard shortcut access.">Key</span>\
|
||||||
|
');
|
||||||
|
|
||||||
|
// Rule rows
|
||||||
|
var rules = JSON.parse(localStorage.formatText_rules);
|
||||||
|
for (var index in rules){
|
||||||
|
if (!rules.hasOwnProperty(index)) continue;
|
||||||
|
self.add_rule(rules[index], index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setting for enabling formatting keybinds
|
||||||
|
$(s1).on(e, function(e) {
|
||||||
|
console.log('Keybind');
|
||||||
|
if (!localStorage.formatText_keybinds || localStorage.formatText_keybinds == 'false') {
|
||||||
|
localStorage.formatText_keybinds = 'true';
|
||||||
|
if (window.Options && Options.get_tab('general')) e.target.checked = true;
|
||||||
|
} else {
|
||||||
|
localStorage.formatText_keybinds = 'false';
|
||||||
|
if (window.Options && Options.get_tab('general')) e.target.checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// setting for toolbar injection
|
||||||
|
$(s2).on(e, function(e) {
|
||||||
|
console.log('Toolbar');
|
||||||
|
if (!localStorage.formatText_toolbar || localStorage.formatText_toolbar == 'false') {
|
||||||
|
localStorage.formatText_toolbar = 'true';
|
||||||
|
if (window.Options && Options.get_tab('general')) e.target.checked = true;
|
||||||
|
formatText.build_toolbars();
|
||||||
|
} else {
|
||||||
|
localStorage.formatText_toolbar = 'false';
|
||||||
|
if (window.Options && Options.get_tab('general')) e.target.checked = false;
|
||||||
|
$('.format-text').remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// make sure the tab settings are switch properly at loadup
|
||||||
|
if (window.Options && Options.get_tab('general')) {
|
||||||
|
if (localStorage.formatText_keybinds == 'true') $(s1)[0].checked = true;
|
||||||
|
else $(s1)[0].checked = false;
|
||||||
|
if (localStorage.formatText_toolbar == 'true') $(s2)[0].checked = true;
|
||||||
|
else $(s2)[0].checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial toolbar injection
|
||||||
|
formatText.build_toolbars();
|
||||||
|
|
||||||
|
//attach listener to <body> so it also works on quick-reply box
|
||||||
|
$('body').on('keydown', '[name="body"]', function(e) {
|
||||||
|
if (!localStorage.formatText_keybinds || localStorage.formatText_keybinds == 'false') return;
|
||||||
|
var key = String.fromCharCode(e.which).toLowerCase();
|
||||||
|
var rules = JSON.parse(localStorage.formatText_rules);
|
||||||
|
for (var index in rules) {
|
||||||
|
if (!rules.hasOwnProperty(index)) continue;
|
||||||
|
if (key === rules[index].key && e.ctrlKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (e.shiftKey) {
|
||||||
|
formatText.wrap(e.target, 'textarea[name="body"]', index, true);
|
||||||
|
} else {
|
||||||
|
formatText.wrap(e.target, 'textarea[name="body"]', index, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$( 'body' ).on( 'keydown', '#quick-reply textarea[name="body"]', {formats: formats}, function (e) {
|
|
||||||
//close quick reply when esc is prssed
|
|
||||||
if (e.which === 27) {
|
|
||||||
$( '.close-btn' ).trigger( 'click' );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$( 'body' ).on( 'click', '.tf-toolbar a[id]', {formats: formats}, function (e) {
|
|
||||||
//toolbar buttons
|
|
||||||
var formats = e.data.formats;
|
|
||||||
var box = $(e.target).parent().next()[0];
|
|
||||||
|
|
||||||
for (var ele in formats) {
|
// Signal that comment-toolbar loading has completed.
|
||||||
if (formats.hasOwnProperty(ele) && (e.target.id === 'tf-' + ele)) {
|
$(document).trigger('formatText');
|
||||||
formats[ele].edit(box, formats[ele].options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
// $( 'body' ).on( 'keydown', function (e) {
|
|
||||||
// if (e.which === 67 &&
|
|
||||||
// e.target.nodeName !== 'INPUT' && //The C, the whole C, and nothing but the C
|
|
||||||
// e.target.nodeName !== 'TEXTAREA' &&
|
|
||||||
// !(e.ctrlKey || e.altKey || e.shiftKey)) {
|
|
||||||
// document.location.href = '//'+ document.location.host +'/'+ board_name +'/catalog.html';
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
});
|
return self;
|
||||||
|
})(jQuery);
|
||||||
}
|
}
|
@ -57,26 +57,42 @@ $(document).ready(function() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var toggle_id = function() {
|
||||||
|
if (localStorage.hideids == 'true'){
|
||||||
|
$(this).addClass('hidden');
|
||||||
|
} else {
|
||||||
|
$(this).removeClass('hidden');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
old_info = {};
|
old_info = {};
|
||||||
forced_anon = localStorage['forcedanon'] ? true : false;
|
forced_anon = localStorage['forcedanon'] ? true : false;
|
||||||
|
|
||||||
var selector, event;
|
|
||||||
if (window.Options && Options.get_tab('general')) {
|
if (window.Options && Options.get_tab('general')) {
|
||||||
selector = '#forced-anon';
|
var s1 = '#hide-ids', s2 = '#forced-anon', e = 'change';
|
||||||
event = 'change';
|
Options.extend_tab("general", "<label id='hide-ids'><input type='checkbox' /> "+_('Hide IDs')+"</label>");
|
||||||
Options.extend_tab("general", "<label id='forced-anon'><input type='checkbox' /> "+_('Forced anonymity')+"</label>");
|
Options.extend_tab("general", "<label id='forced-anon'><input type='checkbox' /> "+_('Forced anonymity')+"</label>");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
selector = '#forced-anon';
|
var s1 = '#hide-ids', s2 = '#forced-anon', e = 'click';
|
||||||
event = 'click';
|
$('hr:first').before('<div id="hide-ids" style="text-align:right"><a class="unimportant" href="javascript:void(0)">Hide IDs</a></div>');
|
||||||
$('hr:first').before('<div id="forced-anon" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
|
$('hr:first').before('<div id="forced-anon" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>');
|
||||||
$('div#forced-anon a').text(_('Forced anonymity')+' (' + (forced_anon ? _('enabled') : _('disabled')) + ')');
|
$('div#forced-anon a').text(_('Forced anonymity')+' (' + (forced_anon ? _('enabled') : _('disabled')) + ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(s1).on(e, function(e) {
|
||||||
|
if (!localStorage.hideids || localStorage.hideids == 'false') {
|
||||||
|
localStorage.hideids = 'true';
|
||||||
|
if (window.Options && Options.get_tab('general')) e.target.checked = true;
|
||||||
|
} else {
|
||||||
|
localStorage.hideids = 'false';
|
||||||
|
if (window.Options && Options.get_tab('general')) e.target.checked = false;
|
||||||
|
}
|
||||||
|
$('.poster_id').each(toggle_id);
|
||||||
|
});
|
||||||
|
|
||||||
$(selector).on(event, function() {
|
$(s2).on(e, function() {
|
||||||
forced_anon = !forced_anon;
|
forced_anon = !forced_anon;
|
||||||
|
|
||||||
if (forced_anon) {
|
if (forced_anon) {
|
||||||
$('div#forced-anon a').text(_('Forced anonymity')+' ('+_('enabled')+')');
|
$('div#forced-anon a').text(_('Forced anonymity')+' ('+_('enabled')+')');
|
||||||
localStorage.forcedanon = true;
|
localStorage.forcedanon = true;
|
||||||
@ -86,13 +102,17 @@ $(document).ready(function() {
|
|||||||
delete localStorage.forcedanon;
|
delete localStorage.forcedanon;
|
||||||
disable_fa();
|
disable_fa();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// initial option setup on script load
|
||||||
|
if (localStorage.hideids == 'true'){
|
||||||
|
if (window.Options && Options.get_tab('general')) $('#hide-ids>input').prop('checked',true);
|
||||||
|
$('.poster_id').each(toggle_id);
|
||||||
|
}
|
||||||
|
|
||||||
if(forced_anon) {
|
if(forced_anon) {
|
||||||
enable_fa();
|
enable_fa();
|
||||||
|
|
||||||
if (window.Options && Options.get_tab('general')) {
|
if (window.Options && Options.get_tab('general')) {
|
||||||
$('#toggle-locked-threads>input').prop('checked', true);
|
$('#toggle-locked-threads>input').prop('checked', true);
|
||||||
}
|
}
|
||||||
@ -101,6 +121,8 @@ $(document).ready(function() {
|
|||||||
$(document).on('new_post', function(e, post) {
|
$(document).on('new_post', function(e, post) {
|
||||||
if (forced_anon)
|
if (forced_anon)
|
||||||
$(post).find('p.intro label').each(force_anon);
|
$(post).find('p.intro label').each(force_anon);
|
||||||
|
if (localStorage.hideids == 'true')
|
||||||
|
$(post).find('.poster_id').each(toggle_id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ var textarea = $("<textarea></textarea>").css({
|
|||||||
"font-size": 12,
|
"font-size": 12,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 35, bottom: 35,
|
top: 35, bottom: 35,
|
||||||
width: "calc(100% - 12px)", margin: 0, padding: 0, border: "1px solid black",
|
width: "calc(100% - 20px)", margin: 0, padding: "4px", border: "1px solid black",
|
||||||
left: 5, right: 5
|
left: 5, right: 5
|
||||||
}).appendTo(tab.content);
|
}).appendTo(tab.content);
|
||||||
var submit = $("<input type='button' value='"+_("Update custom CSS")+"'>").css({
|
var submit = $("<input type='button' value='"+_("Update custom CSS")+"'>").css({
|
||||||
@ -45,7 +45,7 @@ var update_textarea = function() {
|
|||||||
textarea.text("/* "+_("Enter here your own CSS rules...")+" */\n" +
|
textarea.text("/* "+_("Enter here your own CSS rules...")+" */\n" +
|
||||||
"/* "+_("If you want to make a redistributable style, be sure to\nhave a Yotsuba B theme selected.")+" */\n" +
|
"/* "+_("If you want to make a redistributable style, be sure to\nhave a Yotsuba B theme selected.")+" */\n" +
|
||||||
"/* "+_("You can include CSS files from remote servers, for example:")+" */\n" +
|
"/* "+_("You can include CSS files from remote servers, for example:")+" */\n" +
|
||||||
'@import "http://example.com/style.css";');
|
'/* @import "http://example.com/style.css"; */');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
textarea.text(localStorage.user_css);
|
textarea.text(localStorage.user_css);
|
||||||
|
@ -17,7 +17,7 @@ var textarea = $("<textarea></textarea>").css({
|
|||||||
"font-size": 12,
|
"font-size": 12,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 35, bottom: 35,
|
top: 35, bottom: 35,
|
||||||
width: "calc(100% - 12px)", margin: 0, padding: 0, border: "1px solid black",
|
width: "calc(100% - 20px)", margin: 0, padding: "4px", border: "1px solid black",
|
||||||
left: 5, right: 5
|
left: 5, right: 5
|
||||||
}).appendTo(tab.content);
|
}).appendTo(tab.content);
|
||||||
var submit = $("<input type='button' value='"+_("Update custom Javascript")+"'>").css({
|
var submit = $("<input type='button' value='"+_("Update custom Javascript")+"'>").css({
|
||||||
@ -54,7 +54,7 @@ var update_textarea = function() {
|
|||||||
textarea.text("/* "+_("Enter here your own Javascript code...")+" */\n" +
|
textarea.text("/* "+_("Enter here your own Javascript code...")+" */\n" +
|
||||||
"/* "+_("Have a backup of your storage somewhere, as messing here\nmay render you this website unusable.")+" */\n" +
|
"/* "+_("Have a backup of your storage somewhere, as messing here\nmay render you this website unusable.")+" */\n" +
|
||||||
"/* "+_("You can include JS files from remote servers, for example:")+" */\n" +
|
"/* "+_("You can include JS files from remote servers, for example:")+" */\n" +
|
||||||
'load_js("http://example.com/script.js");');
|
'/* load_js("http://example.com/script.js"); */');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
textarea.text(localStorage.user_js);
|
textarea.text(localStorage.user_js);
|
||||||
|
144
js/post-hover.js
144
js/post-hover.js
@ -16,15 +16,15 @@
|
|||||||
onready(function(){
|
onready(function(){
|
||||||
var dont_fetch_again = [];
|
var dont_fetch_again = [];
|
||||||
init_hover = function() {
|
init_hover = function() {
|
||||||
var $link = $(this);
|
var link = $(this);
|
||||||
|
|
||||||
var id;
|
var id;
|
||||||
var matches;
|
var matches;
|
||||||
|
|
||||||
if ($link.is('[data-thread]')) {
|
if (link.is('[data-thread]')) {
|
||||||
id = $link.attr('data-thread');
|
id = link.attr('data-thread');
|
||||||
}
|
}
|
||||||
else if(matches = $link.text().match(/^>>(?:>\/([^\/]+)\/)?(\d+)$/)) {
|
else if(matches = link.text().match(/^>>(?:>\/([^\/]+)\/)?(\d+)$/)) {
|
||||||
id = matches[2];
|
id = matches[2];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -36,39 +36,33 @@ onready(function(){
|
|||||||
board = board.parent();
|
board = board.parent();
|
||||||
}
|
}
|
||||||
var threadid;
|
var threadid;
|
||||||
if ($link.is('[data-thread]')) threadid = 0;
|
if (link.is('[data-thread]')) threadid = 0;
|
||||||
else threadid = board.attr('id').replace("thread_", "");
|
else threadid = board.attr('id').replace("thread_", "");
|
||||||
|
|
||||||
board = board.data('board');
|
board = board.data('board');
|
||||||
|
|
||||||
var parentboard = board;
|
var parentboard = board;
|
||||||
|
|
||||||
if ($link.is('[data-thread]')) parentboard = $('form[name="post"] input[name="board"]').val();
|
if (link.is('[data-thread]')) parentboard = $('form[name="post"] input[name="board"]').val();
|
||||||
else if (matches[1] !== undefined) board = matches[1];
|
else if (matches[1] !== undefined) board = matches[1];
|
||||||
|
|
||||||
var $post = false;
|
var post = false;
|
||||||
var hovering = false;
|
var hovering = false;
|
||||||
var hovered_at;
|
link.hover(function(e) {
|
||||||
$link.hover(function(e) {
|
|
||||||
hovering = true;
|
hovering = true;
|
||||||
hovered_at = {'x': e.pageX, 'y': e.pageY};
|
|
||||||
|
|
||||||
var start_hover = function($link) {
|
var start_hover = function(link) {
|
||||||
if($.contains($post[0], $link[0])) {
|
if(post.is(':visible') &&
|
||||||
// link links to itself or to op; ignore
|
post.offset().top >= $(window).scrollTop() &&
|
||||||
}
|
post.offset().top + post.height() <= $(window).scrollTop() + $(window).height()) {
|
||||||
else if($post.is(':visible') &&
|
|
||||||
$post.offset().top >= $(window).scrollTop() &&
|
|
||||||
$post.offset().top + $post.height() <= $(window).scrollTop() + $(window).height()) {
|
|
||||||
// post is in view
|
// post is in view
|
||||||
$post.addClass('highlighted');
|
post.addClass('highlighted');
|
||||||
} else {
|
} else {
|
||||||
var $newPost = $post.clone();
|
var newPost = post.clone();
|
||||||
$newPost.find('>.reply, >br').remove();
|
newPost.find('>.reply, >br').remove();
|
||||||
$newPost.find('span.mentioned').remove();
|
newPost.find('a.post_anchor').remove();
|
||||||
$newPost.find('a.post_anchor').remove();
|
|
||||||
|
|
||||||
$newPost
|
newPost
|
||||||
.attr('id', 'post-hover-' + id)
|
.attr('id', 'post-hover-' + id)
|
||||||
.attr('data-board', board)
|
.attr('data-board', board)
|
||||||
.addClass('post-hover')
|
.addClass('post-hover')
|
||||||
@ -78,18 +72,75 @@ onready(function(){
|
|||||||
.css('position', 'absolute')
|
.css('position', 'absolute')
|
||||||
.css('font-style', 'normal')
|
.css('font-style', 'normal')
|
||||||
.css('z-index', '100')
|
.css('z-index', '100')
|
||||||
|
.css('left', '0')
|
||||||
.addClass('reply').addClass('post')
|
.addClass('reply').addClass('post')
|
||||||
.insertAfter($link.parent())
|
.appendTo(link.closest('div.post'))
|
||||||
|
|
||||||
$link.trigger('mousemove');
|
// shrink expanded images
|
||||||
|
newPost.find('div.file a[data-expanded="true"]').each(function() {
|
||||||
|
var thumb = $(this).data('src');
|
||||||
|
$(this).find('img.post-image').attr('src', thumb);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Highlight references to the current post
|
||||||
|
if (link.hasClass('mentioned-'+id)) {
|
||||||
|
var postLinks = newPost.find('div.body a:not([rel="nofollow"])');
|
||||||
|
if (postLinks.length > 1) {
|
||||||
|
var originalPost = link.closest('div.post').attr('id').replace("reply_", "").replace("inline_", "");
|
||||||
|
postLinks.each(function() {
|
||||||
|
if ($(this).text() == ">>"+originalPost) {
|
||||||
|
$(this).addClass('dashed-underline');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var previewWidth = newPost.outerWidth(true);
|
||||||
|
var widthDiff = previewWidth - newPost.width();
|
||||||
|
var linkLeft = link.offset().left;
|
||||||
|
var left, top;
|
||||||
|
|
||||||
|
if (linkLeft < $(document).width() * 0.7) {
|
||||||
|
left = linkLeft + link.width();
|
||||||
|
if (left + previewWidth > $(window).width()) {
|
||||||
|
newPost.css('width', $(window).width() - left - widthDiff);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (previewWidth > linkLeft) {
|
||||||
|
newPost.css('width', linkLeft - widthDiff);
|
||||||
|
previewWidth = linkLeft;
|
||||||
|
}
|
||||||
|
left = linkLeft - previewWidth;
|
||||||
|
}
|
||||||
|
newPost.css('left', left);
|
||||||
|
|
||||||
|
top = link.offset().top - 10;
|
||||||
|
|
||||||
|
var scrollTop = $(window).scrollTop();
|
||||||
|
if (link.is("[data-thread]")) {
|
||||||
|
scrollTop = 0;
|
||||||
|
top -= $(window).scrollTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(top < scrollTop + 15) {
|
||||||
|
top = scrollTop;
|
||||||
|
} else if(top > scrollTop + $(window).height() - newPost.height() - 15) {
|
||||||
|
top = scrollTop + $(window).height() - newPost.height() - 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPost.height() > $(window).height()) {
|
||||||
|
top = scrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPost.css('top', top);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$post = $('[data-board="' + board + '"] div.post#reply_' + id + ', [data-board="' + board + '"]div#thread_' + id);
|
post = $('[data-board="' + board + '"] div.post#reply_' + id + ', [data-board="' + board + '"]div#thread_' + id);
|
||||||
if($post.length > 0) {
|
if(post.length > 0) {
|
||||||
start_hover($(this));
|
start_hover($(this));
|
||||||
} else {
|
} else {
|
||||||
var url = $link.attr('href').replace(/#.*$/, '');
|
var url = link.attr('href').replace(/#.*$/, '');
|
||||||
|
|
||||||
if($.inArray(url, dont_fetch_again) != -1) {
|
if($.inArray(url, dont_fetch_again) != -1) {
|
||||||
return;
|
return;
|
||||||
@ -120,46 +171,23 @@ onready(function(){
|
|||||||
$(data).find('div[id^="thread_"]').hide().attr('data-cached', 'yes').prependTo('form[name="postcontrols"]');
|
$(data).find('div[id^="thread_"]').hide().attr('data-cached', 'yes').prependTo('form[name="postcontrols"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
$post = $('[data-board="' + board + '"] div.post#reply_' + id + ', [data-board="' + board + '"]div#thread_' + id);
|
post = $('[data-board="' + board + '"] div.post#reply_' + id + ', [data-board="' + board + '"]div#thread_' + id);
|
||||||
|
|
||||||
if(hovering && $post.length > 0) {
|
if(hovering && post.length > 0) {
|
||||||
start_hover($link);
|
start_hover(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, function() {
|
}, function() {
|
||||||
hovering = false;
|
hovering = false;
|
||||||
if(!$post)
|
if(!post)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
$post.removeClass('highlighted');
|
post.removeClass('highlighted');
|
||||||
if($post.hasClass('hidden') || $post.data('cached') == 'yes')
|
if(post.hasClass('hidden') || post.data('cached') == 'yes')
|
||||||
$post.css('display', 'none');
|
post.css('display', 'none');
|
||||||
$('.post-hover').remove();
|
$('.post-hover').remove();
|
||||||
}).mousemove(function(e) {
|
|
||||||
if(!$post)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var $hover = $('#post-hover-' + id + '[data-board="' + board + '"]');
|
|
||||||
if($hover.length == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var scrollTop = $(window).scrollTop();
|
|
||||||
if ($link.is("[data-thread]")) scrollTop = 0;
|
|
||||||
var epy = e.pageY;
|
|
||||||
if ($link.is("[data-thread]")) epy -= $(window).scrollTop();
|
|
||||||
|
|
||||||
var top = (epy ? epy : hovered_at['y']) - 10;
|
|
||||||
|
|
||||||
if(epy < scrollTop + 15) {
|
|
||||||
top = scrollTop;
|
|
||||||
} else if(epy > scrollTop + $(window).height() - $hover.height() - 15) {
|
|
||||||
top = scrollTop + $(window).height() - $hover.height() - 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$hover.css('left', (e.pageX ? e.pageX : hovered_at['x'])).css('top', top);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
109
js/thread-stats.js
Normal file
109
js/thread-stats.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* thread-stats.js
|
||||||
|
* - Adds statistics of the thread below the posts area
|
||||||
|
* - Shows ID post count beside each postID on hover
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* $config['additional_javascript'][] = 'js/jquery.min.js';
|
||||||
|
* $config['additional_javascript'][] = 'js/thread-stats.js';
|
||||||
|
*/
|
||||||
|
if (active_page == 'thread') {
|
||||||
|
//check if page uses unique ID
|
||||||
|
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];
|
||||||
|
|
||||||
|
$('form[name="postcontrols"] > .delete')
|
||||||
|
.first()
|
||||||
|
.before('<div id="thread_stats" style="float:left;"></div>');
|
||||||
|
var el = $('#thread_stats');
|
||||||
|
el.prepend('Page <span id="thread_stats_page">?</span>');
|
||||||
|
if (IDsupport){
|
||||||
|
el.prepend('<span id="thread_stats_uids">0</span> UIDs | ');
|
||||||
|
}
|
||||||
|
el.prepend('<span id="thread_stats_images">0</span> images | ');
|
||||||
|
el.prepend('<span id="thread_stats_posts">0</span> replies | ');
|
||||||
|
delete el;
|
||||||
|
function update_thread_stats(){
|
||||||
|
var op = $('#thread_'+ thread_id +' > div.post.op:not(.post-hover):not(.inline)').first();
|
||||||
|
var replies = $('#thread_'+ thread_id +' > div.post.reply:not(.post-hover):not(.inline)');
|
||||||
|
// post count
|
||||||
|
$('#thread_stats_posts').text(replies.length);
|
||||||
|
// image count
|
||||||
|
$('#thread_stats_images').text(replies.filter(function(){
|
||||||
|
return $(this).find('> .files').text().trim() != false;
|
||||||
|
}).length);
|
||||||
|
// unique ID count
|
||||||
|
if (IDsupport) {
|
||||||
|
var opID = op.find('> .intro > .poster_id').text();
|
||||||
|
var ids = {};
|
||||||
|
replies.each(function(){
|
||||||
|
var cur = $(this).find('> .intro > .poster_id');
|
||||||
|
var curID = cur.text();
|
||||||
|
if (ids[curID] === undefined) {
|
||||||
|
ids[curID] = 0;
|
||||||
|
}
|
||||||
|
ids[curID]++;
|
||||||
|
});
|
||||||
|
if (ids[opID] === undefined) {
|
||||||
|
ids[opID] = 0;
|
||||||
|
}
|
||||||
|
ids[opID]++;
|
||||||
|
replies.each(function(){
|
||||||
|
var 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) {
|
||||||
|
var size = 0, key;
|
||||||
|
for (key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) size++;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
};
|
||||||
|
$('#thread_stats_uids').text(size(ids));
|
||||||
|
}
|
||||||
|
$.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').success(function(data){
|
||||||
|
var found, page = 'Pruned or Deleted';
|
||||||
|
for (var i=0;data[i];i++){
|
||||||
|
var threads = data[i].threads;
|
||||||
|
for (var j=0; threads[j]; j++){
|
||||||
|
if (parseInt(threads[j].no) == parseInt(thread_id)) {
|
||||||
|
page = data[i].page +1;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) break;
|
||||||
|
}
|
||||||
|
$('#thread_stats_page').text(page);
|
||||||
|
if (!found) $('#thread_stats_page').css('color','red');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 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';
|
||||||
|
for (var i=0;data[i];i++){
|
||||||
|
var threads = data[i].threads;
|
||||||
|
for (var j=0; threads[j]; j++){
|
||||||
|
if (parseInt(threads[j].no) == parseInt(thread_id)) {
|
||||||
|
page = data[i].page +1;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) break;
|
||||||
|
}
|
||||||
|
$('#thread_stats_page').text(page);
|
||||||
|
if (!found) $('#thread_stats_page').css('color','red');
|
||||||
|
});
|
||||||
|
},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);
|
||||||
|
});
|
||||||
|
}
|
@ -26,11 +26,9 @@
|
|||||||
onready(function(){
|
onready(function(){
|
||||||
var do_embed_yt = function(tag) {
|
var do_embed_yt = function(tag) {
|
||||||
$('div.video-container a', tag).click(function() {
|
$('div.video-container a', tag).click(function() {
|
||||||
var videoID = $(this.parentNode).data('video');
|
|
||||||
|
|
||||||
$(this.parentNode).html('<iframe style="float:left;margin: 10px 20px" type="text/html" '+
|
$(this.parentNode).html('<iframe style="float:left;margin: 10px 20px" type="text/html" '+
|
||||||
'width="360" height="270" src="//www.youtube.com/embed/' + videoID +
|
'width="360" height="270" src="//www.youtube.com/embed/' + $(this.parentNode).data('video') +
|
||||||
'?autoplay=1&html5=1" allowfullscreen frameborder="0"/>');
|
'?autoplay=1&html5=1'+ $(this.parentNode).data('params') +'" allowfullscreen frameborder="0"/>');
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
30
post.php
30
post.php
@ -577,6 +577,7 @@ elseif (isset($_POST['post'])) {
|
|||||||
|
|
||||||
|
|
||||||
if ($post['has_file']) {
|
if ($post['has_file']) {
|
||||||
|
$allhashes = '';
|
||||||
foreach ($post['files'] as $key => &$file) {
|
foreach ($post['files'] as $key => &$file) {
|
||||||
if (!in_array($file['extension'], $config['allowed_ext']) && !in_array($file['extension'], $config['allowed_ext_files']))
|
if (!in_array($file['extension'], $config['allowed_ext']) && !in_array($file['extension'], $config['allowed_ext_files']))
|
||||||
error($config['error']['unknownext']);
|
error($config['error']['unknownext']);
|
||||||
@ -586,33 +587,26 @@ elseif (isset($_POST['post'])) {
|
|||||||
// Truncate filename if it is too long
|
// Truncate filename if it is too long
|
||||||
$file['filename'] = mb_substr($file['filename'], 0, $config['max_filename_len']);
|
$file['filename'] = mb_substr($file['filename'], 0, $config['max_filename_len']);
|
||||||
|
|
||||||
if (!isset($filenames)) {
|
|
||||||
$filenames = escapeshellarg($file['tmp_name']);
|
|
||||||
} else {
|
|
||||||
$filenames .= (' ' . escapeshellarg($file['tmp_name']));
|
|
||||||
}
|
|
||||||
$upload = $file['tmp_name'];
|
$upload = $file['tmp_name'];
|
||||||
|
|
||||||
if (!is_readable($upload))
|
if (!is_readable($upload))
|
||||||
error($config['error']['nomove']);
|
error($config['error']['nomove']);
|
||||||
}
|
|
||||||
|
|
||||||
$md5cmd = $config['bsd_md5'] ? 'md5 -r' : 'md5sum';
|
$md5cmd = $config['bsd_md5'] ? 'md5 -r' : 'md5sum';
|
||||||
|
if( ($output = shell_exec_error("cat " . escapeshellarg($upload) . " | $md5cmd")) !== false ) {
|
||||||
if( ($output = shell_exec_error("cat $filenames | $md5cmd")) !== false ) {
|
|
||||||
$explodedvar = explode(' ', $output);
|
$explodedvar = explode(' ', $output);
|
||||||
$hash = $explodedvar[0];
|
$hash = $explodedvar[0];
|
||||||
|
} else {
|
||||||
|
$hash = md5_file($upload);
|
||||||
|
}
|
||||||
|
$file['hash'] = $hash;
|
||||||
|
$allhashes .= $hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($post['files']) == 1) {
|
||||||
$post['filehash'] = $hash;
|
$post['filehash'] = $hash;
|
||||||
}
|
} else {
|
||||||
elseif ($config['max_images'] === 1) {
|
$post['filehash'] = md5($allhashes);
|
||||||
$post['filehash'] = md5_file($upload);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$str_to_hash = '';
|
|
||||||
foreach (explode(' ', $filenames) as $i => $f) {
|
|
||||||
$str_to_hash .= file_get_contents($f);
|
|
||||||
}
|
|
||||||
$post['filehash'] = md5($str_to_hash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,10 @@ body {
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
a,a:visited {
|
a,a:visited {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: #34345C;
|
color: #34345C;
|
||||||
@ -100,6 +104,10 @@ input[type="text"],input[type="password"],textarea {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#upload input[type="file"] {
|
||||||
|
max-width: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
form table tr td {
|
form table tr td {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -309,6 +317,11 @@ div.post_modified div.content-status:first-child {
|
|||||||
margin-top: 1.3em;
|
margin-top: 1.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.dashed-underline {
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dashed;
|
||||||
|
}
|
||||||
|
|
||||||
span.trip {
|
span.trip {
|
||||||
color: #228854;
|
color: #228854;
|
||||||
}
|
}
|
||||||
@ -934,6 +947,10 @@ pre {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.poster_id::before {
|
||||||
|
content: " ID: ";
|
||||||
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
/* Better code tags */
|
/* Better code tags */
|
||||||
max-width:inherit;
|
max-width:inherit;
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
"
|
"
|
||||||
style="width:{{ post.thumbwidth }}px;height:{{ post.thumbheight }}px"
|
style="width:{{ post.thumbwidth }}px;height:{{ post.thumbheight }}px"
|
||||||
|
data-md5="{{ post.hash|hex2bin|base64_encode }}"
|
||||||
>
|
>
|
||||||
</video>
|
</video>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -40,6 +41,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
"
|
"
|
||||||
style="width:{{ post.thumbwidth }}px;height:{{ post.thumbheight }}px" alt=""
|
style="width:{{ post.thumbwidth }}px;height:{{ post.thumbheight }}px" alt=""
|
||||||
|
data-md5="{{ post.hash|hex2bin|base64_encode }}"
|
||||||
/>
|
/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% if config.poster_ids or (mod|hasPermission(config.mod.show_ip_less, board.uri)) %}
|
{% if config.poster_ids or (mod|hasPermission(config.mod.show_ip_less, board.uri)) %}
|
||||||
{% if post.thread %}
|
{% if post.thread %}
|
||||||
ID: <span class="poster_id">{{ poster_id(post.ip, post.thread, board.uri) }}</span>
|
<span class="poster_id">{{ poster_id(post.ip, post.thread, board.uri) }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
ID: <span class="poster_id">{{ poster_id(post.ip, post.id, board.uri) }}</span>
|
<span class="poster_id">{{ poster_id(post.ip, post.id, board.uri) }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user