MyAnimeList BBCode Toolbar

Advanced BBCode Editor for MyAnimeList.net

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MyAnimeList BBCode Toolbar
// @namespace    https://github.com/eskander
// @version      2022-05-26
// @description  Advanced BBCode Editor for MyAnimeList.net
// @author       eskander
// @license      MIT

// @include      https://myanimelist.net/forum/?topicid=*
// @include      https://myanimelist.net/forum/?action=message&msgid=*
// @include      https://myanimelist.net/forum/?action=message&topic_id=*
// @include      https://myanimelist.net/forum/?action=post*
// @include      https://myanimelist.net/forum/index.php?action=post&boardid=*
// @include      https://myanimelist.net/forum/index.php?topicid=*
// @include      https://myanimelist.net/panel.php?go=editmanga&id=*
// @include      https://myanimelist.net/panel.php?go=add&selected_series_id=*
// @include      https://myanimelist.net/panel.php?go=addmanga&selected_manga_id=*
// @include      https://myanimelist.net/panel.php?go=anime_series&do=add
// @include      https://myanimelist.net/panel.php?go=mangadb&do=add
// @include      https://myanimelist.net/mymessages.php?go=send*
// @include      https://myanimelist.net/mymessages.php?toname=*
// @include      https://myanimelist.net/people.php?id=*
// @include      https://myanimelist.net/people/*
// @include      https://myanimelist.net/ownlist/anime/*
// @include      https://myanimelist.net/ownlist/manga/*
// @include      https://myanimelist.net/editprofile.php*
// @include      https://myanimelist.net/myblog.php*
// @include      https://myanimelist.net/clubs.php?cid=*
// @include      https://myanimelist.net/editclub.php?cid=*
// @include      https://myanimelist.net/profile/*
// @include      https://myanimelist.net/modules.php?go=report&type=forummessage&id=*
// @include      https://myanimelist.net/comtocom.php?id1=*
// @include      https://myanimelist.net/comments.php?id=*
// @include      https://myanimelist.net/editlist.php?type=anime&id=*
// @include      https://myanimelist.net/myfriends.php?go=add&id=*
// @include      https://myanimelist.net/blog.php?eid=*

// @exclude      https://myanimelist.net/editprofile.php?go=stylepref&do=cssadv&id=*
// @exclude      https://myanimelist.net/profile/*/*

// @icon         https://cdn.myanimelist.net/images/favicon.ico
// @homepage     https://github.com/Eskander/myanimelist-bbcode-toolbar
// @supportURL   https://github.com/Eskander/myanimelist-bbcode-toolbar/issues/new
// @compatible   firefox
// @compatible   chrome
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

/**************************************************************************************************/
/* Disclaimer: MyAnimeList BBCode Toolbar is based on "BBCodes for MAL" script by Al_eXs          */
/*             and its continuation by Serhiyko.                                                  */
/*             Original source code: https://userscripts-mirror.org/scripts/show/142850 (mirror)  */
/*             Continuation:         https://greasyfork.org/en/scripts/5709-bbcodes-for-mal       */
/**************************************************************************************************/


// ----------------- Toolbar styling ----------------- //

(function () {
    // import font-awesome 
    var link = document.createElement('link');
    link.setAttribute('rel', 'stylesheet');
    link.setAttribute('type', 'text/css');
    link.setAttribute('href', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/fontawesome.min.css');
    document.head.appendChild(link);

    // Greasemonkey compatibility
    if (typeof GM_addStyle == "undefined") {
        function GM_addStyle(css) {
            var node = document.createElement("style");
            node.type = "text/css";
            node.appendChild(document.createTextNode(css));
            var heads = document.getElementsByTagName("head");
            if (heads.length > 0) {
                heads[0].appendChild(node);
            } else {
                // no head yet, stick it whereever
                document.documentElement.appendChild(node);
            }
        }
    }

    // toolbar style
    GM_addStyle(`
        #myBBcode {
            margin: 10px 0px 5px 0px;
            display: block;
            background-color: #2e51a2;
            width: 100%;
        }

        .bbcbtn {
            background-color: #2e51a2;
            border: none;
            color: #fff;
            width: 40px;
            height: 40px;
            font-family: 'FontAwesome';
            text-align: center;
            display: inline-block;
            cursor: pointer;
            outline:none;
        }

        .bbcbtn:hover {
            background-color: #e1e7f5;
            color: #000
        }

        .hidearrow {
            /* for Firefox */
            -moz-appearance: none;
            /* for Chrome */
            -webkit-appearance: none;
        }

        .divider {
            display: inline;
            border-left: 2px solid white;
        }

        .cpanel {
            display: none;
            color: #fff;
            text-align: center;
            padding: 10px;
        }

        .cfgbtn {
            float: right;
        }
    `);

    // forum quick reply overrides
    if (window.location.href.includes('myanimelist.net/forum/?topicid')) {
        GM_addStyle(`
            #myBBcode {
                background-color: #4f74c8 !important;
                min-width: 750px !important;
            }

            .bbcbtn {
                background-color: #4f74c8 !important;
            }

            .bbcbtn:hover {
                background-color: #e1e7f5 !important;
            }
        `);
    }
})();

var Interactive = GM_getValue('Interactive', true);

function interractivePopup(desc) {
    if (Interactive) {
        return prompt(desc, "");
    }
    else {
        return "";
    }
}

// ----------------- Functionality of each button ----------------- //

function addtag(snap, tag) {
    var textareaNumber = getXpathSnapNumber(snap);
    obj = document.getElementsByTagName("textarea")[textareaNumber];

    beforeText = obj.value.substring(0, obj.selectionStart);
    selectedText = obj.value.substring(obj.selectionStart, obj.selectionEnd);
    afterText = obj.value.substring(obj.selectionEnd, obj.value.length);
    newText = null;

    switch (tag) {

        case "bold":
            tagOpen = "[b]";
            tagClose = "[/b]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "strike":
            tagOpen = "[s]";
            tagClose = "[/s]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "italic":
            tagOpen = "[i]";
            tagClose = "[/i]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "underline":
            tagOpen = "[u]";
            tagClose = "[/u]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "code":
            tagOpen = "[code]";
            tagClose = "[/code]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "centre":
            tagOpen = "[center]";
            tagClose = "[/center]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "right":
            tagOpen = "[right]";
            tagClose = "[/right]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "spoiler":
            spoiler = interractivePopup("Enter spoiler name (or leave blank)");

            if (spoiler) {
                spoiler = '="' + spoiler + '"';
            }

            tagOpen = "[spoiler" + spoiler + "]";
            tagClose = "[/spoiler]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "url":
            urlOrDesc = interractivePopup("Enter URL or URL description");

            if (!urlOrDesc) {
                urlOrDesc = selectedText;
                selectedText = '';
            }

            if (urlOrDesc.indexOf("http://") == 0 || urlOrDesc.indexOf("https://") == 0) {
                tagOpen = "[url=" + urlOrDesc + "]";
                tagClose = "[/url]";
            } else {
                tagOpen = "[url=";
                tagClose = "]" + urlOrDesc + "[/url]";
            }

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "image":
            imgValue = document.getElementById("Image").value;
            imgURL = interractivePopup("Enter image URL");

            if (imgURL) {
                selectedText = imgURL;
            }

            tagOpen = "[img" + String(imgValue) + "]";
            tagClose = "[/img]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "size":
            txtSize = document.getElementById("Size");

            if (txtSize.value == "enter") {
                txtSizeName = interractivePopup("Enter the size (1 is minimum, 100 is default)");
            } else {
                txtSizeName = txtSize.value;
            }

            tagOpen = "[size=" + String(txtSizeName) + "]";
            tagClose = "[/size]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "youtube":
            yt = interractivePopup("Enter complete Youtube url");

            if (yt.includes("youtube.com")) {
                yt = yt.replace("https://", "http://");
                yt = yt.replace("http://www.youtube.com/watch?v=", "http://youtube.com/watch?v=");
                yt = yt.replace("http://youtube.com/watch?v=", "");
                yt = yt.substring(0, 11);
            }

            tagOpen = "[yt]";
            tagClose = "[/yt]";

            newText = beforeText + tagOpen + yt + tagClose + afterText;
            break;

        case "colour":
            colour = document.getElementById("Colour");

            if (colour.value == "enter") {
                colourName = interractivePopup("Enter the colour name or hex value (e.g. #abc123)");
            } else {
                colourName = colour.value;
            }

            tagOpen = "[color=" + String(colourName) + "]";
            tagClose = "[/color]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "quote":
            quote = interractivePopup("Enter quoted person name");

            if (quote) {
                quote = "=" + quote;
            }

            tagOpen = "[quote" + quote + "]";
            tagClose = "[/quote]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "list":
            tagOpen = "[list][*]";
            tagClose = "[/list]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "list=1":
            tagOpen = "[list=1][*]";
            tagClose = "[/list]";

            newText = beforeText + tagOpen + selectedText + tagClose + afterText;
            break;

        case "[*]":
            tagOpen = "[*]";
            tagClose = "";

            newText = beforeText + tagOpen + selectedText + afterText;
            break;
    }

    if (newText != null) {
        caretStart = obj.selectionStart;
        caretEnd = obj.selectionEnd;
        if (selectedText.length == 0) {
            caretEnd -= tagClose.length;
        }
        caretEnd += newText.length - obj.value.length;
        caretStart = caretEnd;
        obj.value = newText;
        obj.setSelectionRange(caretStart, caretEnd);
    }
    obj.focus();
}

// ----------------- Some preliminary magic ----------------- //

function xpath(query, object) {
    if (!object) var object = document;
    return document.evaluate(query, object, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
}

function getXpathSnap() {
    var path = xpath("//textarea[@class='textarea']");
    if (path.snapshotLength == 0 || path.snapshotItem(0).id == "add_anime_tags" || path.snapshotItem(0).id == "add_manga_tags") {
        path = xpath("//textarea[@class='inputtext']");
        if (path.snapshotItem(j).previousElementSibling == null) {
            return (path.snapshotLength > 0) ? path.snapshotItem(0) : false;
        }
    }
    for (var j = 0; j < path.snapshotLength; j++) {
        if (path.snapshotItem(j).previousElementSibling == null) {
            if (path.snapshotItem(j).id != "add_anime_tags" && path.snapshotItem(j).id != "add_manga_tags") {
                return path.snapshotItem(j);
            }
        } else {
            if (path.snapshotItem(j).previousElementSibling.id != 'myBBcode' && (path.snapshotItem(j).previousElementSibling.tagName != 'GRAMMARLY-GHOST' && (path.snapshotItem(j).previousElementSibling.previousElementSibling === null || path.snapshotItem(j).previousElementSibling.previousElementSibling.id != 'myBBcode'))) {
                return path.snapshotItem(j);
            }
        }
    }
    return false;
}

function getXpathSnapNumber(xpathToBeNumbered) {
    var path = xpath("//textarea");
    for (var i = 0; i < path.snapshotLength; i++) {
        if (path.snapshotItem(i) === xpathToBeNumbered) {
            return i;
        }
    }
}

setTimeout(function () {
    var allReplies = xpath("//a[@title='Reply to this comment'] | //a[contains(@class, 'ml8')][text() = 'Reply'] | //input[@id='postReply'][@value='Submit']");
    for (var i = 0; i < allReplies.snapshotLength; i++) {
        (function (ind) {
            allReplies.snapshotItem(ind).addEventListener("click", function () {

                var repeatCount0 = 0;
                var replyTimer0 = setInterval(function () {
                    var replied;
                    var replyButton = xpath("//input[@value='Submit Reply'] | //a[@class='quickEdit'][text() = 'Quick Edit']");
                    if (replyButton.snapshotLength > 0) {
                        xpathSnap = getXpathSnap();
                        createButtons();
                        replyButton.snapshotItem(0).addEventListener("click", function () {
                            replied = replyTimer();
                        }, false);  //Modern browsers
                    }

                    function replyTimer() {
                        var repeatCount = 0;
                        return setInterval(function () {
                            addCodeToEdits();
                            repeatCount += 1;
                            if (repeatCount >= 18) {
                                clearInterval(replied);
                            }
                        }, 500);
                    }

                    repeatCount0 += 1;
                    if (repeatCount0 >= 72) {
                        clearInterval(replyTimer0);
                    }
                }, 100);

            }, true);
        })(i);
    }

    addCodeToEdits();
}, 100);

function addCodeToEdits() {
    var allEdits = xpath("//a[@title='Edit Comment']");
    var toEdit;
    for (var i = 0; i < allEdits.snapshotLength; i++) {
        (function (ind) {
            allEdits.snapshotItem(ind).removeEventListener("click", function () {
                toEdit = editTimer();
            });
            allEdits.snapshotItem(ind).addEventListener("click", function () {
                toEdit = editTimer();
            }, true);
        })(i);
    }
    function editTimer() {
        var repeatCount = 0;
        return setInterval(function () {
            xpathSnap = getXpathSnap();
            createButtons();
            repeatCount += 1;
            if (repeatCount >= 12) {
                clearInterval(toEdit);
            }
        }, 400);
    }
}

while (xpathSnap = getXpathSnap()) {
    createButtons();
}

// ----------------- Generate toolbar ----------------- //

function createButtons() {
    if (xpathSnap) {

        var xpathSnapCur = xpathSnap;
        var div1 = document.createElement("div");
        div1.align = "Left";
        div1.id = "myBBcode";
        div1.innerHTML = " ";
        xpathSnap.parentNode.insertBefore(div1, xpathSnap);

        // set button content
        function setButton(name, glyph) {
            var post = document.createElement("button");
            post.type = "button";
            post.innerHTML = glyph;
            post.title = name[0].toUpperCase() + name.slice(1);
            post.setAttribute('class', 'fa bbcbtn');
            post.addEventListener('click', function () {
                addtag(xpathSnapCur, name);
            }, false);
            div1.appendChild(post);
        }

        function setSize(size) {
            var opt = document.createElement("option");
            opt.value = size;
            opt.appendChild(document.createTextNode(size + "%"));
            postSize.appendChild(opt);
        }

        function setColor(color) {
            var opt = document.createElement("option");
            opt.value = color;
            opt.appendChild(document.createTextNode(color));
            postColour.appendChild(opt);
        }

        function setImage(imgText, imgVal) {
            var opt = document.createElement("option");
            opt.value = imgVal;
            opt.appendChild(document.createTextNode(imgText));
            postImage.appendChild(opt);
        }

        function setDivider() {
            var opt = document.createElement("div");
            opt.setAttribute('class', 'divider');
            div1.appendChild(opt);
        }

        // ----------------- Toolbar layout ----------------- //

        setButton("bold", "&#xf032;");
        setButton("italic", "&#xf033;");
        setButton("strike", "&#xf0cc;");
        setButton("underline", "&#xf0cd;");

        // Size selector
        var postSize = document.createElement("select");
        postSize.id = "Size";
        postSize.title = "Size";
        postSize.setAttribute('class', 'fa bbcbtn hidearrow');

        // First size selector
        var opt = document.createElement("option");
        opt.value = "";
        opt.setAttribute('selected', '');
        opt.setAttribute('disabled', '');
        opt.setAttribute('hidden', '');
        opt.appendChild(document.createTextNode(""));
        postSize.appendChild(opt);

        // Predefined sizes
        setSize("20");
        setSize("50");
        setSize("100");
        setSize("300");
        setSize("600");

        // Custom size
        var opt = document.createElement("option");
        opt.value = "enter";
        opt.appendChild(document.createTextNode('Enter size'));
        postSize.appendChild(opt);

        postSize.addEventListener('change', function () {
            document.getElementById("Size").value = postSize.value;
            addtag(xpathSnapCur, 'size');
            postSize.value = 'Size';
            this.selectedIndex = 0;
        }, false);
        div1.appendChild(postSize);

        // Color selector
        var postColour = document.createElement("select");
        postColour.id = "Colour";
        postColour.title = "Color";
        postColour.setAttribute('class', 'fa bbcbtn hidearrow');

        // First color selector
        var opt = document.createElement("option");
        opt.value = "";
        opt.setAttribute('selected', '');
        opt.setAttribute('disabled', '');
        opt.setAttribute('hidden', '');
        opt.appendChild(document.createTextNode(""));
        postColour.appendChild(opt);

        // Predefined colors
        setColor("Grey");
        setColor("Blue");
        setColor("Red");
        setColor("Green");
        setColor("Yellow");
        setColor("Pink");
        setColor("Navy");
        setColor("White");
        setColor("Black");
        setColor("Orange");
        setColor("Purple");

        // Custom color
        var opt = document.createElement("option");
        opt.value = "enter";
        opt.appendChild(document.createTextNode('Enter colour'));
        postColour.appendChild(opt);

        postColour.addEventListener('change', function () {
            document.getElementById("Colour").value = postColour.value;
            addtag(xpathSnapCur, 'colour');
            postColour.value = 'Select';
            this.selectedIndex = 0;
        }, false);
        div1.appendChild(postColour);

        setDivider();

        setButton("centre", "&#xf037;");
        setButton("right", "&#xf038;");

        setDivider();

        setButton("url", "&#xf0c1;");
        setButton("spoiler", "&#xf06a;");

        // Image selector
        var postImage = document.createElement("select");
        postImage.id = "Image";
        postImage.title = "Image";
        postImage.setAttribute('class', 'fa bbcbtn hidearrow');

        // First image selector
        var opt = document.createElement("option");
        opt.value = "";
        opt.setAttribute('selected', '');
        opt.setAttribute('disabled', '');
        opt.setAttribute('hidden', '');
        opt.appendChild(document.createTextNode(""));
        postImage.appendChild(opt);

        // Predefined image alignments
        setImage("Image", "");
        setImage("Image right", " align=right");
        setImage("Image left", " align=left");

        postImage.addEventListener('change', function () {
            document.getElementById("Image").value = postImage.value;
            addtag(xpathSnapCur, 'image');
            postImage.value = 'Select';
            this.selectedIndex = 0;
        }, false);
        div1.appendChild(postImage);

        setButton("youtube", "&#xf03d;");
        setButton("code", "&#xf121;");
        setButton("quote", "&#xf10d;");

        setDivider();

        setButton("list", "&#xf0ca;");
        setButton("list=1", "&#xf0cb;");
        setButton("[*]", "&#xf0fe;");

        // ----------------- Settings panel ----------------- //

        // Create settings button
        var post = document.createElement("button");
        post.type = "button";
        post.innerHTML = "&#xf013;";
        post.title = "Show/Hide settings";
        post.setAttribute('class', 'fa bbcbtn cfgbtn');
        post.addEventListener('click', function () {
            if (document.getElementById("cpanel").style.display == "block") {
                for (i=0; i < document.getElementsByClassName("cpanel").length; i++){
                    document.getElementsByClassName("cpanel")[i].style.display = "none"
                  }
            } else {
                for (i=0; i < document.getElementsByClassName("cpanel").length; i++){
                    document.getElementsByClassName("cpanel")[i].style.display = "block"
                  }
            }
        }, false);
        div1.appendChild(post);

        // Popups checkbox logic
        var InteractiveBoxState = '';

        if (Interactive) {
            InteractiveBoxState = 'checked';
        } else {
            InteractiveBoxState = '';
        }

        var opt = document.createElement("div");
        opt.setAttribute('class', 'cpanel');
        opt.id = "cpanel";
        opt.innerHTML = '<input type="checkbox" id="IntBox" ' + InteractiveBoxState + '><label for="IntBox" title="Show dedicated pop-ups for certain buttons.">Enable pop-ups.</label>';
        div1.appendChild(opt);

        // Remember setting across sessions
        document.getElementById("IntBox").addEventListener('change', function () {
            if (document.getElementById("IntBox").checked == true) {
                Interactive = true;
                GM_setValue('Interactive', true);
            } else {
                Interactive = false;
                GM_setValue('Interactive', false);
            }
        }, false);

    }
}