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('
Wrap
'); + $('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', '\ +
\ + Formatting Options\ + \ + \ +
\ + '); + } else { + var s1 = '#formatText_keybinds', s2 = '#formatText_toolbar', e = 'click'; + $('hr:first').before('
'+ _('Enable formatting keybinds') +'
'); + $('hr:first').before('
'+ _('Show formatting toolbar') +'
'); + } + + // 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('
Hide IDs
'); $('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('