Jump to content

User:Bradv/Scripts/Notepad.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Bradv (talk | contribs) at 03:17, 26 October 2020 (add persistence on all notes page). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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.
(function( $, mw ) {
    'use strict';

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

    var app = {
        styleSheet: mw.util.addCSS(`
            :root {
                --notepad-width: 30vw 
            }
            body.notepad {
                margin-right: var(--notepad-width)
            }
            body.notepad #p-personal {
                margin-right: var(--notepad-width)
            }
            body.notepad #right-navigation {
                margin-right: var(--notepad-width)
            }
            body.notepad #simpleSearch {
                width: 5em
            }
            body:not(.notepad) #notepad-window {
                display: none
            }
            #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 {
                height: 100%;
                width: 100%;
                background-color: transparent;
                border: none;
                resize: none;
            }
            #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 {
                display:none;
            }
            #notepad-container:not([tab=render]) #notepad-render {
                display:none;
            }
            #notepad-container:not([tab=all]) #notepad-all {
                display:none;
            }
            #notepad-container:not([tab=con:wfig]) #notepad-config {
                display:none;
            }

            #notepad-container #notepad-header {
                border-bottom: 1px solid #ccc;
                padding: 8px 0;
                margin: 0;
            }
            #notepad-container #notepad-header a {
                padding: 10px 20px;
                font-weight: bold;
                color: black;
                font-size: 12px;
            }
            #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-container #notepad-render #loading {
                position:absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                color: #a7d7f9;
            }
            #notepad-container #notepad-render #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 = $(mw.util.addPortletLink('p-views', '', 'Notes', 'notepad-portlet', 'Private notepad [ctrl+alt+n]', '', '.mw-watchlink'))
                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();
				var param = mw.util.getParamValue('notepad');
				if (param) {
					app.start(param)
				}
            })
        },
        click: function(e) {
            e.preventDefault();
			app.start();
        },
		start: function(tab) {
			if (!app.loaded) app.load();
			app.$link.toggleClass('selected');
			$("body").toggleClass('notepad');
			app.resize();
			if (tab) {
				$('#notepad-link-' + tab).click();
			}
		},
        load: function() {
            var $wnd = $("<div>", {'id': 'notepad-window'}).appendTo("body");
            var $container = $("<div>", {'id': 'notepad-container'}).appendTo($wnd);

            $wnd.append(
                $("<div>", {'id': 'notepad-slider'})
                .mousedown(function(e) {
                    function mouseup() {
                        $("body").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
                            app.resize("calc(100vw - 12px - " + x + "px)", x==e.pageX);
                        }
                        $("body").mousemove(app.mousemove);
                        $("body").mouseup(mouseup);
                    }
                })
            );
            app.$container = $container;
            app.loaded=true;
            app.onload();
        },
        resize: function(val, suppresstrigger) {
            if (val) document.body.style.setProperty('--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, p0 + ':');
            }
            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) {
			console.log(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.find('a').css('font-weight', 'bold');
            }
        },
        onload: function() {
            var $container = app.$container
            var text = '';
            if (notes.get()) {
                text = notes.get().content;
            }
            $container.attr('tab', 'edit');

            //draw top bar
            $("<nav>", {'id': 'notepad-header'})
                .appendTo($container)
                .append($("<a>", {'id': 'notepad-link-edit'}).text("Notes")
                    .click(function () {
                        $container.attr('tab', 'edit')
                    }))
                .append($("<a>", {'id': 'notepad-link-render'}).text("Preview")
                    .click(function() {
                        $container.attr('tab', 'render');
                        save();
                        render();
                    }))
                .append($("<a>", {'id': 'notepad-link-all'}).text("Recent notes")
                    .click(function() {
                        $container.attr('tab', 'all');
                        save();
                        allnotes();
                    }))

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

            function save() {
                notes.save($textarea.val()).then(function() {
                    $container.attr('status', 'saved');
                });
            }

            var timer = 0;
            $textarea.change(function() {
                clearTimeout(timer);
                $container.attr('status', 'saving');
                save();
            });
            $textarea.keyup(function() {
                clearTimeout(timer);
                $container.attr('status', 'saving');
                timer = setTimeout(function() {
                    save()
                }, 1000);
            });

            //draw render window
            var $renderwindow = $("<article>", {'id': 'notepad-render'}).appendTo($container);
            $('<div>', {'id': 'loading'})
                .append($('<img>', {'src': "/media/wikipedia/commons/3/30/Chromiumthrobber.svg"}))
                .appendTo($renderwindow);

            function render() {
                if (notes.get() && notes.get().content) {
                    var s = notes.get().content;
                    api.parse(s, {'contentmodel': 'wikitext', 'preview': true, 'pst': true})
                        .then(function(data) {
                            $renderwindow.empty().append($(data));
                            $renderwindow.find('.mw-editsection').remove();
                        });
                } else {
                    $renderwindow.empty()
                }
            }
            render();

            //draw all notes
            var $allnotes = $("<article>", {'id': 'notepad-all'}).appendTo($container);
            function allnotes() {
                $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<keys.length; i++) {
                        //var $a = $('<a>', {'href': '/wiki/' + keys[i]}).appendTo($allnotes);
                        var $sec = $('<section>', {'key': keys[i]}).appendTo($allnotes);
                        var $h4 = $('<h4>').appendTo($sec);
						if (keys[i] === util.pagename) {
							$h4.append(
								$('<a>', {'href': '#'})
								.text(keys[i].replace(/_/g, ' '))
								.click(function (e) {
									e.preventDefault();
									$('#notepad-link-edit').click();
								})
							);
						} else {
							$h4.append(
								$('<a>', {'href': '/wiki/' + keys[i] + '?notepad=notes'})
								.text(keys[i].replace(/_/g,' '))
							);
						}
                        var $preview = $('<div>', {'class': 'content'}).appendTo($sec);

                        var content = list[keys[i]].content;
                        $preview.append(content);

						var updated = list[keys[i]].updated.replace(/[TZ]/g,' ');
						$('<div>', {'class': 'updated'}).text(updated).appendTo($sec);

						$('<a>', {'class': 'links', 'href': '#'})
							.text('delete')
							.click(function (e) {
								e.preventDefault();
								var $link = $(e.target);
								var $section = $link.parent();
								console.log($section);
								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');
									if (key === util.pagename) {
										$textarea.val('');
									}
									notes.delete($section.attr('key'));
									$section.remove();
								}							
							})
							.appendTo($sec);

                        if ($preview[0].scrollHeight > $preview[0].offsetHeight) {                            
                            $('<a>', {'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 () {
                    var g = notes.get();
                    if (g) {
                        $textarea.val(notes.get().content);
                    } else {
                        $textarea.val('');
                    }
                    render();
                    allnotes();
                    console.log('focus');
                });                
            });


        }
    }

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

} (jQuery, mediaWiki ));