เก่า: v0.33 - 06-05-2016 - Change icon; begin populating the wiki mode
ใหม่: v0.34 - 06-05-2016 - Add sanity checks; introduce internal download helper
- @@ -1,6 +1,7 @@
- // ==UserScript==
- // @name Instant-Cquotes
-// @version 0.33
- +// @name:it Instant-Cquotes
- +// @version 0.34
- // @description Automatically converts selected FlightGear mailing list and forum quotes into post-processed MediaWiki markup (i.e. 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)
- @@ -14,11 +15,13 @@
- // @require https://code.jquery.com/jquery-1.10.2.js
- // @require https://code.jquery.com/ui/1.11.4/jquery-ui.js
- // @resource jQUI_CSS https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css
- +// @resource myLogo http://wiki.flightgear.org/images/2/25/Quotes-logo-200x200.png
- // @grant GM_registerMenuCommand
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_addStyle
- // @grant GM_getResourceText
- +// @grant GM_getResourceURL
- // @grant GM_setClipboard
- // @grant GM_xmlhttpRequest
- // @noframes
- @@ -49,7 +52,7 @@
- getHost: function() {
-
- // This will determine the script engine in use: http://stackoverflow.com/questions/27487828/how-to-detect-if-a-userscript-is-installed-from-the-chrome-store
- if (typeof GM_info === "undefined") {
- + if (typeof(GM_info) === 'undefined') {
- Environment.scriptEngine = "plain Chrome (Or Opera, or scriptish, or Safari, or rarer)";
- // See http://stackoverflow.com/a/2401861/331508 for optional browser sniffing code.
- }
- @@ -63,7 +66,7 @@
- },
-
- validate: function(host) {
- if(Environment.scriptEngine !== "Greasemonkey" && host.get_persistent('startup.disable_validation',false)!=true)
- + if(Environment.scriptEngine !== "Greasemonkey" && host.get_persistent('startup.disable_validation',false)!==true)
- alert("NOTE: This script has not been tested with script engines other than GreaseMonkey recently!");
- },
-
- @@ -72,28 +75,50 @@
- // for a working example, refer to the JSON test at the end
- // TODO: add jQuery tests
- APITests: [
- {name:'download', test: function() {return true;} },
- {name:'make_doc', test: function() {return true;} },
- {name:'eval_xpath', test: function() {return true;} },
- {name:'JSON de/serialization', test: function() {
- + {name:'download', test: function(recipient) {recipient(true);} },
- + {name:'make_doc', test: function(recipient) { recipient(true);} },
- + {name:'eval_xpath', test: function(recipient) { recipient(true);} },
- + {name:'JSON de/serialization', test: function(recipient) {
- //console.log("running json test");
- var identifier = 'unit_tests.json_serialization';
- var hash1 = {x:1,y:2,z:3};
- Host.set_persistent(identifier, hash1, true);
- var hash2 = Host.get_persistent(identifier,null,true);
-
- return JSON.stringify(hash1) === JSON.stringify(hash2);
- + recipient(JSON.stringify(hash1) === JSON.stringify(hash2));
- } // callback
- },
- {name:'person speech transformation', test: function() {return true;} }
- +
- + // downloads a posting and tries to transform it to 3rd person speech ...
- + // TODO: add another test to check forum postings
- + {name:'text/speech transformation', test: function(recipient) {
- +
- + // the posting we want to download
- + var url='https://sourceforge.net/p/flightgear/mailman/message/35066974/';
- + Host.downloadPosting(url, function (result) {
- +
- + // only process the first sentence by using comma/dot as delimiter
- + var firstSentence = result.posting.substring(result.posting.indexOf(',')+1, result.posting.indexOf('.'));
- +
- + var transformed = transformSpeech(firstSentence, result.author, null, speechTransformations );
- + console.log("3rd person speech transformation:\n"+transformed);
- +
- + recipient(true);
- + }); // downloadPosting()
- +
- + }// test()
- + } // end of speech transform test
-
- ], // end of APITests
-
- runAPITests: function(host, callback) {
- for(var t in Environment.APITests ) {
- var test = Environment.APITests[t];
- + runAPITests: function(host, recipient) {
- + console.log("Running API tests");
- + for(let test of Environment.APITests ) {
- + //var test = Environment.APITests[t];
- // invoke the callback passed, with the hash containing the test specs, so that the console/log or a div can be updated showing the test results
- callback(test);
- + //callback(test);
- + recipient.call(undefined, test);
- + //test.call(undefined, callback)
- +
- } // foreach test
- }, // runAPITests
-
- @@ -117,10 +142,11 @@
- {name:'Setup quotes',callback:setupDialog, hook:'S' },
- {name:'Check quotes',callback:selfCheckDialog, hook:'C' }
- ];
- +
- for (let c of commands ) {
- this.registerMenuCommand(c.name, c.callback, c.hook);
- }
-
- +
- }, // init()
-
- getScriptVersion: function() {
- @@ -152,6 +178,34 @@
- console.log("download did not work");
- }
- }, // download()
- +
- + // is only intended to work with archives supported by the hash
- + downloadPosting: function (url, EventHandler) {
- +
- + Host.download(url, function (response) {
- + var profile = getProfile(url);
- + var blob = response.responseText;
- + var doc = Host.make_doc(blob,'text/html');
- +
- + var xpath_author = '//'+profile.author.xpath;
- + var author = Host.eval_xpath(doc, xpath_author).stringValue;
- + author = profile.author.transform(author);
- +
- + var xpath_date = '//' + profile.date.xpath;
- + var date = Host.eval_xpath(doc, xpath_date).stringValue;
- + date = profile.date.transform(date);
- +
- + var xpath_posting = '//'+profile.content.xpath;
- + var posting = Host.eval_xpath(doc, xpath_posting).stringValue;
- +
- + var result = {author:author, date:date, posting:posting};
- +
- + EventHandler(result);
- +
- + }); // AJAX callback
- +
- +
- + }, // downloadPosting()
-
-
- // turn a string/text blob into a DOM tree that can be queried (e.g. for xpath expressions)
- @@ -202,19 +256,14 @@
-
- }; // Environment hash - intended to help encapsulate host specific stuff (APIs)
-
- +
- +// the first thing we need to do is to determine what APIs are available
- +// and store everything in a Host hash, which is used for API lookups
- // the Host hash contains all platform/browser-specific APIs
- var Host = Environment.getHost();
- Host.init(); // run environment specific initialization code (e.g. logic for GreaseMonkey setup)
-
-
-
-
-/*
-Environment.runAPITests(Host, function(meta) {
- console.log('Running API test '+meta.name);
-});
-*/
-
- // move DEBUG handling to a persistent configuration flag so that we can configure this using a jQuery dialog (defaulted to false)
- // TODO: move DEBUG variable to Environment hash / init() routine
- var DEBUG = Host.get_persistent('debug_mode_enabled', false);
- @@ -248,12 +297,14 @@
-
- var editSections = document.getElementsByClassName('mw-editsection');
- console.log('FlightGear wiki article, number of edit sections: '+editSections.length);
-
- for (let editSection of editSections) {
- var note = document.createTextNode(' (instant-cquotes is lurking) ');
- editSection.appendChild(note);
- }
-
- + // for now, just rewrite edit sections and add a note to them
- +
- + [].forEach.call(editSections, function (sec) {
- + sec.appendChild(
- + document.createTextNode(' (instant-cquotes is lurking) ')
- + );
- + }); //forEach section
-
- }, // the event handler to be invoked
- url_reg: '^(http|https)://wiki.flightgear.org' // ignore: not currently used by the wiki mode
- @@ -266,6 +317,7 @@
- event_handler: instantCquote, // the event handler to be invoked
- url_reg: '^(http|https)://sourceforge.net/p/flightgear/mailman/.*/',
- content: {
- + xpath: 'tbody/tr[2]/td/pre/text()', // NOTE this is only used by the downloadPosting helper to retrieve the posting without having a selection
- selection: getSelectedText,
- idStyle: /msg[0-9]{8}/,
- parentTag: [
- @@ -411,7 +463,9 @@
- 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 style='background-image: url(' + GM_getResourceURL ('myLogo')+ '); background-attachment: local; background-position: center; background-repeat: no-repeat; background-size: 70%; opacity: 1.0;'
- + var diagDiv = $('<div id="MyDialog"><textarea id="quotedtext" rows="10"cols="80" style=" width: 320px; height: 320px">' + msg + '</textarea>' + target_format + '</div>');
- +
- +
- var diagParam = {
- title: 'Copy your quote with Ctrl+c ' + Host.getScriptVersion(),
- modal: true,
- @@ -451,43 +505,58 @@
-
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-var speechTransform_1st_to_3rd = [
- +var speechTransformations = [
- // ordering is crucial here (most specific first, least specific/most generic last)
- {'I myself': '$author himself'},
- {'I am': ' $author is'},
- {'I can': '$author can'},
- {'I have': '$author has'},
- {'I should': '$author should'},
- {'I shall': '$author shall'},
- {'I may': '$author may'},
- {'I will': '$author will'},
- {'I would': '$author would'},
- {'by myself': 'by $author'},
- {'and I': 'and $author'},
- {'and me': 'and $author'},
- {'and myself': 'and $author'},
- + {query:/I have done/gi, replacement:'$author has done'},
- + {query:/I\'ve done/gi, replacement:'$author has done'}, //FIXME. queries should really be vectors ...
- +
- + {query:/I have got/gi, replacement:'$author has got'},
- + {query:/I\'ve got/gi, replacement:'$author has got'},
- +
- +
- + {query:/I myself/gi, replacement:'$author himself'},
- + {query:/I am/gi, replacement:' $author is'},
- + {query:/I can/gi, replacement:'$author can'},
- + {query:/I have/gi, replacement:'$author has'},
- + {query:/I should/g, replacement:'$author should'},
- + {query:/I shall/gi, replacement:'$author shall'},
- + {query:/I may/gi, replacement:'$author may'},
- + {query:/I will/gi, replacement:'$author will'},
- + {query:/I would/gi, replacement:'$author would'},
- + {query:/by myself/gi, replacement:'by $author'},
- + {query:/and I/gi, replacement:'and $author'},
- + {query:/and me/gi, replacement:'and $author'},
- + {query:/and myself/gi, replacement:'and $author'},
- +
- +
-
- // least specific stuff last (broad/generic stuff is kept as is, with author clarification added in parentheses)
- {'I': 'I ($author)'},
- {'me': 'me ($author)'},
- {'my': 'my ($author)'},
- {'myself': 'myself ($author)'},
- {'mine': '$author'}
- + {query:/ I /, replacement:'I ($author)'},
- + {query:/ me /, replacement:'me ($author)'},
- + {query:/ my /, replacement:'my ($author)'},
- + {query:/myself/, replacement:'myself ($author)'},
- + {query:/mine/, replacement:'$author'}
-
- ];
-
- // try to assist in transforming speech using the transformation vector passed in
- // still needs to be exposed via the UI
- function transformSpeech(text, author, gender, transformations) {
- // TODO: foreach transformation in vector, replace the search pattern with the matched string (replacing author/gender as applicable)
- + // WIP: foreach transformation in vector, replace the search pattern with the matched string (replacing author/gender as applicable)
- + for(var i=0;i< transformations.length; i++) {
- + var token = transformations[i];
- + // patch the replacement string using the correct author name
- + var replacement = token.replacement.replace(/\$author/gi, author);
- + text = text.replace(token.query, replacement);
- + } // end of token transformation
- + // console.log("transformed text is:"+text);
- +
- return text;
- } // transformSpeech
-
- // run a self-test
- (function() {
- var author ="John Doe";
-var transformed = transformSpeech("I have decided to commit a new feature", author, null, speechTransform_1st_to_3rd );
-if (transformed !== "John Doe has decided to commit a new feature")
- +var transformed = transformSpeech("I have decided to commit a new feature", author, null, speechTransformations );
- +if (transformed !== author+" has decided to commit a new feature")
- Host.dbLog("FIXME: Speech transformations are not working correctly");
- }) ();
- //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- @@ -598,12 +667,14 @@
- output = {
- },
- field = {
- };
-
- + },
- + post_id=0;
- +
- +
- try {
- var post_id = getPostId(selection, profile);
- + post_id = getPostId(selection, profile);
- }
- catch (error) {
- Host.dbLog('Failed extracting post id
-Profile:' + profile)
- + Host.dbLog('Failed extracting post id
- +Profile:' + profile);
- return;
- }
- if (selection.toString() === '') {
- @@ -678,14 +749,9 @@
- } //runProfileTests
-
- function selfCheckDialog() {
- var sections = '<h3>Important APIs:</h3><div id="api_checks">(to be added here using the Environment.runAPITests() helper)</div>';
- + var sections = '<h3>Important APIs:</h3><div id="api_checks"><font color="red">(to be added here using the Environment.runAPITests() helper)</font></div>';
-
-
- Environment.runAPITests(Host, function(meta) {
- //sections.$('#api_checks').append(meta.name+'<p/>');
- console.log('Running API test '+meta.name);
- console.log((meta.test())?'success':'fail');
- });
-
-
- try {
- @@ -708,6 +774,21 @@
-
-
- var checkDlg = $('<div id="selfCheck" title="Self Check dialog"><p><div id="accordion">' + sections + '</div></p></div>');
- +
- + // run all API tests, invoke the callback to obtain the status
- + Environment.runAPITests(Host, function(meta) {
- +
- + //console.log('Running API test '+meta.name);
- +
- + meta.test(function(result) {
- + var status = (result)?'success':'fail';
- + var test = $("<p></p>").text('Running API test '+meta.name+':'+status); ;
- + $('#api_checks', checkDlg).append(test);
- + });
- +
- + });
- +
- +
- //$('#accordion',checkDlg).accordion();
- checkDlg.dialog({
- width: 700,
- @@ -725,7 +806,7 @@
- // show a simple configuration dialog (WIP)
- function setupDialog() {
- //alert("configuration dialog is not yet implemented");
- var checked = (Host.get_persistent('debug_mode_enabled', false) == true) ? 'checked' : '';
- + var checked = (Host.get_persistent('debug_mode_enabled', false) === true) ? 'checked' : '';
- //dbLog("value is:"+get_persistent("debug_mode_enabled"));
- //dbLog("persistent debug flag is:"+checked);
- var setupDiv = $('<div id="setupDialog" title="Setup dialog">NOTE: this configuration dialog is still work-in-progress</p><label><input id="debugcb" type="checkbox"' + checked + '>Enable Debug mode</label><p/><div id="progressbar"></div></div>');
- @@ -771,15 +852,23 @@
-
- } // downloadOptionsXML
-
-function getProfile() {
- +function getProfile(url=undefined) {
- +
- + if(url === undefined)
- + url=window.location.href;
- + else
- + url=url;
- +
- + Host.dbLog("getProfile call URL is:"+url);
- +
- +
- for (var profile in CONFIG) {
- if (window.location.href.match(CONFIG[profile].url_reg) !== null) {
- + if (url.match(CONFIG[profile].url_reg) !== null) {
- Host.dbLog('Matching website profile found');
- var invocations = Host.get_persistent(Host.getScriptVersion(), 0);
- Host.dbLog('Number of script invocations for version ' + Host.getScriptVersion() + ' is:' + invocations);
-
- // determine if we want to show a config dialog
- if (invocations == 0) {
- + if (invocations === 0) {
- Host.dbLog("ask for config dialog to be shown");
- var response = confirm('This is your first time running version ' + Host.getScriptVersion() + '\nConfigure now?');
- if (response) {
- @@ -793,10 +882,10 @@
- } // increment number of invocations, use the script's version number as the key, to prevent the config dialog from showing up again (except for updated scripts)
-
- Host.dbLog("increment number of script invocations");
- Host.set_persistent(Host.getScriptVersion(), invocations + 1)
- + Host.set_persistent(Host.getScriptVersion(), invocations + 1);
- return CONFIG[profile];
- }
- Host.dbLog('Could not find matching URL in getProfile() call!')
- + Host.dbLog('Could not find matching URL in getProfile() call!');
- }
- }// Get the HTML code that is selected
-
- @@ -956,6 +1045,8 @@
- return html.replace(/<!--.*?-->/g, '');
- }// Not currently used (as of June 2015), but kept just in case
-
- +
- +// currently unused
- function escapePipes(html) {
- html = html.replace(/\|\|/g, '{{!!}}');
- html = html.replace(/\|\-/g, '{{!-}}');
- @@ -1026,7 +1117,7 @@
- }// Strips any whitespace from the beginning and end of a string
-
- function stripWhitespace(html) {
- html = html.replace(/^\s*?(\S)/, '$1')
- + html = html.replace(/^\s*?(\S)/, '$1');
- return html.replace(/(\S)\s*?\z/, '$1');
- }// Process code, including basic detection of language
-
- @@ -1058,6 +1149,7 @@
- return html.replace(/\[.*? Watch on Vimeo\]/g, '');
- }// Not currently used (as of June 2015), but kept just in case
-
- +// currently unused
- function escapeEquals(html) {
- return html.replace(/=/g, '{{=}}');
- }// <br> to newline.
- @@ -1100,7 +1192,8 @@
- } else {
- return 'th';
- }
-};
- +}
- +
- // Formats the date to this format: Apr 26th, 2015
- function datef(text) {
- var date = new Date(text);