Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/// User:PerfektesChaos/js/autoBackup/d.js
/// 2021-05-21 PerfektesChaos@de.wikipedia
// Backup Wiki TEXTAREA in regular intervals
// ResourceLoader: compatible;
// dependencies: user,
// mediawiki.api, mediawiki.user, mediawiki.util
/// Fingerprint: #0#0#
/// @license: CC-by-sa/4.0
/// <nowiki>
/* jshint forin:false */
/* global window:false, JSON:false */
/* jshint bitwise:true, curly:true, eqeqeq:true, latedef:true,
laxbreak:true,
nocomma:true, strict:true, undef:true, unused:true */
( function ( mw, $ ) {
"use strict";
var VERSION = -2.4,
BAK = "autoBackup";
if ( typeof mw.libs[ BAK ] !== "object"
|| ! mw.libs[ BAK ] ) {
mw.libs[ BAK ] = { };
}
mw.libs[ BAK ].type = BAK;
BAK = mw.libs[ BAK ];
BAK.doc = "[[w:en:User:PerfektesChaos/js/" + BAK + "]]";
BAK.vsn = VERSION;
BAK.cnf = { maxAge: 72,
maxHist: 5,
maxPages: 10,
maxRev: 3,
mid: 5,
msec: 300000 };
BAK.disk = { self: "AutoBackupPerfectChaos",
stick: "newest|subject" };
BAK.gui = { };
BAK.util = { };
if ( ! typeof BAK.opt || typeof BAK.opt !== "object" ) {
BAK.opt = { };
}
// User options:
// .maxAge number of full hours to remember
// .maxHist number of history entries per revision to keep
// .maxPages number of pages to handle simultaneously
// .maxRev number of revisions to keep
// .mid number of minutes for scheduled snapshots
// .portlet add portlet link
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*/
// Requires: JavaScript 1.3 (String.charCodeAt String.replace)
// ECMA 262-3 § 11.8.5 (string comparison operators)
// MediaWiki 1.18 (mw.libs, jQuery core)
/*
.pages
wgArticleId { }
newest: timestamp
subject: last known page name
wgCurRevisionId { }
newest: timestamp
history: [
[ timestamp-new, snapshot ],
[ timestamp-old, snapshot ],
[ timestamp-older, snapshot ],
]
*/
BAK.cnf.text = {
// 2014-09-23 PerfektesChaos@de.wikipedia
"BAKself": {"en": "AutoBackup",
"de": "AutoBackup"},
"IntJSONparse": {"en": "Internal ERROR -- JSON.parse",
"de": "Interner FEHLER -- JSON.parse"},
"NoLSavail": {"en": "No localStorage available",
"de": "Kein localStorage zugreifbar"},
"otherPages": {"en": "Other Pages",
"de": "Andere Seiten"},
"pending": {"en": "Recover from pending abort?",
"de": "Wiederherstellung nach Abbruch?"},
"setItemExcept": {"en": "ERROR setting localStorage",
"de": "FEHLER beim Setzen im localStorage"},
"thisPage": {"en": "this page", //oldid
"de": "diese Seite"},
"VanishedLS": {"en": "ERROR: localStorage vanished",
"de": "FEHLER: localStorage verschwunden"},
"WriteCrash": {"en": "Crash on localStorage write attempt",
"de": "Schreibversuch auf localStorage versagt"}
}; // .cnf.text
BAK.cnf.translang = {
// 2012-11-30 PerfektesChaos@de.wikipedia
"de" : "de",
"de-at" : "de",
"de-ch" : "de",
"de-formal" : "de",
"als" : "de",
"bar" : "de",
"dsb" : "de",
"frr" : "de",
"gsw" : "de",
"hsb" : "de",
"ksh" : "de",
"lb" : "de",
"nds" : "de",
"pdc" : "de",
"pdt" : "de",
"pfl" : "de",
"sli" : "de",
"stq" : "de",
"vmf" : "de"
}; // .cnf.translang
BAK.cnf.favorite = function () {
// Guess user language
// Uses:
// this
// > .translang
// >< .slang
// mw.config.get()
// 2012-11-21 PerfektesChaos@de.wikipedia
var s;
if ( ! this.slang ) {
s = mw.config.get( "wgUserLanguage" ).toLowerCase();
s = this.translang[ s ];
if ( s ) {
this.slang = s;
} else {
this.slang = "en";
}
}
}; // .cnf.favorite()
BAK.cnf.feature = function ( apply ) {
// Wrap message text access for user language
// Precondition:
// apply -- text keyword
// Postcondition:
// Return text closest to user language
// Uses:
// this
// > .cnf.text
// .cnf.favorite()
// Remark: To be replaced
// if one day ResourceLoader3 gives access to
// gadget@translatewiki
// 2012-11-04 PerfektesChaos@de.wikipedia
var e, r;
if ( ! this.slang ) {
this.favorite();
}
e = this.text[ apply ];
if ( e ) {
r = e[ this.slang ];
if ( ! r ) {
r = e.en;
if ( ! r ) {
r = "???" + apply + "???";
}
}
} else {
r = "***" + apply + "***";
}
return r;
}; // .cnf.feature()
BAK.cnf.fetch = function () {
// Consider user options
// Uses:
// this
// > .opt
// > .learn
// >< .cnf.load
// < .cnf.maxAge
// < .cnf.maxHist
// < .cnf.maxPages
// < .cnf.maxRev
// < .cnf.mid
// < .cnf.msec
// .gui.facility()
// 2012-11-30 PerfektesChaos@de.wikipedia
if ( ! this.load ) {
if ( typeof BAK.opt === "object" ) {
if ( typeof BAK.opt.maxAge === "number" ) {
if ( BAK.opt.maxAge >= 0 ) {
this.maxAge = Math.floor( BAK.opt.maxAge );
}
}
if ( typeof BAK.opt.maxHist === "number" ) {
if ( BAK.opt.maxHist >= 1 ) {
this.maxHist = Math.ceil( BAK.opt.maxHist );
}
}
if ( typeof BAK.opt.maxPages === "number" ) {
if ( BAK.opt.maxPages >= 1 ) {
this.maxPages = Math.ceil( BAK.opt.maxPages );
}
}
if ( typeof BAK.opt.maxRev === "number" ) {
if ( BAK.opt.maxRev >= 1 ) {
this.maxRev = Math.ceil( BAK.opt.maxRev );
}
}
if ( BAK.learn ) {
if ( typeof BAK.opt.mid === "number" ) {
if ( BAK.opt.mid > 0 ) {
this.mid = BAK.opt.mid;
} else {
this.mid = false;
}
}
if ( this.mid ) {
this.msec = this.mid * 60000;
} else {
this.msec = false;
}
if ( BAK.opt.portlet ) {
BAK.gui.facility();
}
}
}
this.load = true;
}
}; // .cnf.fetch()
BAK.disk.fetch = function () {
// Retrieve pages object from localStorage data
// Postcondition:
// .pages is set to collection object, or false
// Uses:
// this
// > .disk.storage
// >< window.localStorage
// < .pages
// < .disk.lock
// .family()
// JSON.parse()
// .gui.flag()
// 2014-09-23 PerfektesChaos@de.wikipedia
var s;
BAK.pages = false;
if ( typeof window.localStorage === "object" ) {
BAK.family();
s = window.localStorage.getItem( this.storage );
if ( s ) {
try {
BAK.pages = JSON.parse( s );
} catch ( e ) {
BAK.gui.flag( "IntJSONparse", false );
window.localStorage.setItem( this.storage, "" );
}
}
} else {
BAK.gui.flag( "NoLSavail", false );
this.lock = true;
}
}; // .disk.fetch()
BAK.disk.flush = function () {
// Put .pages object into localStorage
// Postcondition:
// Clear empty .pages object
// Uses:
// this
// > .disk.storage
// >< window.localStorage
// >< .pages
// jQuery.toJSON()
// .gui.flag()
// 2014-09-23 PerfektesChaos@de.wikipedia
var store = false,
s;
if ( typeof BAK.pages === "object" ) {
for ( s in BAK.pages ) {
store = true;
break; // for s
} // for s
}
if ( ! store ) {
this.pages = false;
}
if ( typeof window.localStorage === "object" ) {
if ( BAK.pages ) {
store = JSON.stringify( BAK.pages );
if ( store === "{}") {
store = "";
}
} else {
store = "";
}
try {
window.localStorage.setItem( this.storage, store );
s = window.localStorage.getItem( this.storage );
if ( s !== store ) {
BAK.gui.flag( "WriteCrash", false );
}
} catch ( e ) {
BAK.gui.flag( "setItemExcept", false );
}
window.localStorage.setItem( this.storage, store );
} else {
BAK.gui.flag( "VanishedLS", false );
}
}; // .disk.flush()
BAK.gui.face = function ( apply ) {
// Prepare GUI message area for content
// Precondition:
// apply -- HTML message box content
// Uses:
// this
// > .$page
// >< .gui.$top
// >< .gui.$div
// >< .gui.$msg
// mw.util.addCSS()
// jQuery()
// jQuery().prepend()
// jQuery().before()
// jQuery().empty()
// jQuery().attr()
// jQuery().append()
// 2021-05-21 PerfektesChaos@de.wikipedia
var s;
if ( ! this.$div ) {
mw.util.addCSS( ".cn-fundraiser-banner,"
+ "#mw-js-message,"
+ "#siteNotice,"
+ "#fundraising\n"
+ "{display: none ! important;}" );
this.$div = $( "<div class='AutoBackupDiv' />" );
if ( ! this.$top ) {
this.$top = $( "#mw-content-text" );
if ( this.$top.length ) {
this.$top.prepend( this.$div );
} else {
this.$top = $( ".mw-body-content" ).eq( 0 );
if ( ! this.$top.length ) {
this.$top = $( "#article" );
if ( ! this.$top.length ) {
this.$top = $( "#content" );
if ( ! this.$top.length ) {
this.$top = BAK.$page;
}
}
}
this.$top.before( this.$div );
}
}
}
if ( apply ) {
if ( this.$msg ) {
this.$msg.empty();
} else {
this.$msg = $( "<div class='AutoBackupMsg' />" );
this.$div.prepend( this.$msg );
}
if ( this.$msg ) {
s = "display: block;"
+ "font-size: 120%;";
this.$msg.attr( "style", s );
this.$msg.append( apply );
}
} else {
if ( this.$msg ) {
this.$msg.attr( "style", "display: none;" );
}
}
}; // .gui.face()
BAK.gui.facility = function () {
// Equip portlet with link to trigger
// Precondition:
// document.ready
// Uses:
// >< .cnf.self
// > .type
// > .vsn
// .cnf.feature()
// mw.util.addPortletLink()
// (.fresh)
// 2017-01-04 PerfektesChaos@de.wikipedia
var portlet, $portlet;
if ( ! BAK.cnf.self ) {
BAK.cnf.self = BAK.cnf.feature( "BAKself" );
}
portlet = mw.util.addPortletLink( "p-cactions",
"#",
BAK.cnf.self,
"ca-" + BAK.type );
$portlet = $( portlet );
$portlet.click( BAK.fresh );
$portlet.attr( { title: BAK.type + " " + BAK.vsn } );
}; // .gui.facility()
BAK.gui.feed = function () {
// Retrieve content of text area; wpTextbox2 if edit conflict
// Precondition:
// document.ready
// Postcondition:
// Return textarea string; or false
// Uses:
// this
// > .$page
// > .gui.$textarea
// > wikEd
// >< .gui.leading
// < .gui.$editform
// < .gui.$textarea
// jQuery.find()
// wikEd.UpdateTextarea()
// 2019-07-01 PerfektesChaos@de.wikipedia
var r = false,
$form,
$ta;
if ( this.leading ) { // opening
this.leading = false;
this.$editform = false;
this.$textarea = false;
$form = BAK.$page.find( "#editform" );
if ( $form ) {
$ta = BAK.$page.find( "#wpTextbox2" );
if ( $ta.length ) { // edit conflict
r = $ta.val();
}
$ta = $form.find( "#wpTextbox1" );
if ( $ta.length ) { // textarea
if ( ! $ta.attr( "readonly" ) ) { // modifiable
this.$editform = $form;
this.$textarea = $ta;
}
} // textarea
}
}
if ( this.$textarea && ! r ) { // editing
if ( window.wikEd
&& typeof window.wikEd === "object"
&& ! window.wikEd.disabled
&& window.wikEd.turnedOn
&& window.wikEd.useWikEd
&& window.wikEd.UpdateTextarea ) {
window.wikEd.UpdateTextarea();
}
r = this.$textarea.val();
} // editing
return r;
}; // .gui.feed()
BAK.gui.fence = function ( apply ) {
// Draw box around entire autoBAK rectangle
// apply -- true if to be shown, or false to remove
// Uses:
// this
// > .gui.$div
// jQuery().attr()
// 2012-10-29 PerfektesChaos@de.wikipedia
this.$div.attr( "style",
( apply ? "border: solid 1px #606060;"
+ " border-bottom: solid 5px #606060;"
+ " padding: 0.5em;"
+ " margin-bottom: 2em;"
: null ) );
}; // .gui.fence()
BAK.gui.fiat = function ( action, add ) {
// Create button
// Precondition:
// action -- identifier
// add -- data identifier
// Postcondition:
// Return html string; or ""
// 2012-10-29 PerfektesChaos@de.wikipedia
var r;
switch ( action ) {
case "ALL" :
case "SHOW" :
r = "<span"
+ " style='font-size: 200%;"
+ " font-weight: bold;"
+ " color: #008000;'>*</span>";
break;
case "DELETE" :
r = "<span"
+ " style='font-size: 200%;"
+ " font-weight: bold;"
+ " color: #FF0000;'>X</span>";
break;
default:
r = "";
} // switch action
if ( r ) {
r = "\n \n"
+ "<button type='button'"
+ " id='autoBackup" + action
+ ( add ? add : "" ) + "'>"
+ r
+ "</button>";
}
return r;
}; // .gui.fiat()
BAK.gui.fill = function () {
// Fill current page with offers
// Uses:
// > .pages
// > .pageID
// > .gui.$msg
// > .disk.stick
// >< .gui.$box
// >< .revID
// jQuery().find()
// jQuery().remove()
// mw.config.get()
// .gui.fence()
// .gui.face()
// Remark: Used as event handler -- 'this' is not BAK
// 2012-11-04 PerfektesChaos@de.wikipedia
var page = BAK.pages[ BAK.pageID ],
revs = false,
i, n, s;
BAK.gui.$msg.find( "#autoBackupALL" ).remove();
if ( BAK.gui.$box ) {
BAK.gui.$box.remove();
}
if ( page ) {
BAK.gui.$box = $( "<div />" );
BAK.gui.$msg.after( BAK.gui.$box );
if ( ! BAK.revID ) {
BAK.revID = mw.config.get( "wgCurRevisionId" );
}
for ( i in page ) {
if ( BAK.disk.stick.indexOf( i ) < 0 ) {
s = page[ i ].newest + " " + i;
if ( revs ) {
revs.push( s );
} else {
revs = [ s ];
}
}
} // for i in page
n = revs.length;
revs.sort();
for ( i = revs.length-1; i >= 0; i-- ) {
BAK.gui.folder( revs[ i ], page );
} // for i--
BAK.gui.fence( true );
} else {
BAK.gui.face( false );
}
}; // .gui.fill()
BAK.gui.finish = function ( access ) {
// Define additional hook on standard editform button
// Precondition:
// access -- button ID
// Uses:
// this
// > .gui.$editform
// jQuery().find()
// jQuery().click()
// (.fresh)
// 2012-10-29 PerfektesChaos@de.wikipedia
var $btn = this.$editform.find( "#" + access );
if ( $btn.length ) {
$btn.click( BAK.fresh );
}
}; // .gui.finish()
BAK.gui.flag = function ( apply, action ) {
// Display error message similar to mw.util.jsMessage() MW 1.19
// Precondition:
// apply -- text keyword
// action -- identifier for button, or false if error case
// Uses:
// this
// > .cnf.slang
// >< .cnf.self
// .cnf.feature()
// .gui.fiat()
// .gui.face()
// .gui.form()
// 2012-11-03 PerfektesChaos@de.wikipedia
var s;
if ( ! BAK.cnf.self ) {
BAK.cnf.self = BAK.cnf.feature( "BAKself" );
}
s = BAK.cnf.feature( apply );
s = "<div id='autoBackup'"
+ " style='border: solid "
+ (action ? 2 : 5)
+ "px #FF0000;"
+ " padding: 0.5em;"
+ (action ? ""
: " color: #FF0000;")
+ "'>\n"
+ "<span style='font-size: larger; font-weight: bold;'>"
+ BAK.cnf.self + "</span> \n"
+ (action ? ""
: "<span class='error'>")
+ s
+ (action ? "\n"
: "</span>\n")
+ this.fiat( action )
+ "</div>\n";
this.face( s );
if ( action ) {
this.form( action, "" );
}
}; // .gui.flag()
BAK.gui.folder = function ( access, album ) {
// Insert single revision data into current page
// Precondition:
// access -- string sortkey_space_symbol
// album -- page revisions
// Uses:
// this
// > .gui.$box
// > .revID
// jQuery().append()
// jQuery().find()
// .cnf.feature()
// mw.config.get()
// .gui.fiat()
// .gui.form()
// .util.focus()
// .jQuery()
// .gui.full()
// 2012-11-06 PerfektesChaos@de.wikipedia
var e = access.split( " " ),
k = e[ 1 ],
s = "autoBackupR" + k,
i, w, $div, $textarea;
this.$box.append( "<div id='" + s + "' />" );
$div = this.$box.find( "#" + s );
if ( parseInt( k, 10 ) === BAK.revID ) {
s = k + " (" + BAK.cnf.feature( "thisPage" ) + ")";
} else {
s = mw.config.get( "wgArticlePath" );
s = "<a href='"
+ s.replace( /\$1/, "Special:PermanentLink/" ) + k
+ "' target='_blank'>" + k + "</a>";
}
s = "<h4>oldid=" + s + "</h4>";
s = s + this.fiat( "DELETE", k );
//
$div.append( s );
this.form( "DELETE", k );
e = album[ k ].history;
for ( i = 0; i < e.length; i++ ) {
k = e[ i ];
w = k[ 1 ];
s = "<h5>" + BAK.util.focus( k[ 0 ] ) + "</h5>"
+ "<p>" + w.length + " bytes</p>";
$div.append( s );
$textarea = $( "<textarea rows='5' readonly />" );
$textarea.val( w );
$div.append( $textarea );
} // for i++
this.full();
}; // .gui.folder()
BAK.gui.form = function ( action, add ) {
// Provide click action for button
// Precondition:
// action -- identifier
// add -- data distinguisher, or ""
// Uses:
// this
// > .gui.$top
// jQuery().find()
// jQuery().click()
// (.fixed)
// 2012-11-04 PerfektesChaos@de.wikipedia
var fun = false,
sign,
$btn;
switch ( action ) {
case "ALL" :
fun = this.fill;
break;
case "DELETE" :
sign = "DELETE" + add;
fun = function () { BAK.fixed( add ); };
BAK[ sign ] = fun;
break;
case "SHOW" :
break;
} // switch action
if ( fun ) {
$btn = this.$top.find( "#autoBackup" + action + add );
if ( $btn.length ) {
$btn.click( fun );
}
}
}; // .gui.form()
BAK.gui.full = function () {
// Display otherPages offers on GUI
// Uses:
// this
// > .pages
// > .pageID
// > .gui.$div
// >< .gui.reSpace
// jQuery().empty()
// .cnf.feature()
// jQuery().append()
// jQuery().find()
// mw.config.get()
// mw.util.wikiUrlencode()
// .util.focus()
// .gui.fence()
// 2012-11-06 PerfektesChaos@de.wikipedia
var e, g, i, p, s, u, $x;
if ( BAK.pages ) {
g = false;
s = "" + BAK.pageID;
for ( i in BAK.pages ) {
if ( i !== s ) {
p = BAK.pages[ i ];
if ( p.subject ) {
if ( ! this.reSpace ) {
this.reSpace = new RegExp(" +", "g");
}
e = " " + p.subject.replace( this.reSpace, "_" );
} else {
e = "";
}
e = p.newest + " " + i + e;
if ( g ) {
g.push( e );
} else {
g = [ e ];
}
}
} // for i
if ( g ) {
s = "<h3>"
+ BAK.cnf.feature( "otherPages" )
+ "</h3>\n"
+ "<ul id='autoBackupPageList' />";
this.$div.append( s );
$x = this.$div.find( "#autoBackupPageList" );
u = "<a target='_blank'"
+ " href='" + mw.config.get( "wgScript" ) + "?";
g.sort();
for ( i = g.length - 1; i >= 0; i-- ) {
e = g[ i ];
p = e.split( " " );
s = p[ 1 ];
if ( s.substr( 0, 2 ) === "0_" ) {
s = s.substr( 2 );
s = u + "title=" + mw.util.wikiUrlencode( s )
+ "&redirect=no'>" + s + "</a> ";
} else {
s = "curid=" + u + "curid=" + s + "'>" + s + "</a> ";
if ( p[ 2 ] ) {
s = s + "(" + p[ 2 ].replace( /_/g, " " )
.replace( /</g, "<" )
+ ") ";
}
}
s = "<li>" + s + BAK.util.focus( p[ 0 ] )
+ "</li>";
$x.append( s );
} // for i--
}
this.fence( true );
}
}; // .gui.full()
BAK.gui.further = function () {
// Try to define additional hooks on buttons
// Uses:
// this
// > .gui.$editform
// .gui.finish()
// (.fresh)
// 2012-11-03 PerfektesChaos@de.wikipedia
if ( this.$editform ) { // editing
this.finish( "wpDiff" );
this.finish( "wpPreview" );
this.finish( "wpSave" );
this.$editform.find( ".mw-summary" ).focusin( BAK.fresh );
}
}; // .gui.further()
BAK.util.figure = function ( ask ) {
// Is ask a positive number or zero?
// Precondition:
// ask -- string
// Postcondition:
// Return true iff ask contains only digits
// 2012-10-29 PerfektesChaos@de.wikipedia
return (/^[0-9]+$/).test( ask );
}; // .util.figure()
BAK.util.flat = function ( album, amount, arrange ) {
// Reduce number of entries in object; remove lowest sorted
// Precondition:
// album -- object to be limited
// amount -- maximum number of components within album
// arrange -- Array with sort keys and IDs, or false
// every entry is string sortkey_space_symbol
// 2012-11-07 PerfektesChaos@de.wikipedia
var e, i, k, s;
if ( arrange ) {
k = arrange.length - amount;
if ( k > 0 ) {
arrange.sort();
for ( i = 0; i <= k; i++ ) {
e = arrange[ i ];
s = e.split( " " );
delete album[ s[ 1 ] ];
} // for i
}
}
}; // .util.flat()
BAK.util.focus = function ( appoint ) {
// Format ISO 8601 UTC timestamp according to local time
// Precondition:
// appoint -- Date object
// Postcondition:
// Return ISO 8601 string in local time zone
// Uses:
// this
// > mw.user
// > mw.user.options
// >< .cnf.justify
// .format()
// 2014-03-28 PerfektesChaos@de.wikipedia
var r = appoint + " UTC",
s = typeof BAK.cnf.justify,
g;
if ( s !== "boolean" && s !== "number" ) {
BAK.cnf.justify = false;
if ( typeof mw.user === "object" ) {
if ( mw.user.options ) {
s = mw.user.options.get( "timecorrection" );
if ( s ) { // "System|120"
g = /\|?([0-9]+)$/.exec( s );
if ( g ) {
BAK.cnf.justify = parseInt( g[1], 10 );
}
}
}
}
}
if ( BAK.cnf.justify === 0 ) {
r = appoint;
} else if ( typeof BAK.cnf.justify === "number" ) {
g = /^([0-9]+)-([01][0-9])-([0-3][0-9])T([0-2][0-9]):([0-6][0-9]):([0-6][0-9])$/.exec( appoint );
if ( g ) {
r = Date.UTC( parseInt( g[1], 10 ),
parseInt( g[2], 10 ) - 1,
parseInt( g[3], 10 ),
parseInt( g[4], 10 ),
parseInt( g[5], 10 ) + BAK.cnf.justify,
parseInt( g[6], 10 ) );
r = this.format( new Date( r ) );
}
}
return r;
}; // .util.focus()
BAK.util.format = function ( appoint ) {
// Format timestamp according to ISO 8601
// Precondition:
// appoint -- Date object
// Postcondition:
// Return ISO 8601 string in local time zone
// 2012-10-29 PerfektesChaos@de.wikipedia
var r = appoint.getUTCFullYear() + "-",
i = appoint.getUTCMonth() + 1;
if ( i < 10 ) {
i = "0" + i;
}
r = r + i + "-";
i = appoint.getUTCDate();
if ( i < 10 ) {
i = "0" + i;
}
r = r + i + "T";
i = appoint.getUTCHours();
if ( i < 10 ) {
i = "0" + i;
}
r = r + i + ":";
i = appoint.getUTCMinutes();
if ( i < 10 ) {
i = "0" + i;
}
r = r + i + ":";
i = appoint.getUTCSeconds();
if ( i < 10 ) {
i = "0" + i;
}
return r + i;
}; // .util.format()
BAK.family = function () {
// Establish distinguishing page identifier
// Postcondition:
// .pageID is available
// Uses:
// >< .pageID
// < .pageName
// mw.config.get()
// 2014-07-05 PerfektesChaos@de.wikipedia
var env;
if ( ! this.pageID ) {
env = mw.config.get( [ "wgArticleId",
"wgPageName" ] );
this.pageID = env.wgArticleId;
this.pageName = env.wgPageName;
if ( this.pageID === 0 ) {
this.pageID = "0_"
+ this.pageName.replace( /~/, "%7E" );
}
}
}; // .family()
BAK.fast = function () {
// Check sessionStorage for quick page id test on view
// Postcondition:
// Return true iff localStorage should be evaluated
// Uses:
// this
// > .disk.storage
// > window.sessionStorage
// >< .pageID
// >< .pageName
// < .later
// mw.config.get()
// 2013-08-23 PerfektesChaos@de.wikipedia
var r = true,
s = window.sessionStorage.getItem( this.disk.storage ),
q;
if ( s ) {
r = ( s.length > 1 );
if ( r ) {
if ( ! this.pageID ) {
this.pageID = mw.config.get( "wgArticleId" );
}
r = ( s.indexOf( "~" + this.pageID + "~" ) >= 0 );
if ( ! r ) {
if ( s.indexOf( "~0_" ) >= 0 ) {
if ( ! BAK.pageName ) {
BAK.pageName = mw.config.get( "wgPageName" );
}
q = "0_" + BAK.pageName.replace( /~/, "%7E" );
if ( s.indexOf( "~" + q + "~" ) >= 0 ) {
r = true;
this.later = true;
}
}
}
}
}
return r;
}; // .fast()
BAK.filter = function () {
// Cleanup repository, discard old and supernumerous entries
// Precondition:
// .pages is an object
// Uses:
// this
// > .disk.stick
// > .cnf.maxAge
// > .cnf.maxRev
// > .cnf.maxPages
// > .gui.$div
// > .disk.storage
// >< .pages
// >< .now
// >< window.sessionStorage
// .cnf.fetch()
// .util.format()
// .util.flat()
// .gui.fence()
// Requires: ECMA 262-3 § 11.8.5 (string comparison operators)
// 2013-08-23 PerfektesChaos@de.wikipedia
var lazy = true,
store = "~",
hist, page, pid, revs, rid, sift, stamp;
this.cnf.fetch();
if ( this.cnf.maxAge ) {
if ( ! this.now ) {
this.now = new Date();
}
sift = Date.UTC( this.now.getUTCFullYear(),
this.now.getUTCMonth(),
this.now.getUTCDate(),
this.now.getUTCHours() - this.cnf.maxAge,
0,
0 );
sift = this.util.format( new Date( sift ) );
} else {
sift = "1970-01-01T00:00:00";
}
for ( pid in this.pages ) {
page = this.pages[ pid ];
if ( page.newest < sift ) {
delete this.pages[ pid ];
} else {
revs = false;
for ( rid in page ) {
if ( this.disk.stick.indexOf( rid ) < 0 ) {
stamp = page[ rid ].newest;
if ( stamp > sift ) {
stamp = stamp + " " + rid;
if ( revs ) {
revs.push( stamp );
} else {
revs = [ stamp ];
}
} else {
delete page[ rid ];
}
}
} // for rid in page
if ( revs ) {
page = page.newest + " " + pid;
if ( hist ) {
hist.push( page );
} else {
hist = [ page ];
}
this.util.flat( page, this.cnf.maxRev, revs );
store = store + pid + "~";
lazy = false;
} else {
delete this.pages[ pid ];
}
}
} // for pid in .pages
this.util.flat( this.pages, this.cnf.maxPages, hist );
window.sessionStorage.setItem( this.disk.storage, store );
if ( lazy && this.gui.$div ) {
this.gui.fence();
}
}; // .filter()
BAK.finalize = function ( already, add ) {
// Remove trailing whitespace and compare to recent text
// Precondition:
// already -- old text, or empty if new
// add -- new text
// Postcondition:
// Return new text iff significant change, or false
// Requires: JavaScript 1.3 String.charCodeAt
// 2012-10-29 PerfektesChaos@de.wikipedia
var k = add.length,
r = add,
i;
for ( i = k; i > 0; i-- ) {
if ( add.charCodeAt( i - 1 ) > 32 ) {
break; // for i--
}
} // for i--
if ( i > 1 ) {
if ( i < k ) {
r = add.substr( 0, i );
}
if ( already ) {
if ( i === already.length ) {
if ( r === already ) {
r = false;
}
}
}
} else {
r = false;
}
return r;
}; // .finalize()
BAK.find = function () {
// Launch API query for revisions of the same user
// Uses:
// this
// > .pageID
// > .later
// > .pageName
// mw.Api
// mw.util.wikiUrlencode()
// mw.config.get()
// (.founder)
// (.found)
// 2015-07-07 PerfektesChaos@de.wikipedia
var q = new mw.Api(),
w = { action: "query",
"continue": "", // TEMP ??
pageids: this.pageID,
prop: "revisions",
rvlimit: 3,
rvuser: mw.util.wikiUrlencode(
mw.config.get( "wgUserName" ) )
};
if ( this.later ) {
delete w.pageids;
w.titles = this.pageName;
q.get( w ).done( this.founder );
} else {
q.get( w ).done( this.found );
}
}; // .find()
BAK.first = function () {
// Opening edit page; check for pending attempts
// Precondition:
// Page in edit::edit mode
// Uses:
// this
// > .pages
// > mw.user
// > mw.user.options
// > .pageID
// < .gui.leading
// < .livePreview
// .disk.fetch()
// .find()
// jQuery.bind()
// mw.hook()
// (.fresh)
// 2019-06-17 PerfektesChaos@de.wikipedia
this.$editform = false;
this.disk.fetch();
if ( this.pages ) {
if ( this.pages[ this.pageID ] ) {
this.find();
}
}
if ( typeof mw.user === "object" ) {
if ( mw.user.options &&
mw.user.options.get( "uselivepreview" ) ) {
this.livePreview = true;
$( mw ).bind( "LivePreviewDone", this.fresh );
}
}
this.gui.leading = true;
mw.hook( "wikipage.content" ).add( this.fresh );
}; // .first()
BAK.fixed = function ( apply ) {
// Remove current page revision from storage and GUI
// Precondition:
// apply -- revision ID
// Uses:
// this
// > .gui.$top
// > .pageID
// >< .pages
// < .disk.launch
// .flush()
// .gui.fence()
// .gui.face()
// 2012-11-04 PerfektesChaos@de.wikipedia
var p;
this.gui.$top.find( "#autoBackupR" + apply ).remove();
if ( this.pages ) {
p = this.pages[ BAK.pageID ];
if ( p ) {
if ( p[ apply ] ) {
delete p[ apply ];
this.disk.launch = true;
this.flush();
if ( ! this.pages ) {
this.gui.fence( false );
} else if ( ! this.pages[ BAK.pageID ] ) {
this.gui.face( false );
}
}
}
}
}; // .fixed()
BAK.flush = function () {
// Write repository
// Uses:
// this
// > .disk.launch
// > .pages
// .filter()
// .disk.flush()
// 2012-10-29 PerfektesChaos@de.wikipedia
if ( this.disk.launch ) {
if ( this.pages ) {
this.filter();
}
this.disk.flush();
}
}; // .flush()
BAK.folder = function () {
// API: Display list of links to all other pages on current page
// Uses:
// > .gui.$div
// > .gui.$msg
// >< .cnf.self
// .cnf.feature()
// jQuery().prepend()
// .gui.face()
// .gui.fill()
// .gui.filter()
// .gui.full()
// Remark: May be used as event handler -- 'this' is not BAK
// 2012-11-30 PerfektesChaos@de.wikipedia
BAK.gui.face();
if ( ! BAK.gui.$msg ) {
if ( ! BAK.cnf.self ) {
BAK.cnf.self = BAK.cnf.feature( "BAKself" );
}
BAK.gui.$div.prepend( "<h2>" + BAK.cnf.self + "</h2>" );
}
BAK.gui.fill();
BAK.gui.filter();
BAK.gui.full();
}; // .folder()
BAK.follow = function () {
// Check whether current page displays saved edit
// Precondition:
// Page in view mode
// Uses:
// this
// > .pages
// > .later
// > .pageID
// < .revID
// .disk.fetch()
// .find()
// mw.config.get()
// .gui.flag()
// 2012-11-30 PerfektesChaos@de.wikipedia
var h, i, p;
this.disk.fetch();
if ( this.later ) {
this.find();
} else if ( this.pages ) {
p = this.pages[ this.pageID ];
if ( p ) {
this.revID = mw.config.get( "wgCurRevisionId" );
for ( h in p ) {
i = parseInt( h, 10 );
if ( i ) {
if ( this.revID > i ) {
this.find();
break; // for h
} else if ( this.revID === i ) {
this.gui.flag( "pending", "ALL" );
break; // for h
}
}
} // for h in p
}
}
}; // .follow()
BAK.found = function ( arrived ) {
// Postprocess in page view after ajax request
// Precondition:
// arrived -- JSON result of ajax query
// Previously it has been detected that revID is open snapshot.
// Uses:
// > .pageID
// > .revID
// >< .pages
// < .disk.launch
// .flush()
// Remark: Used as event handler -- 'this' is not BAK
// 2012-10-29 PerfektesChaos@de.wikipedia
var query = ( typeof arrived === "object" ),
learnt = false,
i;
if ( query ) {
query = arrived.query;
if ( query ) {
query = query.pages[ BAK.pageID ];
if ( query ) {
query = query.revisions;
if ( query ) {
for ( i = 0; i < query.length; i++ ) {
if ( query[i].revid === BAK.revID ) {
learnt = true;
delete BAK.pages[ BAK.pageID ];
BAK.disk.launch = true;
break; // for i
}
} // for i
}
}
}
}
BAK.flush();
if ( ! learnt ) {
BAK.gui.flag( "pending", "ALL" );
}
}; // .found()
BAK.founder = function ( arrived ) {
// Postprocess a created page in page view after ajax request
// Precondition:
// arrived -- JSON result of ajax query
// Previously it has been detected revID=0 was open snapshot.
// Uses:
// > .pageName
// >< .pages
// < .pageID
// < .disk.launch
// .flush()
// Remark: Used as event handler -- 'this' is not BAK
// 2012-12-02 PerfektesChaos@de.wikipedia
var query = ( typeof arrived === "object" ),
learnt = false;
if ( query ) {
query = arrived.query;
if ( query ) {
query = query.pages[ BAK.pageID ];
if ( query ) {
BAK.pageID = "0_"
+ BAK.pageName.replace( /~/, "%7E" );
delete BAK.pages[ BAK.pageID ];
BAK.disk.launch = true;
learnt = true;
}
}
}
BAK.flush();
if ( ! learnt ) {
BAK.gui.flag( "pending", "ALL" );
}
}; // .founder()
BAK.fresh = function () {
// API: Add snapshot to storage if content changed
// Precondition:
// Start of preview/diff mode, or time-triggered
// Uses:
// > .pageID
// > .pageName;
// > .cnf.maxHist
// > .cnf.msec
// >< .timeoutID
// < .revID
// < .now
// < .disk.launch
// .gui.feed()
// .family()
// mw.config.get()
// .disk.fetch()
// .finalize()
// .util.format()
// .cnf.fetch()
// .flush()
// Remark: Used as event handler -- 'this' is not BAK
// 2013-02-17 PerfektesChaos@de.wikipedia
var shot = BAK.gui.feed(),
page, revs, stamp;
if ( shot ) {
BAK.family();
BAK.revID = mw.config.get( "wgCurRevisionId" );
if ( ! BAK.pages ) {
BAK.disk.fetch();
}
if ( ! BAK.pages ) {
BAK.pages = { };
}
if ( ! BAK.pages[ BAK.pageID ] ) {
BAK.pages[ BAK.pageID ] = { };
}
page = BAK.pages[ BAK.pageID ];
if ( ! page[ BAK.revID ] ) {
page[ BAK.revID ] = { };
}
revs = page[ BAK.revID ].history;
if ( revs ) {
shot = BAK.finalize( revs[0][1], shot );
if ( shot ) {
BAK.now = new Date();
stamp = BAK.util.format( BAK.now );
BAK.cnf.fetch();
if ( revs.length >= BAK.cnf.maxHist - 1 ) {
revs.pop();
}
revs.unshift( [ stamp, shot ] );
}
} else {
shot = BAK.finalize( false, shot );
if ( shot ) {
BAK.now = new Date();
stamp = BAK.util.format( BAK.now );
revs = [ [ stamp, shot ] ];
}
}
if ( shot ) {
page[ BAK.revID ].history = revs;
page[ BAK.revID ].newest = stamp;
page.newest = stamp;
page.subject = BAK.pageName;
BAK.pages[ BAK.pageID ] = page;
BAK.disk.launch = true;
BAK.flush();
}
if ( BAK.cnf.msec ) {
if ( BAK.timeoutID ) {
window.clearTimeout( BAK.timeoutID );
}
BAK.timeoutID = window.setTimeout( BAK.fresh,
BAK.cnf.msec );
}
}
}; // .fresh()
BAK.further = function () {
// Additional triggers in edit mode, to be called only once
// Uses:
// this
// > .gui.$editform
// > .cnf.msec
// >< .learnt
// .gui.further()
// .cnf.fetch()
// (.fresh)
// 2012-10-29 PerfektesChaos@de.wikipedia
if ( ! this.learnt ) { // only once
this.learnt = true;
if ( this.gui.$editform ) { // editing
this.gui.further();
this.cnf.fetch();
if ( this.cnf.msec ) {
window.setTimeout( this.fresh, this.cnf.msec );
}
}
}
}; // .further()
BAK.fire = function () {
// Start page processing; check whether situation is interesting
// Uses:
// > .disk.self
// < .disk.storage
// < .learn
// < .gui.leading
// mw.config.get()
// .follow()
// mw.util.getParamValue()
// mw.hook()
// (.firing)
// Remark: Used as event handler -- 'this' is not BAK
// 2017-01-04 PerfektesChaos@de.wikipedia
var env = mw.config.get( [ "wgAction",
"wgIsArticle",
"wgUserName" ] );
BAK.disk.storage = BAK.disk.self
+ " " + env.wgUserName;
if ( env.wgIsArticle ) {
if ( env.wgAction === "view" &&
BAK.fast() ) {
BAK.follow();
}
} else {
BAK.start = mw.util.getParamValue( "action" );
if ( BAK.start ) {
BAK.learn = true;
if ( "|edit|submit|".indexOf( BAK.start ) > 0 ) {
mw.hook( "wikipage.content" ).add( BAK.firing );
}
}
}
}; // .fire()
BAK.firing = function ( $all ) {
// Start content processing
// Precondition:
// $all -- mw.util.$content
// Uses:
// > .start
// < .gui.leading
// .fast()
// .fresh()
// .further()
// .first()
// Remark: Used as event handler -- 'this' is not BAK
// 2019-07-01 PerfektesChaos@de.wikipedia
BAK.$page = $all;
switch ( BAK.start ) {
case "submit" :
BAK.gui.leading = true;
BAK.fresh();
BAK.further();
break;
case "edit" :
BAK.first();
BAK.further();
} // switch .start
}; // .firing()
function first() {
// Initiate resource loading
// Uses:
// > .type
// < .error
// mw.loader.getState()
// mw.loader.using()
// mw.loader.state()
// (.fire)
// 2018-08-24 PerfektesChaos@de.wikipedia
var signature = "ext.gadget." + BAK.type,
rls;
if ( mw.loader.getState( signature ) !== "ready" ) {
rls = { };
rls[ signature ] = "ready";
mw.loader.state( rls );
if ( mw.config.get( "wgNamespaceNumber" ) >= 0 ) {
mw.loader.using( [ "user",
"mediawiki.api",
"mediawiki.user",
"mediawiki.util" ],
BAK.fire );
}
}
} // first()
first();
}( window.mediaWiki, window.jQuery ) );
// Emacs
// Local Variables:
// coding: utf-8-dos
// fill-column: 80
// End:
/// EOF </nowiki> autoBackup/d.js