vichan-devel / vichan

Vichan is the most popular and widely used imageboard software in the world. It is a free, light-weight, fast, highly configurable and user-friendly imageboard software package.
https://vichan.info
Other
634 stars 198 forks source link

Quick reply from board index #799

Open chanadmins opened 2 months ago

chanadmins commented 2 months ago

I think this should be available as a basic feature. Forks like bazukachan or kissu already have that. Yotsuba, Mitsuba, Lynxchan, JSchan, Willboard, basically almost every imageboard software besides vichan has option to post in thread from quick reply without opening it. Would love to see this added. example on magalichan:

tst
perdedora commented 2 months ago

I can open PR with my changes

perdedora commented 2 months ago

Done

chanadmins commented 2 months ago

Done

I added your changes and quick reply loads from index but I can't post anything from it: "Your request looks automated; Post discarded."

perdedora commented 2 months ago

This is likely happening due to a mismatch between submit button values. The variables in main.js is to fix this.

Edit: Oh shit, I forgot to close the tag.

perdedora commented 2 months ago

Can you try again with this change? 60e2ca2a8816fba0b3060b2c1b938d2304de33ee

chanadmins commented 2 months ago

Can you try again with this change? 60e2ca2

Still the same

sc

I think it's because of that whole antibot thing in functions.php. I remember I implemented quick reply from index once and it involved removing the antibot block from functions.php and some other stuff. It creates non-existing fields or something so it prevents posting from index quick reply.

perdedora commented 2 months ago

Weird. this is literally the code running on magali with antibot on. Going to take a closer look later

perdedora commented 2 months ago

Okay, so the problem was: the hash sent to checkSpam was the hash of the index, not the thread. Which failed everytime because the extra_salt was wrong.

This is the best solution I could come up: 4b20ad55ffe9bd4d33590f90eca1ac14b8c0e6f9

chanadmins commented 2 months ago

Okay, so the problem was: the hash sent to checkSpam was the hash of the index, not the thread. Which failed everytime because the extra_salt was wrong.

This is the best solution I could come up: 4b20ad5

It works now, thanks!

chanadmins commented 2 months ago

Wait, shouldn't quick reply change threads based on the post number I click? c So here I click >>6038 and quick reply appears to thread Quick Reply (5963) but then I click >>6100 and quick reply should change to Quick Reply (6074) but it stays Quick Reply (5963) At least that's how most of the implementations I've seen work (like on Willboard), with dynamic changing threads.

perdedora commented 2 months ago

Wait, shouldn't quick reply change threads based on the post number I click?

In the perfect javascript, yes. We can force to "change" by closing and opening again.

    $(window).on('cite', function(e, id, with_link) {
        if ($(this).width() <= 400)
            return;
        if ($('#quick-reply').length) $('#quick-reply').remove();
        show_quick_reply(id);
        if (with_link) {
            $(document).ready(function() {
                if ($('#' + id).length) {
                    highlightReply(id);
                    $(document).scrollTop($('#' + id).offset().top);
                }

                // Honestly, I'm not sure why we need setTimeout() here, but it seems to work.
                // Same for the "tmp" variable stuff you see inside here:
                setTimeout(function() {
                    var tmp = $('#quick-reply textarea[name="body"]').val();
                    $('#quick-reply textarea[name="body"]').val('').focus().val(tmp);
                }, 1);
            });
        }
    });
chanadmins commented 2 months ago

Wait, shouldn't quick reply change threads based on the post number I click?

In the perfect javascript, yes. We can force to "change" by closing and opening again.

Any way to do this without opening/closing quick reply window and just clicking post number? I remember it worked in my previous implementation of this

/*
 * quick-reply.js
 * https://github.com/savetheinternet/Tinyboard/blob/master/js/quick-reply.js
 *
 * Released under the MIT license
 * Copyright (c) 2013 Michael Save <savetheinternet@tinyboard.org>
 * Copyright (c) 2013-2014 Marcin Łabanowski <marcin@6irc.net>
 *
 * Usage:
 *   $config['additional_javascript'][] = 'js/jquery.min.js';
 *   $config['additional_javascript'][] = 'js/jquery-ui.custom.min.js'; // Optional; if you want the form to be draggable.
 *   $config['additional_javascript'][] = 'js/quick-reply.js';
 *
 */

(function() {
    var new_reply = "Odpowiedz"; // ta zmienna musi być taka sama jak przycisk nowej odpowiedzi w temacie

    if(document.querySelector("body").classList.contains("active-index")) {
        document.addEventListener("click", function(event) {
            if(event.target.classList.contains("post_no") && event.target.id === "") {
                event.preventDefault();
            }
        });
    }

    var settings = new script_settings('quick-reply');

    var do_css = function() {
        $('#quick-reply-css').remove();

        // Find background of reply posts
        var dummy_reply = $('<div class="post reply"></div>').appendTo($('body'));
        var reply_background = dummy_reply.css('backgroundColor');
        var reply_border_style = dummy_reply.css('borderStyle');
        var reply_border_color = dummy_reply.css('borderColor');
        var reply_border_width = dummy_reply.css('borderWidth');
        dummy_reply.remove();

        $('<style type="text/css" id="quick-reply-css">\
        #quick-reply {\
            position: fixed;\
            right: 5%;\
            top: 5%;\
            float: right;\
            display: block;\
            padding: 0 0 0 0;\
            width: 300px;\
            z-index: 100;\
        }\
        #quick-reply table {\
            border-collapse: collapse;\
            background: ' + reply_background + ';\
            border-style: ' + reply_border_style + ';\
            border-width: ' + reply_border_width + ';\
            border-color: ' + reply_border_color + ';\
            margin: 0;\
            width: 100%;\
        }\
        #quick-reply tr td:nth-child(2) {\
            white-space: nowrap;\
            text-align: right;\
            padding-right: 4px;\
        }\
        #quick-reply tr td:nth-child(2) input[type="submit"] {\
            width: 100%;\
        }\
        #quick-reply th, #quick-reply td {\
            margin: 0;\
            padding: 0;\
        }\
        #quick-reply th {\
            text-align: center;\
            padding: 2px 0;\
            border: 1px solid #222;\
        }\
        #quick-reply th .handle {\
            float: left;\
            width: 100%;\
            display: inline-block;\
        }\
        #quick-reply th .close-btn {\
            float: right;\
            padding: 0 5px;\
        }\
        #quick-reply input[type="text"], #quick-reply select {\
            width: 100%;\
            padding: 2px;\
            font-size: 10pt;\
            box-sizing: border-box;\
            -webkit-box-sizing:border-box;\
            -moz-box-sizing: border-box;\
        }\
        #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 horizontal;\
        }\
        #quick-reply input, #quick-reply select, #quick-reply textarea {\
            margin: 0 0 1px 0;\
        }\
        #quick-reply input[type="file"] {\
            padding: 5px 2px;\
        }\
        #quick-reply .nonsense {\
            display: none;\
        }\
        #quick-reply td.submit {\
            width: 1%;\
        }\
        #quick-reply td.recaptcha {\
            text-align: center;\
            padding: 0 0 1px 0;\
        }\
        #quick-reply td.recaptcha span {\
            display: inline-block;\
            width: 100%;\
            background: white;\
            border: 1px solid #ccc;\
            cursor: pointer;\
        }\
        #quick-reply td.recaptcha-response {\
            padding: 0 0 1px 0;\
        }\
        @media screen and (max-width: 400px) {\
            #quick-reply {\
                /* display: none !important; */\
            }\
        }\
        </style>').appendTo($('head'));
    };

    var show_quick_reply = function(target_id){

        if($('div.banner').length == 0){
                var thread_id = $("#reply_" + target_id + ",#op_"+target_id).parent().attr("id").split("_")[1];
                var in_index = true;
        }
        else
            var in_index = false;

        if($('#quick-reply').length != 0) {
            return;
        }

        do_css();

        var $postForm = $('form[name="post"]').clone();

        $postForm.clone();
        $dummyStuff = $('<div class="nonsense"></div>').appendTo($postForm);

        $postForm.find('table tr').each(function() {
            var $th = $(this).children('th:first');
            var $td = $(this).children('td:first');
            if ($th.length && $td.length) {
                $td.attr('colspan', 2);

                if ($td.find('input[type="text"]').length) {
                    // Replace <th> with input placeholders
                    $td.find('input[type="text"]')
                        .removeAttr('size')
                        .attr('placeholder', $th.clone().children().remove().end().text());
                }

                // Move anti-spam nonsense and remove <th>
                $th.contents().filter(function() {
                    return this.nodeType == 3; // Node.TEXT_NODE
                }).remove();
                $th.contents().appendTo($dummyStuff);
                $th.remove();

                if ($td.find('input[name="password"]').length) {
                    // Hide password field
                    $(this).hide();
                }

                // Fix submit button
                if ($td.find('input[type="submit"]').length) {
                    $td.removeAttr('colspan');
                    $('<td class="submit"></td>').append($td.find('input[type="submit"]')).insertAfter($td);
                }

                // reCAPTCHA
                if ($td.find('#recaptcha_widget_div').length) {
                    // Just show the image, and have it interact with the real form.
                    var $captchaimg = $td.find('#recaptcha_image img');

                    $captchaimg
                        .removeAttr('id')
                        .removeAttr('style')
                        .addClass('recaptcha_image')
                        .click(function() {
                            $('#recaptcha_reload').click();
                        });

                    // When we get a new captcha...
                    $('#recaptcha_response_field').focus(function() {
                        if ($captchaimg.attr('src') != $('#recaptcha_image img').attr('src')) {
                            $captchaimg.attr('src', $('#recaptcha_image img').attr('src'));
                            $postForm.find('input[name="recaptcha_challenge_field"]').val($('#recaptcha_challenge_field').val());
                            $postForm.find('input[name="recaptcha_response_field"]').val('').focus();
                        }
                    });

                    $postForm.submit(function() {
                        setTimeout(function() {
                            $('#recaptcha_reload').click();
                        }, 200);
                    });

                    // Make a new row for the response text
                    var $newRow = $('<tr><td class="recaptcha-response" colspan="2"></td></tr>');
                    $newRow.children().first().append(
                        $td.find('input').removeAttr('style')
                    );
                    $newRow.find('#recaptcha_response_field')
                        .removeAttr('id')
                        .addClass('recaptcha_response_field')
                        .attr('placeholder', $('#recaptcha_response_field').attr('placeholder'));

                    $('#recaptcha_response_field').addClass('recaptcha_response_field')

                    $td.replaceWith($('<td class="recaptcha" colspan="2"></td>').append($('<span></span>').append($captchaimg)));

                    $newRow.insertAfter(this);
                }

                // Upload section
                if ($td.find('input[type="file"]').length) {
                    if($td.find('input[name="file"]').length){
                        $file = $td.find('input[name="file"]');
                        $file.attr("style","width:90%");
                    }

                    //captcha stuff
                    var $captcha_span = $td.find('span[name="captchasel"]');
                    var $caprow = $('<tr><td colspan="2"></td></tr>');
                    $captcha_span.clone().appendTo($caprow.find('td'));
                    $caprow.insertBefore(this);

                    if ($td.find('input[name="file_url"]').length) {
                        $file_url = $td.find('input[name="file_url"]');

                        //if (settings.get('show_remote', false)) {
                            // Make a new row for it
                            var $newRow = $('<tr><td colspan="2"></td></tr>');

                            $file_url.clone().attr('placeholder', _('\
                Upload URL')).appendTo($newRow.find('td'));

                            $newRow.insertAfter(this);
                        //}
                        $file_url.parent().remove();

                        $td.find('label').remove();
                        $td.contents().filter(function() {
                            return this.nodeType == 3; // Node.TEXT_NODE
                        }).remove();
                        $td.find('input[name="file_url"]').removeAttr('id');
                    }

                    if ($(this).find('input[name="spoiler"]').length) {
                        $td.removeAttr('colspan');
                    }
                }

                //captcha controls
                if($td.find('input[name=captype]').length > 0){

                }

                // Disable embedding if configured so
                // if (!settings.get('show_embed', false) && $td.find('input[name="embed"]').length) {
                    // $(this).remove();
                // }

                // Remove oekaki if existent
                if ($(this).is('#oekaki')) {
                    $(this).remove();
                }

                // Remove upload selection
                if ($td.is('#upload_selection')) {
                    $(this).remove();
                }

                // Remove mod controls, because it looks shit.
                if ($td.find('input[type="checkbox"]').length) {
                    var tr = this;
                    $td.find('input[type="checkbox"]').each(function() {
                        if ($(this).attr('name') == 'spoiler') {
                            $td.find('label').remove();
                            $(this).attr('id', 'q-spoiler-image');
                            $postForm.find('input[type="file"]').parent()
                                .removeAttr('colspan')
                                .after($('<td class="spoiler"></td>').append(this, ' ', $('<label for="q-spoiler-image">').text(_('Spoiler Image'))));
                        } else if ($(this).attr('name') == 'no_country') {
                            $td.find('label,input[type="checkbox"]').remove();
                        } else {
                            $(tr).remove();
                        }
                    });
                }

                $td.find('small').hide();
            }
            if($td.find('[name="markup-hint"]').length){
                $td.remove();
            }
        });

        $postForm.find('textarea[name="body"]').removeAttr('id').removeAttr('cols').attr('placeholder', _('Comment'));

        $postForm.find('textarea:not([name="body"]),input[type="hidden"]:not(.captcha_cookie)').removeAttr('id').appendTo($dummyStuff);

        $postForm.find('br').remove();
        $postForm.find('table').prepend('<tr><th colspan="2">\
            <span class="handle">\
                <a class="close-btn" href="javascript:void(0)">×</a>\
                ' + _('Quick Reply') + '\
            </span>\
            </th></tr>');

        $postForm.attr('id', 'quick-reply');

        $postForm.appendTo($('body')).hide();
        $origPostForm = $('form[name="post"]:first');

        // insert the thread that this post will be placed in and modify fields
        if(in_index) {
            // ziemniachan fix
            $("#quick-reply").find("input[type=submit]").remove();
            document.querySelectorAll("#quick-reply tr")[2].remove();
            document.querySelector("#quick-reply [name=email]")?.parentNode.setAttribute("colspan", "1");
            document.querySelectorAll("#quick-reply tr")[1].innerHTML += '<td class="submit"><input accesskey="s" style="margin-left:2px;" type="submit" name="post" value="'+new_reply+'"></td>';

            $("#quick-reply textarea").attr("id", "index-body");
            $("<input type='hidden' name='thread' value='" + thread_id + "'>").appendTo($("#quick-reply"));
            $("#quick-reply .handle").append(document.createTextNode("(>>" + thread_id + ")"));
        }
            //$("#quick-reply input[type=submit]").attr("value", "New Reply");
        // }
        // otherwise synchronise body text with original post form
        // else if(!in_index){
            $origPostForm.find('textarea[name="body"]').on('change input propertychange', function() {
                $postForm.find('textarea[name="body"]').val($(this).val());
            });
            $postForm.find('textarea[name="body"]').on('change input propertychange', function() {
                $origPostForm.find('textarea[name="body"]').val($(this).val());
            });
            $postForm.find('textarea[name="body"]').focus(function() {
                $origPostForm.find('textarea[name="body"]').removeAttr('id');
                $(this).attr('id', 'body');
            });
            $origPostForm.find('textarea[name="body"]').focus(function() {
                $postForm.find('textarea[name="body"]').removeAttr('id');
                $(this).attr('id', 'body');
            });
            // Synchronise other inputs
            $origPostForm.find('input[type="text"],select').on('change input propertychange', function() {
                $postForm.find('[name="' + $(this).attr('name') + '"]').val($(this).val());
            });
            $postForm.find('input[type="text"],select').on('change input propertychange', function() {
                $origPostForm.find('[name="' + $(this).attr('name') + '"]').val($(this).val());
            });
        // }
        if (typeof $postForm.draggable != 'undefined') {
            if (localStorage.quickReplyPosition) {
                var offset = JSON.parse(localStorage.quickReplyPosition);
                if (offset.top < 0)
                    offset.top = 0;
                if (offset.right > $(window).width() - $postForm.width())
                    offset.right = $(window).width() - $postForm.width();
                if (offset.top > $(window).height() - $postForm.height())
                    offset.top = $(window).height() - $postForm.height();
                $postForm.css('right', offset.right).css('top', offset.top);
            }
            $postForm.draggable({
                handle: 'th .handle',
                containment: 'window',
                distance: 10,
                scroll: false,
                stop: function() {
                    var offset = {
                        top: $(this).offset().top - $(window).scrollTop(),
                        right: $(window).width() - $(this).offset().left - $(this).width(),
                    };
                    localStorage.quickReplyPosition = JSON.stringify(offset);

                    $postForm.css('right', offset.right).css('top', offset.top).css('left', 'auto');
                }
            });
            $postForm.find('th .handle').css('cursor', 'move');
        }

        $postForm.find('th .close-btn').click(function() {
            $origPostForm.find('textarea[name="body"]').attr('id', 'body');
            $postForm.remove();
            floating_link();
        });

        // Fix bug when table gets too big for form. Shouldn't exist, but crappy CSS etc.
        $postForm.show();
        $postForm.width($postForm.find('table').width());
        $postForm.hide();

        $(window).trigger('quick-reply');
        $(window).ready(function() {
            if (!in_index && settings.get('hide_at_top', true)) {
                $(window).scroll(function() {
                    if ($(this).width() <= 400)
                        return;
                    if ($(this).scrollTop() < $origPostForm.offset().top + $origPostForm.height() - 100)
                        $postForm.fadeOut(100);
                    else
                        $postForm.fadeIn(100);
                }).scroll();
            } else {
                $postForm.show();
            }

            $(window).on('stylesheet', function() {
                do_css();
                if ($('link#stylesheet').attr('href')) {
                    $('link#stylesheet')[0].onload = do_css;
                }
            });
        });
    };

    $(window).on('cite', function(e, id, with_link) {
        if ($(this).width() <= 400)
            return;
        var origin_id = id;
        // find the thread id for a cite if citereply call is done in index
        // pass in null if not found, it won't be evaluated in show_quick_reply
        show_quick_reply(origin_id);

        // captchaSetup();
        if (with_link) {
            $(document).ready(function() {
                if ($('#' + id).length) {
                    highlightReply(id);
                    $(document).scrollTop($('#' + id).offset().top);
                }

                // Honestly, I'm not sure why we need setTimeout() here, but it seems to work.
                // Same for the "tmp" variable stuff you see inside here:
                setTimeout(function() {
                    var tmp = $('#quick-reply textarea[name="body"]').val();
                    $('#quick-reply textarea[name="body"]').val('').focus().val(tmp);
                }, 1);
            });
        }
    });

    var floating_link = function() {
        if (!settings.get('floating_link', false))
            return;
        $('<a href="javascript:void(0)" class="quick-reply-btn">'+_('Quick Reply')+'</a>')
            .click(function() {
                show_quick_reply();
                $(this).remove();
            }).appendTo($('body'));

        $(window).on('quick-reply', function() {
            $('.quick-reply-btn').remove();
        });
    };

    if (settings.get('floating_link', false)) {
        $(window).ready(function() {
            if($('div.banner').length == 0)
                return;
            $('<style type="text/css">\
            a.quick-reply-btn {\
                position: fixed;\
                right: 0;\
                bottom: 0;\
                display: block;\
                padding: 5px 13px;\
                text-decoration: none;\
            }\
            </style>').appendTo($('head'));

            floating_link();

            if (settings.get('hide_at_top', true)) {
                $('.quick-reply-btn').hide();

                $(window).scroll(function() {
                    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);
                    else
                        $('.quick-reply-btn').fadeIn(100);
                }).scroll();
            }
        });
    }
})();

but I can't implement it again because I don't remember changes in other files like functions.php

perdedora commented 2 months ago

It's possible, but I really don't care and also spent too much time in this shit and the last commit of active-page I realized it's not in the perfect state which I thought it was.

But hey, if you wanna do it, be my guess. Just check if the current thread in the quick-reply is the same, if not change the input thread with the new value.

perdedora commented 2 months ago

Ok, I cared enough to try at least. Remove the last patch of removing the quick-reply.


        if($('#quick-reply').length != 0) { // line 138
            if ($('#quick-reply').data('thread-id') !== target_id) {
                $(`<input type='hidden' name='thread' value='${thread_id}'></input>`).appendTo($("#quick-reply"));
                $("#quick-reply .handle #thread-id-number").remove();
                const threadIdSpan = `<span id="thread-id-number">(${thread_id})</span>`;
                $("#quick-reply .handle").append(threadIdSpan);
                $("#quick-reply").attr("data-thread-id", thread_id);
            }
            return;
        }

        if (in_index) { // line 307
            $(`<input type='hidden' name='thread' value='${thread_id}'></input>`).appendTo($("#quick-reply"));
            const threadIdSpan = `<span id="thread-id-number">(${thread_id})</span>`;
            $("#quick-reply .handle").append(threadIdSpan);
            $("#quick-reply").attr("data-thread-id", thread_id);
            $("#quick-reply .form_submit").attr("value", button_reply);

            if (post_captcha == 'false') {
                $("#quick-reply .captcha").remove();
            }
        }
chanadmins commented 2 months ago

Ok, I cared enough to try at least. Remove the last patch of removing the quick-reply.

It works now, thanks ^^

chanadmins commented 2 months ago

Wait, I found a bug: Reply button disapears from quick reply when thread is opened CbunwGv

perdedora commented 2 months ago

What do you mean? How can I reproduce?

perdedora commented 2 months ago

Thread page: image Index page: image