My annotations follow the scholarly lineage. Marginalia (notes in the margin) and Glosses (explanatory notes on a text) are the two great medieval annotation traditions.
All Glosses (215 of them so far):

// if you would rather every new paper page start out flagged in progress. var STUB_SUFFIX = typeof window.wtpslineSuffix === 'string' ? window.wtpslineSuffix : ' What the paper says!?';
// What follows a citation dropped in by pasting a URL. Empty by default. var PASTE_SUFFIX = typeof window.wtpslinePasteSuffix === 'string' ? window.wtpslinePasteSuffix : ;
// Set either of these false in Common.js to keep one half only. var DO_STUB = window.wtpslineStub !== false; var DO_PASTE = window.wtpslinePaste !== false;
var action = mw.config.get( 'wgAction' ); if ( action !== 'edit' && action !== 'submit' ) { return; }
$( function () { if ( DO_PASTE ) { initPaste(); } if ( DO_STUB ) { initStub(); } } );
// ======================================================================= // 1. STUB // =======================================================================
function initStub() { if ( mw.config.get( 'wgNamespaceNumber' ) !== 0 ) { return; } if ( mw.config.get( 'wgArticleId' ) !== 0 ) { return; // the page exists; we are editing, not starting it }
// Anything that means the form was opened with intent — a preloaded // stub, a section edit, an undo — is somebody else's business. var q = new URLSearchParams( location.search ); if ( q.get( 'preload' ) || q.get( 'preloadtitle' ) || q.get( 'section' ) || q.get( 'undo' ) || q.get( 'oldid' ) ) { return; }
var page = mw.config.get( 'wgPageName' ); var key = keyFromTitle( page ); if ( !key ) { return; }
var ed = surface(); if ( !ed || ed.getText().trim() !== ) { return; }
// Only if there is actually a citation to transclude. Asynchronous, so // re-check that the box is still untouched when the answer comes back. templateExists( key ).then( function ( exists ) { if ( !exists ) { return; } var now = surface(); if ( !now || now.getText().trim() !== ) { return; // he started typing while we were asking } var line = 'Template:' + key + '' + STUB_SUFFIX + '\n'; now.focus(); now.insert( line ); mw.notify( 'wtpsline: Template:' + key + '', { tag: 'wtpsline', title: 'Stub inserted — undo to drop it' } ); } ); }
// ======================================================================= // 2. PASTE // =======================================================================
function initPaste() { // Capture on the document: CodeMirror 5 pastes into a hidden textarea // of its own, so binding to #wpTextbox1 alone would miss it. document.addEventListener( 'paste', function ( e ) { if ( !inEditor( e.target ) ) { return; } var data = e.clipboardData && e.clipboardData.getData( 'text/plain' ); if ( !data ) { return; } var key = keyFromUrl( data.trim() ); if ( !key ) { return; // not one of our URLs: paste it as it is }
e.preventDefault(); var ed = surface(); if ( !ed ) { return; } ed.insert( 'Template:' + key + '' + PASTE_SUFFIX );
// Say so afterwards rather than blocking the paste on an API call. templateExists( key ).then( function ( exists ) { if ( exists ) { mw.notify( 'Template:' + key + '', { tag: 'wtpsline' } ); } else { mw.notify( 'Template:' + key + ' does not exist', { tag: 'wtpsline', type: 'warn' } ); } } ); }, true ); }
function inEditor( node ) { var el = node && node.nodeType === 3 ? node.parentNode : node; if ( !el || !el.closest ) { return false; } return !!( el.closest( '#wpTextbox1, .CodeMirror, .cm-editor' ) ); }
// ======================================================================= // TITLES // =======================================================================
// "Carmichael76a" -> "carmichael76a". Underscores are kept: they are what // the citation templates link with, and a template call treats them as // spaces anyway. function keyFromTitle( title ) { if ( !title ) { return ; } title = String( title ).replace( /^\s+|\s+$/g, ).replace( / /g, '_' ); // A namespaced title is not a reference page — except Template: itself, // where the obvious reading is "cite this one". var ns = /^([^:]+):(.*)$/.exec( title ); if ( ns ) { if ( !/^template$/i.test( ns[ 1 ] ) ) { return ; } title = ns[ 2 ]; } // A reference key never has a slash in it. This is what keeps a pasted // 𝕐 month-page URL (Y/2026/August) from becoming a template call. if ( !title || title.indexOf( '/' ) >= 0 ) { return ; } return title.charAt( 0 ).toLowerCase() + title.slice( 1 ); }
// A laussywiki URL -> its key. Both shapes of local URL are accepted, and // laussy.org too, so a link copied from the live site works the same. function keyFromUrl( text ) { if ( !/^https?:\/\//i.test( text ) || /\s/.test( text ) ) { return ; }
var u; try { u = new URL( text ); } catch ( e ) { return ; }
if ( u.host !== location.host && !/(^|\.)laussy\.org$/i.test( u.host ) ) { return ; }
// index.php?title=X&… var t = u.searchParams.get( 'title' ); if ( t ) { return keyFromTitle( decodeURIComponent( t.split( '#' )[ 0 ] ) ); }
// The pretty path. $wgArticlePath is /laussywiki/$1 here but something // else online, so the local one is only tried first, not relied on. var path = u.pathname; var artPath = mw.config.get( 'wgArticlePath' ) || '/$1'; var i = artPath.indexOf( '$1' ); var rest = null;
if ( i >= 0 && path.indexOf( artPath.slice( 0, i ) ) === 0 ) { rest = path.slice( i ); var tail = artPath.slice( i + 2 ); if ( tail && rest.slice( -tail.length ) === tail ) { rest = rest.slice( 0, -tail.length ); } } else { // Another wiki root on a host we trust — laussy.org/wiki/Neu12a. // Drop one leading directory, keep the rest, subpages and all. var seg = path.split( '/' ).filter( Boolean ); rest = ( seg.length > 1 ? seg.slice( 1 ) : seg ).join( '/' ); }
// index.php, load.php, api.php: an endpoint, not an article. if ( !rest || /\.php$/i.test( rest.split( '/' )[ 0 ] ) ) { return ; } try { rest = decodeURIComponent( rest ); } catch ( e ) { /* leave it as it came */ } return keyFromTitle( rest ); }
var known = {};
function templateExists( key ) { var title = 'Template:' + key; if ( Object.prototype.hasOwnProperty.call( known, title ) ) { return $.Deferred().resolve( known[ title ] ).promise(); } return new mw.Api().get( { action: 'query', titles: title, formatversion: 2 } ).then( function ( d ) { var pages = ( d && d.query && d.query.pages ) || []; var exists = !!( pages[ 0 ] && !pages[ 0 ].missing ); known[ title ] = exists; return exists; }, function () { return false; // API down: say nothing rather than guess } ); }
// ======================================================================= // EDITING SURFACE // =======================================================================
// One facade over the three editors this wiki can show. Insertion always // goes through the editor's own API (or execCommand for a textarea) so the // change joins the undo stack instead of wiping it. function surface() { // CodeMirror 5 — the instance hangs off its wrapper element. var wrap = document.querySelector( '.CodeMirror' ); var cm5 = wrap && wrap.CodeMirror; if ( cm5 && cm5.replaceSelection ) { return { kind: 'cm5', getText: function () { return cm5.getValue(); }, focus: function () { cm5.focus(); }, insert: function ( text ) { cm5.replaceSelection( text, 'end' ); cm5.focus(); } }; }
// CodeMirror 6 — reached through its back-pointer on the content DOM. var view = null; try { var dom = document.querySelector( '.cm-editor .cm-content' ); view = dom && dom.cmView && dom.cmView.view; } catch ( e ) { view = null; } if ( view && view.dispatch && view.state ) { return { kind: 'cm6', getText: function () { return view.state.doc.toString(); }, focus: function () { view.focus(); }, insert: function ( text ) { view.dispatch( view.state.replaceSelection( text ) ); view.focus(); } }; }
var box = document.getElementById( 'wpTextbox1' ); if ( !box ) { return null; } return { kind: 'textarea', getText: function () { return box.value; }, focus: function () { box.focus(); }, insert: function ( text ) { box.focus(); // Undoable, unlike assigning .value. Deprecated but the only // thing that keeps the undo stack in a textarea; the manual // splice below is the fallback if it ever stops working. var ok = false; try { ok = document.execCommand( 'insertText', false, text ); } catch ( e ) { ok = false; } if ( !ok ) { var s = box.selectionStart, t = box.selectionEnd; box.value = box.value.slice( 0, s ) + text + box.value.slice( t ); box.selectionStart = box.selectionEnd = s + text.length; $( box ).trigger( 'change' ); } } }; }
mw.log( 'wtpsline v' + VERSION + ' loaded' );
}() );Usage:
<nowiki>
{{Gloss|ref=laussy04a|diary=2026-07-19}} ← on a paper page
{{Marginalium|film=El hoyo|diary=2026-07-19}} ← on a movie page
</nowiki>