diff options
| author | Simon McFarlane <simon@piston.pw> | 2015-06-01 16:08:19 -0700 |
|---|---|---|
| committer | Simon McFarlane <simon@piston.pw> | 2015-06-01 16:08:19 -0700 |
| commit | 209bc5400144e448e95a68e01d842c6fab53ae71 (patch) | |
| tree | ab4cc6ad241e8daccb546514330d3a5f2906e846 /js | |
| parent | d1c1e5a589f30d640f5eb6303e7059105a6297f8 (diff) | |
| parent | 7ab9cc43d101ed002bce392beefe1c435ab2d5b1 (diff) | |
Merge pull request #18 from lainchan/vichan-merge
Merge with the latest upstream vichan
Diffstat (limited to 'js')
35 files changed, 1705 insertions, 74 deletions
@@ -107,6 +107,7 @@ $(window).ready(function() { }, 'html'); } $(form).find('input[type="submit"]').val(_('Posted...')); + $(document).trigger("ajax_after_post", post_response); } else { alert(_('An unknown error occured when posting!')); $(form).find('input[type="submit"]').val(submit_txt); @@ -114,17 +115,10 @@ $(window).ready(function() { } }, error: function(xhr, status, er) { - // An error occured - do_not_ajax = true; - $(form).find('input[type="submit"]').each(function() { - var $replacement = $('<input type="hidden">'); - $replacement.attr('name', $(this).attr('name')); - $replacement.val(submit_txt); - $(this) - .after($replacement) - .replaceWith($('<input type="button">').val(submit_txt)); - }); - $(form).submit(); + console.log(xhr); + alert(_('The server took too long to submit your post. Your post was probably still submitted. If it wasn\'t, we might be experiencing issues right now -- please try your post again later. Error information: ') + "<div><textarea>" + JSON.stringify(xhr) + "</textarea></div>"); + $(form).find('input[type="submit"]').val(submit_txt); + $(form).find('input[type="submit"]').removeAttr('disabled'); }, data: formData, cache: false, @@ -140,6 +134,7 @@ $(window).ready(function() { }; setup_form($('form[name="post"]')); $(window).on('quick-reply', function() { + $('form#quick-reply').off('submit'); setup_form($('form#quick-reply')); }); }); diff --git a/js/catalog-link.js b/js/catalog-link.js index 899e6859..05ee9efb 100644 --- a/js/catalog-link.js +++ b/js/catalog-link.js @@ -15,13 +15,8 @@ function catalog() { var board = $("input[name='board']"); -if (board.length>0) { -if (window.location.href.indexOf("/res/")==-1){ //if we are inside a thread -var catalog_url = 'catalog.html'; -} -else { -var catalog_url = '../catalog.html'; -} +var catalog_url = configRoot + board.first().val() + "/catalog.html"; + var pages = document.getElementsByClassName('pages')[0]; var bottom = document.getElementsByClassName('boardlist bottom')[0] var subtitle = document.getElementsByClassName('subtitle')[0]; @@ -57,7 +52,6 @@ if (subtitle) { subtitle.appendChild(link2); } } -} if (active_page == 'thread' || active_page == 'index') { $(document).ready(catalog); diff --git a/js/expand-all-images.js b/js/expand-all-images.js index fc273e5e..5545045c 100644 --- a/js/expand-all-images.js +++ b/js/expand-all-images.js @@ -27,7 +27,10 @@ onready(function(){ $(this).parent().click(); }); - $('hr:first').before('<div id="shrink-all-images" style="text-align:right"><a class="unimportant" href="javascript:void(0)"></a></div>'); + if (!$('#shrink-all-images').length) { + $('hr:first').before('<div id="shrink-all-images" style="text-align:right"><a class="unimportant" href="javascript:void(0)"></a></div>'); + } + $('div#shrink-all-images a') .text(_('Shrink all images')) .click(function(){ diff --git a/js/favorites.js b/js/favorites.js index e325e110..daf7b732 100644 --- a/js/favorites.js +++ b/js/favorites.js @@ -54,7 +54,6 @@ if (active_page == 'thread' || active_page == 'index') { $(document).ready(function(){ var favorites = JSON.parse(localStorage.favorites); var is_board_favorite = ~$.inArray(board_name, favorites); - console.log(is_board_favorite); $('header>h1').append('<a id="favorite-star" href="#" data-active="'+(is_board_favorite ? 'true' : 'false')+'" style="color: '+(is_board_favorite ? 'yellow' : 'grey')+'; text-decoration:none">\u2605</span>'); add_favorites(); diff --git a/js/file-selector.js b/js/file-selector.js new file mode 100644 index 00000000..207a5ae9 --- /dev/null +++ b/js/file-selector.js @@ -0,0 +1,191 @@ +/* + * file-selector.js - Add support for drag and drop file selection, and paste from clipbboard on supported browsers. + * + * Usage: + * $config['additional_javascript'][] = 'js/jquery.min.js'; + * $config['additional_javascript'][] = 'js/file-selector.js'; + */ +function init_file_selector(max_images) { + +$(document).ready(function () { + // add options panel item + if (window.Options && Options.get_tab('general')) { + Options.extend_tab('general', '<label id="file-drag-drop"><input type="checkbox">' + _('Drag and drop file selection') + '</label>'); + + $('#file-drag-drop>input').on('click', function() { + if ($('#file-drag-drop>input').is(':checked')) { + localStorage.file_dragdrop = 'true'; + } else { + localStorage.file_dragdrop = 'false'; + } + }); + + if (typeof localStorage.file_dragdrop === 'undefined') localStorage.file_dragdrop = 'true'; + if (localStorage.file_dragdrop === 'true') $('#file-drag-drop>input').prop('checked', true); + } +}); + +// disabled by user, or incompatible browser. +if (localStorage.file_dragdrop == 'false' || !(window.URL.createObjectURL && window.File)) + return; + +// multipost not enabled +if (typeof max_images == 'undefined') { + var max_images = 1; +} + +$('<div class="dropzone-wrap" style="display: none;">'+ + '<div class="dropzone" tabindex="0">'+ + '<div class="file-hint">'+_('Select/drop/paste files here')+'</div>'+ + '<div class="file-thumbs"></div>'+ + '</div>'+ + '</div>'+ +'</div>').prependTo('#upload td'); + +var files = []; +$('#upload_file').remove(); // remove the original file selector +$('.dropzone-wrap').css('user-select', 'none').show(); // let jquery add browser specific prefix + +function addFile(file) { + if (files.length == max_images) + return; + + files.push(file); + addThumb(file); +} + +function removeFile(file) { + files.splice(files.indexOf(file), 1); +} + +function getThumbElement(file) { + return $('.tmb-container').filter(function(){return($(this).data('file-ref')==file);}); +} + +function addThumb(file) { + + var fileName = (file.name.length < 24) ? file.name : file.name.substr(0, 22) + '…'; + var fileType = file.type.split('/')[0]; + var fileExt = file.type.split('/')[1]; + var $container = $('<div>') + .addClass('tmb-container') + .data('file-ref', file) + .append( + $('<div>').addClass('remove-btn').html('✖'), + $('<div>').addClass('file-tmb'), + $('<div>').addClass('tmb-filename').html(fileName) + ) + .appendTo('.file-thumbs'); + + var $fileThumb = $container.find('.file-tmb'); + if (fileType == 'image') { + // if image file, generate thumbnail + var objURL = window.URL.createObjectURL(file); + $fileThumb.css('background-image', 'url('+ objURL +')'); + } else { + $fileThumb.html('<span>' + fileExt.toUpperCase() + '</span>'); + } +} + +$(document).on('ajax_before_post', function (e, formData) { + for (var i=0; i<max_images; i++) { + var key = 'file'; + if (i > 0) key += i + 1; + if (typeof files[i] === 'undefined') break; + formData.append(key, files[i]); + } +}); + +// clear file queue and UI on success +$(document).on('ajax_after_post', function () { + files = []; + $('.file-thumbs').empty(); +}); + +var dragCounter = 0; +var dropHandlers = { + dragenter: function (e) { + e.stopPropagation(); + e.preventDefault(); + + if (dragCounter === 0) $('.dropzone').addClass('dragover'); + dragCounter++; + }, + dragover: function (e) { + // needed for webkit to work + e.stopPropagation(); + e.preventDefault(); + }, + dragleave: function (e) { + e.stopPropagation(); + e.preventDefault(); + + dragCounter--; + if (dragCounter === 0) $('.dropzone').removeClass('dragover'); + }, + drop: function (e) { + e.stopPropagation(); + e.preventDefault(); + + $('.dropzone').removeClass('dragover'); + dragCounter = 0; + + var fileList = e.originalEvent.dataTransfer.files; + for (var i=0; i<fileList.length; i++) { + addFile(fileList[i]); + } + } +}; + + +// attach handlers +$(document).on(dropHandlers); + +$(document).on('click', '.dropzone .remove-btn', function (e) { + e.stopPropagation(); + + var file = $(e.target).parent().data('file-ref'); + + getThumbElement(file).remove(); + removeFile(file); +}); + +$(document).on('keypress click', '.dropzone', function (e) { + e.stopPropagation(); + + // accept mouse click or Enter + if ((e.which != 1 || e.target.className != 'file-hint') && + e.which != 13) + return; + + var $fileSelector = $('<input type="file" multiple>'); + + $fileSelector.on('change', function (e) { + if (this.files.length > 0) { + for (var i=0; i<this.files.length; i++) { + addFile(this.files[i]); + } + } + $(this).remove(); + }); + + $fileSelector.click(); +}); + +$(document).on('paste', function (e) { + var clipboard = e.originalEvent.clipboardData; + if (typeof clipboard.items != 'undefined' && clipboard.items.length != 0) { + + //Webkit + for (var i=0; i<clipboard.items.length; i++) { + if (clipboard.items[i].kind != 'file') + continue; + + //convert blob to file + var file = new File([clipboard.items[i].getAsFile()], 'ClipboardImage.png', {type: 'image/png'}); + addFile(file); + } + } +}); + +} diff --git a/js/forced-anon.js b/js/forced-anon.js index 5ea68199..11327ec9 100644 --- a/js/forced-anon.js +++ b/js/forced-anon.js @@ -64,7 +64,7 @@ $(document).ready(function() { if (window.Options && Options.get_tab('general')) { selector = '#forced-anon'; event = 'change'; - 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 { selector = '#forced-anon'; diff --git a/js/gallery-view.js b/js/gallery-view.js new file mode 100644 index 00000000..2d7d85b4 --- /dev/null +++ b/js/gallery-view.js @@ -0,0 +1,165 @@ +if (active_page == 'index' || active_page == 'thread') +$(function(){ + + var gallery_view = false; + + $('hr:first').before('<div id="gallery-view" style="text-align:right"><a class="unimportant" href="javascript:void(0)">-</a></div>'); + $('#gallery-view a').html(gallery_view ? _("Disable gallery mode") : _("Enable gallery mode")).click(function() { + gallery_view = !gallery_view; + $(this).html(gallery_view ? _("Disable gallery mode") : _("Enable gallery mode")); + toggle_gview(document); + }); + + var toggle_gview = function(elem) { + if (gallery_view) { + $(elem).find('img.post-image').parent().each(function() { + this.oldonclick = this.onclick; + this.onclick = handle_click; + $(this).attr('data-galid', Math.random()); + }); + } + else { + $(elem).find('img.post-image').parent().each(function() { + if (this.onclick == handle_click) this.onclick = this.oldonclick; + }); + } + }; + + $(document).on('new_post', toggle_gview); + + var gallery_opened = false; + + var handle_click = function(e) { + e.stopPropagation(); + e.preventDefault(); + + if (!gallery_opened) open_gallery(); + + gallery_setimage($(this).attr('data-galid')); + }; + + var handler, images, active, toolbar; + + var open_gallery = function() { + $('body').css('overflow', 'hidden'); + + gallery_opened = true; + + handler = $("<div id='alert_handler'></div>").hide().appendTo('body').css('text-align', 'left'); + + $("<div id='alert_background'></div>").click(close_gallery).appendTo(handler); + + images = $("<div id='gallery_images'></div>").appendTo(handler); + toolbar = $("<div id='gallery_toolbar'></div>").appendTo(handler); + active = $("<div id='gallery_main'></div>").appendTo(handler); + + active.on('click', function() { + close_gallery(); + }); + + $('img.post-image').parent().each(function() { + var thumb = $(this).find('img').attr('src'); + + var i = $('<img>').appendTo(images); + i.attr('src', thumb); + i.attr('data-galid-th', $(this).attr('data-galid')); + + i.on('click', function(e) { + gallery_setimage($(this).attr('data-galid-th')); + }); + }); + + $("<a href='javascript:void(0)'><i class='fa fa-times'></i></div>") + .click(close_gallery).appendTo(toolbar); + + $('body').on('keydown.gview', function(e) { + if (e.which == 39 || e.which == 40) { // right or down arrow + gallery_setimage(+1); + e.preventDefault(); + } + else if (e.which == 37 || e.which == 38) { // left or up arrow + gallery_setimage(-1); + e.preventDefault(); + } + }); + + handler.fadeIn(400); + }; + + var gallery_setimage = function(a) { + if (a == +1 || a == -1) { + var meth = (a == -1) ? 'prev' : 'next'; + a = $('#gallery_images img.active')[meth]().attr('data-galid-th'); + if (!a) return; + } + + $('#gallery_images img.active').removeClass('active'); + + var thumb = $('#gallery_images [data-galid-th="'+a+'"]'); + var elem = $('a[data-galid="'+a+'"]'); + + thumb.addClass('active'); + + var topscroll = thumb.position().top + images.scrollTop(); + topscroll -= images.height() / 2; + topscroll += thumb.height() / 2; + images.animate({'scrollTop': topscroll}, 300); + + var img = elem.attr('href'); + + active.find('img, video').fadeOut(200, function() { $(this).remove(); }); + + var i; + if (img.match(/player\.php/)) { + img = img.replace(/.*player\.php\?v=|&t=.*/g, ''); + } + if (img.match(/\.webm$|\.mp4$|\.ogv$/i)) { // We are handling video nao + i = $('<video>'); + i.attr('src', img); + i.attr('autoplay', true); + i.attr('controls', true); + i.appendTo(active); + i.hide(); + } + else { // Just a plain image + i = $('<img>'); + i.attr('src', img); + i.appendTo(active); + i.hide(); + } + + // Let's actually preload the next few images + var nextimg = $('#gallery_images active'); + for (var j = 0; j < 3; j++) { + nextimg = nextimg.next(); + var attr; + if (attr = nextimg.attr('data-gaild-th')) { + var href = $('a[data-galid="'+attr+'"]').attr('href'); + if (href.match(/\.webm|\.mp4|\.ogv/i)) { j--; continue; } + if ($('[data-galid-preload="'+attr+'"]').length) continue; + var img = $('<img>').attr('src', href).attr('data-galid-preload', attr).hide().appendTo('body').on('load', function() { $(this).remove(); }); + } + else break; + } + + i.one('load canplay', function() { + i.css('left', 'calc(50% - '+i.width()+'px / 2)'); + i.css('top', 'calc(50% - '+i.height()+'px / 2)'); + i.fadeIn(200); + }).on('click', function(e) { + e.stopPropagation(); + gallery_setimage(+1); + }); + }; + + var close_gallery = function() { + $('body').css('overflow', 'auto'); + + gallery_opened = false; + + $('body').off('keydown.gview'); + + handler.fadeOut(400, function() { handler.remove(); }); + }; + +}); diff --git a/js/hide-images.js b/js/hide-images.js index ad5f401c..023c166c 100644 --- a/js/hide-images.js +++ b/js/hide-images.js @@ -96,6 +96,6 @@ $(document).ready(function(){ $('div.post > a > img.post-image, div > a > img.post-image').each(handle_images); $(document).on('new_post', function(e, post) { - $(post).find('> a > img.post-image').each(handle_images); + $(post).find('a > img.post-image').each(handle_images); }); }); diff --git a/js/hide-threads.js b/js/hide-threads.js index f83b479b..175018ed 100644 --- a/js/hide-threads.js +++ b/js/hide-threads.js @@ -36,7 +36,7 @@ $(document).ready(function(){ } } - var fields_to_hide = 'div.post,div.video-container,video,img:not(.unanimated),canvas,p.fileinfo,a.hide-thread-link,div.new-posts,br'; + var fields_to_hide = 'div.post,div.video-container,video,iframe,img:not(.unanimated),canvas,p.fileinfo,a.hide-thread-link,div.new-posts,br'; var do_hide_threads = function() { var id = $(this).children('p.intro').children('a.post_no:eq(1)').text(); diff --git a/js/id_colors.js b/js/id_colors.js new file mode 100644 index 00000000..17f8b4d9 --- /dev/null +++ b/js/id_colors.js @@ -0,0 +1,71 @@ +if (active_page == 'thread' || active_page == 'index') { + $(document).ready(function(){ + if (window.Options && Options.get_tab('general')) { + selector = '#color-ids>input'; + event = 'change'; + Options.extend_tab("general", "<label id='color-ids'><input type='checkbox' /> "+_('Color IDs')+"</label>"); + } + + else { + selector = '#color-ids'; + event = 'click'; + $('hr:first').before('<div id="color-ids" style="text-align:right"><a class="unimportant" href="javascript:void(0)">'+_('Color IDs')+'</a></div>') + } + + $(selector).on(event, function() { + if (localStorage.color_ids === 'true') { + localStorage.color_ids = 'false'; + } else { + localStorage.color_ids = 'true'; + } + }); + + if (!localStorage.color_ids || localStorage.color_ids === 'false') { + return; + } else { + $('#color-ids>input').attr('checked','checked'); + } + + function IDToRGB(id_str){ + var id = id_str.match(/.{1,2}/g); + var rgb = new Array(); + + for (i = 0; i < id.length; i++) { + rgb[i] = parseInt(id[i], 16); + } + + return rgb; + } + + function colorPostId(el) { + var rgb = IDToRGB($(el).text()); + var ft = "#fff"; + + if ((rgb[0]*0.299 + rgb[1]*0.587 + rgb[2]*0.114) > 125) + ft = "#000"; + + $(el).css({ + "background-color": "rgb("+rgb[0]+", "+rgb[1]+", "+rgb[2]+")", + "padding": "0px 5px", + "border-radius": "8px", + "color": ft + }); + + $(el).mouseover(function() { + $(this).css('color', '#800000'); // how about a CSS :hover rule instead? + }).mouseout(function() { + $(this).css('color', ft); + }); + } + + $(".poster_id").each(function(k, v){ + colorPostId(v); + }); + + $(document).on('new_post', function(e, post) { + $(post).find('.poster_id').each(function(k, v) { + colorPostId(v); + }); + }); + }); +} diff --git a/js/id_highlighter.js b/js/id_highlighter.js new file mode 100644 index 00000000..d0a5fabf --- /dev/null +++ b/js/id_highlighter.js @@ -0,0 +1,50 @@ +if (active_page == 'thread' || active_page == 'index') { + $(document).ready(function(){ + function arrayRemove(a, v) { a.splice(a.indexOf(v) == -1 ? a.length : a.indexOf(v), 1); } + + var idshighlighted = []; + + function getPostsById(id){ + return $(".poster_id").filter(function(i){ + return $(this).text() == id; + }); + } + + function getMasterPosts(parents){ + if(!parents.hasClass("post")) return; + + var toRet = []; + + $(parents).each(function(){ + if($(this).hasClass("post")) + toRet.push($(this)); + }); + + return toRet; + } + + var id_highlighter = function(){ + var id = $(this).text(); + + if($.inArray(id, idshighlighted) !== -1){ + arrayRemove(idshighlighted, id); + + $(getMasterPosts(getPostsById(id).parents())).each(function(i){ + $(this).removeClass("highlighted"); + }); + }else{ + idshighlighted.push(id); + + $(getMasterPosts(getPostsById(id).parents())).each(function(i){ + $(this).addClass("highlighted"); + }); + } + } + + $(".poster_id").on('click', id_highlighter); + + $(document).on('new_post', function(e, post) { + $(post).find('.poster_id').on('click', id_highlighter); + }); + }); +} diff --git a/js/infinite-scroll.js b/js/infinite-scroll.js new file mode 100644 index 00000000..e212f9d6 --- /dev/null +++ b/js/infinite-scroll.js @@ -0,0 +1,91 @@ +/* + * infinite-scroll.js + * https://github.com/vichan-devel/vichan/blob/master/js/infinite-scroll.js + * + * Released under the MIT license + * Copyright (c) 2013-2014 Marcin Łabanowski <marcin@6irc.net> + * + * Usage: + * $config['additional_javascript'][] = 'js/jquery.min.js'; + * $config['additional_javascript'][] = 'js/infinite-scroll.js'; + * + */ + +$(function() { +if (active_page == 'index') { + + +var loading = false; + +var activate = function() { + if (document.location.hash != '#all') return false; + + $(window).on("scroll", function() { + scrolltest(); + }); + scrolltest(); + + return true; +}; + +var scrolltest = function() { + if ($(window).scrollTop() + $(window).height() + 1000 > $(document).height() && !loading) { + load_next_page(); + } +}; + +var load_next_page = function() { + if (loading) return; + loading = true; + + var this_page = $(".pages a.selected:last"); + var next_page = this_page.next(); + + var href = next_page.prop("href"); + if (!href) return; + + var boardheader = $('<h2>'+_('Page')+' '+next_page.html()+'</h2>'); + var loading_ind = $('<h2>'+_('Loading...')+'</h2>').insertBefore('form[name="postcontrols"]>.delete:first'); + + $.get(href, function(data) { + var doc = $(data); + + loading_ind.remove(); + boardheader.insertBefore('form[name="postcontrols"]>.delete:first'); + + var i = 0; + + doc.find('div[id*="thread_"]').each(function() { + var checkout = $(this).attr('id').replace('thread_', ''); + var $this = this; + + if ($('div#thread_' + checkout).length == 0) { + // Delay DOM insertion to lessen the lag. + setTimeout(function() { + $($this).insertBefore('form[name="postcontrols"]>.delete:first'); + $(document).trigger('new_post', $this); + $($this).hide().slideDown(); + }, 500*i); + i++; + } + }); + setTimeout(function() { + loading = false; + scrolltest(); + }, 500*(i+1)); + + next_page.addClass('selected'); + }); + +}; + +var button = $("<a href='#all'>"+_("All")+" </a>").prependTo(".pages"); + +$(window).on("hashchange", function() { + return !activate(); +}); +activate(); + + +} +}); diff --git a/js/inline.js b/js/inline.js new file mode 100644 index 00000000..4f8c9a72 --- /dev/null +++ b/js/inline.js @@ -0,0 +1,154 @@ +$(document).ready(function() { + var App = { + cache: {}, + get: function(url, cb) { + var $page = App.cache[url] + if ($page) + return cb($page) + + $.get(url, function(data) { + var $page = $(data) + App.cache[url] = $page + cb($page) + }) + }, + options: { + add: function(key, description, tab) { + tab || (tab = 'general') + + var checked = App.options.get(key) + var $el = $( + '<div>' + + '<label>' + + '<input type="checkbox">' + + description + + '</label>' + + '</div>') + + $el + .find('input') + .prop('checked', checked) + .on('change', App.options.check(key)) + + window.Options.extend_tab(tab, $el) + }, + get: function(key) { + if (localStorage[key]) + return JSON.parse(localStorage[key]) + }, + check: function(key) { + return function(e) { + var val = this.checked + localStorage[key] = JSON.stringify(val) + } + } + } + } + + var inline = function(e) { + e.preventDefault() + + var $root = $(this).closest('.post') + var targetNum = this.textContent.slice(2) + + var srcOP = $root.closest('[id^=thread]').attr('id').match(/\d+/)[0] + + var node, targetOP + var isBacklink = !!this.className + if (isBacklink) { + node = $root.find('> .intro') + targetOP = srcOP + } else { + node = $(this) + + var to_search = inMod ? this.search : this.pathname; + targetOP = to_search.match(/(\d+).html/)[1] + } + + var link = { + id: 'inline_' + targetNum, + isBacklink: isBacklink, + node: node + } + + var selector = targetNum === targetOP + ? '#op_' + srcOP + : '#reply_' + targetNum + + var $clone = $root.find('#inline_' + targetNum) + if ($clone.length) { + $clone.remove() + $(selector) + .show() + .next() + .show() + return + } + + if (srcOP === targetOP) { + if (targetNum === targetOP) + link.node = link.node.next()// bypass `(OP)` + + var $target = $(selector) + if ($target.length) + return add(link, $target) + } + + var $loading = $('<div class="inline post">loading...</div>') + .attr('id', link.id) + .insertAfter(link.node) + + App.get(this.pathname, function($page) { + $loading.remove() + var $target = $page.find(selector) + add(link, $target) + }) + } + + var add = function(link, $target) { + var $clone = $target.clone(true) + + if (link.isBacklink && App.options.get('hidePost')) + $target + .hide() + .next() + .hide() + + $clone.find('.inline').remove() + $clone.attr({ + "class": 'inline post', + id: link.id, + style: null// XXX remove post hover styling + }) + $clone.insertAfter(link.node) + } + + App.options.add('useInlining', 'Enable inlining') + App.options.add('hidePost', 'Hide inlined backlinked posts') + + $('head').append( + '<style>' + + '.inline {' + + 'border: 1px dashed black;' + + 'white-space: normal;' + + 'overflow: auto;' + // clearfix + '}' + + '</style>') + + // don't attach to outbound links + + if (App.options.get('useInlining')) { + var assign_inline = function() { + $('.body a:not([rel]), .mentioned a') + .attr('onclick', null)// XXX disable highlightReply + .off('click') + .click(inline) + } + + assign_inline(); + + $(document).on('new_post', function(e, post) { + assign_inline(); + }); + } +}); diff --git a/js/live-index.js b/js/live-index.js index 463f7596..017d0c1f 100644 --- a/js/live-index.js +++ b/js/live-index.js @@ -58,7 +58,7 @@ if (active_page == 'index' && (""+document.location).match(/\/(index\.html)?(\?| update_new_threads(new_threads); }); - }, 1000); + }, 20000); }); $(document).on("new_post", function(e, post) { diff --git a/js/local-time.js b/js/local-time.js index e6b3495d..a4e25d6d 100644 --- a/js/local-time.js +++ b/js/local-time.js @@ -25,16 +25,6 @@ onready(function(){ return [Math.pow(10, count - num.toString().length), num].join('').substr(1); }; - var datelocale = - { days: [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')] - , shortDays: [_("Sun"), _("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat")] - , months: [_('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December')] - , shortMonths: [_('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'), _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec')] - , AM: _('AM') - , PM: _('PM') - , am: _('am') - , pm: _('pm') - }; var dateformat = (typeof strftime === 'undefined') ? function(t) { return zeropad(t.getMonth() + 1, 2) + "/" + zeropad(t.getDate(), 2) + "/" + t.getFullYear().toString().substring(2) + " (" + [_("Sun"), _("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat"), _("Sun")][t.getDay()] + ") " + diff --git a/js/longtable/longtable.js b/js/longtable/longtable.js new file mode 100644 index 00000000..fe3ad1c6 --- /dev/null +++ b/js/longtable/longtable.js @@ -0,0 +1,184 @@ +$.fn.longtable = function(fields, options, data) { + var elem = $(this).addClass("longtable"); + + var orig_data = data; + + options.row_h = options.row_h || 22; + options.checkbox = options.checkbox || false; + + var shown_rows = {}; + + var sorted_by = undefined; + var sorted_reverse = false; + + var filter = function() { return true; }; + + var lt = { + _gen_field_td: function(field, id) { + var el; + if (id === undefined) { + el = $("<th></th>"); + el.html(fields[field].name || field); + + if (!fields[field].sort_disable) { + el.addClass("sortable").click(function() { + lt._sort_by(field); + }); + } + } + else { + el = $("<td></td>"); + if (fields[field].fmt) { // Special formatting? + el.html(fields[field].fmt(data[id])); + } + else { + el.html(data[id][field]); + } + } + el.css("width", fields[field].width); + el.css("height", options.row_h); + return el; + }, + _gen_tr: function(id) { + var el = $("<tr></tr>"); + $.each(fields, function(field, f) { + lt._gen_field_td(field, id).appendTo(el); + }); + if (id !== undefined) { + el.addClass("row").addClass("row_"+id); + el.css({position: "absolute", top: options.row_h * (id+1)}); + } + return el; + }, + _clean: function() { + elem.find('.row').remove(); + shown_rows = {}; + }, + _remove: function(id) { + elem.find('.row_'+id).remove(); + delete shown_rows[id]; + }, + _insert: function(id) { + var el = lt._gen_tr(id).appendTo(elem); + $(elem).trigger("new-row", [data[id], el]); + shown_rows[id] = true; + }, + + _sort_by: function(field) { + if (field !== undefined) { + if (sorted_by == field) { + sorted_reverse = !sorted_reverse; + } + else { + sorted_reverse = !!fields[field].sort_reverse; + sorted_by = field; + } + } + lt.sort_by(sorted_by, sorted_reverse); + }, + + _apply_filter: function() { + data = data.filter(filter); + }, + _reset_data: function() { + data = orig_data; + }, + + + set_filter: function(f) { + filter = f; + lt._reset_data(); + lt._apply_filter(); + lt._sort_by(); + }, + + sort_by: function(field, reverse) { + if (field !== undefined) { + sorted_by = field; + sorted_reverse = reverse; + + var ord = fields[field].sort_fun || function(a,b) { return lt.sort_alphanum(a[field], b[field]); }; + + data = data.sort(ord); + if (reverse) data = data.reverse(); + } + + lt.update_data(); + }, + + update_viewport: function() { + var first = $(window).scrollTop() - $(elem).offset().top - options.row_h; + var last = first + $(window).height(); + + first = Math.floor(first / options.row_h); + last = Math.ceil (last / options.row_h); + + first = first < 0 ? 0 : first; + last = last >= data.length ? data.length - 1 : last; + + $.each(shown_rows, function(id) { + if (id < first || id > last) { + lt._remove(id); + } + }); + + for (var id = first; id <= last; id++) { + if (!shown_rows[id]) lt._insert(id); + } + }, + + update_data: function() { + $(elem).height((data.length + 1) * options.row_h); + + lt._clean(); + lt.update_viewport(); + }, + + get_data: function() { + return data; + }, + + destroy: function() { + }, + + // http://web.archive.org/web/20130826203933/http://my.opera.com/GreyWyvern/blog/show.dml/1671288 + sort_alphanum: function(a, b) { + function chunkify(t) { + var tz = [], x = 0, y = -1, n = 0, i, j; + + while (i = (j = t.charAt(x++)).charCodeAt(0)) { + var m = (/* dot: i == 46 || */(i >=48 && i <= 57)); + if (m !== n) { + tz[++y] = ""; + n = m; + } + tz[y] += j; + } + return tz; + } + + var aa = chunkify((""+a).toLowerCase()); + var bb = chunkify((""+b).toLowerCase()); + + for (x = 0; aa[x] && bb[x]; x++) { + if (aa[x] !== bb[x]) { + var c = Number(aa[x]), d = Number(bb[x]); + if (c == aa[x] && d == bb[x]) { + return c - d; + } else return (aa[x] > bb[x]) ? 1 : -1; + } + } + return aa.length - bb.length; + } + // End of foreign code + }; + + + + lt._gen_tr().appendTo(elem); + lt.update_data(); + + $(window).on("scroll resize", lt.update_viewport); + + return lt; +}; diff --git a/js/mod/ban-list.js b/js/mod/ban-list.js new file mode 100644 index 00000000..7e5fdac0 --- /dev/null +++ b/js/mod/ban-list.js @@ -0,0 +1,157 @@ +var banlist_init = function(token, my_boards, inMod) { + inMod = !inMod; + + var lt; + + var selected = {}; + + var time = function() { return Date.now()/1000|0; } + + $.getJSON(inMod ? ("?/bans.json/"+token) : token, function(t) { + $("#banlist").on("new-row", function(e, drow, el) { + var sel = selected[drow.id]; + if (sel) { + $(el).find('input.unban').prop("checked", true); + } + $(el).find('input.unban').on("click", function() { + selected[drow.id] = $(this).prop("checked"); + }); + + + if (drow.expires && drow.expires != 0 && drow.expires < time()) { + $(el).find("td").css("text-decoration", "line-through"); + } + }); + + var selall = "<input type='checkbox' id='select-all' style='float: left;'>"; + + lt = $("#banlist").longtable({ + mask: {name: selall+_("IP address"), width: "220px", fmt: function(f) { + var pre = ""; + if (inMod && f.access) { + pre = "<input type='checkbox' class='unban'>"; + } + + if (inMod && f.single_addr && !f.masked) { + return pre+"<a href='?/IP/"+f.mask+"'>"+f.mask+"</a>"; + } + return pre+f.mask; + } }, + reason: {name: _("Reason"), width: "calc(100% - 715px - 6 * 4px)", fmt: function(f) { + var add = "", suf = ''; + if (f.seen == 1) add += "<i class='fa fa-check' title='"+_("Seen")+"'></i>"; + if (f.message) { + add += "<i class='fa fa-comment' title='"+_("Message for which user was banned is included")+"'></i>"; + suf = "<br /><br /><strong>"+_("Message:")+"</strong><br />"+f.message; + } + + if (add) { add = "<div style='float: right;'>"+add+"</div>"; } + + if (f.reason) return add + f.reason + suf; + else return add + "-" + suf; + } }, + board: {name: _("Board"), width: "60px", fmt: function(f) { + if (f.board) return "/"+f.board+"/"; + else return "<em>"+_("all")+"</em>"; + } }, + created: {name: _("Set"), width: "100px", fmt: function(f) { + return ago(f.created) + _(" ago"); // in AGO form + } }, + // duration? + expires: {name: _("Expires"), width: "235px", fmt: function(f) { + if (!f.expires || f.expires == 0) return "<em>"+_("never")+"</em>"; + return strftime(window.post_date, new Date((f.expires|0)*1000), datelocale) + + ((f.expires < time()) ? "" : " <small>"+_("in ")+until(f.expires|0)+"</small>"); + } }, + username: {name: _("Staff"), width: "100px", fmt: function(f) { + var pre='',suf='',un=f.username; + if (inMod && f.username && f.username != '?' && !f.vstaff) { + pre = "<a href='?/new_PM/"+f.username+"'>"; + suf = "</a>"; + } + if (!f.username) { + un = "<em>"+_("system")+"</em>"; + } + return pre + un + suf; + } } + }, {}, t); + + $("#select-all").click(function(e) { + var $this = $(this); + $("input.unban").prop("checked", $this.prop("checked")); + lt.get_data().forEach(function(v) { v.access && (selected[v.id] = $this.prop("checked")); }); + e.stopPropagation(); + }); + + var filter = function(e) { + if ($("#only_mine").prop("checked") && ($.inArray(e.board, my_boards) === -1)) return false; + if ($("#only_not_expired").prop("checked") && e.expires && e.expires != 0 && e.expires < time()) return false; + if ($("#search").val()) { + var terms = $("#search").val().split(" "); + + var fields = ["mask", "reason", "board", "staff", "message"]; + + var ret_false = false; + terms.forEach(function(t) { + var fs = fields; + + var re = /^(mask|reason|board|staff|message):/, ma; + if (ma = t.match(re)) { + fs = [ma[1]]; + t = t.replace(re, ""); + } + + var found = false + fs.forEach(function(f) { + if (e[f] && e[f].indexOf(t) !== -1) { + found = true; + } + }); + if (!found) ret_false = true; + }); + + if (ret_false) return false; + } + + return true; + }; + + $("#only_mine, #only_not_expired, #search").on("click input", function() { + lt.set_filter(filter); + }); + lt.set_filter(filter); + + $(".banform").on("submit", function() { return false; }); + + $("#unban").on("click", function() { + $(".banform .hiddens").remove(); + $("<input type='hidden' name='unban' value='unban' class='hiddens'>").appendTo(".banform"); + + $.each(selected, function(e) { + $("<input type='hidden' name='ban_"+e+"' value='unban' class='hiddens'>").appendTo(".banform"); + }); + + $(".banform").off("submit").submit(); + }); + + if (device_type == 'desktop') { + // Stick topbar + var stick_on = $(".banlist-opts").offset().top; + var state = true; + $(window).on("scroll resize", function() { + if ($(window).scrollTop() > stick_on && state == true) { + $("body").css("margin-top", $(".banlist-opts").height()); + $(".banlist-opts").addClass("boardlist top").detach().prependTo("body"); + $("#banlist tr:not(.row)").addClass("tblhead").detach().appendTo(".banlist-opts"); + state = !state; + } + else if ($(window).scrollTop() < stick_on && state == false) { + $("body").css("margin-top", "auto"); + $(".banlist-opts").removeClass("boardlist top").detach().prependTo(".banform"); + $(".tblhead").detach().prependTo("#banlist"); + state = !state; + } + }); + } + }); +} diff --git a/js/multi-image.js b/js/multi-image.js index 32d1f256..db4c6bd2 100644 --- a/js/multi-image.js +++ b/js/multi-image.js @@ -18,7 +18,12 @@ function multi_image() { var images_len = $('form:not([id="quick-reply"]) [type=file]').length; if (!(images_len >= max_images)) { - $('.add_image').after('<br class="file_separator"/><input type="file" name="file'+(images_len+1)+'" id="upload_file'+(images_len+1)+'">'); + var new_file = '<br class="file_separator"/><input type="file" name="file'+(images_len+1)+'" id="upload_file'+(images_len+1)+'">'; + + $('[type=file]:last').after(new_file); + if ($("#quick-reply").length) { + $('form:not(#quick-reply) [type=file]:last').after(new_file); + } if (typeof setup_form !== 'undefined') setup_form($('form[name="post"]')); } }) diff --git a/js/no-animated-gif.js b/js/no-animated-gif.js index 7e777d87..a10a6497 100644 --- a/js/no-animated-gif.js +++ b/js/no-animated-gif.js @@ -13,7 +13,10 @@ */ function unanimate_gif(e) { - var c = $('<canvas class="post-image"></canvas>'); + if (active_page === "catalog") + var c = $('<canvas class="thread-image"></canvas>'); + else + var c = $('<canvas class="post-image"></canvas>'); $(e).parent().prepend(c); c.attr('width', $(e).width()); c.attr('height',$(e).height()); @@ -32,7 +35,7 @@ function unanimate_gif(e) { } function no_animated_gif() { - var anim_gifs = $('img.post-image[src$=".gif"]'); + var anim_gifs = $('img.post-image[src$=".gif"], img.thread-image[src$=".gif"]'); localStorage.no_animated_gif = true; $('#no-animated-gif>a').text(_('Animate GIFs')); $('#no-animated-gif>input').prop('checked', true); @@ -48,13 +51,13 @@ function animated_gif() { $('#no-animated-gif>input').prop('checked', false); } -if (active_page == 'thread' || active_page == 'index' || active_page == 'ukko') { +if (active_page == 'thread' || active_page == 'index' || active_page == 'ukko' || active_page == 'catalog') { $(function(){ var selector, event; if (window.Options && Options.get_tab('general')) { selector = '#no-animated-gif>input'; event = 'change'; - Options.extend_tab("general", "<label id='no-animated-gif'><input type='checkbox' /> "+_('Unanimate GIFs')+"</label>"); + Options.extend_tab("general", "<label id='no-animated-gif'><input type='checkbox' />"+_('Unanimate GIFs')+"</label>"); } else { selector = '#no-animated-gif'; diff --git a/js/options/user-css.js b/js/options/user-css.js index a6b2d137..840bcd1f 100644 --- a/js/options/user-css.js +++ b/js/options/user-css.js @@ -43,7 +43,7 @@ var apply_css = function() { var update_textarea = function() { if (!localStorage.user_css) { textarea.text("/* "+_("Enter here your own CSS rules...")+" */\n" + - "/* "+_("If you want to make a redistributable style, be sure to\n have 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" + '@import "http://example.com/style.css";'); } diff --git a/js/options/user-js.js b/js/options/user-js.js index 240e7b95..0b7d140f 100644 --- a/js/options/user-js.js +++ b/js/options/user-js.js @@ -52,7 +52,7 @@ var apply_js = function() { var update_textarea = function() { if (!localStorage.user_js) { textarea.text("/* "+_("Enter here your own Javascript code...")+" */\n" + - "/* "+_("Have a backup of your storage somewhere, as messing here\n may 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" + 'load_js("http://example.com/script.js");'); } diff --git a/js/own-board.js b/js/own-board.js new file mode 100644 index 00000000..497c568c --- /dev/null +++ b/js/own-board.js @@ -0,0 +1,116 @@ +/***************************************************************** + * ------- WARNING! --------- * + ***************************************************************** + * This script is at the current time undocumented and * + * unsupported. It is still a work in progress and will likely * + * change. You are on your own. * + *****************************************************************/ + ++function() { + +var uniq = function(a) { + var b = {}; + var c = []; + a.forEach(function(i) { + if (!b[i]) { + c.push(i); + b[i] = true; + } + }); + return c; +}; + + +if (active_page == 'thread' || active_page == 'index') { + var board = null; + + $(function() { + board = $('input[name="board"]').first().val(); + }); + + $(document).on('ajax_after_post', function(e, r) { + var threads = JSON.parse(localStorage.obthreads || '[]'); + + var thread = null; + if (active_page == 'index') { + thread = r.id|0; + } + else { + thread = $('[id^="thread_"]').first().attr('id').replace("thread_", "")|0; + } + + threads.push([board, thread]); + threads = uniq(threads); + localStorage.obthreads = JSON.stringify(threads); + }); +} + +var loaded = false; +$(function() { + loaded = true; +}); + +var activate = function() { + if (document.location.hash != '#own') return false; + + if (loaded) late_activate(); + else $(function() { late_activate(); }); + + return true; +}; + +var late_activate = function() { + $('[id^="thread_"]').remove(); + + var threads = JSON.parse(localStorage.obthreads || '[]'); + + threads.forEach(function(v) { + var board = v[0]; + var thread = v[1]; + var url = "/"+board+"/res/"+thread+".html"; + + $.get(url, function(html) { + var s = $(html).find('[id^="thread_"]'); + + s[0].bumptime = (new Date(s.find("time").last().attr("datetime"))).getTime(); + + var added = false; + $('[id^="thread_"]').each(function() { + if (added) return; + if (s[0].bumptime > this.bumptime) { + added = true; + s.insertBefore(this); + } + }); + if (!added) { + s.appendTo('[name="postcontrols"]'); + } + + s.find('.post.reply').addClass('hidden').hide().slice(-3).removeClass('hidden').show(); + + s.find('.post.reply.hidden').next().addClass('hidden').hide(); // Hide <br> elements + + var posts_omitted = s.find('.post.reply.hidden').length; + var images_omitted = s.find('.post.reply.hidden img').length; + + if (posts_omitted > 0) { + var omitted = $(fmt('<span class="omitted">'+_('{0} posts and {1} images omitted.')+' '+_('Click reply to view.')+'</span>', + [posts_omitted, images_omitted])); + + omitted.appendTo(s.find('.post.op')); + } + + var reply = $('<a href="'+url+'">['+_('Reply')+']</a>').appendTo(s.find('.intro').first()); + + $(document).trigger('new_post', s[0]); + }); + }); +}; + +$(window).on("hashchange", function() { + return !activate(); +}); +activate(); + + +}(); diff --git a/js/post-hover.js b/js/post-hover.js index e3e4dc62..e30cbf43 100644 --- a/js/post-hover.js +++ b/js/post-hover.js @@ -58,8 +58,8 @@ onready(function(){ // link links to itself or to op; ignore } else if($post.is(':visible') && - $post.offset().top + $post.height() >= $(window).scrollTop() && - $post.offset().top <= $(window).scrollTop() + $(window).height()) { + $post.offset().top >= $(window).scrollTop() && + $post.offset().top + $post.height() <= $(window).scrollTop() + $(window).height()) { // post is in view $post.addClass('highlighted'); } else { diff --git a/js/quick-reply.js b/js/quick-reply.js index 07fe4aef..12cca366 100644 --- a/js/quick-reply.js +++ b/js/quick-reply.js @@ -83,11 +83,12 @@ }\ #quick-reply textarea {\ width: 100%;\ + min-width: 100%;\ box-sizing: border-box;\ -webkit-box-sizing:border-box;\ -moz-box-sizing: border-box;\ font-size: 10pt;\ - resize: vertical;\ + resize: vertical horizontal;\ }\ #quick-reply input, #quick-reply select, #quick-reply textarea {\ margin: 0 0 1px 0;\ @@ -115,7 +116,7 @@ #quick-reply td.recaptcha-response {\ padding: 0 0 1px 0;\ }\ - @media screen and (max-width: 800px) {\ + @media screen and (max-width: 400px) {\ #quick-reply {\ display: none !important;\ }\ @@ -363,7 +364,7 @@ $(window).ready(function() { if (settings.get('hide_at_top', true)) { $(window).scroll(function() { - if ($(this).width() <= 800) + if ($(this).width() <= 400) return; if ($(this).scrollTop() < $origPostForm.offset().top + $origPostForm.height() - 100) $postForm.fadeOut(100); @@ -384,7 +385,7 @@ }; $(window).on('cite', function(e, id, with_link) { - if ($(this).width() <= 800) + if ($(this).width() <= 400) return; show_quick_reply(); if (with_link) { @@ -439,7 +440,7 @@ $('.quick-reply-btn').hide(); $(window).scroll(function() { - if ($(this).width() <= 800) + if ($(this).width() <= 400) return; if ($(this).scrollTop() < $('form[name="post"]:first').offset().top + $('form[name="post"]:first').height() - 100) $('.quick-reply-btn').fadeOut(100); diff --git a/js/settings.js b/js/settings.js index 02d5658d..81e88f7a 100644 --- a/js/settings.js +++ b/js/settings.js @@ -55,13 +55,11 @@ tb_settings['wpaint'] = { // auto-reload.js tb_settings['auto-reload'] = { // Minimum delay before reloading the page when at the bottom - min_delay_bottom: 3000, - // Minimum delay before reloading the page when not at the bottom - min_delay_top: 10000, + min_delay_bottom: 5000, // Maximum delay before reloading the page max_delay: 600000, - //Delay to wait before reloading when the user scrolls to the bottom - quick_delay: 100, + // Delay if there was an error + error_delay: 30000, // Reset the delay to the minimum upon focussing the window. reset_focus: true }; diff --git a/js/show-own-posts.js b/js/show-own-posts.js new file mode 100644 index 00000000..8b83f4cd --- /dev/null +++ b/js/show-own-posts.js @@ -0,0 +1,86 @@ +/* + * show-own-posts.js + * https://github.com/savetheinternet/Tinyboard/blob/master/js/show-op.js + * + * Adds "(You)" to a name field when the post is yours. Update references as well. + * + * Released under the MIT license + * Copyright (c) 2014 Marcin Łabanowski <marcin@6irc.net> + * + * Usage: + * $config['additional_javascript'][] = 'js/jquery.min.js'; + * $config['additional_javascript'][] = 'js/ajax.js'; + * $config['additional_javascript'][] = 'js/show-own-posts.js'; + * + */ + + ++function(){ + + +var update_own = function() { + if ($(this).is('.you')) return; + + var thread = $(this).parents('[id^="thread_"]').first(); + if (!thread.length) { + thread = $(this); + } + + var board = thread.attr('data-board'); + var posts = JSON.parse(localStorage.own_posts || '{}'); + + var id = $(this).attr('id').split('_')[1]; + + if (posts[board] && posts[board].indexOf(id) !== -1) { // Own post! + $(this).addClass('you'); + $(this).find('span.name').first().append(' '+_('(You)')); + } + + // Update references + $(this).find('div.body:first a:not([rel="nofollow"])').each(function() { + var postID; + + if(postID = $(this).text().match(/^>>(\d+)$/)) + postID = postID[1]; + else + return; + + if (posts[board] && posts[board].indexOf(postID) !== -1) { + $(this).after(' <small>'+_('(You)')+'</small>'); + } + }); +}; + +var update_all = function() { + $('div[id^="thread_"], div.post.reply').each(update_own); +}; + +var board = null; + +$(function() { + board = $('input[name="board"]').first().val(); + + update_all(); +}); + +$(document).on('ajax_after_post', function(e, r) { + var posts = JSON.parse(localStorage.own_posts || '{}'); + posts[board] = posts[board] || []; + posts[board].push(r.id); + localStorage.own_posts = JSON.stringify(posts); +}); + +$(document).on('new_post', function(e,post) { + var $post = $(post); + if ($post.is('div.post.reply')) { // it's a reply + $post.each(update_own); + } + else { + $post.each(update_own); // first OP + $post.find('div.post.reply').each(update_own); // then replies + } +}); + + + +}(); diff --git a/js/thread-stats.js b/js/thread-stats.js new file mode 100644 index 00000000..8873dbdb --- /dev/null +++ b/js/thread-stats.js @@ -0,0 +1,116 @@ +/* + * 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') { +$(document).ready(function(){ + //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].split('.')[0]; + + $('.boardlist.bottom, footer') + .first() + .before('<div id="thread_stats"></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]++; + var cur = op.find('>.intro >.poster_id'); + cur.find('+.posts_by_id').remove(); + cur.after('<span class="posts_by_id"> ('+ ids[cur.text()] +')</span>'); + replies.each(function(){ + 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)); + } + var board_name = $('input[name="board"]').val(); + $.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').success(function(data){ + var found, page = '???'; + 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'); + else $('#thread_stats_page').css('color',''); + }); + } + // 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(){ + var board_name = $('input[name="board"]').val(); + $.getJSON('//'+ document.location.host +'/'+ board_name +'/threads.json').success(function(data){ + var found, page = '???'; + 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'); + else $('#thread_stats_page').css('color',''); + }); + },30000); + $('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); +}); +} diff --git a/js/thread-watcher.js b/js/thread-watcher.js new file mode 100644 index 00000000..22656e48 --- /dev/null +++ b/js/thread-watcher.js @@ -0,0 +1,195 @@ +// Thanks to Khorne on #8chan at irc.rizon.net +// https://gitlab.com/aymous/8chan-watchlist + +'use strict'; +/* jshint globalstrict:true, quotmark:single */ +/* jshint browser:true, jquery:true, devel:true, unused:true, undef:true */ +/* global active_page:false, board_name:false */ +if(!localStorage.watchlist){ + //If the watchlist is undefined in the localStorage, + //initialize it as an empty array. + localStorage.watchlist = '[]'; +} +var watchlist = {}; + +/** + * [render /> Creates a watchlist container and populates it with info + * about each thread that's currently being watched. If the watchlist container + * already exists, it empties it out and repopulates it.] + * @param {[Bool]} reset [If true and the watchlist is rendered, remove it] + */ +watchlist.render = function(reset) { + /* jshint eqnull:true */ + if (reset == null) reset = false; + /* jshint eqnull:false */ + if (reset && $('#watchlist').length) $('#watchlist').remove(); + var threads = []; + //Read the watchlist and create a new container for each thread. + JSON.parse(localStorage.watchlist).forEach(function(e, i) { + //look at line 69, that's what (e) is here. + threads.push('<div class="watchlist-inner" id="watchlist-'+i+'">' + + '<span>Board: '+e[0]+'</span> ' + + '<span>Thread: '+'<a href="'+e[3]+'">'+e[1]+'</a></span> ' + + '<span>Replies: '+e[2]+'</span> ' + + '<a class="watchlist-remove">[Unwatch]</a>'+ + '</div>'); + }); + if ($('#watchlist').length) { + //If the watchlist is already there, empty it and append the threads. + $('#watchlist').children('.watchlist-inner').remove(); + $('#watchlist').append(threads.join('')); + } else { + //If the watchlist has not yet been rendered, create it. + $('form[name="post"]').before( + $('<div id="watchlist">'+ + '<div class="watchlist-controls">'+ + '<span><a id="clearList">[Clear List]</a></span> '+ + '<span><a id="clearGhosts">[Clear Ghosts]</a></span>'+ + '</div>'+ + threads.join('')+ + '</div>').css({ + background: $('.reply').css('background'), + borderColor : $('.reply').css('border-color') + })); + } + return this; +}; + +/** + * [add /> adds the given item to the watchlist] + * @param {[Obj/Str]} sel [An unwrapped jquery selector.] + */ +watchlist.add = function(sel) { + var threadName, threadInfo; + + if (active_page === 'thread') { + if ($('.subject').length){ + //If a subject is given, use the first 20 characters as the thread name. + threadName = $('.subject').text().substring(0,20); + } else { //Otherwise use the thread id. + threadName = $('.op').parent().attr('id'); + } + //board name, thread name as defined above, current amount of posts, thread url + threadInfo = [board_name, threadName, $('.post').length, location.href]; + + } else if (active_page === 'index') { + + var postCount; + //Figure out the post count. + if ($(sel).parents('.op').children('.omitted').length) { + postCount = $(sel).parents('.op').children('.omitted').text().split(' ')[0]; + } else { + postCount = $(sel).parents('.op').siblings('.post').length+1; + } + //Grab the reply link. + var threadLink = $(sel).siblings('a:contains("[Reply]")').attr('href'); + //Figure out the thread name. If anon, use the thread id. + if ($(sel).parent().find('.subject').length) { + threadName = $(sel).parent().find('.subject').text().substring(0,20); + } else { + threadName = $(sel).parents('div').last().attr('id'); + } + + threadInfo = [board_name, threadName, postCount, threadLink]; + + } else { + alert('Functionality not yet implemented for this type of page.'); + return this; + } + + //if the thread is already being watched, cancel the function. + if (localStorage.watchlist.indexOf(JSON.stringify(threadInfo)) !== -1) { + return this; + } + + var _watchlist = JSON.parse(localStorage.watchlist); //Read the watchlist + _watchlist.push(threadInfo); //Add the new watch item. + localStorage.watchlist = JSON.stringify(_watchlist); //Save the watchlist. + return this; +}; + +/** + * [remove /> removes the given item from the watchlist] + * @param {[Int]} n [The index at which to remove.] + */ +watchlist.remove = function(n) { + var _watchlist = JSON.parse(localStorage.watchlist); + _watchlist.splice(n, 1); + localStorage.watchlist = JSON.stringify(_watchlist); + return this; +}; + +/** + * [clear /> resets the watchlist to the initial empty array] + */ +watchlist.clear = function() { + localStorage.watchlist = '[]'; + return this; +}; + +/** + * [exists /> pings every watched thread to check if it exists and removes it if not] + * @param {[Obj/Str]} sel [an unwrapped jq selector] + */ +watchlist.exists = function(sel) { + $.ajax($(sel).children().children('a').attr('href'), { + type :'HEAD', + error: function() { + watchlist.remove(parseInt($(sel).attr('id').split('-')[1])).render(); + }, + success : function(){ + return; + } + }); +}; + +$(document).ready(function(){ + //Append the watchlist toggle button. + $('.boardlist').append('<span>[ <a id="watchlist-toggle">watchlist</a> ]</span>'); + //Append a watch thread button after every OP. + $('.op>.intro').append('<a class="watchThread">[Watch Thread]</a>'); + + //Draw the watchlist, hidden. + watchlist.render(); + + //Show or hide the watchlist. + $('#watchlist-toggle').on('click', function(e) { + //if ctrl+click, reset the watchlist. + if (e.ctrlKey) { + watchlist.render(true); + } + if ($('#watchlist').css('display') !== 'none') { + $('#watchlist').css('display', 'none'); + } else { + $('#watchlist').css('display', 'block'); + } //Shit got really weird with hide/show. Went with css manip. Probably faster anyway. + }); + + //Trigger the watchlist add function. + //The selector is passed as an argument in case the page is not a thread. + $('.watchThread').on('click', function() { + watchlist.add(this).render(); + }); + + //The index is saved in .watchlist-inner so that it can be passed as the argument here. + //$('.watchlist-remove').on('click') won't work in case of re-renders and + //the page will need refreshing. This works around that. + $(document).on('click', '.watchlist-remove', function() { + var item = parseInt($(this).parent().attr('id').split('-')[1]); + watchlist.remove(item).render(); + }); + + //Empty the watchlist and redraw it. + $('#clearList').on('click', function(){ + watchlist.clear().render(); + }); + + //Get rid of every watched item that no longer directs to an existing page. + $('#clearGhosts').on('click', function() { + $('.watchlist-inner').each(function(){ + watchlist.exists(this); + }); + }); + +}); + diff --git a/js/threadscroll.js b/js/threadscroll.js new file mode 100644 index 00000000..0a233f58 --- /dev/null +++ b/js/threadscroll.js @@ -0,0 +1,37 @@ +if(active_page == "index" || active_page == "ukko"){ + var hoverElem = null; + + $(document).mouseover(function(e){ + var x = e.clientX, y = e.clientY, + elementOnMouseOver = document.elementFromPoint(x, y); + hoverElem = $(elementOnMouseOver); + }); + + $(document).keydown(function(e){ + //Up arrow + if(e.which == 38){ + var ele = hoverElem; + var par = $(ele).parents('div[id^="thread_"]'); + + if(par.length == 1){ + if(par.prev().attr("id") != null){ + if(par.prev().attr("id").match("^thread")){ + par.prev()[0].scrollIntoView(true); + } + } + } + //Down arrow + }else if(e.which == 40){ + var ele = hoverElem; + var par = $(ele).parents('div[id^="thread_"]'); + + if(par.length == 1){ + if(par.next().attr("id") != null){ + if(par.next().attr("id").match("^thread")){ + par.next()[0].scrollIntoView(true); + } + } + } + } + }); +} diff --git a/js/toggle-images.js b/js/toggle-images.js index 20940ccf..b8940252 100644 --- a/js/toggle-images.js +++ b/js/toggle-images.js @@ -60,7 +60,7 @@ $(document).ready(function(){ if (window.Options && Options.get_tab('general')) { selector = '#toggle-images>input'; event = 'change'; - Options.extend_tab("general", "<label id='toggle-images'><input type='checkbox' /> "+_('Hide images')+"</label>"); + Options.extend_tab("general", "<label id='toggle-images'><input type='checkbox' />"+_('Hide images')+"</label>"); } else { selector = '#toggle-images a'; diff --git a/js/treeview.js b/js/treeview.js index 78ad620b..1709debb 100644 --- a/js/treeview.js +++ b/js/treeview.js @@ -11,14 +11,28 @@ * */ -if (active_page == 'thread') +if (active_page == 'thread' || active_page == 'ukko' || active_page == 'index') $(function() { - $('hr:first').before('<div id="treeview" style="text-align:right"><a class="unimportant" href="javascript:void(0)"></a></div>'); - $('div#treeview a') - .text(_('Tree view')) - .click(function(e) { - e.preventDefault(); + if (window.Options && Options.get_tab('general')) { + var selector = '#treeview-global>input'; + Options.extend_tab("general", "<label id='treeview-global'><input type='checkbox' /> "+_('Use tree view by default')+"</label>"); + $(selector).on('change', function() { + if (localStorage.treeview === 'true') { + localStorage.treeview = 'false'; + } else { + localStorage.treeview = 'true'; + } + }); + if (localStorage.treeview === 'true') { + $(selector).attr('checked', 'checked'); + } + } +}); +if (active_page == 'thread') +$(function() { + var treeview = function(enable) { + if (enable === true) { $('.post.reply').each(function(){ var references = []; $(this).find('.body a').each(function(){ @@ -26,7 +40,6 @@ $(function() { references.push(parseInt($(this).html().replace('>>', ''))); } }); - var maxref = references.reduce(function(a,b) { return a > b ? a : b; }, 0); var parent_post = $("#reply_"+maxref); @@ -39,7 +52,24 @@ $(function() { post.detach().css("margin-left", margin).insertAfter(parent_post.next()); br.detach().insertAfter(post); - }); - }); + } else { + $('.post.reply').sort(function(a,b) { + return parseInt(a.id.replace('reply_', '')) - parseInt(b.id.replace('reply_', '')); + }).each(function () { + var post = $(this); + var br = post.next(); + post.detach().css('margin-left', '').appendTo('.thread'); + br.detach().insertAfter(post); + }); + } + } + + $('hr:first').before('<div class="unimportant" style="text-align:right"><label for="treeview"><input type="checkbox" id="treeview"> '+_('Tree view')+'</label></div>'); + $('input#treeview').on('change', function(e) { treeview($(this).is(':checked')); }); + + if (localStorage.treeview === 'true') { + treeview(true); + $('input#treeview').attr('checked', true); + } }); diff --git a/js/upload-selection.js b/js/upload-selection.js index e2032748..b342a134 100644 --- a/js/upload-selection.js +++ b/js/upload-selection.js @@ -25,6 +25,7 @@ $(function(){ $("#upload_url").hide(); $("#upload_embed").hide(); $(".add_image").hide(); + $(".dropzone-wrap").hide(); $('[id^=upload_file]').each(function(i, v) { $(v).val(''); @@ -40,6 +41,7 @@ $(function(){ enable_file = function() { disable_all(); $("#upload").show(); + $(".dropzone-wrap").show(); $(".file_separator").show(); $("[id^=upload_file]").show(); $(".add_image").show(); diff --git a/js/wPaint b/js/wPaint -Subproject 2c272dffca0f3d7b7163bd82ba15629f5440927 +Subproject f11220032e7edb8349516d3751d3aaee0fd0de6 diff --git a/js/watch.js b/js/watch.js index a202130f..07c6942a 100644 --- a/js/watch.js +++ b/js/watch.js @@ -98,6 +98,9 @@ $(function(){ else { bc.threads = bc.threads || {}; bc.threads[thread] = Date.now(); + + bc.slugs = bc.slugs || {}; + bc.slugs[thread] = document.location.pathname + document.location.search; } st[board] = bc; storage_save(st); @@ -121,7 +124,7 @@ $(function(){ var tag; if (variant == 'desktop') { - tag = $("<a href='"+modRoot+board+"/res/"+tid+".html'><span>#"+tid+"</span><span class='cb-uri watch-remove'>"+newposts+"</span>"); + tag = $("<a href='"+((storage()[board].slugs && storage()[board].slugs[tid]) || (modRoot+board+"/res/"+tid+".html"))+"'><span>#"+tid+"</span><span class='cb-uri watch-remove'>"+newposts+"</span>"); tag.find(".watch-remove").mouseenter(function() { this.oldval = $(this).html(); $(this).css("min-width", $(this).width()); @@ -132,7 +135,7 @@ $(function(){ }) } else if (variant == 'mobile') { - tag = $("<a href='"+modRoot+board+"/res/"+tid+".html'><span>#"+tid+"</span><span class='cb-uri'>"+newposts+"</span>" + tag = $("<a href='"+((storage()[board].slugs && storage()[board].slugs[tid]) || (modRoot+board+"/res/"+tid+".html"))+"'><span>#"+tid+"</span><span class='cb-uri'>"+newposts+"</span>" +"<span class='cb-uri watch-remove'><i class='fa fa-minus'></i></span>"); } @@ -245,7 +248,7 @@ $(function(){ var st = storage(); var sched = 0; - var sched_diff = 300; + var sched_diff = 2000; for (var i in st) { if (st[i].watched) { diff --git a/js/wpaint.js b/js/wpaint.js index 9c619070..a245a22b 100644 --- a/js/wpaint.js +++ b/js/wpaint.js @@ -13,12 +13,7 @@ * $config['additional_javascript'][] = 'js/jquery.min.js'; * $config['additional_javascript'][] = 'js/jquery-ui.custom.min.js'; * $config['additional_javascript'][] = 'js/ajax.js'; - * $config['additional_javascript'][] = 'js/wPaint/lib/wColorPicker.min.js'; - * $config['additional_javascript'][] = 'js/wPaint/wPaint.min.js'; - * $config['additional_javascript'][] = 'js/wPaint/plugins/main/wPaint.menu.main.min.js'; - * $config['additional_javascript'][] = 'js/wPaint/plugins/text/wPaint.menu.text.min.js'; - * $config['additional_javascript'][] = 'js/wPaint/plugins/shapes/wPaint.menu.main.shapes.min.js'; - * $config['additional_javascript'][] = 'js/wPaint/plugins/file/wPaint.menu.main.file.min.js'; + * $config['additional_javascript'][] = 'js/wPaint/8ch.js'; * $config['additional_javascript'][] = 'js/wpaint.js'; * $config['additional_javascript'][] = 'js/upload-selection.js'; * @@ -49,7 +44,7 @@ oekaki.init = function() { var oekaki_form = '<tr id="oekaki"><th>Oekaki</th><td><div id="wpaintctr"><div id="wpaintdiv"></div></div></td></tr>'; // Add oekaki after the file input - $('form[name="post"]:not(#quick-reply) input[type="file"]').parent().parent().after(oekaki_form); + $('form[name="post"]:not(#quick-reply) [id="upload"]').after(oekaki_form); $('<link class="wpaintcss" rel="stylesheet" href="'+configRoot+'js/wPaint/wPaint.min.css" />').appendTo($("head")); $('<link class="wpaintcss" rel="stylesheet" href="'+configRoot+'js/wPaint/lib/wColorPicker.min.css" />').appendTo($("head")); |
