您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Retains text entered into textareas and contentEditables, and expires after certain time span.
- // ==UserScript==
- // @author Crend King
- // @version 2.4
- // @name Textarea Backup with expiry
- // @namespace http://users.soe.ucsc.edu/~kjin
- // @description Retains text entered into textareas and contentEditables, and expires after certain time span.
- // @include http://*
- // @include https://*
- // @homepage http://userscripts.org/scripts/show/42879
- // ==/UserScript==
- // this script was originally based on http://userscripts.org/scripts/review/7671
- /*
- version history
- 2.4 on 08/15/2013:
- - Support Google Chrome.
- 2.3 on 07/20/2013:
- - Support dynamically created textareas. To restore, such textareas need to be created first and then use the script command.
- 2.2.1 on 01/06/2013:
- - Remove restriction for textarea under a form.
- 2.2 on 01/03/2013:
- - Add support for elements with "contentEditable" attribute.
- 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 element 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;
- // 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 backed up elements in the page
- var targets = [];
- // element_id: whether this element is prompted for restoration
- var prompted = {};
- // CSS selector for backup-able elements
- var target_selector = 'textarea, *[contentEditable]';
- var get_element_id = function(element)
- {
- /*
- return the reference ID of the element
- multiple elements with no name or id will collide
- but element without either would be useless
- */
- return element.name || element.id || '';
- };
- var get_element_key = function(element)
- {
- // Greasemonkey key for the backup
- // take URI into consideration
- return element.baseURI + ';' + get_element_id(element);
- };
- 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 get_element_value = function(element)
- {
- if (element.nodeName == 'TEXTAREA')
- return element.value;
- else
- return element.innerHTML;
- };
- var set_element_value = function(element, value)
- {
- if (element.nodeName == 'TEXTAREA')
- element.value = value;
- else
- element.innerHTML = value;
- };
- var commit_backup = function(element)
- {
- var element_value = get_element_value(element);
- // backup if value is not empty
- if (!/^\s*$/.test(element_value))
- {
- var bak_payload = append_timestamp(element_value);
- GM_setValue(get_element_key(element), bak_payload);
- }
- };
- var confirm_restore;
- var get_backup_content = function(element)
- {
- // 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_element_key(element));
- if (!bak_payload)
- return false;
- var bak_content = remove_timestamp(bak_payload);
- // ignore if backup text is identical to current value
- if (bak_content == get_element_value(element))
- return false;
- else
- return bak_content;
- };
- var restore_backup = function(elements, 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(elements[index]);
- if (bak_content !== false)
- {
- confirm_restore(elements, index, bak_content);
- }
- }
- else
- {
- for (var i = 0; i < elements.length; ++i)
- {
- var element = elements[i];
- set_element_value(element, get_backup_content(element));
- }
- }
- };
- confirm_restore = function(elements, index, bak_content)
- {
- var element = elements[index];
- element.scrollIntoView(false);
- // flash the element
- var ori_border = element.style.border;
- var new_border = '2px solid red';
- var toggle = true;
- var flashed = flash_count;
- var interval_id;
- var toggle_border = function()
- {
- element.style.border = (toggle ? new_border : ori_border);
- toggle = !toggle;
- --flashed;
- if (flashed == 0)
- {
- clearInterval(interval_id);
- var msg = "[Textarea Backup] Backup exists for this element, proceed to overwrite with this backup?\n\n";
- msg += bak_content.length > 750 ?
- bak_content.substr(0, 500) + "\n..." :
- bak_content;
- if (confirm(msg))
- set_element_value(element, bak_content);
- if (index + 1 < elements.length)
- {
- // setTimeout is an asynchronized operation
- // need recursion to serialize restoration on elements
- restore_backup(elements, index + 1);
- }
- }
- };
- interval_id = setInterval(toggle_border, flash_frequency);
- };
- var on_focus = function(event)
- {
- var element = event.target;
- var element_id = get_element_id(element);
- if (!prompted[element_id])
- {
- // set prompted status disregarding user's choice of overwriting
- prompted[element_id] = true;
- restore_backup([element], 0);
- }
- };
- var on_blur = function(event)
- {
- commit_backup(event.target);
- };
- var on_submit = function(event)
- {
- for (var i = 0; i < targets.length; ++i)
- GM_deleteValue(get_element_key(targets[i]));
- };
- var init_backup = function(element)
- {
- prompted[get_element_id(element)] = false;
- element.addEventListener('focus', on_focus, true);
- // save buffer when the element loses focus
- if (blur_backup)
- element.addEventListener('blur', on_blur, true);
- // delete buffer when the form is submitted
- if (!keep_after_submission && element.form)
- element.form.addEventListener('submit', on_submit, true);
- };
- var restore_all = function()
- {
- // restore all targets and set prompted status
- for (var i = 0; i < targets.length; ++i)
- {
- var target = targets[i];
- var target_id = get_element_id(target);
- if (!prompted[target_id])
- prompted[target_id] = true;
- restore_backup(targets, i);
- }
- };
- var backup_dynamic = function(evt)
- {
- if (evt.target.querySelectorAll == undefined)
- return;
- var new_textareas = evt.target.querySelectorAll(target_selector);
- for (var i = 0; i < new_textareas.length; ++i)
- {
- var new_textarea = new_textareas.item(i);
- targets.push(new_textarea);
- init_backup(new_textarea);
- }
- };
- // 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 stored_bak_index = 0; stored_bak_index < stored_bak.length; ++stored_bak_index)
- {
- var bak_payload = GM_getValue(stored_bak[stored_bak_index]);
- 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[stored_bak_index]);
- }
- }
- var query_result = document.querySelectorAll(target_selector);
- for (var query_result_index = 0; query_result_index < query_result.length; ++query_result_index)
- {
- var query_item = query_result.item(query_result_index);
- targets.push(query_item);
- init_backup(query_item);
- }
- if (targets.length > 0)
- {
- // save buffer in interval fashion
- if (timely_backup_interval > 0)
- {
- var backup_all = function()
- {
- for (var i = 0; i < targets.length; ++i)
- {
- var target = targets[i];
- if (prompted[get_element_id(target)])
- commit_backup(target);
- }
- };
- setInterval(backup_all, timely_backup_interval);
- }
- }
- document.addEventListener('DOMNodeInserted', backup_dynamic);
- GM_registerMenuCommand('Restore all textareas in this page', restore_all);