FR Posting Form Enhancer

Makes the posting page easier to use.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name           FR Posting Form Enhancer
// @namespace      http://cynwoody.googlepages.com/fr_posting_form_enhancer.html
// @description    Makes the posting page easier to use.
// @date           2015-11-04
// @include        http://*.freerepublic.com/perl/post*
// @include        http://freerepublic.com/perl/post*
// @version 0.0.1.20151117015424
// ==/UserScript==

const INPUT_FRACTION = 0.5; // of window height to give to the main input box

function $(id) {return document.getElementById(id);}

function $x(xpath, contextNode, resultType)
{
    contextNode = contextNode || document.body;
    resultType = resultType || XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE;
    return document.evaluate(xpath, contextNode, null, resultType, null);
}

function $xFirst(xpath, contextNode)
{
    var xpr = $x(xpath, contextNode, XPathResult.FIRST_ORDERED_NODE_TYPE);
    return xpr.singleNodeValue;
}

// Removes those extra blank lines that seem to crop up at the end
// of certain posts.

function removeBlankLines(doc)
{
    var list = $x('.//br[@clear="all"]', doc);
    for (var x=0, limit=list.snapshotLength; x<limit; ++x) {
        var br = list.snapshotItem(x);
        br.parentNode.removeChild(br);
    }
}

// Adds a button to the HTML toolbar. Parameters are the name on the
// button, the button's handler function, the title text to display
// when the mouse hovers over the button, and the control key to
// trigger the button's handler via the keyboard.

function addButton(name, handler, title, key)
{
    var b = document.createElement('input');
    b.type = 'button';
    b.value = name;
    if (title.slice(-1) != '.')
        title += ' the selection.';
    var keyCode;
    if (key) {
        keyCode = key.charCodeAt(0);
        if (key.match(/^enter$/i))
            keyCode = 13;
        else if (key == '<')
            keyCode = ','.charCodeAt(0);
        var keyName = 'Ctrl-';
        if (key.match(/^[A-Z<]/)) {
            keyName += 'Shift-';
            keyCode = -keyCode;
        }
        if (key.match(/^.\+/)) {
        	key = key[0];
        	keyName += 'Alt-';
        	keyCode |= 1024;
        }
        keyName += key;
        title += '[' + keyName + ']';
    }
    b.title = title;
    b.style.cssText = 'padding-left:0px;padding-right:0px';
    if (typeof handler == 'string') {
        if (handler.charAt(0) == '<')
            handler = wrap(addTag, handler);
        else
            handler = wrap(enTag, handler);
    }
    addKeyHandler(key, keyCode, handler);
    b.addEventListener('click', handler, false);
    buttons.appendChild(b);
    return b;
}

// Adds a keyboard shortcut to the key handler table.

function addKeyHandler(key, keyCode, handler)
{
    if (keyCode) {
        if (keyHandlers[keyCode])
            alert('Duplicate shortcut key: ' + key);
        keyHandlers[keyCode] = handler;
    }
}

// Returns a closure that invokes the handler with the supplied arg.

function wrap(handler, arg)
{
    return function() {handler(arg)};
}

// Adds an extra space between buttons in the HTML toolbar.

function addSpacer()
{
    var spacer = document.createElement('span');
    spacer.innerHTML = '&nbsp';
    buttons.appendChild(spacer);
}

// Generates HTML to quote from a post. Italicizes the quote and adds
// a <p> tag to leave a line before the reply begins.

function quote()
{
    var end = enTag('i');
    var ss = replyBox.selectionStart;
    var se = replyBox.selectionEnd;
    setSelection(end);
    addOnOwnLine('<p>');
    if (ss == se)
        setSelection(ss);
}

// Surrounds the selection with a pair of <blockquote> tags.

function blockQuote()
{
    enclose('<blockquote>\n', '\n</blockquote>');
}

// Link-enables the selection, using a URL supplied via a prompt.

function link()
{
    var url = window.prompt('Enter link URL:', 'http://');
    if (!url)
        return;
    enclose('<a href="' + url + '">', '</a>');
}

// Adds an image, using a URL supplied via a prompt.

function image()
{
    var url = window.prompt('Enter image URL:', 'http://');
    if (!url)
        return;
    addTag('<img border=0 src="' + url + '">');
}

// Adds a table with a single cell (which contains the selection, if any).

function makeTable()
{
    enclose('<table><tbody align=center>\n<tr>\n<td>',
            '</td>\n</tr>\n</tbody></table>');
}

// Adds a table row.

function makeRow()
{
    enclose('<tr>\n<td>', '</td>\n</tr>');
}
// Turns the selection into an ordered or unordered list, depending
// on the tagName. Changes any leading asterisks in the selection into
// <li> tags.

function makeList(tagName)
{
    startTag = '<' + tagName + '>\n';
    endTag = '\n</' + tagName + '>';
    var s = replyBox.selectionStart;
    var e = replyBox.selectionEnd;
    if (e <= s) {
        enclose(startTag, endTag);
        return;
    }
    var t = replyBox.value;
    var selection = t.slice(s, e);
    selection = selection.replace(/^\s*\*/gm, '<li>');
    setText(t.slice(0, s) + selection + t.slice(e));
    setSelection(s, s + selection.length);
    enclose(startTag, endTag);
}

// Makes opening and closing tags for the supplied tag name and encloses
// the selection between them.

function enTag(tagName)
{
    var startTag = '<' + tagName + '>';
    var endTag = '</' + tagName + '>';
    return enclose(startTag, endTag);
}

// Encloses the selection between the two supplied tags. If there is
// no selection, the cursor is left positioned to add content.

function enclose(startTag, endTag)
{
    var s = replyBox.selectionStart;
    var e = replyBox.selectionEnd;
    var t = replyBox.value;
    var selection = t.slice(s, e);
    var replacement;
    replacement = startTag + selection + endTag;
    setText(t.slice(0, s) + replacement + t.slice(e));
    replyBox.focus();
    if (selection.length > 0)
        setSelection(s, s + replacement.length);
    else
        setSelection(s + startTag.length);
    return s + replacement.length;
}

// Adds a tag at the cursor and leaves the cursor after it.

function addTag(tag)
{
    var s = replyBox.selectionStart;
    var p = replyBox.selectionEnd;
    var t = replyBox.value;
    replyBox.focus();
    setText(t.slice(0, s) + tag + t.slice(p));
    setSelection(p + tag.length);
}

// Adds the supplied tag on a line by itself in the reply box.

function addOnOwnLine(tag)
{
    var p = replyBox.selectionEnd;
    var t = replyBox.value;
    if (t.length > 0 && p > 0 && t.charAt(p-1) != "\n")
        tag = "\n" + tag;
    if (p >= t.length || t.charAt(p) != '\n')
        tag += "\n";
    addTag(tag);
    if (t.charAt(p) == "\n") {
        ++replyBox.selectionStart;
    }
}

// Sets which text in the reply box is selected. Begins with the character
// indexed by start and ends with the character at end-1. If start == end,
// there is no selection.

function setSelection(start, end)
{
    end = end || start;
    replyBox.selectionStart = start;
    replyBox.selectionEnd = end;
}

// Replaces the current contents of the reply box with the supplied
// new content, restores the reply box's scroll position, and
// unchecks the "I've previewed" checkbox.

function setText(newText)
{
    var scrollTop = replyBox.scrollTop;
    replyBox.value = newText;
    replyBox.scrollTop = scrollTop;
    elements.namedItem('safety').checked = false;
}

// Monitors keystrokes in the reply box and dispatches the proper
// handler if one has been defined.

function onKeyPress(event)
{
    if (event.ctrlKey) {
        var key = event.charCode || event.keyCode;
        if (event.shiftKey)
            key = -key;
        if (event.altKey)
            key += 1024;
        var handler = keyHandlers[key];
        if (handler) {
            handler(event);
            event.preventDefault();
        }
    }
}

// Adds a Ctrl-Alt keyboard shortcut to go to the Spell, Preview, or
// Post button.

function addShortcut(name, key)
{
    var button = document.forms[0].elements.namedItem(name);
    if (!button)
        return;
    button.title = '[Ctrl-Alt-' + key + ']';
    var keyCode = key.charCodeAt(0);
    if (key == 'enter')
        keyCode = 13;
    keyCode += 1024;
    addKeyHandler(key, keyCode, wrap(press, button));
}

// Handles a keyboard shortcut to the Spell, Preview, or Post buttons.

function press(button)
{
    button.click();
}

// Returns the character represented by an entity of the form &#n+;.

function entityToChar(entity, number) {
    return String.fromCharCode(number);
}

// Scans a string and substitutes character values for all HTML entities
// of the form &#n+;

function deEntitize(s) {
    return s.replace(/&#(\d+);/g, entityToChar);
}

// Substitutes an HTML entity for the passed character.

function entitize(char) {
    return "&#" + char.charCodeAt() + ';';
}

// On form submission, replaces replaces any non-7-bit ASCII characters with
// the corresponding HTML entity.

function onSubmit() {
    var reply = document.forms['replyForm'].reply;
    reply.value = reply.value.replace(/[\u0080-\uffff]/g, entitize);
}

// *****************************************************************************

// Use the full width of the browser window.

var keyHandlers = {};

$xFirst('//table[@border=0]').width = '100%';

removeBlankLines(document.body);

// Lighten up the borders on the tables having them.

var tables = $x('//table[@border=1]');
for (var x=0; x<tables.snapshotLength; ++x) {
    var table = tables.snapshotItem(x);
    table.cellPadding = '3';
    table.cellSpacing = '0';
    table.style.borderCollapse = 'collapse';
}

// Replace the To: input TEXTAREA with an INPUT field. Wastes less vertical
// room. Make it extend clear across the window.

var elements = document.forms[0].elements;
var nameField = elements.namedItem('name');
var newNameField = document.createElement('input');
newNameField.name = nameField.name;
newNameField.value = nameField.value;
newNameField.style.width = '100%';
newNameField.style.cssText = 'font-size:inherit;' +
                'font-family:DejaVu Sans Mono, monospaced;';
nameField.parentNode.replaceChild(newNameField, nameField);

// Give the reply box more vertical space and make it use the full width.

var replyBox = elements.namedItem('reply');
replyBox.style.cssText = 'width:100%;height:' +
               (window.innerHeight*INPUT_FRACTION) + 'px;' +
                'font-size:inherit;' +
                'font-family:DejaVu Sans Mono, monospaced;';

// Let the Tagline field extend clear across.

elements.namedItem('sig').style.width = '100%';

// Add an HTML toolbar

var buttons = document.createElement('div');
buttons.style.cssFloat = 'left';
replyBox.parentNode.insertBefore(buttons, replyBox);
addButton('Quote', quote, 'Quotes', 'q');
addButton('BlockQuote', blockQuote, 'Block-quotes', 'Q');
addSpacer();
addButton('B', 'b', 'Bolds', 'b');
addButton('I', 'i', 'Italicizes', 'i').style.paddingLeft = '1px';
addButton('U', 'u', 'Underlines', 'u');
addButton('S', 's', 'Strikes out', 's');
addButton('Q', 'q', 'Curly-quotes', 'q+');
addButton('Small', 'small', 'Sets the selection in smaller font.', 'S');
addButton('Big', 'big', 'Sets the selection in larger font.', 'B');
addButton('<', wrap(addTag, '&lt;'), 'Adds a < as text.', '<');
addSpacer();
addButton('Font', 'font', 'Encloses the selection in <font> tags', 'F');
addButton('Pre', 'pre', 'Encloses the selection in preformat tags.', 'P');
addSpacer();
addButton('UL', wrap(makeList, 'ul'),
                           'Makes the selection a bulleted list.', 'U');
addButton('OL', wrap(makeList, 'ol'),
                           'Makes the selection a numbered list.', 'O');
addButton('HR', wrap(addOnOwnLine, '<hr width=97%>'),
                           'Adds a horizontal rule at the cursor.', 'h');
addSpacer();
addButton('Table', makeTable, 'Adds a single-cell table at the cursor.', 'T');
addButton('TR', makeRow, 'Adds a table row at the cursor.', 'R');
addButton('TH', 'th', 'Adds a table header cell at the cursor.', 'H');
addButton('TD', 'td', 'Adds a table cell at the cursor.', 'C');
addSpacer();
addButton('Image', image, 'Adds an image tag at the cursor.', 'I');
addButton('Link', link, 'Link-enables', 'L');
addSpacer();
addButton('BR', wrap(addOnOwnLine, '<br>'),
                      'Adds a <br> tag at the cursor.', 'Enter');
addButton('P', wrap(addOnOwnLine, '<p>'),
                      'Adds a <p> tag at the cursor.', 'enter');

// Finish adding keyboard shortcuts and install the reply box's
// keyboard listener.

addShortcut('spell', 's');
addShortcut('preview', 'p');
addShortcut('post', 'enter');
replyBox.addEventListener('keypress', onKeyPress, true);

// Add a documentation link over to the right of the HTML toolbar

var help = document.createElement('div');
help.style.cssFloat = 'right';
help.innerHTML = '<a ' +
     'href="http://cynwoody.googlepages.com/fr_posting_form_enhancer.html" ' +
     'target=help>Help</a>';
replyBox.parentNode.insertBefore(help, replyBox);

// Compensate for strange effects seen when using the buttons to insert
// HTML before the reply box has first received the focus.

if (replyBox.value == '') {
    replyBox.value = '?';
    replyBox.value = '';
}

// Decode any HTML entities in the reply box, so that the user sees normal
// graphics, but schedule the reply box to be re-encoded into entities upon
// resubmission (see the onSubmit function).
replyBox.value = deEntitize(replyBox.value);
document.forms['replyForm'].onsubmit = onSubmit;
replyBox.focus();