AO3 Floating Comment Box

Floating comment box for AO3

As of 31.01.2020. See ბოლო ვერსია.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        AO3 Floating Comment Box
// @description Floating comment box for AO3
// @include     *://archiveofourown.org/*works/*
// @namespace   https://greasyfork.org/en/scripts/395902-ao3-floating-comment-box
// @version     0.9
// @run-at      document-end
// @grant       GM.getValue
// @grant       GM.setValue
// @grant       GM.deleteValue
// ==/UserScript==


    'use strict';

    const primary = "#0275d8"
    const success = "#5cb85c"
    const danger = "#d9534f"

    let curURL = document.URL
    if(curURL.includes("#")){
        curURL = document.URL.slice(0,document.URL.indexOf("#"))
    }
    let newURL = curURL

   const addStyles = () => {
    const styles = document.createElement("style")
    styles.innerHTML = fullStyles() + "\n" + addMediaStyles()
    return styles
}

const fullStyles = () => {
    let full = ""
    for(let [key, value] of Object.entries(allStyles)){
        let newStyle = key + " {"
        for(let [key2, val2] of Object.entries(value)){
            newStyle += "\n" + key2 + ": " + val2 + ";"
        }
        newStyle += "\n}\n"
        full += newStyle
    }
    return full
}

const addMediaStyles = () => {
    let full = ""
    for(let [key, value] of Object.entries(mediaStyles)){
        let newStyle = key + "{"
        for(let [key2, val2] of Object.entries(value)){
            newStyle += "\n" + key2 + "{"
            for (let [key3, val3] of Object.entries(val2)){
                newStyle += "\n" + key3 + ": " + val3 + ";"
            }
            newStyle +="\n}\n"
        }
        newStyle += "\n}\n"
        full += newStyle
    }
    return full
}

const mediaStyles = {
    "@media (min-width: 1375px)": {
        ".float-div": {
            "width": "80%",
            "max-width": "80%",
            "left": "10%"
        },
        ".float-cmt-btn": {
            "font-size": "1em"
        },
        "#openCmtBtn": {
            "font-size": "1.15em",
            "padding": "2px 4px"
        }
    },
    "@media (min-width: 1575px)": {
        ".float-div": {
            "width": "70%",
            "max-width": "70%",
            "left": "15%"
        },
        ".float-cmt-btn": {
            "font-size": "1em"
        },
        "#openCmtBtn": {
            "font-size": "1.3em",
            "padding": "4px 8px"
        }
    },
    "@media (min-width: 1850px)": {
        ".float-div": {
            "width": "60%",
            "max-width": "60%",
            "left": "20%"
        },
        ".float-cmt-btn": {
            "font-size": "1.1em"
        },
        "#openCmtBtn": {
            "font-size": "1.5em",
            "padding": "5px 10px"
        }
    }
}

const allStyles = {
    ".float-div": {
        "display": "none",
        "position": "fixed",
        "z-index": "1",
        "bottom": ".5%",
        "width": "98%",
        "height": "30%",
        "background-color": "#ddd",
        "border-style": "double",
        "border-color": "grey",
        "padding": "5px",
        "resize": "both",
        "overflow": "auto",
        "border-radius": "25px",
        "border-width": "5px"
    },
    ".btn-div": {
        "display": "flex",
        "justify-content": "space-around",
        "top": "0px",
        "width": "100%",
        "max-width": "100%",
        "height": "15%"
    },
    ".char-count": {
        "font-size": ".8em"
    },
    ".float-box": {
        "min-height": "70%",
        "max-width": "98%",
        "background-color": "white"
    },
    ".float-cmt-btn": {
        "border": "none",
        "text-align": "center",
        "text-decoration": "none",
        "display": "inline-block",
        "font-size": ".8em",
        "padding": ".2% 3%",
        "top": "10%",
        "bottom": "10%",
        "height": "70%"
    },
    "#openCmtBtn": {
        "position": "fixed",
        "z-index": "1",
        "top": "0px",
        "left": "0px",
        "font-size": ".9em",
        "padding": "1px 2px",
        "border": "none",
        "text-align": "center",
        "text-decoration": "none",
        "display": "inline-block",
        "background": primary
    },
    "#addCmtBtn": {
        "background": primary
    },
    "#delCmtBtn": {
        "background": danger
    },
    "#insCmtBtn": {
        "background": primary
    },
    ".font-select": {
        "float": "right",
        "top": "10%",
        "bottom": "10%",
        "width": "10%",
        "height": "80%"

    },
    ".btn-font": {
        "color": "white"
    }

}

const createBox = () => {
    const textBox = document.createElement("textarea")
    textBox.className = "float-box"
    textBox.addEventListener("keyup", async () => {
        await GM.setValue(newURL, textBox.value)
        const addBtn = document.querySelector("#addCmtBtn")
        const charCount = document.querySelector(".char-count")
        const newCount = 10000 - textBox.value.length
        charCount.textContent = `Characters left: ${newCount}`
        addBtn.style.background = primary
        addBtn.textContent = "Add to Comment Box"
    })

    return textBox
}

const createChangeFontSize = () => {
    const selectMenu = document.createElement("select")
    selectMenu.className = "font-select"
    const optNums = [".5em",".7em", ".85em", "1em", "1.25em", "1.5em"]
    for(let num of optNums){
        const opt = document.createElement("option")
        opt.value = num
        opt.className = "font-option"
        opt.style.fontSize = num
        opt.textContent = "Font size"
        selectMenu.appendChild(opt)
    }
    selectMenu.addEventListener("click", () => {
        const textBox = document.querySelector(".float-box")
        textBox.style.fontSize = selectMenu.value
    })
    return selectMenu
}

const charCount = () => {
    const newDiv = document.createElement("div")
    newDiv.className = "char-count"
    newDiv.textContent = "Characters left: 10000"
    return newDiv
}


const createButton = () => {
    const newButton = document.createElement("button")
    newButton.className = "btn-font"
    newButton.id = "openCmtBtn"
    newButton.textContent = "O"
    newButton.addEventListener("click", () => {
        const div = document.querySelector(".float-div")
        if(div.style.display === "block"){
            div.style.display = "none"
            newButton.textContent = "O"
            newButton.style.background = primary
        } else {
            div.style.display = "block"
            newButton.textContent = "X"
            newButton.style.background = danger
            const textBox = document.querySelector(".float-box")
            textBox.scrollTop = textBox.scrollHeight
        }

    })
    return newButton
}

const createMainDiv = () => {
    const newDiv = document.createElement("div")
    newDiv.className = "float-div"
    const btnDiv = document.createElement("div")
    btnDiv.className = "btn-div"
    btnDiv.appendChild(insertButton())
    btnDiv.appendChild(addButton())
    btnDiv.appendChild(createDelete())
    btnDiv.appendChild(chapterRadio())
    btnDiv.appendChild(createChangeFontSize())
    newDiv.appendChild(btnDiv)
    newDiv.appendChild(createBox())
    newDiv.appendChild(charCount())
    return newDiv
}

const createDelete = () => {
    const newButton = document.createElement("button")
    newButton.textContent = "Delete"
    newButton.className = "float-cmt-btn btn-font"
    newButton.id = "delCmtBtn"
    newButton.addEventListener("click", async () => {
        if(confirm("Are you sure you want to delete your comment?")){
            if((await GM.getValue(newURL, "noCmtHere")) !== "noCmtHere"){
                await GM.deleteValue(newURL)
                document.querySelector(".float-box").value = ""
                document.querySelector("textarea[id^='comment_content_for']").value = ""
            }
        }
    })
    return newButton
}

const chapterRadio = () => {
    const radioDiv = document.createElement("div")
    radioDiv.className = "radio-div"
    const radioOne = document.createElement("input")
    const radioTwo = document.createElement("input")
    radioOne.type = "radio"
    radioTwo.type = "radio"
    radioOne.name = "chapters"
    radioTwo.name = "chapters"
    radioOne.className = "chapter-toggle"
    radioTwo.className = "chapter-toggle"
    radioOne.id = "entireCmt"
    radioTwo.id = "chapterCmt"
    const labelOne = document.createElement("label")
    const labelTwo = document.createElement("label")
    labelOne.setAttribute("for", "entireCmt")
    labelTwo.setAttribute("for","chapterCmt")
    labelOne.textContent = "Full Work"
    labelTwo.textContent = "By Chapter"

    if(curURL.includes("chapters")){
        radioOne.checked = false
        radioTwo.checked = true
    } else {
        radioDiv.style.display = "none"
        radioOne.disabled = true
        radioTwo.disabled = true
    }

    radioOne.addEventListener("click", () => {
            if(newURL.includes("chapters")){
                newURL = curURL.slice(0,curURL.indexOf("/chapters"))
                addStoredText()
            }
    })
    radioTwo.addEventListener("click", () => {
        if(!newURL.includes("chapters")){
            newURL = curURL
            addStoredText()
        }

    })
    radioDiv.appendChild(radioOne)
    radioDiv.appendChild(labelOne)
    radioDiv.appendChild(radioTwo)
    radioDiv.appendChild(labelTwo)
    return radioDiv
}

const addButton = () => {
    const newButton = document.createElement("button")
    newButton.textContent = "Add to Comment Box"
    newButton.className = "float-cmt-btn btn-font"
    newButton.id = "addCmtBtn"
    const realCmtBox = document.querySelector("textarea[id^='comment_content_for']")
    newButton.addEventListener("click", async () => {
        realCmtBox.value = document.querySelector(".float-box").value
        newButton.style.background = success
        newButton.textContent = "Added to Comment Box"
    })
    return newButton
}

const insertButton = () => {
    const newButton = document.createElement("button")
    newButton.textContent = "Insert Selection"
    newButton.className = "float-cmt-btn btn-font"
    newButton.id = "insCmtBtn"
    newButton.addEventListener("click", async () => {
        const selection = `<i>${window.getSelection().toString().trim()}</i>`
        const textBox = document.querySelector(".float-box")
        const newText = `${textBox.value}${selection}\n`
        textBox.value = newText
        await GM.setValue(newURL, newText)
    })
    return newButton
}

const addStoredText = async () => {
    const textBox = document.querySelector(".float-box")
    if(curURL.includes("full")){
        newURL = curURL.slice(0, curURL.indexOf("?"))
    }
    const storedText = await GM.getValue(newURL,"")
    textBox.value = storedText
}


const init = () => {
    const body = document.body
    body.appendChild(createButton())
    body.appendChild(addStyles())
    body.appendChild(createMainDiv())
    addStoredText()
}

init()