User:Zhaofeng Li/Scratchpad.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Zhaofeng Li/Scratchpad. |
// Scratchpad - A sandbox in your browser
// by Zhaofeng Li
// Code is very ugly, I know. :D
var scratch_var_curtab = ''; // contains a guid
// from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
function scratch_guid_s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
};
function scratch_guid() {
return scratch_guid_s4() + scratch_guid_s4() + '-' + scratch_guid_s4() + '-' + scratch_guid_s4() + '-' + scratch_guid_s4() + '-' + scratch_guid_s4() + scratch_guid_s4() + scratch_guid_s4();
}
function scratch_ui_setup() { // call scratch_setup() instead
$( "#mw-content-text" ).prepend( "\
<div id='scratch'>\
<div id='scratch-top' style='margin-bottom:3px;padding-top:5px;'>\
<ul id='scratch-controls-left' class='scratch-controls' style='display:inline;'>\
<li id='scratch-teardown' title='Close Scratchpad'>x</li>\
<!--<li id='scratch-prefs' title='Toggle settings'>prefs</li>-->\
</ul>\
<ul id='scratch-tabs' style='display:inline;white-space:nowrap;'></ul>\
<ul id='scratch-controls' class='scratch-controls' style='display:inline;'>\
<li id='scratch-newtab' title='Create new tab'>+</li>\
<li id='scratch-help' title='Show help information'>?</li>\
<li id='scratch-fork' title='Fork current page'>fork</li>\
<li id='scratch-preview' title='Preview current tab'>preview</li>\
<li id='scratch-rename' title='Rename current tab'>rename</li>\
</ul>\
</div>\
<div id='scratch-body' style='padding:10px;border:1px solid rgba(0,0,0,0.2);border-radius:5px;background:rgba(0,0,0,0.2);min-height:100px;'>\
<textarea id='scratch-textarea' style='min-height:500px;border:0;border-radius:3px;'></textarea>\
</div>\
</div>\
" );
var height = $( "#scratch" ).height();
$( "#scratch" ).css( { 'opacity':'0', 'height':'0' } ); // hide it from view for now
$( ".scratch-controls li" ).css( { 'cursor':'pointer', 'display':'inline', 'background':'#999', 'border-radius':'3px', 'padding':'2px 5px' } );
$( ".scratch-controls li a" ).css( { 'color':'white' } );
$( "#scratch-top a" ).css( { 'color':'rgba(0,0,0,0.8)' } )
$( "#scratch-teardown" ).click( scratch_teardown );
$( "#scratch-prefs" ).click( function() {
$( "#scratch-body" ).append( "<div id='scratch-prefs-area' style='background:white;border-radius:3px;padding:10px;margin-top:10px;'><h1>Preferences</h1><button id='scratch-prefs-close'>Close</button><button id='scratch-prefs-reset'>Reset Scratchpad</button></div>" );
scratch_ui_scrollTo( "#scratch-prefs-area" );
$( "#scratch-prefs-close" ).click( function() {
$( "#scratch-prefs-area" ).remove();
scratch_ui_scrollTo( "#scratch" );
} );
$( "#scratch-prefs-reset" ).click( function() { // remove everything
if( confirm( "Doing this will delete all tabs by clearing the Scratchpad storage. This is *irreversible*! Do you want to continue?" ) ) {
scratch_teardown();
var del = 0;
for ( var i = localStorage.length - 1; i >= 0; i-- ) {
if ( localStorage.key( i ).substring( 0, 8 ) == "scratch_" ) {
console.log( "Removed " + localStorage.key( i ) );
localStorage.removeItem( localStorage.key( i ) );
del++;
}
}
alert( "Reset complete. " + del + " items removed from localStorage. Click the toolbox link to start a fresh Scratchpad." );
}
} );
} );
$( "#scratch-newtab" ).click( function() {
scratch_storage_newTab( scratch_guid(), "New tab", "" );
scratch_ui_refreshTabs();
} );
$( "#scratch-help" ).click( function(){
var guid = scratch_guid();
scratch_storage_newTab( guid, "Help", "\
== Basic wikitext ==\n\
''italic'' '''bold''' <s>strike</s> <ins>insert</ins>\n\
[[link]] [[link|caption]]\n\
[[File:Bad Title Example.png]] [[File:Bad Title Example.png|thumb|Text]]\n\
<ref>[http://example.com Reference]</ref>\n\
== Scratchpad ==\n\
* [+]: Create a new tab\n\
* [?]: Show help information\n\
* [fork]: Create a new tab with current page's source\n\
* [preview]: Preview the tab\n\
== References ==\n\
{{Reflist}}\
" );
scratch_ui_refreshTabs();
scratch_ui_selectTab( guid );
} );
$( "#scratch-fork" ).click( function() {
console.log( "Generating preview..." );
var title = mw.config.get( 'wgPageName' ); // get page name
var indexphp = mw.config.get( 'wgServer' ) + mw.config.get( 'wgScript' ); // get url to index.php
var url = indexphp + "?action=raw&title=" + encodeURIComponent(title);
var guid = scratch_guid();
scratch_storage_newTab( guid, title, "Loading source from [[" + title + "]], please wait...");
scratch_ui_refreshTabs();
scratch_ui_selectTab( guid );
$.get( url, function( data ){
scratch_storage_setTabContent( guid, data );
scratch_ui_selectTab( guid );
} );
} );
$( "#scratch-preview" ).click( function() { // really messy...
$( "#scratch-preview-area" ).remove();
var wikitext = scratch_storage_getTabContent( scratch_var_curtab );
var api = mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + "/api.php"; // get url to index.php
var url = api + "?action=parse&format=json&text=" + encodeURIComponent(wikitext);
$( "#scratch-body" ).append( "<div id='scratch-preview-area' style='background:white;border-radius:3px;padding:10px;margin-top:10px;'><h1>Preview</h1><p>Loading, please wait...</p></div>" );
scratch_ui_scrollTo( "#scratch-preview-area" );
$.getJSON( url, function( data ) {
html = data.parse.text['*'];
$( "#scratch-preview-area p" ).remove(); // remove notice
$( "#scratch-preview-area" ).append( "<a id='scratch-preview-close'>[Close preview]</a><a id='scratch-preview-expand'>[Expand to page]</a><div id='scratch-preview-page'>" + html + "</div>" ); // add preview
$( "#scratch-preview-area .mw-editsection" ).hide(); // remove useless edit section links
$( "#scratch-preview-close" ).click( function() {
$( "#scratch-preview-area" ).remove();
scratch_ui_scrollTo( "#scratch" );
} );
$( "#scratch-preview-expand" ).click( function() {
var preview = $( "#scratch-preview-page" ).html();
$( "#mw-content-text" ).html( preview );
scratch_teardown();
} )
} );
} );
$( "#scratch-rename" ).click( function() {
console.log( "Rename clicked on " + scratch_var_curtab );
var curname = scratch_storage_getTabName( scratch_var_curtab );
var newname = prompt( "Enter a new name for the tab:", curname );
scratch_storage_setTabName( scratch_var_curtab, newname );
scratch_ui_refreshTabs();
scratch_ui_selectTab( scratch_var_curtab );
} );
$( "#scratch-textarea" ).keyup( function() {
scratch_storage_setTabContent( scratch_var_curtab, $( "#scratch-textarea" ).val() );
} );
$( "#scratch" ).animate( { 'height':height }, 300, function() { // toggle height
$( "#scratch" ).css( 'height', 'auto' ); // set height to auto
$( "#scratch" ).animate( { 'opacity':'1' }, 300 );
} );
}
function scratch_ui_teardown() { // call scratch_teardown() instead
$( "#scratch" ).animate( { "opacity": "0" }, 300, function() {
$( "#scratch" ).animate( { "height": "toggle" }, 300, function() {
$( "#scratch" ).remove();
} );
} ); // close elegantly
}
function scratch_ui_scrollTo( element ) {
// from http://www.abeautifulsite.net/blog/2010/01/smoothly-scroll-to-an-element-without-a-jquery-plugin/
$( "html, body" ).animate( {
scrollTop: $( element ).offset().top
}, 500 );
}
function scratch_ui_addTab( guid ) { // add a tab to ui
var name = scratch_storage_getTabName( guid );
$( "#scratch-tabs" ).append( "<li id='scratch-tab-" + guid + "' style='cursor:pointer;display:inline;padding:5px;border:1px solid rgba(0,0,0,0.2);border-top-left-radius:5px;border-top-right-radius:5px;margin-right:5px;'><span style='display:inline-block;max-width:80px;text-overflow:ellipsis;overflow:hidden;vertical-align:middle;'>" + name + "</span> <a class='delete'>[x]</a></li>" );
$( "#scratch-tab-" + guid ).click( function(){ // tab switching
var guid = this.id.substring( 12 ); // "scratch-tab-123456"
scratch_ui_selectTab( guid );
} ); // bind the click event
$( "#scratch-tab-" + guid + " .delete" ).click( function(){ // delete
guid = this.parentNode.id.substring( 12 );
scratch_storage_deleteTab( guid ); // bye
scratch_ui_refreshTabs();
} ); // bind the click event
}
function scratch_ui_selectTab( guid ) {
console.log( "Selecting tab " + guid );
tablist = scratch_storage_getTabList(); // get the tab list
$( "#scratch-tabs li" ).css( { 'background':'white', 'border':'1px solid rgba(0,0,0,0.2)' } );
if ( tablist.indexOf( guid ) == -1 ) { // not found...
guid = tablist[0]; // there will always be one tab, scratch_storage_deleteTab() ensures this
}
scratch_var_curtab = guid; // set the current tab
$( "#scratch-tab-" + guid ).css( { 'background':'rgba(0,0,0,0.2)', 'border-bottom':'none' } ); // highlight it
$( "#scratch-textarea" ).val( scratch_storage_getTabContent( guid ) ); // populate the textarea
}
function scratch_ui_refreshTabs() {
tablist = scratch_storage_getTabList();
$( "#scratch-tabs li" ).remove(); // clear tabs
for ( var i = 0; i < tablist.length; i++ ) {
scratch_ui_addTab( tablist[i] );
}
if ( tablist.indexOf( scratch_var_curtab ) == -1 ) { // the current tab is gone
scratch_ui_selectTab( tablist[0] ); // reset it
} else {
scratch_ui_selectTab( scratch_var_curtab ); // refresh the current tab
}
}
function scratch_storage_access( key, value ) { // low-level access
if ( typeof value === "undefined" ) {
return localStorage.getItem( "scratch_" + key ); // web storage can only store strings, beware!
} else {
console.log( "Setting localStorage key 'scratch_" + key + "'' to " + value );
localStorage.setItem( "scratch_" + key, value );
}
}
function scratch_storage_delete( key ) { // low-level delete
localStorage.removeItem( "scratch_" + key );
}
function scratch_storage_getTabList() {
tablist = scratch_storage_access( "tabs" );
if ( tablist != null ) {
return JSON.parse( tablist );
} else {
return [];
}
}
function scratch_storage_setTabList( value ) {
return scratch_storage_access( "tabs", JSON.stringify( value ) );
}
function scratch_storage_accessTab( guid, value ) { // low-level tab access
if ( typeof value === "undefined" ) {
return JSON.parse( scratch_storage_access( "tab_" + guid ) );
} else {
scratch_storage_access( "tab_" + guid, JSON.stringify( value ) );
}
}
function scratch_storage_deleteTab( guid ) { // delete tab in storage, does not refresh ui!
scratch_storage_delete( "tab_" + guid );
tablist = scratch_storage_getTabList();
index = tablist.indexOf( guid );
if ( index != -1 ) { // exists in tab list
tablist.splice( index, 1 );
scratch_storage_setTabList( tablist );
} // if it doesn't exist in tablist, no actions are needed
if ( tablist.length === 0 ) { // empty tablist?
scratch_storage_newTab( scratch_guid(), "New tab", "" ); // create a new tab to prevent tablist being empty
}
// do scratch_ui_refreshTabs() after you call this
}
function scratch_storage_getTabName( guid ) {
return scratch_storage_accessTab( guid ).name;
}
function scratch_storage_getTabContent( guid ) {
return scratch_storage_accessTab( guid ).content;
}
function scratch_storage_setTabContent( guid, content ) { // tabs not in tablist are inaccessible (hidden tab is coming soon)!
var tab = scratch_storage_accessTab( guid );
tab.content = content;
scratch_storage_accessTab( guid, tab );
}
function scratch_storage_setTabName( guid, name ) { // tabs not in tablist are inaccessible (hidden tab is coming soon)!
var tab = scratch_storage_accessTab( guid );
tab.name = name;
scratch_storage_accessTab( guid, tab );
}
function scratch_storage_newTab( guid, name, content ){ // create or overwrite a tab
console.log( "Creating new tab: GUID='" + guid + "', name='" + name + "', content='" + content + "'");
var tab = {
"name": name,
"content": content
}; // create the tab object
scratch_storage_accessTab( guid, tab ); // put the tab into the storage (not in the tab list yet)
tablist = scratch_storage_getTabList(); // get the tab list
if ( tablist.indexOf( guid ) == -1 ) { // non-existant
tablist.push( guid ); // push it into the list
scratch_storage_setTabList( tablist );
} // if it exists, no actions are needed
}
function scratch_storage_init() {
console.log( "Initializing storage..." );
if ( scratch_storage_getTabList().length == 0 ) { // first time user
console.log( "First time user, creating a welcome tab..." );
var guid = scratch_guid(); // generate a guid
scratch_storage_newTab( guid, "Welcome", "\
== Welcome to Scratchpad! ==\n\
Want to try out a new template quickly without sandboxes? Want to keep a track of your patrol progress? Want to have a lot of sandboxes but don't want get your userspace messy? Scratchpad is the answer!\n\n\
Scratchpad is a sandbox which resides entirely in your browser, saved automatically as you type. Changes are echoed between multiple pages with Scratchpad opened in your browser. You can create multiple tabs, experiment with wikitext, preview and delete them, without making any changes to the actual wiki.\n\
=== Try it out! ===\n\
Everything you expect from a normal sandbox is here. ''Italic'', '''bold''', <s>strike</s>, [[WP:SANDBOX|links]] and all other magic works on Scratchpad. Hit ''Preview'' and see it for yourself!\n\
{{mbox|text=Of course, templates work as well.}}\n\n\
You can even use [[WP:SUBST|template subsitutions]] and [[WP:SIGN|signatures]]! Try them out!\n\n\
Open another browser tab, fire up Scratchpad and change this line - does the other one reflect the changes?\n\n\
=== Before you continue... ===\n\
Remember that your Scratchpad is visible to all other scripts and sent to the server every time you use preview. It's not completely private.\n\
=== Any suggestions? ===\n\
If you have any suggestions, please send them to [[User talk:Zhaofeng Li]], Scratchpad's maker.\n\
\n\
Enjoy Scratchpad!\n\
" );
}
scratch_ui_refreshTabs();
// listen for storage changes
$( window ).bind( 'storage', scratch_storage_listener );
}
function scratch_storage_listener( event ) { // storage update event
console.log( "Storage event catched!" );
event = event.originalEvent; // remove jquery's wrapper
key = event.key.substring( 8 ); // get the part after "scratch_"
if ( key == "tabs" ) { // the tablist has changed
scratch_ui_refreshTabs();
} else { // one of the tabs has changed
guid = key.substring( 4 ); // get the part after "tab_"
if ( guid == scratch_var_curtab ) { // that's what we are looking at as well!
scratch_ui_selectTab( guid ); // refresh the tab content
}
}
}
function scratch_setup() {
$( scratch_var_portlet ).hide();
scratch_ui_setup();
console.log( "Scratchpad is starting..." );
if ( typeof( localStorage ) == "undefined" ) {
console.log( "No Web Storage support! Aborting..." );
$( "#scratch-body" ).append( "Failed to initialize storage! Scratchpad needs HTML5 Web Storage support to work, try changing to a newer browser. Sorry about that." );
return;
}
scratch_storage_init();
}
function scratch_teardown() {
console.log( "Scratchpad is closing down..." );
// remove listeners
$( window ).unbind( 'storage', scratch_storage_listener );
scratch_ui_teardown(); // remove the ui
$( scratch_var_portlet ).show();
}
// Add portlet link
var scratch_var_portlet = mw.util.addPortletLink( "p-tb", "#", "Scratchpad");
$( scratch_var_portlet ).click( scratch_setup );