User:Bradv/Scripts/Notepad.js

(function( $, mw ) {   'use strict';

var api = new mw.Api({});

var app = { width: 30, styleSheet: mw.util.addCSS(`           /* VARIABLES */            :root {                --notepad-width: 30vw;                --speed: 50ms;            }

/* TRANSITIONS */ body, #p-personal, #right-navigation, .mw-header, .mw-workspace-container { transition: all var(--speed) ease-out; }           #notepad-window { z-index: 101; transition: margin var(--speed) ease-out, width var(--speed) ease-out; }

/* RULES FOR LEGACY VECTOR */ body.notepad.skin-vector-legacy { margin-right: var(--notepad-width); }           body.notepad.skin-vector-legacy #p-personal { margin-right: var(--notepad-width); }           body.notepad.skin-vector-legacy #right-navigation { margin-right: var(--notepad-width); }                       body.notepad.skin-vector-legacy #simpleSearch { width: 10em; }

/* RULES FOR NEW VECTOR */ @media (max-width:2000px) { body.notepad.skin-vector:not(.skin-vector-legacy) { margin-right: var(--notepad-width); }               body.notepad.skin-vector:not(.skin-vector-legacy) .mw-header #p-search { min-width: inherit; width: inherit; }           }               @media (min-width:2000px) { body.notepad.skin-vector:not(.skin-vector-legacy) .mw-header { padding-right: max(calc(20px + var(--notepad-width) - ((100vw - 100%) / 2)), 0px); }               body.notepad.skin-vector:not(.skin-vector-legacy) .mw-workspace-container { padding-right: max(calc(20px + var(--notepad-width) - ((100vw - 100%) / 2)), 0px); }           }

/* NOTEPAD STYLES */ body:not(.notepad) #notepad-window { margin-right: calc(-1 * var(--notepad-width)); visibility: hidden;; transition: visibility var(--speed) ease-out, margin var(--speed) ease-out, width var(--speed) ease-out; }           #notepad-icon { position:fixed; right:-2px; top:40px; transform-origin: 100% 0; transform: rotate(90deg) translateX(100%); border: 2px solid #a7d7f9; border-radius: 0 0 5px 5px; box-shadow: 0 0 4px 2px #eee; padding: 0px 10px 1px; background-color: #f5faff; color: #0645ad; font-size: 13px; z-index:102; transition: all var(--speed) ease-out; }           #notepad-icon:hover { cursor: pointer; background-color: #a7d7f9; }           body.notepad #notepad-icon { right: var(--notepad-width) }           body.notepad #notepad-icon, #notepad-icon.selected { background-color: #a7d7f9; font-weight: bold; }                       #notepad-window { position:fixed; top:0; right:0; bottom: 0; width: var(--notepad-width); border-left: 2px solid #a7d7f9; box-shadow: 0px 0px 4px 2px #eee; background-color: #fcfdfe; }           #notepad-window:before { top: -100px; content: ''; position:absolute; left: -30px; width: 0; height: 0; border: 15px solid transparent; border-right-color: #a7d7f9; margin-top:-15px; }           #notepad-window:after { top: -100px; content: ''; position:absolute; left: -25px; width: 0; height: 0; border: 15px solid transparent; border-right-color: transparent; margin-top:-15px; }           #notepad-window #notepad-slider { position:absolute; top: 20px; left: -10px; bottom: 0; width: 15px; opacity: 0; cursor: ew-resize; }           #notepad-window #notepad-container { height: 100%; width: 100%; padding: 0px 10px; line-height: 1.6; font-size: 0.875em; }           #notepad-window article { position:absolute; top: 50px; bottom: 0px; width: calc(100% - 15px); overflow: auto; }           #notepad-window textarea:focus { outline-color: transparent; }

#notepad-container:not([status=saving]) #notepad-saving { display:none; }           #notepad-container #notepad-saving { position:fixed; top: 0px; right: 0px; font-size: 10px; padding: 2px; width: 40px; text-align:center; background-color: #fef6e7; }

#notepad-container:not([tab=edit]) #notepad-edit, #notepad-container:not([tab=render]) #notepad-render, #notepad-container:not([tab=all]) #notepad-all { visibility: hidden; opacity: 0; }           #notepad-container article { transition: visibility var(--speed) ease-out, opacity var(--speed) ease-out; }

#notepad-container #notepad-header { border-bottom: 1px solid #ccc; padding: 8px 0; margin: 0; white-space: nowrap; }           #notepad-container #notepad-header a { padding: 10px 20px; font-weight: bold; color: black; font-size: 12px; border-bottom: 2px solid #0645ad00; transition: color var(--speed) ease-out, border var(--speed) ease-out; }           #notepad-container[tab=edit] #notepad-link-edit, #notepad-container[tab=render] #notepad-link-render, #notepad-container[tab=all] #notepad-link-all, #notepad-container #notepad-header a:hover { color: #0645ad; border-bottom: 2px solid #0645ad; text-decoration: none; }

#notepad-edit textarea { height: 100%; width: 100%; background-color: transparent; border: none; resize: none; }           #notepad-container #loading { position:absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #a7d7f9; }           #notepad-container #loading img { width: 30px; height: 30px; opacity:0.5; }

#notepad-container #notepad-all section { box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); width: calc(100% - 32px); margin:10px auto; padding:10px; clear: both; overflow: auto; }           #notepad-container #notepad-all section:hover { box-shadow: 0 4px 8px 0 rgba(6,69,173,0.2); outline: 2px solid #a7d7f9; }           #notepad-container #notepad-all section h4 { color: #0645ad; padding: 0; }           #notepad-container #notepad-all section > .content { font-family: monospace; overflow: hidden; margin-bottom: 5px; }           #notepad-container #notepad-all section:not([expanded]) > .content { max-height: 90px; }			#notepad-container #notepad-all section > .updated { font-size: 0.8em; color: gray; float: left; }			#notepad-container #notepad-all section > .links { font-size: 0.8em; float: right; margin-left: 10px; }			#notepad-container #notepad-all section > .links.confirm { color: red; font-weight: bold; }

`),       init: function(oninit, onload) {            app.onload = onload;            mw.loader.using(["mediawiki.util", "mediawiki.user"]).then(function  { app.$link = $(' ', {'id': 'notepad-icon', 'title': 'Open notepad [ctrl+alt+n]'}) .append($(' ').text('Note')) .append($(' ').text('pad')) .appendTo("body");

app.$link.click(app.click);

document.addEventListener('keydown', function(e) {                   if (e.altKey && e.ctrlKey && e.code == 'KeyN') {                        app.$link.click; //Ctrl+Alt+N                    }                });

oninit; app.load; })       },        click: function(e) {            e.preventDefault;			app.start;        },		start: function(tab) {            app.load;			app.resize;			if (tab) {				$('#notepad-link-' + tab).click;			}			$("body").toggleClass('notepad');		},        load: function {            if (!app.loaded) {                app.loaded=true;                app.$window = $(" ", {'id': 'notepad-window'}).appendTo("body");                app.$container = $(" ", {'id': 'notepad-container'}).appendTo(app.$window);

app.$window.append(                   $(" ", {'id': 'notepad-slider'})                    .mousedown(function(e) { if ($('body').hasClass('notepad')) { function mouseup { $(document).off("mousemove", app.mousemove); app.resize; }                           if (e.which==1) { mouseup; app.mousemove = function(e) { e.preventDefault; var x = e.pageX; if (x * 2 < window.innerWidth) x = (window.innerWidth/2)+1 if (window.innerWidth - x < 100) x = window.innerWidth - 99 var vw = ((1 - ((x+15) / window.innerWidth)) * 100); app.resize(vw + "vw"); }                               $(document).mousemove(app.mousemove); $(document).mouseup(mouseup); }                       } else { app.start; }                   })                );                app.onload; }       },        resize: function(val, suppresstrigger) { if (val) { $(':root').css('--notepad-width', val); }

if (!suppresstrigger) window.dispatchEvent(new Event('resize')); }   }

var util = { get pagename { var ns = mw.config.get('wgNamespaceNumber'); var page = mw.config.get('wgPageName'); var user = mw.config.get('wgRelevantUserName'); if (ns % 2 == 1) { var p1 = mw.config.get('wgFormattedNamespaces')[ns].replace(' ', '_'); var p0 = mw.config.get('wgFormattedNamespaces')[ns-1].replace(' ', '_'); var r = new RegExp("^" + p1 + ':'); page = page.replace(r, ns==1 ? p0 : p0 + ':'); console.log(page); }           if (ns == -1 && user) { page = 'User:' + user.replace(' ', '_'); }

return page; },       get now { function pad(n) { if (n<10) { return '0' + n;               } else { return '' + n;               } }           var d = new Date return d.getUTCFullYear + '-' + pad(d.getUTCMonth+1) + '-' + pad(d.getUTCDate) + 'T'            + pad(d.getUTCHours) + ':' + pad(d.getUTCMinutes) + ':' + pad(d.getUTCSeconds) + 'Z'; }   }

var notes = { get: function { var s = mw.user.options.get("userjs-pagenotes"); if (s && s.length) { var obj = JSON.parse(s); return obj[util.pagename] } else { return ""; }       },        refresh: function { var deferred = new $.Deferred; api.get({               action: 'query',                meta: 'userinfo',                uiprop: 'options'            }).done(function (response, data) {                var s = data.responseJSON.query.userinfo.options['userjs-pagenotes'];                mw.user.options.set('userjs-pagenotes', s);                deferred.resolve(s);            }); return deferred.promise; },       save: function(content) { var deferred = new $.Deferred; var cur = mw.user.options.get("userjs-pagenotes"); var obj = {}; if (cur && cur.length) { obj = JSON.parse(cur); }           if (!obj[util.pagename] || (obj[util.pagename] && (content != obj[util.pagename].content))) { if (content) { obj[util.pagename] = { 'updated': util.now, 'content': content };               } else { delete obj[util.pagename] }               var out = JSON.stringify(obj); mw.user.options.set("userjs-pagenotes", out); api.saveOption("userjs-pagenotes", out).then(function {                   deferred.resolve;                }); } else { deferred.resolve; }           return deferred.promise; },		delete: function(key) { var deferred = new $.Deferred; var cur = mw.user.options.get("userjs-pagenotes"); var obj = {}; if (cur && cur.length) { obj = JSON.parse(cur); }			if (obj[key]) { delete obj[key]; var out = JSON.stringify(obj); mw.user.options.set("userjs-pagenotes", out); api.saveOption("userjs-pagenotes", out).then(function {					deferred.resolve;				}); } else { deferred.resolve; }			return deferred.promise; },       init: function { var $link = app.$link; if (notes.get) { $link.addClass('selected'); }

var param = mw.util.getParamValue('notepad'); if (param) { app.start(param); }       },        render: function(wikitext, $target) { var deferred = new $.Deferred; if (wikitext && wikitext.length) { api.parse(wikitext, {                   'contentmodel': 'wikitext',                     'preview': true,                     'pst': true                }) .then(function(data) {                   $target.empty.append($(data));                    $target.find('.mw-editsection').remove;                    deferred.resolve;                }) } else { $target.empty; deferred.resolve; }           return deferred.promise; },       onload: function { var $container = app.$container $container.attr('tab', 'edit');

var $topbar = $(" ", {'id': 'notepad-header'}) .appendTo($container) .append($("", {'id': 'notepad-link-edit'}).text("Notes")                   .click(function  { $container.attr('tab', 'edit') }))               .append($("", {'id': 'notepad-link-render'}).text("Preview")                    .click(function { $container.attr('tab', 'render'); tabs.render.draw; }))               .append($("", {'id': 'notepad-link-all'}).text("Recent notes")                    .click(function { $container.attr('tab', 'all'); tabs.edit.save .then(tabs.allnotes.draw); }))

var tabs = { edit: new function { //draw edit window var $editwindow = $(" ", {'id': 'notepad-edit'}).appendTo($container); var $saving = $(' ', {'id': 'notepad-saving'}).appendTo($editwindow); $saving.append('Saving...'); var $textarea = $(" ",                        {'id': 'notepad-text', 'placeholder': 'Record a private note here...'}) .appendTo($editwindow);

this.draw = function { if (notes.get) { $textarea.val(notes.get.content); } else { $textarea.val(''); }                   }                    this.draw; this.save = function { var deferred = new $.Deferred; var val = $textarea.val; notes.save(val).then(function {                           $container.attr('status', 'saved');                            deferred.resolve;                        }); return deferred.promise; }                   var edit = this; var timer = 0; $textarea.change(function {                       clearTimeout(timer);                        $container.attr('status', 'saving');                        edit.save;                    }); $textarea.keyup(function {                       clearTimeout(timer);                        $container.attr('status', 'saving');                        timer = setTimeout(function { edit.save }, 1000);                   });                        },                render: new function { //draw render window var $renderwindow = $(" ", {'id': 'notepad-render'}).appendTo($container); $(' ', {'id': 'loading'}) .append($(' ', {'src': "https://upload.wikimedia.org/wikipedia/commons/3/30/Chromiumthrobber.svg"})) .appendTo($renderwindow);

this.draw = function { var s = ""; if (notes.get) s = notes.get.content; notes.render(s, $renderwindow); }                   this.draw; },               allnotes: new function { //draw all notes window var $allnotes = $(" ", {'id': 'notepad-all'}).appendTo($container); $(' ', {'id': 'loading'}) .append($(' ', {'src': "https://upload.wikimedia.org/wikipedia/commons/3/30/Chromiumthrobber.svg"})) .appendTo($allnotes);

this.draw = function { $allnotes.empty; var s = mw.user.options.get("userjs-pagenotes"); if (s && s.length) { var list = JSON.parse(s); var keys = Object.keys(list).sort(                               function(a,b) {                                    if (list[a].updated > list[b].updated) return -1;                                    if (list[a].updated < list[b].updated) return 1;                                    return 0;                                }                            ); for (var i=0; i', {'href': '#'})                                        .text(keys[i].replace(/_/g, ' '))                                        .click(function (e) { e.preventDefault; $('#notepad-link-edit').click; })                                   );                                } else { $h4.append(                                       $('', {'href': '/wiki/' + keys[i] + '?notepad=notes'})                                        .text(keys[i].replace(/_/g,' '))                                    ); }                               var $preview = $(' ', {'class': 'content'}).appendTo($sec); var content = list[keys[i]].content; $preview.append(content); var updated = list[keys[i]].updated.replace(/[TZ]/g,' '); $(' ', {'class': 'updated'}).text(updated).appendTo($sec); $('', {'class': 'links', 'href': '#'}) .text('delete') .click(function (e) {                                       e.preventDefault;                                        var $link = $(e.target);                                        var $section = $link.parent;                                        if ($link.text === 'delete') {                                            $link.text('confirm delete');                                            $link.addClass('confirm');                                            $section.focusout(function  { $link.text('delete'); $link.removeClass('confirm'); $section.off('focusout'); });                                       } else {                                            var key = $section.attr('key');                                            notes.delete($section.attr('key'))                                            .then(function { if (key === util.pagename) { tabs.edit.draw; }                                               });                                            $section.remove;                                        }							                                    }) .appendTo($sec); if ($preview[0].scrollHeight > $preview[0].offsetHeight) { $('', {'class': 'links', 'href': '#'}) .text('expand') .click(function (e) {                                           e.preventDefault;                                            var $section = $(e.target.parentNode);                                            if ($section.attr('expanded')===) {                                                $(e.target).text('expand');                                                $section.removeAttr('expanded');                                            } else {                                                $(e.target).text('collapse');                                                $section.attr('expanded', );                                            }                                        }) .appendTo($sec); }                           }                        }                    }                }            }

//refresh from server on focus window.addEventListener('focus', function {               notes.refresh.then(function  { tabs.edit.draw; tabs.render.draw; tabs.allnotes.draw; });                           });        },    }

app.init(notes.init, notes.onload);

} (jQuery, mediaWiki ));