AO3: Add Paragraph Breaks

Convert line breaks to paragraphs on Archive of our Own fics

// ==UserScript==
// @name AO3: Add Paragraph Breaks
// @description Convert line breaks to paragraphs on Archive of our Own fics
// @author the_wanlorn
// @version 1.0
// @license MIT
// @grant GM_getValue
// @grant GM_setValue
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @require https://openuserjs.org/src/libs/sizzle/GM_config.js
// @include https://archiveofourown.org/works/*
// @include https://archiveofourown.org/*/works/*
// @include http://archiveofourown.org/works/*
// @include http://archiveofourown.org/*/works/*
// @namespace https://greasyfork.org/users/844343
// ==/UserScript==

(function ($) {
  var frame = document.createElement('div');
  document.body.appendChild(frame);

  var gmc = new GM_configStruct({
    'id': 'ao3_add_paragraphs',
    'title': 'AO3: Add Paragraphs Settings',
    'fields': {
      'autoFormat': {
        'label': 'Auto-Format Fics',
        'type': 'checkbox',
        'default': false
      },
      'doubleBreaks': {
        'label': 'Convert single paragraph breaks to double paragraph breaks',
        'type': 'checkbox',
        'default': false
      }
    },
    'frame': frame,
    'events': {
      'open': addStyling,
      'save': function () { gmc.close(); },
      'close': initFormat,
      'reset': function () { gmc.save(); }
    }
  });

  initFormat();

  /**
   * Add UI elements and potentially format the page, if autoformatting is
   * turned on.
   */
  function initFormat() {
    var auto_format = gmc.get('autoFormat'),
      double_breaks = gmc.get('doubleBreaks');

    if (auto_format) {
      formatPage(double_breaks);
    }

    addLinks(auto_format);
  }

  /**
   * Add UI elements
   *
   * @param {bool} auto_format Whether the page is to be formatted automatically.
   */
  function addLinks(auto_format) {
    var $header_menu = $('ul.primary.navigation.actions'),
      $menu_link = $('.ao3_add_pars_settings'),
      $format_button = $('.ao3_add_pars');

    // If we don't already have a menu link there, add it.
    if ($menu_link.length === 0) {
      $menu_link = $('<li class="ao3_add_pars_settings dropdown"></li>').html('<a>Format Settings</a>');
      $header_menu.find('li.search').before($menu_link);
      $menu_link.on('click', function () {
        // Remove the open class since it's not actually a dropdown, we're just
        // cribbing the styling.
        $(this).toggleClass('open');
        // Open the settings dialogue
        gmc.open();
      });
    }

    // If we're not autoformatting the page, add a button to trigger it.
    if (!auto_format && $format_button.length === 0) {
      $format_button = $('<input class="button ao3_add_pars" type="button" value="Format Page"></input>');
      $format_button.css('marginLeft', '1em').css('marginRight', '1em');
      $header_menu.find('#search').prepend($format_button);
      $format_button.on('click', function() {
        var double_breaks = gmc.get('doubleBreaks');
        formatPage(double_breaks);
      });
    }
    // Otherwise, if we are autoformatting, get rid of the button.
    else if (auto_format && $format_button) {
      $format_button.remove();
    }
  }

  /**
   * Replace the current tags with the new tags.
   *
   * @param {bool} double Whether to add a double <br> tag to already-existing <p> tags.
   */
  function formatPage(double) {
    var $chapters = $('#chapters'),
      html_string = $chapters.html();

    if (double) {
      html_string = html_string.replaceAll('</p>', '<dblbrkspc></p>');
    }

    html_string = html_string.replaceAll('<br>', '</p><p>');

    if (double) {
      html_string = html_string.replaceAll('<dblbrkspc>', '<br /><br />');
    }

    $chapters.html(html_string);
  }

  /**
   * Add the required styling to the settings dialogue.
   *
   * @param {Object} document The document the given frame is in.
   * @param {Object} window   The window of the given frame.
   * @param {Object} frame    The frame to add styling to.
   */
  function addStyling(document, window, frame) {
    var frame_style = 'border: 1px solid #000; left: 50%; position: fixed; '
      + 'top: 50%; width: 310px; z-index: 9999; transform: translate(-50%, -50%);',
      $frame_obj = $(frame);

    $frame_obj.attr('style', frame_style);
    $frame_obj.find('#ao3_add_paragraphs_closeBtn').hide();
    $frame_obj.find('.field_label').css({"font-size": "1em", "font-weight": "400"});
    $frame_obj.find('.config_header').css({"font-size": "2em", "paddingBottom": "1em"});
    $frame_obj.find('.config_var').css({"margin": "0 0 10px 40px", "position": "relative"});
    $frame_obj.find('input[type="checkbox"]').css({"position": "absolute", "left": "-25px", "top": "0"});
  }
})(jQuery);