FlightGear wiki:Instant-Refs

From FlightGear wiki
Revision as of 13:41, 22 June 2014 by Bigstones (talk | contribs) (fixed conversion of resized wiki images)
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 (as well as allowing people to easily reuse forum/devel list announcements in wiki articles, e.g. to update the changelog, newsletter or "release plan/lessons learnt" section) - 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. Use this article's discussion page to get in touch (e.g. if there are any bugs/problems or features requests).

Example Output

The output may look like this (click the link to see the original forum posting):

Cquote1.png The upcoming FlightGear version (3.2) will contain a canvas-based map dialog, including a modular "plugin" system for creating custom map layers and charts with roughly ~50 lines of code, most of it boilerplate.


This is entirely XML/Nasal based (scripted) - symbols can be pretty much anything, raster or vector images (png or svg), but even animated. Styling can be customized, too.

For more info, I suggest to check out:

MapStructure#Porting_the_map_dialog
MapStructureDialog.png


— Hooray (Sat Jun 14). Re: Get objects to show up on Map/Radar.
(powered by Instant-Cquotes)
Cquote2.png

Background & Installation

  • A simple userscript extension implemented in JavaScript
  • Supported by FireFox, Chrome, Chromium, probably Opera and others
  • in case of doubt, just install the GreaseMonkey/TamperMonkey extension
  • install the script (on firefox, save the script as instant_cquotes.user.js, then drag'n'drop it into firefox).

Usage

or to any forum message, such as: http://forum.flightgear.org/viewtopic.php?f=71&t=23299#p212558
  • copy/mark some relevant portion of text (the script converts links, images and even youtube videos - wiki images are properly converted, too and it will try to retain basic formatting)
  • and directly get a full cquote paragraph (including author, date, thread and URL) that you can add to the wiki here using CTRL+C & CTRL-V

Known Limitations

  • newline2br addNewline() should also insert CR/LF for separating paragraphs in the cquote (also, the trailing slash is not added) Done Done
  • quoting code doesn't yet work properly Not done Not done
  • our regexes may fail once the HTML DOM of the source changes (e.g. phpBB/theme update), so we should better show a warning when that's the case Not done Not done
  • it may make sense to provide multiple regexes that are tried in order Not done Not done
  • The script uses a conventional JavaScript alert() box for providing the output, these dialogs are typically restricted to a max size of ~10kb-we may need to explore other/extended options Not done Not done
  • The script has already seen several iterations and created dozens of cquotes we're using in various places, but it might be possible to use the script to update previously created cquotes/FGCquotes automatically, instead of using the getSelection() helper, we could register a match for wiki.flightgear.org with the action=edit set, so that we can directly process all text of an edited page (using AJAX calls to open the URL in the background would make sense) Not done Not done

Feature Requests & Ideas

  • gitorious links converted to wiki markup could/should probably use the "Git link" template, e.g. see the quote at [1]

The Script

// ==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(),
                        escapePipes(),
                        a2wikilink(),
                        phpBB_smilies2text(),
                        img2link(),
                        phpBB_fontstyle2wikistyle(),
                        phpBB_code2syntaxhighlight(), // FIXME: could be much better, see below
                        phpBB_quote2cquote(),
                        escapeEquals(),
                        addNewlines()]
        },
        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, firstmost links to images (File:xxx), because
        // they need special treatment, or they get displayed
        html = html.replace(/<a[^(?:href)]*href="https?:\/\/wiki.flightgear.org\/(File:.*?)".*?>(.*?)<\/a>/g, "[[:$1|$2]]");
        // automatic links to the wiki (we don't use the displayed text)
        html = html.replace(/<a[^(?:href)]*href="https?:\/\/wiki.flightgear.org\/(.*?)".*?>https?:\/\/wiki.flightgear.org.*?<\/a>/g, "[[$1]]");
        // links to the wiki with custom text (we preserve it)
        html = html.replace(/<a[^(?:href)]*href="https?:\/\/wiki.flightgear.org\/(.*?)".*?>(.*?)<\/a>/g, "[[$1|$2]]");
 
        // then the others
        html = html.replace(/<a[^(?:href)]*href="(.*?)".*?>(.*?)<\/a>/g, "[$1 $2]");
        return html;
    };
}
 
// converts embedded html images to external links,
// but shows them as little images if they're from the wiki
function img2link() {
    return function(html) {
        html = html.replace(/<img.*?src="https?:\/\/wiki.flightgear.org\/images\/.*?\/(?:[0-9]+px-)?([^\/]*?)".*?>/g, "[[File:$1|250px]]");
        return html.replace(/<img.*?src="(.*?)".*?>/g, "(see the [$1 linked image])");
    };
}

// puts newlines where it makes for more readable wiki "source"
function addNewlines() {
    return function(html) {
        html = html.replace(/<br\/?>/g,"<br/>\n");
        html = html.replace(/(<\/?[uo]l>)/g,"$1\n");
        return html.replace(/<\/li>/g,"</li>\n");
    }
}
 
// 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''");
        bbhtml = bbhtml.replace(/<span class="posthilit">(.*?)<\/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>(?:<cite>.*?<\/cite>)?(.*?)<\/div><\/blockquote>/g, "{{cquote|$1}}");
    };
}

// converts smilies images from phpBB.
// Must be used BEFORE img2link(), because it makes a broken link of them
function phpBB_smilies2text() {
    return function(bbhtml) {
        return bbhtml.replace(/<img.*?src="\.\/images\/smilies\/icon_.*?\.gif".*?alt="(.*?)".*?>/g,"$1");
    }
}
 
// escapes pipe characters that can be problematic inside a template.
// Must be used BEFORE html tags were converted, or they get messed up.
function escapePipes() {
    return function(text) {
        text = text.replace(/\|\|/g,"{{!!}}");
        text = text.replace(/\|\-/g,"{{!-}}");
        return text.replace(/\|/g,"{{!}}");
    }
}
 
// escapes the equal sign that can be problematic inside a template.
// Must be used AFTER html tags were converted, or they get messed up.
function escapeEquals() {
    return function(text) {
        return text.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 = "{{FGCquote" + "\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++;
    }
}