FlightGear wiki:Instant-Refs

From FlightGear wiki
Jump to navigation Jump to search
Instant-Cquotes script in Firefox
Instant-Cquotes screenshot prototyping runtime format selection

The Instant-Cquotes script is a browser addon (user script) implemented in JavaScript in order to convert excerpts from forum or mailing list postings into MediaWiki markup/quotes. It is supported by Firefox, Google Chrome/Chromium, Opera and Safari. It is being developed and maintained by a group of volunteers involved in maintaining the wiki and in trying to provide more up-to-date information to end-users who may not be as involved in the various FlightGear-related communication channels.

Motivation

Cquote1.png As someone new coming to FG, it's pretty tricky to get a handle on what's going on, what's being worked on, what needs fixed and so on. Of course this is an issue for all software projects, and there's different solutions depending on the nature of the team, how many active contributors exist and so on. FG seems to have a few potential tracking 'points' - the Developer Wiki, the SourceForge tracker / bug system, some (mandated) files in CVS (ToDos) and the usual source code 'FIXME' annotations (which something like Doxygen can extract, if formatted suitably).

[...] From reading the code, the list, and watching CVS commits, plus the odd naiive email, it's possible to get p partial answers to all of the above, but they are incomplete answers at best. [...]

The main question is about visibility, i.e if there's a place any of the above is recorded by a working consensus (eg, > 60%) of developers.
— James Turner (Mon, 28 Jul 2008 10:06:05 -0700). [Flightgear-devel] Project tracking.
(powered by Instant-Cquotes)
Cquote2.png

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 with a certai track record/reputation 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.

Equally, documentation is usually lacking behind significantly - writing documentation from scatch takes time. Writing good documentation takes even more time. While it used to be common practice among FlightGear core developers to document new features in the form of special README.feature files in $FG_ROOT/Docs, this is no longer commonly done by most people - and many FlightGear core developers are also not involved in writing/maintaining the official manual, so that recent developments are de-facto undocumented.

In addition, FlightGear is a moving target, so that archived discussions among developers on the forum/devel list are often the only reliable documentation available. And quotes are a good way to bootstrap new articles until features have stabilized sufficiently or until developers find time to document their work, e.g. see Integrated Qt5 Launcher.

Cquote1.png The documentation is in great need of improvement. Improving the documentation would make things better in a hurry.
— John Denker (Jul 16th, 2007). [Flightgear-devel] development process (was: chaos...).
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png there is always a need for more and better documentation and I certainly agree that FlightGear is under documented.
— Curtis Olson (Jul 27th, 2011). Re: [Flightgear-devel] The state of things in Flight Gear.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png One response to all of this that we see frequently and is greatly appreciated (!!!) is when someone comes to our project, recognies a need or lack of something (like documentation) and decides they are going to roll up their sleeves and do something about it.
— Curtis Olson (Jul 27th, 2011). Re: [Flightgear-devel] The state of things in Flight Gear.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png The lack of internal documentation is an issue for many of not most open source projects. One reason for this is that it is a big undertaking to completely document a system of the complexity of FG.
— Hal V. Engel (Jul 27th, 2011). Re: [Flightgear-devel] The state of things in Flight Gear.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png In the long run having this documentation would help the project but it is a huge undertaking. In addition, it is an undertaking that has little if any short term impact on the project which makes it even less attractive for potential contributers.
— Hal V. Engel (Jul 27th, 2011). Re: [Flightgear-devel] The state of things in Flight Gear.
(powered by Instant-Cquotes)
Cquote2.png
Cquote1.png some of the worst documenters make the best programmers
— Brian Fahrlander (Nov 27th, 2009). [Flightgear-users] Documentation Idea.
(powered by Instant-Cquotes)
Cquote2.png

However, there is often tons of useful information hidden in the archives (devel list, forum, issue tracker etc). People unfamiliar with FlightGear may however have a difficult time locating/filtering and identifying such information in a timely fashion, so that features seem basically undocumented to novice users.

Unfortunately, that even applies to long-term development/issues, i.e. those that are known to affect FlightGear in terms of architectural/design challenges.

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, but also to bootstrap new articles/documentation, until a dedicated rewrite/article is finished.

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 Issues/limitations or Feature requests & ideas.

On quoting

1rightarrow.png See FlightGear wiki:Quoting Guidelines for the main article about this subject.

Using the Instant-Cquotes script is a good way to bootstrap and write some preliminary notes; however, while quotes might be useful to understand how undocumented subsystems and features work and are definitely better than nothing, they are not meant to replace proper, structured and well-written wiki articles.[1]

One way to convert pages bootstrapped using quotes is to extract relevant information from them and keep citations only as references; in case important details are missing, they can be asked for on the mailing lists (on the forum, the chance to get a complete answer might be lower).[2] Another option might be moving the quotes to the Talk page for each entry, which would preserve the sources without clogging up the articles.[3]

Changelog

Note  Contributors are invited to document their changes here, please also add your wiki handle so that others can more easily get in touch.
  • hosting is moved, to allow auto-updates [1] [2] [3]
  • changed to ref-only quotes for now, not using the FGCquote template anymore, due to its obnoxious appearance on quote-heavy pages (should probably become a runtime option instead)
  • updated to use https for forum postings
  • add version info to each created quote, i.e. for future updates
  • add helper for opening websites asynchronously using AJAX (also via GM helper API)
  • display version number in output dialog
  • begin using the GreaseMonkey API for setting clipboard content

Example Output

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 customied, 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

screen shot showing the greasemonkey setup dialog (on firefox)
  • 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 wildcard towards the end of included pages, such as below for multi-page support.

https://sourceforge.net/p/flightgear/mailman/flightgear-devel/*

  • 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).

Mobile Use

WIP.png Work in progress
This article or section will be worked on in the upcoming hours or days.
See history for the latest developments.

For the time being, there's no separate version available for mobile use - thus, your best chance is installing a userscript addon on Android, e.g. one of those:

For installation instructions, refer to:

testing/feedback would obviously be appreciated - if in doubt, feel free to just edit the wiki page to add your findings/questions.

Usage

Instant-Cquotes script, with updates contributed by Red Leader
Screenshot showing the most recent Instant-Cquotes script at work


  1. Go to some mailing list archive URL, for example [4] or any forum message, such as [5].
  2. Select the relevant portion of text.
  3. When you release the mouse button, a box will appear containing the converted text (for now, mainly properly-referenced quotes for the wiki).
  4. As the text will already be selected for you, press Ctrl+c to copy it (no longer necessary).
  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.
Instant-Cquotes debug mode

Mobile Edition

Note  As of 02/2016, Hooray is contemplating to make this available as an addon for Android phones.

Issues/limitations

Bugs

  • It's eating characters, apparently related to regex/xpath handling - e.g. words like "analyzing" are turned into "analying" [6]

Non working URLs

Feature requests & ideas

  • encode script settings in created markup, for future processing/updating of quotes
  • look up [x] references and replace with the corresponding link [7]
  • support named refs for combining identical refs [8]
  • adopt Template:Forumref
  • implement a less obnoxious quoting mode, without quotes, where only the ref part would be added, e.g. see the example at Graphics Card Profiles (it's still 99% quotes, but much less annoying) Done Done
  • attachment support: identify attachments and link to them: https://sourceforge.net/p/flightgear/mailman/message/11683451/
  • bulletin points: if there is a colon (:) followed by at least two dashes (-), split up everything after the colon to turn each dash into an asterisk (wiki markup for bulletin points), followed by a newline [9]
  • generic URL/template matching, e.g. for for sourceforge commit IDs
  • make filters/conversions configurable via checkboxes (nowiki, wrap in alert/note boxes)
  • make syntax highlighting configurable (language, mode) ?
  • consider using something like the Roles template to turn contributor names into tooltips where contributor roles are shown (core dev, fgdata committer etc)
  • introduce support for tag clouds to help categorize/classify related quotes
  • consider making transformations optional/configurable using check boxes in the jQuery dialog
  • add new input method, for quotes that need to be updated/converted (added script version specifically for this purpose), should also add extraction/processing date
  • investigate why not all mailing list archives/postings are supported correctly: http://sourceforge.net/p/flightgear/mailman/message/8090479/ (problem traced to getPostID())
  • explore having a ref-only mode without using cquotes, i.e. just copy/paste quotes with proper ref tags and a references section, to rewrite the whole thing (possibly with templates for different purposes, e.g. newsletter/changelog)
  • should use Template:Forumref (category:link templates)
  • should be updated to use the new repo/flightgear file templates created by Johan & RedLeader Not done Not done
  • resolve links to forum threads to look up the title for the topic/posting, so that the posting's title can be used for those links, instead of just the URL - we can probably do that by making an AJAX call to open URLs asynchronously and extract the title for the thread/posting to come up with something like A call to developers-Lockheed -L188 Electra ([10]) 50}% completed
  • convert quoted bug tracker URLs to use the issue template on the wiki Not done Not done
  • do regex/xpath validation, and display any errors (e.g. template/theme changes on the forum would currently break the script) Not done Not done
  • increased focus on supporting different output formats, maybe using a simple jQuery based wizard (wiki, forum, newsletter, changelog) Not done Not done
  • token matching for keywords/acronyms to link to the corresponding wiki articles (e.g. Nasal, Canvas, FG_ROOT, FG_HOME etc) Not done Not done
  • Add support for tickets, merge requests comments and FGUK forum. (also see FlightGear wiki talk:Instant-Cquotes#more sources)
  • 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
    • See MW:API:Edit § Editing via Ajax

The Script

Public domain This work has been released into the public domain by its author, FlightGear contributors. This applies worldwide.
In some countries this may not be legally possible; if so:
FlightGear contributors grants anyone the right to use this work for any purpose, without any conditions, unless such conditions are required by law.
Note  Anybody interested in contributing to the code is invited to directly edit this wiki article. As of 05/2016, the script is in the process of being hosted separately, so that scripts can auto-update. Any changes addded below, will be reviewed/integrated manually and then uploaded so that clients receive updates automatically.
// ==UserScript==
// @name        Instant-Cquotes
// @version     0.29
// @description Automatically converts FlightGear mailing list and forum quotes into MediaWiki markup (cquotes).
// @description:it Converte automaticamente citazioni dalla mailing list e dal forum di FlightGear in marcatori MediaWiki (cquote).
// @author      Hooray, bigstones, Philosopher, Red Leader & Elgaton (2013-2016)
// @icon        http://wiki.flightgear.org/images/6/62/FlightGear_logo.png
// @match       https://sourceforge.net/p/flightgear/mailman/*
// @match       http://sourceforge.net/p/flightgear/mailman/*
// @match       https://forum.flightgear.org/*
// @namespace   http://wiki.flightgear.org/FlightGear_wiki:Instant-Cquotes
// @run-at      document-end
// @require     https://code.jquery.com/jquery-2.1.4.min.js
// @require     https://code.jquery.com/ui/1.11.4/jquery-ui.min.js
// @resource    jQUI_CSS https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css
// @grant       GM_addStyle
// @grant       GM_getResourceText
// @grant       GM_setClipboard
// @grant       GM_xmlhttpRequest
// @noframes
// ==/UserScript==
//
// This work has been released into the public domain by their authors. This
// applies worldwide.
// In some countries this may not be legally possible; if so:
// The authors grant anyone the right to use this work for any purpose, without
// any conditions, unless such conditions are required by law.
//
// Check if Greasemonkey/Tampermonkey is available
try {
  // TODO: add version check for clipboard API and check for TamperMonkey/Scriptish equivalents ?
  GM_addStyle(GM_getResourceText('jQUI_CSS'));
  // http://wiki.greasespot.net/GM_info
  var scriptVersion = ' (v'+GM_info.script.version+')';
  //console.log(GM_info);
}
catch (error) {
}
'use strict';
// Define constants
var DEBUG = false;

// set this to true continue working on the new mode supporting
// asynchronous content fetching via AJAX
var USE_NG=false;


// hash with supported websites/URLs

var CONFIG = {
  'Mailing list': {
    url_reg: "^(http|https):\/\/sourceforge\.net\/p\/flightgear\/mailman\/.*/",
    content: {
      selection: getSelectedText,
      idStyle: /msg[0-9]{8}/,
      parentTag: [
        'tagName',
        'PRE'
      ]
    },
// regex/xpath and transformations for extracting various required fields
    author: {
      xpath: 'tbody/tr[1]/td/div/small/text()',
      transform: extract(/From: (.*) <.*@.*>/)
    },
    title: {
      xpath: 'tbody/tr[1]/td/div/div[1]/b/a/text()'
    },
    date: {
      xpath: 'tbody/tr[1]/td/div/small/text()',
      transform: extract(/- (.*-.*-.*) /)
    },
    url: {
      xpath: 'tbody/tr[1]/td/div/div[1]/b/a/@href',
      transform: prepend('https://sourceforge.net')
    }
  },
// next website/URL (forum)
  'FlightGear forum': {
    url_reg: /https:\/\/forum\.flightgear\.org\/.*/,
    content: {
      selection: getSelectedHtml,
      idStyle: /p[0-9]{6}/,
      parentTag: [
        'className',
        'content',
        'postbody'
      ],
      transform: [
        removeComments,
        forum_quote2cquote,
        forum_smilies2text,
        forum_fontstyle2wikistyle,
        forum_code2syntaxhighlight,
        img2link,
        a2wikilink,
        vid2wiki,
        list2wiki,
        forum_br2newline
      ]
    },
    author: {
      xpath: 'div/div[1]/p/strong/a/text()'
    },
    title: {
      xpath: 'div/div[1]/h3/a/text()'
    },
    date: {
      xpath: 'div/div[1]/p/text()[2]',
      transform: extract(/» (.*?[0-9]{4})/)
    },
    url: {
      xpath: 'div/div[1]/p/a/@href',
      transform: [
        extract(/\.(.*)/),
        prepend('https://forum.flightgear.org')
      ]
    }
  }
};

// this being a greasemonkey user-script, we are not 
// subject to usual browser restrictions

function setClipboard(msg) {
  // http://wiki.greasespot.net/GM_setClipboard
  GM_setClipboard(msg);
}

// hash to map URLs (wiki article, issue tracker, sourceforge link, forum thread etc) to existing wiki templates

var URL2TemplateTable = {
// placeholder for now
}; // TemplateTable

var EventHandlers = {
 updateTarget: function() {alert("not yet implement");},
 updateFormat: function() {alert("not yet implement");},
}; // EventHandlers

// output methods (alert and jQuery for now)
var METHODS = {
  // Shows a window.prompt() message box
  msgbox: function (msg) {
    window.prompt('Copy to clipboard'+scriptVersion, msg);
    setClipboard(msg);
  },
  // Show a jQuery dialog
  jQueryDiag: function (msg) {

    // WIP: add separate Target/Format combo boxes for changing the template to be used (e.g. for refs instead of quotes)
    var target_format = "<form name='target'>Target: <select name='selection' onchange='EventHandlers.updateTarget();'><option value='0'>to wiki</option><option value='1'>to forum</option></select>Format: <select name='format' onchange='EventHandlers.updateFormat();'><option value='0'>refonly</option><option value='1'>fgcquote</option></select></form>";
    var diagDiv = $('<div id="MyDialog"><textarea id="quotedtext" rows="10"cols="80" style="width: 290px; height: 290px">' + msg + '</textarea>' + target_format + '</div>');
    var diagParam = {
      title: 'Copy your quote with Ctrl+c'+scriptVersion,
      modal: true,
      width: 'auto',
      buttons: [
        {
          text: 'Select all',
          click: function () {
            setClipboard(msg);
            $('#quotedtext').select();
          }
        },
        {
          text: 'OK',
          click: function () {
            setClipboard(msg);
            $(this).dialog('close');
          }
        }
      ]
    };
    diagDiv.dialog(diagParam);
  }
};
var MONTHS = [
  'Jan',
  'Feb',
  'Mar',
  'Apr',
  'May',
  'Jun',
  'Jul',
  'Aug',
  'Sep',
  'Oct',
  'Nov',
  'Dec'
];
// Conversion for forum emoticons
var EMOTICONS = [
  [/:shock:/g,
  'O_O'],
  [
    /:lol:/g,
    '(lol)'
  ],
  [
    /:oops:/g,
    ':$'
  ],
  [
    /:cry:/g,
    ';('
  ],
  [
    /:evil:/g,
    '>:)'
  ],
  [
    /:twisted:/g,
    '3:)'
  ],
  [
    /:roll:/g,
    '(eye roll)'
  ],
  [
    /:wink:/g,
    ';)'
  ],
  [
    /:!:/g,
    '(!)'
  ],
  [
    /:\?:/g,
    '(?)'
  ],
  [
    /:idea:/g,
    '(idea)'
  ],
  [
    /:arrow:/g,
    '(->)'
  ],
  [
    /:mrgreen:/g,
    'xD'
  ]
];
// ##################
// # Main functions #
// ##################
window.addEventListener('load', init);
dbLog('Instant Cquotes: page load handler registered');
// Initialize
function init() {
  dbLog('page load handler');
   if (Boolean(USE_NG)) {
    dbLog("devel version (WIP)");
    document.onmouseup = instantCquoteNG;
   }else {
     dbLog("standard version");
     document.onmouseup = instantCquote;
   }
}

function OpenLink(url, callback){
  // http://wiki.greasespot.net/GM_xmlhttpRequest
  GM_xmlhttpRequest({
  method: "GET",
  url: url,
  onload: callback
});
}

// an experimental version (non-functional for now)
function instantCquoteNG() {
  dbLog("experimental NN callback triggered");
  
  //self-test
  OpenLink("http://sourceforge.net/p/flightgear/mailman/message/27369425/",function(response) {
    console.log("ajax request status:"+response.statusText);
  });
  
  
} // instantCquoteNG


// The main function

function instantCquote() {
  var profile = getProfile(),
  selection = document.getSelection(),
  output = {
  },
  field = {
  };
  try {
    var post_id = getPostId(selection, profile);
  } 
  catch (error) {
    dbLog("Failed extracting post id\nProfile:"+profile)
    return;
  }
  if (selection.toString() === '') {
    dbLog('instantCquote(): No text is selected, aborting function');
    return;
  }
  if (!checkValid(selection, profile)) {
    dbLog('instantCquote(): Selection is not valid, aborting function');
    return;
  }
  for (field in profile) {
    if (field === 'name') continue;
    var fieldData = extractFieldInfo(profile, post_id, field);
    var transform = profile[field].transform;
    if (transform !== undefined) {
      dbLog('instantCquote(): Field \'' + field + '\' before transformation:\n\'' + fieldData + '\'');
      fieldData = applyTransformations(fieldData, transform);
      dbLog('instantCquote(): Field \'' + field + '\' after transformation:\n\'' + fieldData + '\'');
    }
    output[field] = fieldData;
  }
  output.content = stripWhitespace(output.content);
  output = createCquote(output);
  outputText(output);
}
// Gets to correct profile

function getProfile() {
  for (var profile in CONFIG) {
    if (window.location.href.match(CONFIG[profile].url_reg) !== null) {
      return CONFIG[profile];
    }
    dbLog("Could not find matching URL in getProfile() call!")
  }
}
// Get the HTML code that is selected

function getSelectedHtml() {
  // From http://stackoverflow.com/a/6668159
  var html = '',
  selection = document.getSelection();
  if (selection.rangeCount) {
    var container = document.createElement('div');
    for (var i = 0; i < selection.rangeCount; i++) {
      container.appendChild(selection.getRangeAt(i).cloneContents());
    }
    html = container.innerHTML;
  }
  dbLog('instantCquote(): Unprocessed HTML\n\'' + html + '\'');
  return html;
}
// Gets the selected text

function getSelectedText() {
  return document.getSelection().toString();
}
// Get the ID of the post

function getPostId(selection, profile, focus) {
  if (focus !== undefined) {
    selection = selection.focusNode.parentNode;
  } else {
    selection = selection.anchorNode.parentNode;
  }
  while (selection.id.match(profile.content.idStyle) === null) {
    selection = selection.parentNode;
  }
  return selection.id;
}
// Checks that the selection is valid

function checkValid(selection, profile) {
  var ret = true,
  selection_cp = {
  },
  tags = profile.content.parentTag;
  for (var n = 0; n < 2; n++) {
    if (n === 0) {
      selection_cp = selection.anchorNode.parentNode;
    } else {
      selection_cp = selection.focusNode.parentNode;
    }
    while (true) {
      if (selection_cp.tagName === 'BODY') {
        ret = false;
        break;
      } else {
        var cont = false;
        for (var i = 0; i < tags.length; i++) {
          if (selection_cp[tags[0]] === tags[i]) {
            cont = true;
            break;
          }
        }
        if (cont) {
          break;
        } else {
          selection_cp = selection_cp.parentNode;
        }
      }
    }
  }
  ret = ret && (getPostId(selection, profile) === getPostId(selection, profile, 1));
  return ret;
}
// Extracts the raw text from a certain place, using an XPath

function extractFieldInfo(profile, id, field) {
  if (field === 'content') {
    return profile[field].selection();
  } else {
    var xpath = '//*[@id="' + id + '"]/' + profile[field].xpath;
    return document.evaluate(xpath, document, null, XPathResult.STRING_TYPE, null).stringValue;
  }
}
// Change the text using specified transformations

function applyTransformations(fieldInfo, trans) {
  if (typeof trans === 'function') {
    return trans(fieldInfo);
  } else if (Array.isArray(trans)) {
    for (var i = 0; i < trans.length; i++) {
      fieldInfo = trans[i](fieldInfo);
      dbLog('applyTransformations(): Multiple transformation, transformation after loop #' + (i + 1) + ':\n\'' + fieldInfo + '\'');
    }
    return fieldInfo;
  }
}
// Formats the quote

function createCquote(data, light_quotes=true) {
// skip FGCquote (experimental)
if (light_quotes) return nonQuotedRef(data);

  var date_added = new Date();
  var wikiText = '{{FGCquote\n' + ((data.content.match(/^\s*?{{cquote/) === null) ? '|1= ' : '| ') + data.content + '\n' +
  '|2= ' + createCiteWeb(data)+'\n'+
  '}}';
  return wikiText;
}

function nonQuotedRef(data) {
 return addContentBlob(data) + createRefCite(data);
}

// 
function addContentBlob(data) {
return data.content;
}

// wrap citation in ref tags
function createRefCite(data) {
 return '<ref>'+createCiteWeb(data)+'</ref>';
}

function createCiteWeb(data) {
  var date_added = new Date();
  var wikiText = '{{cite web\n' +
  '  |url    = ' + data.url + '\n' +
  '  |title  = ' + nowiki(data.title) + '\n' +
  '  |author = ' + nowiki(data.author) + '\n' +
  '  |date   = ' + datef(data.date) + '\n' +
  '  |added  = ' + datef(date_added.toDateString()) + '\n' +
  '  |script_version = ' + GM_info.script.version + '\n' +
  '  }}\n';
  return wikiText;
}


// Output the text.
// Tries the jQuery dialog, and falls back to window.prompt()

function outputText(msg) {
  try {
    METHODS.jQueryDiag(msg);
    // TODO: unify code & call setClipboard() here
  } 
  catch (err) {
    msg = msg.replace(/&lt;\/syntaxhighligh(.)>/g, '</syntaxhighligh$1');
    METHODS.msgbox(msg);
  }
}
// #############
// # Utilities #
// #############

function extract(regex) {
  return function (text) {
    return text.match(regex) [1];
  };
}
function prepend(prefix) {
  return function (text) {
    return prefix + text;
  };
}
function removeComments(html) {
  return html.replace(/<!--.*?-->/g, '');
}
// Not currently used (as of June 2015), but kept just in case

function escapePipes(html) {
  html = html.replace(/\|\|/g, '{{!!}}');
  html = html.replace(/\|\-/g, '{{!-}}');
  return html.replace(/\|/g, '{{!}}');
}
// Converts HTML <a href="...">...</a> tags to wiki links, internal if possible.

function a2wikilink(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
  // TODO: identify forum/devel list links, and use the AJAX/OpenLink helper to get a title/subject for unnamed links (using the existing xpath/regex helpers for that)
  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;
}
// Converts images, including images in <a> links

function img2link(html) {
  html = html.replace(/<a[^<]*?href="([^<]*?)"[^<]*?><img.*?src="http:\/\/wiki\.flightgear\.org\/images\/.*?\/.*?\/(.*?)".*?><\/a>/g, '[[File:$2|250px|link=$1]]');
  html = html.replace(/<img.*?src="http:\/\/wiki\.flightgear\.org\/images\/.*?\/.*?\/(.*?)".*?>/g, '[[File:$1|250px]]');
  html = html.replace(/<a[^<]*?href="([^<]*?)"[^<]*?><img.*?src="(.*?)".*?><\/a>/g, '(see [$2 image], links to [$1 here])');
  return html.replace(/<img.*?src="(.*?)".*?>/g, '(see the [$1 linked image])');
}
// Converts smilies

function forum_smilies2text(html) {
  html = html.replace(/<img src="\.\/images\/smilies\/icon_.*?\.gif" alt="(.*?)".*?>/g, '$1');
  for (var i = 0; i < EMOTICONS.length; i++) {
    html = html.replace(EMOTICONS[i][0], EMOTICONS[i][1]);
  }
  return html;
}
// Converts font formatting

function forum_fontstyle2wikistyle(html) {
  html = html.replace(/<span style="font-weight: bold">(.*?)<\/span>/g, '\'\'\'$1\'\'\'');
  html = html.replace(/<span style="text-decoration: underline">(.*?)<\/span>/g, '<u>$1</u>');
  html = html.replace(/<span style="font-style: italic">(.*?)<\/span>/g, '\'\'$1\'\'');
  return html.replace(/<span class="posthilit">(.*?)<\/span>/g, '$1');
}
// Converts code blocks

function forum_code2syntaxhighlight(html) {
  var list = html.match(/<dl class="codebox">.*?<code>(.*?)<\/code>.*?<\/dl>/g),
  data = [
  ];
  if (list === null) return html;
  for (var n = 0; n < list.length; n++) {
    data = html.match(/<dl class="codebox">.*?<code>(.*?)<\/code>.*?<\/dl>/);
    html = html.replace(data[0], processCode(data));
  }
  return html;
}
// Strips any whitespace from the beginning and end of a string

function stripWhitespace(html) {
  html = html.replace(/^\s*?(\S)/, '$1')
  return html.replace(/(\S)\s*?\z/, '$1');
}
// Process code, including basic detection of language

function processCode(data) {
  var lang = '',
  code = data[1];
  code = code.replace(/&nbsp;/g, ' ');
  if (code.match(/=?.*?\(?.*?\)?;/) !== null) lang = 'nasal';
  if (code.match(/&lt;.*?&gt;.*?&lt;\/.*?&gt;/) !== null || code.match(/&lt;!--.*?--&gt;/) !== null) lang = 'xml';
  code = code.replace(/<br\/?>/g, '\n');
  return '<syntaxhighlight lang="' + lang + '" enclose="div">\n' + code + '\n&lt;/syntaxhighlight>';
}
// Converts quote blocks to Cquotes

function forum_quote2cquote(html) {
  html = html.replace(/<blockquote class="uncited"><div>(.*?)<\/div><\/blockquote>/g, '{{cquote|$1}}');
  if (html.match(/<blockquote>/g) === null) return html;
  var numQuotes = html.match(/<blockquote>/g).length;
  for (var n = 0; n < numQuotes; n++) {
    html = html.replace(/<blockquote><div><cite>(.*?) wrote.*?:<\/cite>(.*?)<\/div><\/blockquote>/, '{{cquote|$2|$1}}');
  }
  return html;
}
// Converts videos to wiki style

function vid2wiki(html) {
  // YouTube
  html = html.replace(/<div class="video-wrapper">\s.*?<div class="video-container">\s*?<iframe class="youtube-player".*?width="(.*?)" height="(.*?)" src="http:\/\/www\.youtube\.com\/embed\/(.*?)".*?><\/iframe>\s*?<\/div>\s*?<\/div>/g, '{{#ev:youtube|$3|$1x$2}}');
  // Vimeo
  html = html.replace(/<iframe src="http:\/\/player\.vimeo\.com\/video\/(.*?)\?.*?" width="(.*?)" height="(.*?)".*?>.*?<\/iframe>/g, '{{#ev:vimeo|$1|$2x$3}}');
  return html.replace(/\[.*? Watch on Vimeo\]/g, '');
}
// Not currently used (as of June 2015), but kept just in case

function escapeEquals(html) {
  return html.replace(/=/g, '{{=}}');
}
// <br> to newline.

function forum_br2newline(html) {
  html = html.replace(/<br\/?><br\/?>/g, '\n');
  return html.replace(/<br\/?>/g, '\n\n');
}
// Forum list to wiki style

function list2wiki(html) {
  var list = html.match(/<ul>(.*?)<\/ul>/g);
  if (list !== null) {
    for (var i = 0; i < list.length; i++) {
      html = html.replace(/<li>(.*?)<\/li>/g, '* $1\n');
    }
  }
  list = html.match(/<ol.*?>(.*?)<\/ol>/g);
  if (list !== null) {
    for (var i = 0; i < list.length; i++) {
      html = html.replace(/<li>(.*?)<\/li>/g, '# $1\n');
    }
  }
  html = html.replace(/<\/?[uo]l>/g, '');
  return html;
}
function nowiki(text) {
  return '<nowiki>' + text + '</nowiki>';
}
// 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, ' ');
}
function dbLog(message) {
  if (Boolean(DEBUG)) {
    console.log("Instant cquotes:"+message);
  }
}
  1. bugman (Apr 17th, 2016). Re: What is the QT launcher?.
  2. James Turner (Mar 21st, 2016). Re: [Flightgear-devel] Wiki Quotes.
  3. Stuart Buchanan (Mar 19th, 2016). Re: [Flightgear-devel] Wiki Quotes.