AO3 Adjust Paragraph Margins

Increase or decrease the margins between the paragraphs in a work. Set on a per work or per author basis.

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         AO3 Adjust Paragraph Margins
// @description  Increase or decrease the margins between the paragraphs in a work. Set on a per work or per author basis.
// @author       Ifky_
// @namespace    https://greasyfork.org/en/scripts/542569
// @version      1.0.1
// @history      1.0.1 — Reset current setting when resetting everything
// @history      1.0.0 — Increase or decrease the margin between a work's paragraphs
// @match        https://archiveofourown.org/works/*
// @icon         https://archiveofourown.org/images/logo.png
// @license      GPL-3.0-only
// @grant        none
// ==/UserScript==
"use strict";
(function () {
    // Add styles to increase or decrease margin
    // If you want to use different values, you can either edit this code or override it with a site skin
    const styleTag = document.createElement("style");
    styleTag.textContent = `
    #workskin.a-p-m--i [role=article] .userstuff p,
    #workskin.a-p-m--i [role=article].userstuff p {
      margin-block: 3.5em;
    }

    #workskin.a-p-m--d [role=article] .userstuff p,
    #workskin.a-p-m--d [role=article].userstuff p {
      margin-block: .15em;
    }

    #a-p-m fieldset {
      display: block;
    }

    #a-p-m fieldset ul {
      display: flex;
      gap: 1em;
      flex-wrap: wrap;
    }

    #a-p-m fieldset label {
      padding-inline: .75em !important;
    }
  `;
    document.head.appendChild(styleTag);
    // Get necessary elements and data
    const workskin = document.querySelector("#workskin");
    const actions = document.querySelector("#main .work.actions");
    const workId = window.location.pathname.split("/")[2];
    const authorEl = document.querySelector("#workskin a[rel='author'][href*='/users/']");
    const author = authorEl?.textContent ?? null;
    // Get existing settings from local storage
    const getLocal = (key) => {
        const value = localStorage.getItem(key);
        return new Set(value ? value.split(',') : []);
    };
    const addLocal = (key, value) => {
        const local = getLocal(key);
        local.add(value);
        const string = Array.from(local).join(',');
        localStorage.setItem(key, string);
    };
    const removeLocal = (key, value) => {
        const local = getLocal(key);
        local.delete(value);
        const string = Array.from(local).join(',');
        localStorage.setItem(key, string);
    };
    // Get current settings (if they exist)
    const workIncrease = getLocal("a-p-m--w-i");
    const workDecrease = getLocal("a-p-m--w-d");
    const workStandard = getLocal("a-p-m--w-s");
    const authorIncrease = getLocal("a-p-m--a-i");
    const authorDecrease = getLocal("a-p-m--a-d");
    let marginType;
    (function (marginType) {
        marginType[marginType["STANDARD"] = 0] = "STANDARD";
        marginType[marginType["INCREASE"] = 1] = "INCREASE";
        marginType[marginType["DECREASE"] = 2] = "DECREASE";
        marginType[marginType["AUTHOR"] = 3] = "AUTHOR";
    })(marginType || (marginType = {}));
    const setMarginClass = (value) => {
        switch (value) {
            case marginType.STANDARD:
                workskin.classList.remove("a-p-m--i");
                workskin.classList.remove("a-p-m--d");
                break;
            case marginType.INCREASE:
                workskin.classList.add("a-p-m--i");
                workskin.classList.remove("a-p-m--d");
                break;
            case marginType.DECREASE:
                workskin.classList.remove("a-p-m--i");
                workskin.classList.add("a-p-m--d");
                break;
        }
    };
    const setMarginLocal = (key, value) => {
        if (key === "work") {
            switch (value) {
                case marginType.STANDARD:
                    removeLocal("a-p-m--w-i", workId);
                    removeLocal("a-p-m--w-d", workId);
                    addLocal("a-p-m--w-s", workId);
                    break;
                case marginType.INCREASE:
                    addLocal("a-p-m--w-i", workId);
                    removeLocal("a-p-m--w-d", workId);
                    removeLocal("a-p-m--w-s", workId);
                    break;
                case marginType.DECREASE:
                    removeLocal("a-p-m--w-i", workId);
                    addLocal("a-p-m--w-d", workId);
                    removeLocal("a-p-m--w-s", workId);
                    break;
                case marginType.AUTHOR:
                    removeLocal("a-p-m--w-i", workId);
                    removeLocal("a-p-m--w-d", workId);
                    removeLocal("a-p-m--w-s", workId);
                    break;
            }
        }
        else if (key === "author" && author !== null) {
            switch (value) {
                case marginType.STANDARD:
                    removeLocal("a-p-m--a-i", author);
                    removeLocal("a-p-m--a-d", author);
                    break;
                case marginType.INCREASE:
                    addLocal("a-p-m--a-i", author);
                    removeLocal("a-p-m--a-d", author);
                    break;
                case marginType.DECREASE:
                    removeLocal("a-p-m--a-i", author);
                    addLocal("a-p-m--a-d", author);
                    break;
            }
        }
    };
    // Set margin based on setting
    const wi = workIncrease.has(workId);
    const wd = workDecrease.has(workId);
    const ws = workStandard.has(workId);
    const wa = !(wi || wd || ws);
    const ai = authorIncrease.has(author);
    const ad = authorDecrease.has(author);
    const aSt = !(ai || ad);
    let workSetting = 0;
    let authorSetting = 0;
    if (wi) {
        workSetting = marginType.INCREASE;
    }
    else if (wd) {
        workSetting = marginType.DECREASE;
    }
    else if (wa) {
        workSetting = marginType.AUTHOR;
    }
    if (ai) {
        authorSetting = marginType.INCREASE;
    }
    else if (ad) {
        authorSetting = marginType.DECREASE;
    }
    if (workSetting !== marginType.AUTHOR) {
        setMarginClass(workSetting);
    }
    else {
        setMarginClass(authorSetting);
    }
    if (actions) {
        // The HTML for the action button and expandable secondary
        const marginHtml = `
    <li id="a-p-m" aria-haspopup="true">
      <a id="a-p-m--toggle" href="#" class="collapsed">Margins</a>
      <ul id="a-p-m--secondary" class="expandable secondary hidden">
        <form id="a-p-m--form" class="verbose">
          <!-- Work settings -->
          <fieldset>
            <legend>Work settings</legend>
              <ul>
                <li>
                  <input type="radio" value="0" ${ws ? 'checked="checked"' : ''} name="a-p-m--w" id="a-p-m--w-s">
                  <label for="a-p-m--w-s">Standard</label>
                </li>
                <li>
                  <input type="radio" value="1" ${wi ? 'checked="checked"' : ''} name="a-p-m--w" id="a-p-m--w-i">
                  <label for="a-p-m--w-i">Increase</label>
                </li>
                <li>
                  <input type="radio" value="2" ${wd ? 'checked="checked"' : ''} name="a-p-m--w" id="a-p-m--w-d">
                  <label for="a-p-m--w-d">Decrease</label>
                </li>
                <li>
                  <input type="radio" value="3" ${wa ? 'checked="checked"' : ''} name="a-p-m--w" id="a-p-m--w-a">
                  <label for="a-p-m--w-a">Author</label>
                </li>
              </ul>
            </fieldset>

            <!-- Author settings -->
            ${author ?
            `<fieldset>
                <legend>Author settings</legend>
                <ul>
                  <li>
                    <input type="radio" value="0" ${aSt ? 'checked="checked"' : ''} name="a-p-m--a" id="a-p-m--a-s">
                    <label for="a-p-m--a-s">Standard</label>
                  </li>
                  <li>
                    <input type="radio" value="1" ${ai ? 'checked="checked"' : ''} name="a-p-m--a" id="a-p-m--a-i">
                    <label for="a-p-m--a-i">Increase</label>
                  </li>
                  <li>
                    <input type="radio" value="2" ${ad ? 'checked="checked"' : ''} name="a-p-m--a" id="a-p-m--a-d">
                    <label for="a-p-m--a-d">Decrease</label>
                  </li>
                </ul>
              </fieldset>
            ` : ''}

            <!-- Actions -->
            <ul class="actions">
              <li><button type="submit">Apply</button></li>
              <li><button type="button" id="a-p-m--reset">Reset All</button></li>
              <li><button type="button" id="a-p-m--close">Close</button></li>
            </ul>
        </form>
      </ul>
    </li>
    `;
        // Append element to DOM (actions)
        actions.insertAdjacentHTML('afterbegin', marginHtml);
        // Apply new settings: update settings and store new data locally
        const submitApmForm = (e) => {
            e.preventDefault();
            const formData = new FormData(e.target);
            const workSetting = Number(formData.get("a-p-m--w"));
            const authorSetting = Number(formData.get("a-p-m--a"));
            if (workSetting !== marginType.AUTHOR) {
                setMarginClass(workSetting);
            }
            else {
                setMarginClass(authorSetting);
            }
            setMarginLocal("work", workSetting);
            setMarginLocal("author", authorSetting);
        };
        const secondary = document.querySelector("#a-p-m--secondary");
        const form = document.querySelector("#a-p-m--form");
        const reset = document.querySelector("#a-p-m--reset");
        reset.onclick = () => {
            const response = confirm('Resetting all will permanently delete all work settings and author settings. Are you sure?');
            if (response === true) {
                localStorage.removeItem("a-p-m--w-i");
                localStorage.removeItem("a-p-m--w-d");
                localStorage.removeItem("a-p-m--w-s");
                localStorage.removeItem("a-p-m--a-i");
                localStorage.removeItem("a-p-m--a-d");
                setMarginClass(marginType.STANDARD);
                form.reset();
            }
        };
        const toggleButton = document.querySelector("#a-p-m--toggle");
        toggleButton.onclick = () => {
            toggleButton.classList.toggle('collapsed');
            toggleButton.classList.toggle('expanded');
            secondary.classList.toggle('hidden');
        };
        const close = () => {
            toggleButton.classList.add('collapsed');
            toggleButton.classList.remove('expanded');
            secondary.classList.add('hidden');
        };
        const closeButton = document.querySelector("#a-p-m--close");
        closeButton.onclick = close;
        form.addEventListener('submit', (e) => {
            submitApmForm(e);
            close();
        });
    }
})();