FlightGear wiki:Instant-Refs

From FlightGear wiki
Revision as of 21:50, 13 June 2014 by Bigstones (talk | contribs) (0.10: escaping pipes and equals, made a workaround for code quoting)
Jump to navigation Jump to search
Note  FlightGear development is not centrally coordinated in any way - at the very best, FlightGear development is "self-coordinated" - i.e. contributors discuss ideas and make proposals to contribute in a certain fashion and then team up to implement certain features and building blocks temporarily.

Obviously, ideas and feature requests made by end-users are also appreciated. But at the end of the day, people who do the actual work have obviously more say about future development than those who merely make suggestions. And contributors tend to prioritize work on items that are prioritized/suggested by other contributors, especially those offering to get involved and help in some way, and of those having some kind of track record in the form of past contributions.

But given the lack of development manpower, many good ideas tend to have a fairly long shelf life unfortunately. So that good ideas may be forgotten over time.

This is also why it is important for other developers/contributors to know who came up with a certain idea and who originally supported/supports it, possibly months (or even years) after a discussion took place - which is why good ideas should not just be preserved, but accompanied by corresponding quotes, linking back to the original discussion, so that potential contributors can make up their own minds to determine if/how they want to get involved in some effort or not. The script that you can find below is intended to help with this - it allows people to easily copy&paste important discussions over to the wiki, without having to rewrite any text, and without having to put together proper cquotes manually. In fact, it's a matter of just a few seconds.


or to any forum message
  • copy/mark some relevant portion of text
  • and directly get a full cquote paragraph that you can add to the wiki here
// ==UserScript==
// @name       instant-cquotes
// @description automatically create proper wikimedia cquotes for sourceforge ml and forum text selections
// @namespace  http://wiki.flightgear.org/
// @version    0.10
// @icon       http://wiki.flightgear.org/skins/common/images/icons-fg-135.png
// @match      http://sourceforge.net/p/flightgear/mailman/* 
// @match      http://forum.flightgear.org/*
// @copyright  2013+, FlightGear.org (bigstones, Philosopher & Hooray)
// ==/UserScript==
 
var debug = 0; 
 
/* content:
 *    - 'selection' supports only getSelectedText, will support getSelectedHtml
 *    - 'transform' takes a "tranform operator", or an ordered array of operators
 * author, title, date, url:
 *    - 'xpath' takes the path to the field, relative to the html parent of the selected text
 *    - 'transform' takes a "transform operator", or an ordered array of operators
*/
var CONFIG = {
    "sourceforge.net": {
        content: {
            selection: getSelectedText,
            transform: newline2br()
        }, 
        author: {
            xpath: "../../../tr/td/div/small/text()",
            transform: extract(/^From: (.*) <.*@.*>/)
        },
        title: {
            xpath: "../../../tr/td/div/div/b/a/text()"
        },
        date: {
            xpath: "../../../tr/td/div/small/text()",
            transform: extract(/ - (.*-.*-.*) /)
        },
        url: {
            xpath: "../../../tr/td/div/div/b/a/@href",
            transform: prepend("http://sourceforge.net")
        }
    },
    "forum.flightgear.org": {
        content: {
            selection: getSelectedHtml,
            transform: [removeComments(),
                        a2wikilink(),
                        img2link(),
                        phpBB_fontstyle2wikistyle(),
                        phpBB_code2syntaxhighlight(), // FIXME: could be much better, see below
                        phpBB_quote2cquote(),
                        escapePipeAndEquals()]
        },
        author: {
            xpath: "../p/strong/a/text()"
        },
        title: {
            xpath: "../h3/a/text()"
        },
        date: {
            xpath: "../p/text()[2]",
            transform: extract(/ » (.*),/)
        },
        url: {
            xpath: "../p/a/@href",
            transform: [extract(/\.(.*)/),
                        prepend("http://forum.flightgear.org")]
        }
    }    
};
 
var OUTPUT = {
    // shows a message box 
    msgbox: function(msg) {
        if (window.prompt ("Copy to clipboard: Ctrl+C, Enter", msg) !== null)
            window.getSelection().removeAllRanges(); // deselect all text
    }
};
 
//////////////////////
// TRANSFORM OPERATORS
 
// prepend 'prefix'
function prepend(prefix) {
    return function(text) {
        return prefix+text;
    };
}
 
// extract the first capture group in the regex (i.e. '(xxx)') 
function extract(regex) {
    return function(text) {
        return text.match(regex)[1];
    };
}
 
// replaces newlines with "unescaped" <br/>'s
function newline2br() {
    return function(text) {
        return text.replace(/\n/g,"<br/>\n");
    };
}
 
// remove html comments (e.g. '<!-- this is a comment -->')
function removeComments() {
    return function(html) {
        return html.replace(/<!--.*?-->/g,"");
    };
}
 
// converts html <a>...</a>'s to wiki links, internal if possible
function a2wikilink() {
    return function(html) {
        // first tries internal links
        // -- WARNING: if it's a link to a wiki file (wiki.fg.org/File:xxx), it will become a shown image
        html = html.replace(/<a.*?href="https?:\/\/wiki.flightgear.org\/(.*?)".*?>(.*?)<\/a>/g, "[[$1|$2]]");
 
        // then the others
        return html.replace(/<a.*?href="(.*?)".*?>(.*?)<\/a>/g, "[$1 $2]");
    };
}
 
// converts embedded html images to external links
function img2link() {
    return function(html) {
        return html.replace(/<img.*?src="(.*?)".*?>/g, "(see the [$1 linked image])");
    };
}
 
// converts phpBB way of doing font style to the wiki way
function phpBB_fontstyle2wikistyle() {
    return function(bbhtml) {
        bbhtml = bbhtml.replace(/<span style="font-weight: bold">(.*?)<\/span>/g, "'''$1'''");
        bbhtml = bbhtml.replace(/<span style="text-decoration: underline">(.*?)<\/span>/g, "<u>$1</u>");
        bbhtml = bbhtml.replace(/<span style="font-style: italic">(.*?)<\/span>/g, "''$1''");
        return bbhtml;
    };
}
 
// converts (html) phpBB code blocks to wiki <syntaxhighlight>
// FIXME: not actually using the above tag, because the copied html has
//        unconverted html entities and br's are not interpreted.
//        Solution would be to extract the code, fix it and use the proper tag...
function phpBB_code2syntaxhighlight() {
    return function(bbhtml) {
        return bbhtml.replace(/<dl.*?><dt>.*?<\/dt><dd><code>(.*?)<\/code><\/dd><\/dl>/g, "<tt>\n$1\n</tt>");
    };
}
 
// converts (html) phpBB quotes to simple wiki cquotes (author not cited, it'd get messy anyway)
function phpBB_quote2cquote() {
    return function(bbhtml) {
        return bbhtml.replace(/<blockquote.*?><div>(.*?)<\/div><\/blockquote>/g, "{{cquote|$1}}");
    };
}

// escapes characters that can be problematic inside a template. Must be used after html tags were converted, or they get messed up.
function escapePipeAndEquals() {
    return function(html) {
        html = html.replace(/\|\|/g,"{{!!}}");
        html = html.replace(/\|\-/g,"{{!-}}");
        html = html.replace(/\|/g,"{{!}}");
        return html.replace(/=/g,"{{=}}");
    }
}
 
 
///////////////////
// SCRIPT MAIN BODY
 
window.addEventListener('load', register);
 
function register() {
    // check if we have a matching domain in the CONFIG hash
    if (CONFIG.hasOwnProperty(window.location.hostname)) {
        document.onmouseup = instantCquote; // register the callback for "mouse button up" event
    }
}
 
// main function
function instantCquote() {
    if(getSelectedText() === "") return;
 
    var profile = CONFIG[window.location.hostname];
    var parent = getSelectionParent();
 
    var info = parent ? extractInfo(profile, parent) : extractInfoFallback();
 
    if (info) {
        var cquote = createCquote(info);
        OUTPUT.msgbox(cquote);
    } else {
        alert("info == null");
    }
}
 
function extractInfoFallback() { // XXX untested
    var info = {};
    info.content = getSelectedText();
    info.url = document.URL;
    info.title = "from " + window.location.hostname;
    info.author = "";
    info.date = "";
    return info;
}
 
function extractInfo(profile, parent) {
    var info = {};
 
    for (var field in profile) {
        if (!profile.hasOwnProperty(field)) continue; 
 
            // this part gets the data, both for field and content (i.e. text, html)
            var fieldInfo = extractFieldInfo(profile, parent, field);
 
            if (debug) alert("pre trans:\n" + field + " = " + fieldInfo);
 
            var transform = profile[field].transform;
            if(transform) fieldInfo = applyTransformations(fieldInfo, transform);
 
            if (debug) alert("post trans:\n" + field + " = " + fieldInfo);
 
            info[field] = fieldInfo;
 
    }
 
    return info;
}
 
function extractFieldInfo(profile, parent, field) {
    if (field != "content") {
        var xpath = profile[field].xpath;
        var parentXPath = getXPathTo(parent);
        var fullPath =  parentXPath + "/" + xpath;
        return document.evaluate(fullPath, document, null, XPathResult.STRING_TYPE, null).stringValue;
    } else {
        return profile[field].selection(); // here we extract the contents of the selection
    }
}
 
function applyTransformations(fieldInfo, trans) {
    if(typeof trans === "function") {
        return trans(fieldInfo);
    } else if (Array.isArray(trans)) {
        for (var i = 0; i < trans.length; i++) {
            if (debug) alert("pre trans in array:\n = " + fieldInfo);
            fieldInfo = trans[i](fieldInfo);
            if (debug) alert("post trans in array:\n = " + fieldInfo);
        }
        return fieldInfo;
    }
}
 
function createCquote(info) {
    //TODO: add template string to profile
    var template = "{{cquote" + "\n" +
        "  |" + info.content + "\n" +
        "  |{{cite web |url=" + info.url + "\n" +
        "     |title=" + nowiki(info.title) + "\n" +
        "     |author=" + nowiki(info.author) + "\n" +
        "     |date=" + nowiki(info.date) + "\n" +
        "   }}" + "\n" +
        "}}";
    return template;
}
 
function createForumQuote(info) {
    //TODO: add template string to profile
    // nowiki(info.date)
    var template = "[url='"+info.url+"']"+info.title+"("+nowiki(info.date)+")[/url][quote='"+nowiki(info.author)+"']" + nowiki(info.content) + "\n" +
        "[/quote]";
    return template;
}
 
 
function nowiki(text) {
    return "<nowiki>" + text + "</nowiki>";
}
 
////////////////////
// UTILITY FUNCTIONS
 
// IE8 compat. function
function getSelectionParent() {
    if(document.getSelection) { // for modern, standard compliant browsers
        // anchorNode is the textnode, parentNode is the parent html element
        return document.getSelection().anchorNode.parentNode;
 
    } else if (document.selection) { // for IE <= v8 - XXX: not tested!
        return document.selection.createRange().parentElement();
    }
}
 
// IE8 compat. function
function getSelectedText() {
    if(document.getSelection) { // for modern, standard compliant browsers
        return document.getSelection().toString();
 
    } else if (document.selection) { // for IE <= v8 - XXX: not tested!
        return document.selection.createRange().text;
    }
}
 
// IE8 compat. function (copied from http://stackoverflow.com/a/6668159 )
function getSelectedHtml() {
    var html = "";
    if (typeof window.getSelection != "undefined") {
        var sel = window.getSelection();
        if (sel.rangeCount) {
            var container = document.createElement("div");
            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
                container.appendChild(sel.getRangeAt(i).cloneContents());
            }
            html = container.innerHTML;
        }
    } else if (typeof document.selection != "undefined") {
        if (document.selection.type == "Text") {
            html = document.selection.createRange().htmlText;
        }
    }
    return html;
}
 
// copied from http://stackoverflow.com/a/2631931
function getXPathTo(element) {
    if (element.id !== '')
        return 'id("' + element.id + '")';
    if (element === document.body)
        return element.tagName;
 
    var ix= 0;
    var siblings = element.parentNode.childNodes;
    for (var i= 0; i<siblings.length; i++) {
        var sibling = siblings[i];
        if (sibling === element)
            return getXPathTo(element.parentNode) + '/' + element.tagName + '[' + (ix+1) + ']';
        if (sibling.nodeType === 1 && sibling.tagName === element.tagName)
            ix++;
    }
}