MyAnimeList BBCode Toolbar

Advanced BBCode Editor for MyAnimeList.net

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);

    }
}