MediaWiki:Gadget-DiagnosticTree.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* DiagnosticTree.js — v6 PATCH: choice node support
*
* THREE changes required. Search for each FIND block and replace
* with the corresponding REPLACE block. Nothing else changes.
*
* ─────────────────────────────────────────────────────────────────
* CHANGE 1 of 3 — Add 'choice' to NODE_META
* ─────────────────────────────────────────────────────────────────
*
* FIND (the last entry in NODE_META, around line 13):
*
* neuro_referral: { color:'#b91c1c', icon:'', label:'Neurological Referral' }
*
* REPLACE WITH:
*
* neuro_referral: { color:'#b91c1c', icon:'', label:'Neurological Referral' },
* choice: { color:'#7c3aed', icon:'', label:'Select Best Match' }
*
*
* ─────────────────────────────────────────────────────────────────
* CHANGE 2 of 3 — Route choice nodes to _choice() in render()
* ─────────────────────────────────────────────────────────────────
*
* FIND (inside DiagnosticTree.prototype.render, around line 120):
*
* if ( node.type === 'result' ) cardHtml = this._result( node );
* else if ( node.type === 'overlap' ) cardHtml = this._overlap( node );
* else if ( node.type === 'neuro_referral') cardHtml = this._neuroRef( node );
* else cardHtml = this._question( node, meta );
*
* REPLACE WITH:
*
* if ( node.type === 'result' ) cardHtml = this._result( node );
* else if ( node.type === 'overlap' ) cardHtml = this._overlap( node );
* else if ( node.type === 'neuro_referral') cardHtml = this._neuroRef( node );
* else if ( node.type === 'choice' ) cardHtml = this._choice( node, meta );
* else cardHtml = this._question( node, meta );
*
*
* ─────────────────────────────────────────────────────────────────
* CHANGE 3 of 3 — Wire option button clicks in render()
* AND add the _choice() method
* ─────────────────────────────────────────────────────────────────
*
* FIND (inside DiagnosticTree.prototype.render, the yes/no wiring
* block near the bottom, around line 140):
*
* b( '#dt-back', function () { s.state.back(); s.render(); } );
* b( '#dt-reset', function () { s.state.reset(); s.render(); } );
* b( '#dt-yes', function () { var n = s.state.node(); if ( n.yes ) { s.state.go( n.yes ); s.render(); } } );
* b( '#dt-no', function () { var n = s.state.node(); if ( n.no ) { s.state.go( n.no ); s.render(); } } );
*
* REPLACE WITH:
*
* b( '#dt-back', function () { s.state.back(); s.render(); } );
* b( '#dt-reset', function () { s.state.reset(); s.render(); } );
* b( '#dt-yes', function () { var n = s.state.node(); if ( n.yes ) { s.state.go( n.yes ); s.render(); } } );
* b( '#dt-no', function () { var n = s.state.node(); if ( n.no ) { s.state.go( n.no ); s.render(); } } );
*
* this.el.querySelectorAll( '.dt-option-btn' ).forEach( function ( btn ) {
* btn.addEventListener( 'click', function () {
* var next = btn.getAttribute( 'data-next' );
* if ( next ) { s.state.go( next ); s.render(); }
* } );
* } );
*
*
* ─────────────────────────────────────────────────────────────────
* Then add _choice() as a new method.
*
* FIND (the opening line of the existing _question method):
*
* DiagnosticTree.prototype._question = function ( node, meta ) {
*
* INSERT the entire _choice() method ABOVE that line:
* ─────────────────────────────────────────────────────────────────
*/
// ─── INSERT THIS ENTIRE BLOCK above _question ───────────────────
DiagnosticTree.prototype._choice = function ( node, meta ) {
var rat = node.clinical_rationale || '';
var opts = node.options.map( function ( opt, i ) {
return '<button class="dt-option-btn" data-opt-index="' + i + '" data-next="' + opt.next + '">' +
'<div class="dt-option-content">' +
'<span class="dt-option-label">' + opt.label + '</span>' +
( opt.sublabel
? '<span class="dt-option-sublabel">' + opt.sublabel + '</span>'
: '' ) +
'</div>' +
'</button>';
} ).join( '' );
return '<div class="dt-card" style="border-color:' + meta.color + '22">' +
'<div class="dt-type-badge" style="background:' + meta.color + '">' +
meta.icon + ' ' + meta.label +
'</div>' +
'<div class="dt-question">' + node.question + '</div>' +
( rat ? '<div class="dt-rationale">' + rat + '</div>' : '' ) +
'<div class="dt-options">' + opts + '</div>' +
'</div>';
};
// ─── END INSERT ──────────────────────────────────────────────────
/**
* ─────────────────────────────────────────────────────────────────
* CSS ADDITIONS — append to MediaWiki:Gadget-DiagnosticTree.css
* ─────────────────────────────────────────────────────────────────
*
* Add the following rules at the end of the existing CSS file.
* They use only existing CSS variables — no new variables needed.
*/
/*
.dt-options {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: 1rem;
}
.dt-option-btn {
display: flex;
align-items: flex-start;
gap: 0.85rem;
width: 100%;
padding: 0.75rem 0.9rem;
background: var(--paper-2);
border: 1.5px solid var(--rule);
border-radius: 8px;
cursor: pointer;
text-align: left;
font-family: 'DM Sans', sans-serif;
position: relative;
overflow: hidden;
transition: border-color 0.15s, background 0.15s, transform 0.1s, box-shadow 0.15s;
}
.dt-option-btn::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 4px;
background: var(--opt-color, var(--blue));
border-radius: 8px 0 0 8px;
transition: width 0.15s;
}
.dt-option-btn:hover {
border-color: var(--opt-color, var(--blue));
background: #fff;
transform: translateY(-1px);
box-shadow: 0 3px 10px rgba(0,0,0,0.08);
}
.dt-option-btn:hover::before { width: 6px; }
.dt-option-btn:active { transform: translateY(0); box-shadow: none; }
.dt-option-btn[data-opt-index="0"] { --opt-color: var(--blue); }
.dt-option-btn[data-opt-index="1"] { --opt-color: var(--teal); }
.dt-option-btn[data-opt-index="2"] { --opt-color: var(--slate); }
.dt-option-btn[data-opt-index="3"] { --opt-color: var(--urgent); }
.dt-option-btn[data-opt-index="0"]:hover { background: #eff6ff; border-color: var(--blue); }
.dt-option-btn[data-opt-index="1"]:hover { background: #f0fdfa; border-color: var(--teal); }
.dt-option-btn[data-opt-index="2"]:hover { background: #f8fafc; border-color: var(--slate); }
.dt-option-btn[data-opt-index="3"]:hover { background: #fffbeb; border-color: var(--urgent); }
.dt-option-content {
display: flex;
flex-direction: column;
gap: 0.15rem;
padding-left: 0.25rem;
}
.dt-option-label {
font-size: 0.9rem;
font-weight: 600;
color: var(--ink);
line-height: 1.35;
}
.dt-option-sublabel {
font-size: 0.75rem;
color: var(--ink-soft);
line-height: 1.4;
font-weight: 400;
}
*/
/**
* ─────────────────────────────────────────────────────────────────
* SUMMARY OF ALL CHANGES
* ─────────────────────────────────────────────────────────────────
*
* DiagnosticTree.js — 3 edits:
* 1. Add 'choice' entry to NODE_META (~1 line)
* 2. Add choice branch in render() dispatcher (~1 line)
* 3. Add option button click wiring in render() (~5 lines)
* 4. Add _choice() method above _question() (~20 lines)
*
* DiagnosticTree.css — 1 edit:
* Append ~50 lines of new rules at the end of the file.
* All rules use only existing CSS variables.
* No existing rules are modified.
*
* Backward compatibility:
* All existing binary (yes/no) nodes are completely unchanged.
* The new code only activates when node.type === 'choice'.
* Existing EarTMJ and FrontOfChest trees work identically.
*
* JSON schema for a choice node:
* {
* "type": "choice",
* "question": "...",
* "clinical_rationale": "...", <- optional
* "options": [
* { "label": "...", "sublabel": "...", "next": "node-id" },
* { "label": "...", "sublabel": "...", "next": "node-id" },
* { "label": "...", "sublabel": "...", "next": "node-id" }
* ]
* }
* sublabel is optional. Maximum 4 options (index 0–3 are styled).
* ─────────────────────────────────────────────────────────────────
*/