// ==UserScript==
// @name Instant-Cquotes
// @version 0.30
// @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_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @noframes
// @downloadURL none
// ==/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;
function set_persistent(key,value) {
// https://wiki.greasespot.net/GM_setValue
GM_setValue(key,value);
} //make_persistent
function get_persistent(key, default_value) {
// https://wiki.greasespot.net/GM_getValue
return GM_getValue(key,default_value);
} // get_persistent
// 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 = "
";
var diagDiv = $('' + target_format + '
');
var diagParam = {
title: 'Copy your quote with Ctrl+c'+scriptVersion,
modal: true,
width: 'auto',
buttons: [
{
text: 'Setup',
click: setupDialog
},
{
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('No text is selected, aborting function');
return;
}
if (!checkValid(selection, profile)) {
dbLog('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('Field \'' + field + '\' before transformation:\n\'' + fieldData + '\'');
fieldData = applyTransformations(fieldData, transform);
dbLog('Field \'' + field + '\' after transformation:\n\'' + fieldData + '\'');
}
output[field] = fieldData;
}
output.content = stripWhitespace(output.content);
output = createCquote(output);
outputText(output);
}
// Gets to correct profile
function setupDialog() {
alert("configuration dialog is not yet implemented");
} // setupDialog
function getProfile() {
for (var profile in CONFIG) {
if (window.location.href.match(CONFIG[profile].url_reg) !== null) {
dbLog("Matching profile found");
var invocations=get_persistent(GM_info.script.version,0);
dbLog("Number of script invocations for version "+GM_info.script.version+" is:"+invocations);
if (invocations==0) {
var response = confirm("This is your first time running version "+GM_info.script.version+ "\nConfigure now?");
if (response){
// show configuration dialog (jQuery)
setupDialog();
}
else{
} // don't configure
}
// increment number of invocations, use the version number as the key, to prevent the config dialog from showing up again
set_persistent(GM_info.script.version, invocations+1)
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 '['+createCiteWeb(data)+']';
}
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(/<\/syntaxhighligh(.)>/g, '/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 ... 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>/g, '[[Media:$1|$2]]');
// Wiki links without custom text.
html = html.replace(/http:\/\/wiki\.flightgear\.org\/.*?<\/a>/g, '[[$1]]');
// Links to the wiki with custom text
html = html.replace(/(.*?)<\/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>/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 links
function img2link(html) {
html = html.replace(/<\/a>/g, '[[File:$2|250px|link=$1]]');
html = html.replace(//g, '[[File:$1|250px]]');
html = html.replace(/<\/a>/g, '(see [$2 image], links to [$1 here])');
return html.replace(//g, '(see the [$1 linked image])');
}
// Converts smilies
function forum_smilies2text(html) {
html = html.replace(/
/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>/g, '\'\'\'$1\'\'\'');
html = html.replace(/(.*?)<\/span>/g, '$1');
html = html.replace(/(.*?)<\/span>/g, '\'\'$1\'\'');
return html.replace(/(.*?)<\/span>/g, '$1');
}
// Converts code blocks
function forum_code2syntaxhighlight(html) {
var list = html.match(/.*?(.*?)<\/code>.*?<\/dl>/g),
data = [
];
if (list === null) return html;
for (var n = 0; n < list.length; n++) {
data = html.match(/.*?(.*?)<\/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(/ /g, ' ');
if (code.match(/=?.*?\(?.*?\)?;/) !== null) lang = 'nasal';
if (code.match(/<.*?>.*?<\/.*?>/) !== null || code.match(/<!--.*?-->/) !== null) lang = 'xml';
code = code.replace(/
/g, '\n');
return '\n' + code + '\n</syntaxhighlight>';
}
// Converts quote blocks to Cquotes
function forum_quote2cquote(html) {
html = html.replace(/(.*?)<\/div><\/blockquote>/g, '{{cquote|$1}}');
if (html.match(/
/g) === null) return html;
var numQuotes = html.match(//g).length;
for (var n = 0; n < numQuotes; n++) {
html = html.replace(/(.*?) wrote.*?:<\/cite>(.*?)<\/div><\/blockquote>/, '{{cquote|$2|$1}}');
}
return html;
}
// Converts videos to wiki style
function vid2wiki(html) {
// YouTube
html = html.replace(/