There are 30 previous versions of this script.
// ==UserScript==
// @author Crend King
// @version 2.1
// @name Textarea Backup with expiry
// @namespace http://users.soe.ucsc.edu/~kjin
// @description Retains text entered into textareas, and expires after certain time span.
// @include http://*
// @include https://*
// ==/UserScript==
// this script was originally based on http://userscripts.org/scripts/review/7671
// check latest version at http://userscripts.org/scripts/show/42879
/*
version history
2.1 on 05/09/2011:
- Add user menu command to restore all textarea in the page.
2.0 on 05/06/2011:
- Completely rewrite the script. New script should be faster, stronger and more standard-compliant.
- Fix bugs in previous versions and the original script.
1.0.4 on 04/22/2009:
- Synchronize with the original Textarea Backup script.
1.0.3 on 03/08/2009:
- Add "ask overwrite" option.
1.0.2 on 03/04/2009:
- Add "keep after submission" option.
1.0.1 on 02/22/2009:
- Extract the expiry time stamp codes to stand-alone functions.
1.0 on 02/21/2009:
- Initial version.
*/
///// preference section /////
// backup when textarea loses focus
var blur_backup = true;
// interval for timely backup, in millisecond. 0 disables timely backup
var timely_backup_interval = 0;
// keep backup even form is submitted
// make sure expiration is enabled or backup will never be deleted
var keep_after_submission = false;
// set true to display a confirmation window for restoration
// otherwise restore unconditionally
var confirm_overwrite = true;
// auxiliary variable to compute expiry_timespan
// set all 0 to disable expiration
var expire_after_days = 0;
var expire_after_hours = 0;
var expire_after_minutes = 30;
///// code section /////
// expiry time for a backup, in millisecond
var expiry_timespan = (((expire_after_days * 24) + expire_after_hours) * 60 + expire_after_minutes) * 60000;
// RegExp to check if string is consisted of all spaces
var empty_regexp = /^\s*$/;
// how many times to flash. must be a even number, or the border style will not revert
var flash_count = 6;
// how fast is the flash
var flash_frequency = 100;
// array of all textarea elements in the page
var textareas = [];
// textarea_id: whether this textarea is prompted for restoration
var prompted = {};
var get_ta_id = function(ta)
{
/*
return the reference ID of the textarea
multiple textareas with no name or id will collide
but textarea without either would be useless
*/
return ta.name || ta.id || '';
};
var get_ta_key = function(ta)
{
// Greasemonkey key for the backup
// take URI into consideration
return ta.baseURI + ';' + get_ta_id(ta);
};
var append_timestamp = function(str)
{
return str + '@' + (new Date()).getTime();
};
var remove_timestamp = function(str)
{
return str.replace(/@\d+$/, '');
};
var get_timestamp = function(str)
{
var time_pos = str.lastIndexOf('@');
return str.substr(time_pos + 1);
};
var commit_backup = function(ta)
{
// backup if value is not empty
if (!empty_regexp.test(ta.value))
{
var bak_payload = append_timestamp(ta.value);
GM_setValue(get_ta_key(ta), bak_payload);
}
};
var flash_textarea = function(ta)
{
// flash the textarea
var ori_border = ta.style.border;
var new_border = '2px solid red';
var toggle = true;
var flashed = flash_count;
var interval_id;
var toggle_border = function()
{
ta.style.border = (toggle ? new_border : ori_border);
toggle = !toggle;
--flashed;
if (flashed == 0)
clearInterval(interval_id);
};
interval_id = setInterval(toggle_border, flash_frequency);
};
var confirm_restore = function(tas, index, bak_content)
{
var ta = tas[index];
ta.scrollIntoView();
flash_textarea(ta);
var confirmation = function()
{
var msg = "[Textarea Backup] Backup exists for this textarea, proceed to overwrite with this backup?\n\n";
msg += bak_content.length > 750 ?
bak_content.substr(0, 500) + "\n..." :
bak_content;
if (confirm(msg))
ta.value = bak_content;
if (index + 1 < tas.length)
{
// setTimeout is an asynchronized operation
// need recursion to serialize restoration on textareas
restore_backup(tas, index + 1);
}
};
// setInterval in flash_textarea is an asynchronized operation
// need to wait until flashing is finished
setTimeout(confirmation, (flash_count + 1) * flash_frequency);
};
var get_backup_content = function(ta)
{
// backup payload is in format of "backup_text@save_time",
// where save_time is the millisecond from Javascript Date object's getTime()
var bak_payload = GM_getValue(get_ta_key(ta));
if (!bak_payload)
return false;
var bak_content = remove_timestamp(bak_payload);
// ignore if backup text is identical to current value
if (bak_content == ta.value)
return false;
else
return bak_content;
};
var restore_backup = function(tas, index)
{
// check with user before overwriting existing content with backup
// asynchronized when confirmation is enabled, synchronized otherwise
if (confirm_overwrite)
{
var bak_content = get_backup_content(tas[index]);
if (bak_content !== false)
confirm_restore(tas, index, bak_content);
}
else
{
for (var i = 0; i < tas.length; ++i)
{
var ta = tas[i];
ta.value = get_backup_content(ta);
}
}
};
var on_focus = function(event)
{
var ta = event.target;
var ta_id = get_ta_id(ta);
if (!prompted[ta_id])
{
// set prompted status disregarding user's choice of overwriting
prompted[ta_id] = true;
restore_backup([ta], 0);
}
};
var on_blur = function(event)
{
commit_backup(event.target);
};
var on_submit = function(event)
{
for (var i = 0; i < textareas.length; ++i)
GM_deleteValue(get_ta_key(textareas[i]));
};
var init_backup = function(ta)
{
// textarea-specific initialization
prompted[get_ta_id(ta)] = false;
ta.addEventListener('focus', on_focus, true);
// save buffer when the textarea loses focus
if (blur_backup)
ta.addEventListener('blur', on_blur, true);
// delete buffer when the form is submitted
if (!keep_after_submission)
ta.form.addEventListener('submit', on_submit, true);
};
var restore_all = function()
{
// restore all textareas and set prompted status
for (var i = 0; i < textareas.length; ++i)
{
var ta = textareas[i];
var ta_id = get_ta_id(ta);
if (!prompted[ta_id])
prompted[ta_id] = true;
}
restore_backup(textareas, 0);
};
// expiration check routine
if (expiry_timespan > 0)
{
// get all associated backups for this page, and compare timestamp now and then
var curr_time = new Date().getTime();
var stored_bak = GM_listValues();
for (var i = 0; i < stored_bak.length; ++i)
{
var bak_payload = GM_getValue(stored_bak[i]);
var bak_content = remove_timestamp(bak_payload);
var bak_time = get_timestamp(bak_payload);
if (curr_time - bak_time >= expiry_timespan)
GM_deleteValue(stored_bak[i]);
}
}
textareas = document.getElementsByTagName('textarea');
for (var i = 0; i < textareas.length; ++i)
{
var ta = textareas[i];
// process textarea only if it is under a form
if (ta.form)
init_backup(ta);
}
if (textareas.length > 0)
{
// save buffer in interval fashion
if (timely_backup_interval > 0)
{
var backup_all = function()
{
for (var i = 0; i < textareas.length; ++i)
{
var ta = textareas[i];
if (prompted[get_ta_id(ta)])
commit_backup(ta);
}
};
setInterval(backup_all, timely_backup_interval);
}
GM_registerMenuCommand('Restore all textareas in this page', restore_all);
}