FlightGear wiki:Instant-Refs: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
(new vid2wiki function)
m (→‎Installation: Firefox: Hint)
Line 36: Line 36:
# Install [http://www.greasespot.net/ Greasemonkey].
# Install [http://www.greasespot.net/ Greasemonkey].
# Save [[#The Script|the script]] below as <code>instant_cquotes.user.js</code>, then drag-and-drop it into Firefox.
# Save [[#The Script|the script]] below as <code>instant_cquotes.user.js</code>, then drag-and-drop it into Firefox.
{{Tip|go to Tools > Addons, and adjust preference for websites, such that it includes a wilecard towards the end of included pages, such as <pre>https://sourceforge.net/p/flightgear/mailman/flightgear-devel/*</pre>, for multi-page support.}}
* '''Chrome/Chromium''', '''Opera''', or '''Safari'''
* '''Chrome/Chromium''', '''Opera''', or '''Safari'''
# Install [https://tampermonkey.net/ Tampermonkey] (download links: [https://tampermonkey.net/index.php?ext=dhdg&browser=chrome Chrome/Chromium], [https://tampermonkey.net/index.php?ext=dhdg&browser=opera Opera], [https://tampermonkey.net/index.php?ext=dhdg&browser=safari Safari]).
# Install [https://tampermonkey.net/ Tampermonkey] (download links: [https://tampermonkey.net/index.php?ext=dhdg&browser=chrome Chrome/Chromium], [https://tampermonkey.net/index.php?ext=dhdg&browser=opera Opera], [https://tampermonkey.net/index.php?ext=dhdg&browser=safari Safari]).

Revision as of 22:08, 8 June 2015

The Instant-Cquotes script is a userscript implemented in JavaScript in order to convert forum or mailing list quotes into MediaWiki markup. It is supported by Firefox, Chrome/Chromium, Opera, Safari.

Background

FlightGear's development is not centrally coordinated in any way – at best, it 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.

Obviously, ideas and feature requests made by end-users are also appreciated. But at the end of the day, it's the people who do the actual work that have more say about future development than those who make suggestions. Contributors tend to prioritize items that are prioritized/suggested by other contributors, especially regular contributors and/or those offering to get involved and help in some way.

Unfortunately, because of the lack of development manpower, many good ideas tend to have a fairly long shelf life; there is a danger that good ideas may be forgotten over time.

This is why it is important for other developers/contributors to know who came up with a certain idea and who originally supported it, possibly months (or even years) after a discussion took place. 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 Instant-Cquotes script is intended to help with this. It also allows people to easily reuse forum or mailing list announcements in wiki articles, e.g., to update the changelog, newsletter or the Release plan/Lessons learned page. It does this by making it easy to copy & paste important discussions over to the wiki, without having to rewrite any text or manually put together a proper Cquote. It doesn't take more than a few seconds.

If you want to suggest a new feature/improvement, or have discovered a bug, please add details to the Known Limitations or Feature Requests & Ideas.

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 (Jun 14th, 2014). Re: Get objects to show up on Map/Radar.
(powered by Instant-Cquotes)
Cquote2.png

Installation

  • Firefox
  1. Install Greasemonkey.
  2. Save the script below as instant_cquotes.user.js, then drag-and-drop it into Firefox.
Tip  go to Tools > Addons, and adjust preference for websites, such that it includes a wilecard towards the end of included pages, such as
https://sourceforge.net/p/flightgear/mailman/flightgear-devel/*
, for multi-page support.
  • Chrome/Chromium, Opera, or Safari
  1. Install Tampermonkey (download links: Chrome/Chromium, Opera, Safari).
  2. Navigate to "Add a new Script"
  3. Copy and paste the script below into the editing window.
  4. Click the save button (just above the Search button).

Usage

  1. Go to some mailing list archive URL, for example [1] or any forum message, such as [2].
  2. Select the relevant portion of text.
  3. When you release the mouse button, a box will appear containing the converted text.
  4. As the text will already be selected for you, press Ctrl+c to copy it.
  5. Paste the text into the desired wiki page.

Development

Note  A Chrome/Chromium-specific extension that will not need Tampermonkey installed is under development.

Issues/limitations

  • Taking quotes from the forum will not work when the user is logged in.
  • The JavaScript alert(); boxes used are typically restricted to a max size of about 10 KB; other options should be looked at. Not done Not done

Feature requests & ideas

  • Internet Explorer stuff can be removed. It seems that Internet Explorer plugins are implemented completely differently. Not done Not done
  • GET-encoded SID arguments should be stripped from forum URLs. Not done Not done
  • Links to repositories should be converted to use wiki templates. Not done Not done
  • The regexes used may fail if the HTML DOM of the source changes (e.g., phpBB/theme update)
    • Show a warning when that's the case. Not done Not done
    • Try multiple regexes in order. Not done Not done
  • Use the script to update previously created Cquotes automatically
    • Instead of using the getSelection() helper, we could register a match for wiki.flightgear.org with 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. Not done Not done

The Script

Note  This code is put into the public domain, anybody interested in working with/contributing to the code, is invited to directly edit this wiki article.
// ==UserScript==
// @name       Instant-Cquotes
// @license    Public Domain
// @description Automatically converts mailing list and forum quotes into Mediawiki markup.
// @namespace  http://wiki.flightgear.org/
// @version    0.17
// @icon       http://wiki.flightgear.org/images/6/62/FlightGear_logo.png
// @require    http://code.jquery.com/jquery-1.11.1.min.js
// @require    https://code.jquery.com/ui/1.11.0/jquery-ui.min.js
// @resource   jqUI_CSS  https://code.jquery.com/ui/1.11.0/themes/smoothness/jquery-ui.css
// @grant      GM_addStyle
// @grant      GM_getResourceText
// @match      http://sourceforge.net/p/flightgear/mailman/* 
// @match      http://forum.flightgear.org/*
// @copyright  2013-2015, FlightGear.org (bigstones, Philosopher, Red Leader & Hooray)
// ==/UserScript==

var jqUI_CssSrc = GM_getResourceText("jqUI_CSS");
GM_addStyle(jqUI_CssSrc);

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(),
			vid2wiki(),
                        escapeEquals(),
                        addNewlines()]
        },
        author: {
            xpath: "../p/strong/a/text()"
        },
        title: {
            xpath: "../h3/a/text()"
        },
        date: {
            xpath: "../p/text()[2]",
            transform: extract(/» (.*, [0-9][0-9][0-9][0-9])/)
        },
        url: {
            xpath: "../p/a/@href",
            transform: [extract(/\.(.*)/),
                        prepend("http://forum.flightgear.org")]
        }
    }
};
 
var OUTPUT = OutputMethods().msgbox;
 
function OutputMethods(){
    var methods = {
        // Shows an alert(); message box 
        msgbox: function(msg){
            if (window.prompt("Copy to clipboard: Ctrl+C, Enter", msg) !== null){
                window.getSelection().removeAllRanges(); // deselect all text
            }
        },

        // Show a jQuery UI dialog
        jqueryDiag: function(msg){
            var diagDiv = $('<div id="MyDialog"><textarea id="quotedtext" rows="10" cols="80" style="width: 290px; height: 290px">' + msg + '</textarea></div>');

            var diagParam = {
                title: "Copy your quote with Ctrl-c",
                modal: true,
                width: 'auto',
                buttons: [
                    { text: "Select all", click: function(){ $('#quotedtext').select(); } },
                    { text: "Cancel", click: function(){ $(this).dialog('close'); } }
                ]
            };

            diagDiv.dialog(diagParam);
        },
    };

    return methods;
};
 
//////////////////////
// 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 href="...">...</a> tags to wiki links, internal if possible.
function a2wikilink(){
	return function(html){ 
		// Links to wiki images, because
		// they need special treatment, or else they get displayed.
		html = html.replace(/<a.*?href="http:\/\/wiki\.flightgear\.org\/File:(.*?)".*?>(.*?)<\/a>/g, "[[Media:$1|$2]]");

		// Wiki links without custom text.
		html = html.replace(/<a.*?href="http:\/\/wiki\.flightgear\.org\/(.*?)".*?>http:\/\/wiki\.flightgear\.org\/.*?<\/a>/g, "[[$1]]");

		// Links to the wiki with custom text
		html = html.replace(/<a.*?href="http:\/\/wiki\.flightgear\.org\/(.*?)".*?>(.*?)<\/a>/g, "[[$1|$2]]");

		// Remove underscores from all wiki links
		var list = html.match(/\[\[.*?\]\]/g);
		if(list !== null){
			for(var i = 0; i < list.length; i++){
				html = html.replace(list[i], underscore2Space(list[i]));
			}
		}

		// Convert non-wiki links
		html = html.replace(/<a.*?href="(.*?)".*?>(.*?)<\/a>/g, "[$1 $2]");

		// Remove triple dots from external links.
		// Replace with raw URL (MediaWiki converts it to a link).
		list = html.match(/\[.*?(\.\.\.).*?\]/g);
		if(list !== null){
			for(var i = 0; i < list.length; i++){
				html = html.replace(list[i], list[i].match(/\[(.*?) .*?\]/)[1]);
			}
		}

		return html;
	};
}

function vid2wiki(){
	return function(html){
		// YouTube
		html = html.replace(/<div.*?><iframe width="(.*?)" height="(.*?)" src="http:\/\/www\.youtube\.com\/embed\/(.*?)".*?><\/iframe><\/div>/g, "{{#ev:youtube|$3|$1x$2}}");
		html = html.replace(/\[.*? Watch on Youtube\]/g, "");

		// Vimeo: Doesn't currently work (cannot select video viewer on post)
		// html = html.replace(/<iframe src="http:\/\/player\.vimeo\.com\/video\/(.*?)\?.*?" width="(.*?)" height="(.*?)".*?>.*?<\/iframe>/g, "{{#ev:vimeo|$3|$1x$2}}");
		// html = html.replace(/\[.*? Watch on Vimeo\]/g, "");
		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="http:\/\/wiki\.flightgear\.org\/images\/.*?\/.*?\/(.*?)".*?>/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, '<pre style="white-space:pre-wrap">\n$1\n</pre>');
    };
}
 
// 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(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" + "\n" +
		" |url    = " + info.url + "\n" +
        " |title  = " + nowiki(info.title) + "\n" +
        " |author = " + nowiki(info.author) + "\n" +
        " |date   = " + datef(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

var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

// Returns the correct ordinal adjective
function ordAdj(date){
	date = date.toString();
	if(date == "11" || date == "12" || date == "13"){
		return "th";
	}else if(date.substr(1) == "1" || date == "1"){
		return "st";
	}else if(date.substr(1) == "2" || date == "2"){
		return "nd";
	}else if(date.substr(1) == "3" || date == "3"){
		return "rd";
	}else{
		return "th";
	}
};

// Formats the date to this format: Apr 26th, 2015
function datef(text){
	var date = new Date(text);
	return MONTHS[date.getMonth()] + " " + date.getDate() + ordAdj(date.getDate()) + ", " + date.getFullYear();
}

function underscore2Space(str){
	return str.replace(/_/g, " ");
}
 
// 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++;
    }
}
 
// copied from http://stackoverflow.com/a/12034334
var entityMap = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': '&quot;',
    "'": '&#39;',
    "/": '&#x2F;',
    "\n": "<br/>"
};
 
function escapeHtml(string) {
    return String(string).replace(/[&<>"'\/]|[\n]/g, function (s) {
        return entityMap[s];
    });
}