diff --git a/inc/api.php b/inc/api.php
index 16329e85..6691d758 100644
--- a/inc/api.php
+++ b/inc/api.php
@@ -85,13 +85,13 @@ class Api {
}
}
- private function translateFile($file, $post, &$apiPost) {
+ private function translateFile($file, &$apiPost) {
$this->translateFields($this->fileFields, $file, $apiPost);
$apiPost['filename'] = @substr($file->name, 0, strrpos($file->name, '.'));
$dotPos = strrpos($file->file, '.');
$apiPost['ext'] = substr($file->file, $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) {
@@ -116,17 +116,16 @@ class Api {
}
// 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) {
$file = $post->files[0];
- $this->translateFile($file, $post, $apiPost);
+ $this->translateFile($file, $apiPost);
if (sizeof($post->files) > 1) {
$extra_files = array();
foreach ($post->files as $i => $f) {
if ($i == 0) continue;
$extra_file = array();
- $this->translateFile($f, $post, $extra_file);
+ $this->translateFile($f, $extra_file);
$extra_files[] = $extra_file;
}
diff --git a/inc/config.php b/inc/config.php
index ddf23279..10c9faa5 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -999,8 +999,8 @@
// It's very important that you match the entire input (with ^ and $) or things will not work correctly.
$config['embedding'] = 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',
+ ''
),
array(
'/^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}';
// Youtube.js embed HTML code
- $config['youtube_js_html'] = '
'.
+ $config['youtube_js_html'] = '
';
diff --git a/inc/display.php b/inc/display.php
index 9b4c2d83..a58d92f4 100644
--- a/inc/display.php
+++ b/inc/display.php
@@ -354,8 +354,13 @@ class Post {
$this->{$key} = $value;
}
- if (isset($this->files) && $this->files)
+ if (isset($this->files) && $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->name = utf8tohtml($this->name);
diff --git a/inc/instance-config.php b/inc/instance-config.php
index f6b4d0b6..5076da2a 100644
--- a/inc/instance-config.php
+++ b/inc/instance-config.php
@@ -162,7 +162,7 @@
$config['embedding'] = 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']
),
array(
diff --git a/inc/template.php b/inc/template.php
index 48b2b1bb..b0d7bfb2 100644
--- a/inc/template.php
+++ b/inc/template.php
@@ -29,6 +29,9 @@ function load_twig() {
));
$twig->addExtension(new Twig_Extensions_Extension_Tinyboard());
$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) {
diff --git a/js/catalog-search.js b/js/catalog-search.js
new file mode 100644
index 00000000..97121e3b
--- /dev/null
+++ b/js/catalog-search.js
@@ -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('
');
+ $('#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('
[]');
+ $('#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();
+ }
+ });
+ }
+ });
+}
diff --git a/js/comment-toolbar.js b/js/comment-toolbar.js
index 7ae11a73..65bdc529 100644
--- a/js/comment-toolbar.js
+++ b/js/comment-toolbar.js
@@ -8,253 +8,376 @@
* $config['additional_javascript'][] = 'js/comment-toolbar.js';
*/
if (active_page == 'thread' || active_page == 'index') {
- $(document).ready(function () {
- 'use strict';
- var formats = {
- bold: {
- displayText: 'B',
- altText: 'bold',
- styleCSS: 'font-weight: bold;',
- options: {
- prefix: "'''",
- suffix: "'''"
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- },
- shortcutKey: 'b'
+ var formatText = (function($){
+ "use strict";
+ var self = {};
+ self.rules = {
+ spoiler: {
+ text: 'Spoiler',
+ key: 's',
+ multiline: false,
+ exclusiveline: false,
+ prefix:'**',
+ suffix:'**'
},
italics: {
- displayText: 'i',
- altText: 'italics',
- styleCSS: 'font-style: italic;',
- options: {
- prefix: "''",
- suffix: "''"
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- },
- shortcutKey: 'i'
+ text: 'Italics',
+ key: 'i',
+ multiline: false,
+ exclusiveline: false,
+ prefix: "''",
+ suffix: "''"
},
- under: {
- displayText: 'U',
- altText: 'underline',
- styleCSS: 'text-decoration: underline;',
- options: {
- prefix: '__',
- suffix: '__'
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- },
- shortcutKey: 'u'
+ bold: {
+ text: 'Bold',
+ key: 'b',
+ multiline: false,
+ exclusiveline: false,
+ prefix: "'''",
+ suffix: "'''"
},
- spoiler: {
- displayText: 'spoiler',
- altText: 'mark as spoiler',
- styleCSS: '',
- options: {
- prefix: '[spoiler]',
- suffix: '[/spoiler]'
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- },
- shortcutKey: 's'
+ underline: {
+ text: 'Underline',
+ key: 'u',
+ multiline: false,
+ exclusiveline: false,
+ prefix:'__',
+ suffix:'__'
},
code: {
- displayText: 'code',
- altText: "code formatting",
- styleCSS: 'font-family: "Courier New", Courier, monospace;',
- options: {
- prefix: '[code]',
- suffix: '[/code]',
- multiline: true
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- },
- shortcutKey: 'd'
+ text: 'Code',
+ key: 'f',
+ multiline: true,
+ exclusiveline: false,
+ prefix: '[code]',
+ suffix: '[/code]'
},
strike: {
- displayText: 'strike',
- altText: 'strikethrough',
- styleCSS: 'text-decoration: line-through;',
- options: {
- prefix: '~~',
- suffix: '~~'
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- }
+ text: 'Strike',
+ key: 'd',
+ multiline:false,
+ exclusiveline:false,
+ prefix:'~~',
+ suffix:'~~'
},
heading: {
- displayText: 'heading',
- altText: 'redtext',
- styleCSS: 'color: #AF0A0F; font-weight: bold;',
- options: {
- prefix: '==',
- suffix: '==',
- exclusiveLine: true
- },
- edit: function (box, options) {
- wrapSelection(box, options);
- }
+ text: 'Heading',
+ key: 'r',
+ multiline:false,
+ exclusiveline:true,
+ prefix:'==',
+ suffix:'=='
}
};
-
- var key, name, altText, ele;
- var strBuilder = [];
- var subStr = '';
- var styleRules = '';
-
- //not exactly mine
- var wrapSelection = function (box, options) {
- if (box == null) {
- return;
+
+ self.toolbar_wrap = function(node) {
+ var parent = $(node).parents('form[name="post"]');
+ self.wrap(parent.find('#body')[0],'textarea[name="body"]', parent.find('.format-text > select')[0].value, false);
+ };
+
+ self.wrap = function(ref, target, option, expandedwrap) {
+ // clean and validate arguments
+ if (ref == null) 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]);
+ }
+ } else {
+ res.push(target[indexa]);
+ }
+ }
+ }
+ } 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);
+ }
}
- var prefix = options.prefix;
- var suffix = options.suffix;
- var multiline = options.multiline || false;
- var exclusiveLine = options.exclusiveLine || false;
-
+ target = res;
//record scroll top to restore it later.
- var scrollTop = box.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);
+ var scrollTop = ref.scrollTop;
+ //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 trailingSpace = "";
- var cursor = selectedText.length - 1;
-
- //remove trailing space
- while (cursor > 0 && selectedText[cursor] === " ") {
- trailingSpace += " ";
- cursor--;
- }
- selectedText = selectedText.substring(0, cursor + 1);
-
- if (!multiline)
- selectedText = selectedText.replace(/(\r|\n|\r\n)/g, suffix +"$1"+ prefix);
-
- if (exclusiveLine) {
+ var cursor;
+
+ // handles multiline selections on formatting that doesn't support spanning over multiple lines
+ if (!settings.multiline) selected = selected.replace(/(\r|\n|\r\n)/g,settings.suffix +"$1"+ settings.prefix);
+
+ // 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
+ if (settings.exclusiveline || expandedwrap) {
// buffer the begining of the selection until a linebreak
- cursor = beforeSelection.length -1;
- while (cursor >= 0 && breakSpace.indexOf(beforeSelection.charAt(cursor)) == -1) {
+ cursor = before.length -1;
+ while (cursor >= 0 && breakSpace.indexOf(before.charAt(cursor)) == -1) {
cursor--;
}
- selectedText = beforeSelection.substring(cursor +1) + selectedText;
- beforeSelection = beforeSelection.substring(0, cursor +1);
+ selected = before.substring(cursor +1) + selected;
+ before = before.substring(0, cursor +1);
// buffer the end of the selection until a linebreak
cursor = 0;
- while (cursor < afterSelection.length && breakSpace.indexOf(afterSelection.charAt(cursor)) == -1) {
+ while (cursor < after.length && breakSpace.indexOf(after.charAt(cursor)) == -1) {
cursor++;
}
- selectedText += afterSelection.substring(0, cursor);
- afterSelection = afterSelection.substring(cursor);
+ selected += after.substring(0, cursor);
+ after = after.substring(cursor);
}
-
- box.value = beforeSelection + prefix + selectedText + suffix + trailingSpace + afterSelection;
-
- box.selectionEnd = beforeSelection.length + prefix.length + selectedText.length;
+
+ // set values
+ var res = before + settings.prefix + selected + settings.suffix + after;
+ $(target).val(res);
+
+ // restore the selection area and scroll of the reference
+ ref.selectionEnd = before.length + settings.prefix.length + selected.length;
if (selectionStart === selectionEnd) {
- box.selectionStart = box.selectionEnd;
+ ref.selectionStart = ref.selectionEnd;
} else {
- box.selectionStart = beforeSelection.length + prefix.length;
+ ref.selectionStart = before.length + settings.prefix.length;
}
- box.scrollTop = scrollTop;
+ ref.scrollTop = scrollTop;
};
+
+ self.build_toolbars = function(){
+ if (localStorage.formatText_toolbar == 'true'){
+ // remove existing toolbars
+ if ($('.format-text').length > 0) $('.format-text').remove();
+
+ // Place toolbar above each textarea input
+ var name, options = '', rules = JSON.parse(localStorage.formatText_rules);
+ for (var index in rules) {
+ if (!rules.hasOwnProperty(index)) continue;
+ name = rules[index].text;
- /* Generate the HTML for the toolbar
- */
- for (ele in formats) {
- if (formats.hasOwnProperty(ele) && formats[ele].displayText != null) {
- name = formats[ele].displayText;
- altText = formats[ele].altText || '';
- key = formats[ele].shortcutKey;
-
- //add tooltip text
- if (altText) {
- if (key) {
- altText += ' (ctrl+'+ key +')';
+ //add hint if key exists
+ if (rules[index].key) {
+ name += ' (CTRL + '+ rules[index].key.toUpperCase() +')';
}
- altText = 'title="'+ altText +'"';
+ options += '
';
}
-
- subStr = '
'+ name +'';
- strBuilder.push(subStr);
- } else {
- continue;
+ $('[name="body"]').before('
');
+ $('body').append('');
}
- }
-
- $( 'textarea[name="body"]' ).before( '
' );
- $( '.tf-toolbar' ).html( strBuilder.join(' | ') );
-
- /* Sets the CSS style
- */
- styleRules = '\n/* generated by 8chan Formatting Tools */'+
- '\n.tf-toolbar {padding: 0px 5px 1px 5px;}'+
- '\n.tf-toolbar :link {text-decoration: none;}';
- for (ele in formats) {
- if (formats.hasOwnProperty(ele) && formats[ele].styleCSS) {
- styleRules += ' \n#tf-' + ele + ' {' + formats[ele].styleCSS + '}';
+ };
+
+ self.add_rule = function(rule, index){
+ if (rule === undefined) rule = {
+ text: 'New Rule',
+ key: '',
+ multiline:false,
+ exclusiveline:false,
+ prefix:'',
+ suffix:''
+ }
+
+ // generate an id for the rule
+ 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);
}
}
- //add CSS rule to user's custom CSS if it exist
- if ($( '.user-css' ).length !== 0) {
- $( '.user-css' ).append( styleRules );
- } else {
- $( 'body' ).append( '' );
+ if (window.Options && Options.get_tab('formatting')){
+ var html = $('
').html('\
+
\
+
\
+
\
+
\
+
\
+
\
+
\
+ ');
+
+ if ($('.format_rule').length > 0) {
+ $('.format_rule').last().after(html);
+ } else {
+ Options.extend_tab('formatting', html);
+ }
}
-
- /* Attach event listeners
- */
- $( 'body' ).on( 'keydown', 'textarea[name="body"]', {formats: formats}, function (e) {
- //shortcuts
- if (e.ctrlKey) {
- var ch = String.fromCharCode(e.which).toLowerCase();
- var box = e.target;
- var formats = e.data.formats;
- for (var ele in formats) {
- if (formats.hasOwnProperty(ele) && (ch === formats[ele].shortcutKey)) {
- formats[ele].edit(box, formats[ele].options);
+ };
+
+ self.save_rules = function(){
+ 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();
+ };
+
+ self.reset_rules = function(to_default) {
+ $('.format_rule').remove();
+ var rules;
+ if (to_default) rules = self.rules;
+ else rules = JSON.parse(localStorage.formatText_rules);
+ for (var index in rules){
+ if (!rules.hasOwnProperty(index)) continue;
+ self.add_rule(rules[index], index);
+ }
+ };
+
+ // setup default rules for customizing
+ 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', '\
+
\
+ ');
+ } else {
+ var s1 = '#formatText_keybinds', s2 = '#formatText_toolbar', e = 'click';
+ $('hr:first').before('
');
+ $('hr:first').before('
');
+ }
+
+ // add the tab for customizing the format settings
+ if (window.Options && !Options.get_tab('formatting')) {
+ Options.add_tab('formatting', 'angle-right', 'Customize Formatting');
+ Options.extend_tab('formatting', '\
+ \
+ ');
+
+ // Data control row
+ Options.extend_tab('formatting', '\
+
\
+
\
+
\
+
\
+ ');
+
+ // Descriptor row
+ Options.extend_tab('formatting', '\
+
Name\
+
ML\
+
EL\
+
Prefix\
+
Suffix\
+
Key\
+ ');
+
+ // 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 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();
+ if (e.shiftKey) {
+ formatText.wrap(e.target, 'textarea[name="body"]', index, true);
+ } else {
+ formatText.wrap(e.target, 'textarea[name="body"]', index, false);
+ }
}
}
- }
+ });
+
+ // Signal that comment-toolbar loading has completed.
+ $(document).trigger('formatText');
});
- $( '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) {
- if (formats.hasOwnProperty(ele) && (e.target.id === 'tf-' + ele)) {
- 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);
+}
\ No newline at end of file
diff --git a/js/forced-anon.js b/js/forced-anon.js
index 5ea68199..26d0cb4f 100644
--- a/js/forced-anon.js
+++ b/js/forced-anon.js
@@ -18,10 +18,10 @@
if (active_page == 'ukko' || active_page == 'thread' || active_page == 'index' || (window.Options && Options.get_tab('general')))
$(document).ready(function() {
var force_anon = function() {
- if($(this).children('a.capcode').length == 0) {
+ if ($(this).children('a.capcode').length == 0) {
var id = $(this).parent().children('a.post_no:eq(1)').text();
- if($(this).children('a.email').length != 0)
+ if ($(this).children('a.email').length != 0)
var p = $(this).children('a.email');
else
var p = $(this);
@@ -29,7 +29,7 @@ $(document).ready(function() {
old_info[id] = {'name': p.children('span.name').text(), 'trip': p.children('span.trip').text()};
p.children('span.name').text('Anonymous');
- if(p.children('span.trip').length != 0)
+ if (p.children('span.trip').length != 0)
p.children('span.trip').text('');
}
};
@@ -40,44 +40,60 @@ $(document).ready(function() {
var disable_fa = function() {
$('p.intro label').each(function() {
- if($(this).children('a.capcode').length == 0) {
+ if ($(this).children('a.capcode').length == 0) {
var id = $(this).parent().children('a.post_no:eq(1)').text();
if(old_info[id]) {
- if($(this).children('a.email').length != 0)
+ if ($(this).children('a.email').length != 0)
var p = $(this).children('a.email');
else
var p = $(this);
p.children('span.name').text(old_info[id]['name']);
- if(p.children('span.trip').length != 0)
+ if (p.children('span.trip').length != 0)
p.children('span.trip').text(old_info[id]['trip']);
}
}
});
};
+ var toggle_id = function() {
+ if (localStorage.hideids == 'true'){
+ $(this).addClass('hidden');
+ } else {
+ $(this).removeClass('hidden');
+ }
+ };
+
old_info = {};
forced_anon = localStorage['forcedanon'] ? true : false;
- var selector, event;
- if (window.Options && Options.get_tab('general')) {
- selector = '#forced-anon';
- event = 'change';
- Options.extend_tab("general", "
");
- }
- else {
- selector = '#forced-anon';
- event = 'click';
+ if (window.Options && Options.get_tab('general')) {
+ var s1 = '#hide-ids', s2 = '#forced-anon', e = 'change';
+ Options.extend_tab("general", "
");
+ Options.extend_tab("general", "
");
+ }
+ else {
+ var s1 = '#hide-ids', s2 = '#forced-anon', e = 'click';
+ $('hr:first').before('
');
$('hr:first').before('
');
$('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;
-
- if(forced_anon) {
+ if (forced_anon) {
$('div#forced-anon a').text(_('Forced anonymity')+' ('+_('enabled')+')');
localStorage.forcedanon = true;
enable_fa();
@@ -86,21 +102,27 @@ $(document).ready(function() {
delete localStorage.forcedanon;
disable_fa();
}
-
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) {
enable_fa();
-
- if (window.Options && Options.get_tab('general')) {
- $('#toggle-locked-threads>input').prop('checked', true);
- }
+ if (window.Options && Options.get_tab('general')) {
+ $('#toggle-locked-threads>input').prop('checked', true);
+ }
}
$(document).on('new_post', function(e, post) {
- if(forced_anon)
+ if (forced_anon)
$(post).find('p.intro label').each(force_anon);
+ if (localStorage.hideids == 'true')
+ $(post).find('.poster_id').each(toggle_id);
});
});
diff --git a/js/options/user-css.js b/js/options/user-css.js
index 840bcd1f..22b005fa 100644
--- a/js/options/user-css.js
+++ b/js/options/user-css.js
@@ -17,7 +17,7 @@ var textarea = $("
").css({
"font-size": 12,
position: "absolute",
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
}).appendTo(tab.content);
var submit = $("
").css({
@@ -45,7 +45,7 @@ var update_textarea = function() {
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" +
"/* "+_("You can include CSS files from remote servers, for example:")+" */\n" +
- '@import "http://example.com/style.css";');
+ '/* @import "http://example.com/style.css"; */');
}
else {
textarea.text(localStorage.user_css);
diff --git a/js/options/user-js.js b/js/options/user-js.js
index 0b7d140f..221ca5d9 100644
--- a/js/options/user-js.js
+++ b/js/options/user-js.js
@@ -17,7 +17,7 @@ var textarea = $("
").css({
"font-size": 12,
position: "absolute",
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
}).appendTo(tab.content);
var submit = $("
").css({
@@ -54,7 +54,7 @@ var update_textarea = function() {
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" +
"/* "+_("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 {
textarea.text(localStorage.user_js);
diff --git a/js/post-hover.js b/js/post-hover.js
index 0becfb70..eefb3875 100644
--- a/js/post-hover.js
+++ b/js/post-hover.js
@@ -16,15 +16,15 @@
onready(function(){
var dont_fetch_again = [];
init_hover = function() {
- var $link = $(this);
+ var link = $(this);
var id;
var matches;
- if ($link.is('[data-thread]')) {
- id = $link.attr('data-thread');
+ if (link.is('[data-thread]')) {
+ id = link.attr('data-thread');
}
- else if(matches = $link.text().match(/^>>(?:>\/([^\/]+)\/)?(\d+)$/)) {
+ else if(matches = link.text().match(/^>>(?:>\/([^\/]+)\/)?(\d+)$/)) {
id = matches[2];
}
else {
@@ -36,39 +36,33 @@ onready(function(){
board = board.parent();
}
var threadid;
- if ($link.is('[data-thread]')) threadid = 0;
+ if (link.is('[data-thread]')) threadid = 0;
else threadid = board.attr('id').replace("thread_", "");
board = board.data('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];
- var $post = false;
+ var post = false;
var hovering = false;
- var hovered_at;
- $link.hover(function(e) {
+ link.hover(function(e) {
hovering = true;
- hovered_at = {'x': e.pageX, 'y': e.pageY};
- var start_hover = function($link) {
- if($.contains($post[0], $link[0])) {
- // link links to itself or to op; ignore
- }
- else if($post.is(':visible') &&
- $post.offset().top >= $(window).scrollTop() &&
- $post.offset().top + $post.height() <= $(window).scrollTop() + $(window).height()) {
+ var start_hover = function(link) {
+ if(post.is(':visible') &&
+ post.offset().top >= $(window).scrollTop() &&
+ post.offset().top + post.height() <= $(window).scrollTop() + $(window).height()) {
// post is in view
- $post.addClass('highlighted');
+ post.addClass('highlighted');
} else {
- var $newPost = $post.clone();
- $newPost.find('>.reply, >br').remove();
- $newPost.find('span.mentioned').remove();
- $newPost.find('a.post_anchor').remove();
+ var newPost = post.clone();
+ newPost.find('>.reply, >br').remove();
+ newPost.find('a.post_anchor').remove();
- $newPost
+ newPost
.attr('id', 'post-hover-' + id)
.attr('data-board', board)
.addClass('post-hover')
@@ -78,18 +72,75 @@ onready(function(){
.css('position', 'absolute')
.css('font-style', 'normal')
.css('z-index', '100')
+ .css('left', '0')
.addClass('reply').addClass('post')
- .insertAfter($link.parent())
-
- $link.trigger('mousemove');
+ .appendTo(link.closest('div.post'))
+
+ // 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);
- if($post.length > 0) {
+ post = $('[data-board="' + board + '"] div.post#reply_' + id + ', [data-board="' + board + '"]div#thread_' + id);
+ if(post.length > 0) {
start_hover($(this));
} else {
- var url = $link.attr('href').replace(/#.*$/, '');
+ var url = link.attr('href').replace(/#.*$/, '');
if($.inArray(url, dont_fetch_again) != -1) {
return;
@@ -120,46 +171,23 @@ onready(function(){
$(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) {
- start_hover($link);
+ if(hovering && post.length > 0) {
+ start_hover(link);
}
}
});
}
}, function() {
hovering = false;
- if(!$post)
+ if(!post)
return;
- $post.removeClass('highlighted');
- if($post.hasClass('hidden') || $post.data('cached') == 'yes')
- $post.css('display', 'none');
+ post.removeClass('highlighted');
+ if(post.hasClass('hidden') || post.data('cached') == 'yes')
+ post.css('display', 'none');
$('.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);
});
};
diff --git a/js/thread-stats.js b/js/thread-stats.js
new file mode 100644
index 00000000..59fed912
--- /dev/null
+++ b/js/thread-stats.js
@@ -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('
');
+ var el = $('#thread_stats');
+ el.prepend('Page
?');
+ if (IDsupport){
+ el.prepend('
0 UIDs | ');
+ }
+ el.prepend('
0 images | ');
+ el.prepend('
0 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('
('+ ids[cur.text()] +')');
+ });
+ 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('');
+ update_thread_stats();
+ $('#update_thread').click(update_thread_stats);
+ $(document).on('new_post',update_thread_stats);
+ });
+}
\ No newline at end of file
diff --git a/js/youtube.js b/js/youtube.js
index 9fe81b60..c4ef77fb 100644
--- a/js/youtube.js
+++ b/js/youtube.js
@@ -26,11 +26,9 @@
onready(function(){
var do_embed_yt = function(tag) {
$('div.video-container a', tag).click(function() {
- var videoID = $(this.parentNode).data('video');
-
$(this.parentNode).html('
');
+ 'width="360" height="270" src="//www.youtube.com/embed/' + $(this.parentNode).data('video') +
+ '?autoplay=1&html5=1'+ $(this.parentNode).data('params') +'" allowfullscreen frameborder="0"/>');
return false;
});
diff --git a/post.php b/post.php
index 099ea6d7..881c7e01 100644
--- a/post.php
+++ b/post.php
@@ -577,6 +577,7 @@ elseif (isset($_POST['post'])) {
if ($post['has_file']) {
+ $allhashes = '';
foreach ($post['files'] as $key => &$file) {
if (!in_array($file['extension'], $config['allowed_ext']) && !in_array($file['extension'], $config['allowed_ext_files']))
error($config['error']['unknownext']);
@@ -586,33 +587,26 @@ elseif (isset($_POST['post'])) {
// Truncate filename if it is too long
$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'];
if (!is_readable($upload))
error($config['error']['nomove']);
- }
-
- $md5cmd = $config['bsd_md5'] ? 'md5 -r' : 'md5sum';
-
- if( ($output = shell_exec_error("cat $filenames | $md5cmd")) !== false ) {
- $explodedvar = explode(' ', $output);
- $hash = $explodedvar[0];
- $post['filehash'] = $hash;
- }
- elseif ($config['max_images'] === 1) {
- $post['filehash'] = md5_file($upload);
- }
- else {
- $str_to_hash = '';
- foreach (explode(' ', $filenames) as $i => $f) {
- $str_to_hash .= file_get_contents($f);
+
+ $md5cmd = $config['bsd_md5'] ? 'md5 -r' : 'md5sum';
+ if( ($output = shell_exec_error("cat " . escapeshellarg($upload) . " | $md5cmd")) !== false ) {
+ $explodedvar = explode(' ', $output);
+ $hash = $explodedvar[0];
+ } else {
+ $hash = md5_file($upload);
}
- $post['filehash'] = md5($str_to_hash);
+ $file['hash'] = $hash;
+ $allhashes .= $hash;
+ }
+
+ if (count($post['files']) == 1) {
+ $post['filehash'] = $hash;
+ } else {
+ $post['filehash'] = md5($allhashes);
}
}
diff --git a/stylesheets/style.css b/stylesheets/style.css
index 548af4a8..bebd4813 100644
--- a/stylesheets/style.css
+++ b/stylesheets/style.css
@@ -8,6 +8,10 @@ body {
padding-right: 4px;
}
+.hidden {
+ display:none;
+}
+
a,a:visited {
text-decoration: underline;
color: #34345C;
@@ -100,6 +104,10 @@ input[type="text"],input[type="password"],textarea {
max-width: 100%;
}
+#upload input[type="file"] {
+ max-width: 230px;
+}
+
form table tr td {
text-align: left;
margin: 0;
@@ -309,6 +317,11 @@ div.post_modified div.content-status:first-child {
margin-top: 1.3em;
}
+a.dashed-underline {
+ text-decoration: none;
+ border-bottom: 1px dashed;
+}
+
span.trip {
color: #228854;
}
@@ -934,6 +947,10 @@ pre {
cursor: pointer;
}
+.poster_id::before {
+ content: " ID: ";
+}
+
pre {
/* Better code tags */
max-width:inherit;
diff --git a/templates/post/image.html b/templates/post/image.html
index 818ccb57..9b5449ad 100644
--- a/templates/post/image.html
+++ b/templates/post/image.html
@@ -21,6 +21,7 @@
{% endif %}
"
style="width:{{ post.thumbwidth }}px;height:{{ post.thumbheight }}px"
+ data-md5="{{ post.hash|hex2bin|base64_encode }}"
>
{% else %}
@@ -40,6 +41,7 @@
{% endif %}
"
style="width:{{ post.thumbwidth }}px;height:{{ post.thumbheight }}px" alt=""
+ data-md5="{{ post.hash|hex2bin|base64_encode }}"
/>
{% endif %}
diff --git a/templates/post/poster_id.html b/templates/post/poster_id.html
index f23f8df6..1624b91c 100644
--- a/templates/post/poster_id.html
+++ b/templates/post/poster_id.html
@@ -1,7 +1,7 @@
{% if config.poster_ids or (mod|hasPermission(config.mod.show_ip_less, board.uri)) %}
{% if post.thread %}
- ID:
{{ poster_id(post.ip, post.thread, board.uri) }}
+
{{ poster_id(post.ip, post.thread, board.uri) }}
{% else %}
- ID:
{{ poster_id(post.ip, post.id, board.uri) }}
+
{{ poster_id(post.ip, post.id, board.uri) }}
{% endif %}
{% endif %}