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.
// SourceAssess
// <nowiki>
/**
 * SourceAssess.js
 *
 * Wikipedia user script to review article references and generate a
 * {{source assess table}} or {{ORGCRIT assess table}} templates for 
 * use in AfC, AfD or similar discussions.
 *
 * Version: v26.04.29 - Stable release
 * Release notes: added the ability to edit the list of sources and
 * reset the list of sources from the scanned page. Scanned references
 * on a page are no longer required to build a reference list.
 *
 */

var SourceAssess = {};
window.SourceAssess = SourceAssess;

// ─── Portlet link ────────────────────────────────────────────────────────────

$.when(
	mw.loader.using(['mediawiki.api', 'ext.gadget.morebits']),
	$.ready
).then(function () {
	var portletLink = mw.util.addPortletLink(
		'p-cactions',
		'#',
		'Assess sources',
		'ca-sourceassess',
		'Review references and generate a source assessment table'
	);
	$(portletLink).on('click', function (e) {
		e.preventDefault();
		SourceAssess.run();
	});
});

// ─── Entry point ─────────────────────────────────────────────────────────────

SourceAssess.run = function () {
	// If we already have state from a previous open, go straight to the window
	if (SourceAssess._refs && SourceAssess._refs.length > 0) {
		SourceAssess.displayWindow(SourceAssess._refs, SourceAssess._assessments);
		return;
	}

	var pageName = mw.config.get('wgPageName');
	var api = new mw.Api();
	api.get({
		action: 'query',
		titles: pageName,
		prop: 'revisions',
		rvprop: 'content',
		rvslots: 'main',
		formatversion: 2
	}).then(function (data) {
		var page = data.query.pages[0];
		if (!page || page.missing) {
			mw.notify('SourceAssess: could not load page wikitext.', { type: 'error' });
			return;
		}
		var wikitext = page.revisions[0].slots.main.content;
		var refs = SourceAssess.extractRefs(wikitext);
		if (refs.length === 0) {
			mw.notify('SourceAssess: no <ref> tags found on this page. You can add references manually.', { type: 'warn' });
			SourceAssess._refs = [''];
			SourceAssess._originalRefs = [''];
			SourceAssess._assessments = [{ i: '', r: '', s: '', ij: '', rj: '', sj: '', '2': '', '2j': '', skipped: false }];
			SourceAssess._currentIndex = 0;
			SourceAssess.showRefEditor(SourceAssess._refs, SourceAssess._assessments, { close: function () {} });
			return;
		}
		
        // Cache before displaying
        SourceAssess._refs = refs;
		SourceAssess._originalRefs = refs.slice(); // snapshot for Reset
        SourceAssess._assessments = refs.map(function () {
            return { i: '', r: '', s: '', ij: '', rj: '', sj: '', '2': '', '2j': '', skipped: false };
        });
	SourceAssess.displayWindow(SourceAssess._refs, SourceAssess._assessments);
	}).catch(function (err) {
		mw.notify('SourceAssess: API error – ' + err, { type: 'error' });
	});
};

// ─── Reference extraction ─────────────────────────────────────────────────────

/**
 * Extract all unique <ref ...>...</ref> contents from wikitext.
 * Ignores self-closing <ref ... /> tags and named back-references <ref name="x" />.
 * Deduplicates by inner content so named refs that repeat are only listed once.
 */
SourceAssess.extractRefs = function (wikitext) {
	// Match opening ref tag (with optional attributes) and capture content
	var refPattern = /<ref(?:\s[^>]*[^/])?>([^<]+(?:<(?!\/ref>)[^<]*)*)<\/ref>/gi;
	var seen = {};
	var refs = [];
	var match;
	while ((match = refPattern.exec(wikitext)) !== null) {
		var content = match[1].trim();
		if (content && !seen[content]) {
			seen[content] = true;
			refs.push(content);
		}
	}
	return refs;
};

// ─── Main window ──────────────────────────────────────────────────────────────

SourceAssess.displayWindow = function (refs, assessments) {
	var currentIndex = SourceAssess._currentIndex || 0;

	// ── Window setup ──
	var win = new Morebits.simpleWindow(780, 600);
	win.setTitle('Source assessment');
	win.setScriptName('SourceAssess');
	win.addFooterLink('Documentation', 'User:Bobby Cohn/SourceAssess');
	win.addFooterLink('Leave feedback', 'User talk:Bobby Cohn');

	SourceAssess._win = win;

	// ── Top-level container (we rebuild inner content each step) ──
	var outerForm = new Morebits.quickForm(function () { /* no submit — handled manually */ });

	var container = outerForm.append({ type: 'div', id: 'sa-container' });

	// Progress bar / header
	var progressRow = container.append({
		type: 'div',
		style: 'display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;'
	});
	progressRow.append({
		type: 'div',
		id: 'sa-progress',
		style: 'font-weight:bold;'
	});
	var progressBtns = progressRow.append({
		type: 'div',
		style: 'display:flex; gap:6px;'
	});
	progressBtns.append({
		type: 'button',
		label: 'Edit references',
		name: 'sa-edit-refs'
	});
	progressBtns.append({
		type: 'button',
		label: 'Generate table',
		name: 'sa-finish'
	});

	// Rendered reference preview
	container.append({
		type: 'div',
		id: 'sa-ref-preview',
		style: [
			'border:1px solid #a2a9b1',
			'border-radius:3px',
			'padding:8px 10px',
			'min-height:30px',
			'max-height:120px',
			'overflow-y:auto',
			'margin-bottom:6px'
		].join(';')
	});

	// Reference display (wikitext)
	container.append({
		type: 'div',
		id: 'sa-reftext',
		style: [
			'background:#f8f9fa',
			'border:1px solid #a2a9b1',
			'border-radius:3px',
			'padding:8px 10px',
			'font-family:monospace',
			'font-size:0.85em',
			'white-space:pre-wrap',
			'word-break:break-all',
			'max-height:60px',
			'overflow-y:auto',
			'margin-bottom:8px'
		].join(';')
	});

	// Assessment fields table
	var table = container.append({
		type: 'div',
		id: 'sa-fields',
		style: 'display:table; width:100%; border-collapse:separate; border-spacing:0 6px;'
	});

	var labels = [
		'<a href="http://wiki.nitrosworld.org/wiki/Wikipedia:Independent_sources" target="_blank" rel="noopener" style="font-style:normal;">Independence</a>',
		'<a href="http://wiki.nitrosworld.org/wiki/Wikipedia:Reliable_sources" target="_blank" rel="noopener" style="font-style:normal;">Reliability</a>',
		'<a href="http://wiki.nitrosworld.org/wiki/Wikipedia:Significant_coverage" target="_blank" rel="noopener" style="font-style:normal;">Significant coverage</a>'
	];
	var keys   = ['i', 'r', 's'];
	var justifKeys = ['ij', 'rj', 'sj'];

	keys.forEach(function (key, idx) {
		var row = table.append({
			type: 'div',
			style: 'display:table-row;'
		});

		// Label cell
		row.append({
			type: 'div',
			label: labels[idx],
			style: 'display:table-cell; width:200px; vertical-align:middle; padding-right:8px; font-weight:bold;'
		});

		// Dropdown cell
		var dropCell = row.append({
			type: 'div',
			style: 'display:table-cell; vertical-align:middle; padding-right:12px;'
		});
		dropCell.append({
			type: 'select',
			name: 'sa-sel-' + key,
			list: [
				{ label: '(Empty)', value: '' },
				{ label: 'Yes',   value: 'y' },
				{ label: 'No',    value: 'n' },
				{ label: 'Unsure', value: '?' }
			]
		});

		// Justification cell
		var justCell = row.append({
			type: 'div',
			style: 'display:table-cell; vertical-align:middle; width:100%;'
		});
		justCell.append({
			type: 'input',
			name: 'sa-just-' + justifKeys[idx],
			placeholder: 'Optional justification ...',
			style: 'width:99%;'
		});
	});

	// Fourth field — hidden unless ORGCRIT mode is active
	var orgcritRow = table.append({
		type: 'div',
		id: 'sa-row-2',
		style: 'display:' + (SourceAssess._orgcrit ? 'table-row' : 'none') + ';'
	});
	orgcritRow.append({
		type: 'div',
		label: '<a href="http://wiki.nitrosworld.org/wiki/WP:PSTS" target="_blank" rel="noopener" style="font-style:normal;">Secondary</a>',
		style: 'display:table-cell; width:200px; vertical-align:middle; padding-right:8px; font-weight:bold;'
	});
	var orgcritDrop = orgcritRow.append({
		type: 'div',
		style: 'display:table-cell; vertical-align:middle; padding-right:12px;'
	});
	orgcritDrop.append({
		type: 'select',
		name: 'sa-sel-2',
		list: [
			{ label: '(Empty)', value: '' },
			{ label: 'Yes',   value: 'y' },
			{ label: 'No',    value: 'n' },
			{ label: 'Unsure', value: '?' }
		]
	});
	orgcritRow.append({
		type: 'div',
		style: 'display:table-cell; vertical-align:middle; width:100%;'
	}).append({
		type: 'input',
		name: 'sa-just-2j',
		placeholder: 'Optional justification ...',
		style: 'width:99%;'
	});

	// Skip checkbox
	container.append({
		type: 'checkbox',
		name: 'sa-skip-cb',
		list: [{ label: 'Ignore this reference', value: 'skip' }]
	});

	// ORGCRIT checkbox
	container.append({
		type: 'checkbox',
		name: 'sa-orgcrit',
		list: [{ label: 'Apply ORGCRIT assessment', value: 'orgcrit' }]
	});

	// Navigation buttons
	var btnRow = container.append({
		type: 'div',
		style: 'margin-top:14px; display:flex; gap:8px;'
	});
	btnRow.append({ type: 'button', label: '← Previous', name: 'sa-prev' });
	btnRow.append({ type: 'button', label: 'Next →', name: 'sa-next' });
	
	var rendered = outerForm.render();
	win.setContent(rendered);
	win.display();

	var dialog = document.querySelector('.morebits-dialog');
	if (dialog) {
		dialog.style.zIndex = '10000';
	}

	win.close = (function (originalClose) {
		return function () {
			readForm(currentIndex); // flush current form state before closing
			SourceAssess._currentIndex = currentIndex;
			originalClose.apply(win, arguments);
		};
	})(win.close);
	
	// ── Helper: populate form with current ref data ──
	function populateForm(idx) {
		var a = assessments[idx];
		var refCount = refs.length;

		// Progress
		document.getElementById('sa-progress').textContent =
			'Reference ' + (idx + 1) + ' of ' + refCount;

		// Rendered preview
		var api = new mw.Api();
		api.post({
			action: 'parse',
			text: refs[idx],
			contentmodel: 'wikitext',
			prop: 'text',
			disablelimitreport: true,
			formatversion: 2
		}).then(function (data) {
			var preview = document.getElementById('sa-ref-preview');
			if (preview) {
				preview.innerHTML = data.parse.text;
				preview.querySelectorAll('a').forEach(function (a) {
					a.setAttribute('target', '_blank');
					a.setAttribute('rel', 'noopener');
				});
			}
		}).catch(function () {
			var preview = document.getElementById('sa-ref-preview');
			if (preview) preview.textContent = '(Preview unavailable)';
		});

		// Ref text
		document.getElementById('sa-reftext').textContent = refs[idx];

		// Dropdowns & justification
		keys.forEach(function (key, i) {
			var sel = document.querySelector('[name="sa-sel-' + key + '"]');
			if (sel) sel.value = a[key];
			var inp = document.querySelector('[name="sa-just-' + justifKeys[i] + '"]');
			if (inp) inp.value = a[justifKeys[i]];
		});

		// Optional ORGCRIT assessment
		var orgcritCb = document.querySelector('[name="sa-orgcrit"]');
		if (orgcritCb) orgcritCb.checked = SourceAssess._orgcrit || false;

		var row2 = document.getElementById('sa-row-2');
		if (row2) row2.style.display = SourceAssess._orgcrit ? 'table-row' : 'none';

		var sel2 = document.querySelector('[name="sa-sel-2"]');
		if (sel2) sel2.value = a['2'] || '';
		var inp2 = document.querySelector('[name="sa-just-2j"]');
		if (inp2) inp2.value = a['2j'] || '';

		// Skip checkbox
		var skipCb = document.querySelector('[name="sa-skip-cb"]');
		if (skipCb) skipCb.checked = a.skipped;
		updateSkipState(a.skipped);

		// Prev button
		var prevBtn = document.querySelector('[name="sa-prev"]');
		if (prevBtn) prevBtn.disabled = (idx === 0);

		// Next button
		var nextBtn = document.querySelector('[name="sa-next"]');
		if (nextBtn) nextBtn.disabled = (idx === refs.length - 1);
	}

	function updateSkipState(skipped) {
		var fieldsDiv = document.getElementById('sa-fields');
		if (fieldsDiv) {
			fieldsDiv.style.opacity = skipped ? '0.4' : '1';
			var inputs = fieldsDiv.querySelectorAll('select, input');
			inputs.forEach(function (el) { el.disabled = skipped; });
		}
	}

	function readForm(idx) {
		var a = assessments[idx];
		var skipCb = document.querySelector('[name="sa-skip-cb"]');
		a.skipped = skipCb ? skipCb.checked : false;

		keys.forEach(function (key, i) {
			var sel = document.querySelector('[name="sa-sel-' + key + '"]');
			a[key] = (sel && !a.skipped) ? sel.value : '';
			var inp = document.querySelector('[name="sa-just-' + justifKeys[i] + '"]');
			a[justifKeys[i]] = (inp && !a.skipped) ? inp.value : '';
		});

		var sel2 = document.querySelector('[name="sa-sel-2"]');
		a['2'] = (sel2 && !a.skipped) ? sel2.value : '';
		var inp2 = document.querySelector('[name="sa-just-2j"]');
		a['2j'] = (inp2 && !a.skipped) ? inp2.value : '';
	}

	// ── Wire up events ──
	$(document).off('.sourceassess');  // Remove ALL previous SourceAssess handlers in one shot
	
	$(document).on('change.sourceassess', '[name="sa-skip-cb"]', function () {
		updateSkipState(this.checked);
	});
	
	$(document).on('click.sourceassess', '[name="sa-prev"]', function (e) {
		e.preventDefault();
		readForm(currentIndex);
		if (currentIndex > 0) {
			currentIndex--;
			SourceAssess._currentIndex = currentIndex;
			populateForm(currentIndex);
		}
	});
	
	$(document).on('click.sourceassess', '[name="sa-next"]', function (e) {
		e.preventDefault();
		readForm(currentIndex);
		if (currentIndex < refs.length - 1) {
			currentIndex++;
			SourceAssess._currentIndex = currentIndex;
			populateForm(currentIndex);
		}
	});

	$(document).on('click.sourceassess', '[name="sa-finish"]', function (e) {
		e.preventDefault();
		readForm(currentIndex);
		SourceAssess.showSummary(refs, assessments, win);
	});

	$(document).on('change.sourceassess', '[name="sa-orgcrit"]', function () {
		SourceAssess._orgcrit = this.checked;
		var row = document.getElementById('sa-row-2');
		if (row) row.style.display = this.checked ? 'table-row' : 'none';
	});

	$(document).on('click.sourceassess', '[name="sa-edit-refs"]', function (e) {
		e.preventDefault();
		readForm(currentIndex);
		SourceAssess.showRefEditor(refs, assessments, win);
	});

	// Initialise first ref
	populateForm(currentIndex);
};

// ─── Reference editor window ──────────────────────────────────────────────────

SourceAssess.showRefEditor = function (refs, assessments, prevWin) {
    prevWin.close();

    var win3 = new Morebits.simpleWindow(780, 620);
    win3.setTitle('Edit references');
    win3.setScriptName('SourceAssess');
    win3.addFooterLink('Documentation', 'User:Bobby Cohn/SourceAssess');
    win3.addFooterLink('Leave feedback', 'User talk:Bobby Cohn');

    var form3 = new Morebits.quickForm(function () {});

    form3.append({
        type: 'div',
        style: 'font-style:italic; color:#555; margin-bottom:8px;',
        label: 'Reference editing is in development and may affect source assessment. Review assessments after changing or modifying the reference list. See <a href="http://wiki.nitrosworld.org/wiki/User:Bobby Cohn/SourceAssess#Known_issues" target="_blank" rel="noopener">known issues</a> for more information. '
    });

    // Placeholder div — rebuildRefList will populate this after render
    form3.append({
        type: 'div',
        id: 'sa-ref-editor-list'
    });

    // Bottom button row
    var bottomRow = form3.append({
        type: 'div',
        style: 'display:flex; justify-content:space-between; margin-top:8px; margin-bottom:12px;'
    });
    var leftBtns = bottomRow.append({
        type: 'div',
        style: 'display:flex; gap:6px;'
    });
    leftBtns.append({
        type: 'button',
        label: '+',
        name: 'sa-ref-add-new',
        style: 'width:28px;'
    });
    leftBtns.append({
        type: 'button',
        label: 'Reset',
        name: 'sa-ref-reset'
    });
    bottomRow.append({
        type: 'button',
        label: '← Return to assessment',
        name: 'sa-ref-editor-return'
    });

    var rendered3 = form3.render();
    win3.setContent(rendered3);
    win3.display();

    var dialog3 = document.querySelector('.morebits-dialog');
    if (dialog3) {
        dialog3.style.zIndex = '10000';
    }

    // ── Helpers ──

    function readRefEdits() {
        var result = [];
        var idx = 0;
        while (true) {
            var ta = document.querySelector('[name="sa-ref-edit-' + idx + '"]');
            if (!ta) break;
            result.push({
				value: ta.value.trim(),
				original: ta.getAttribute('data-original-content') || ta.value.trim()
			});
            idx++;
        }
        return result;
    }

    // UPDATE rebuildRefList to accept objects:
	function rebuildRefList(values) {
		var list = document.getElementById('sa-ref-editor-list');
		if (!list) return;
		list.innerHTML = '';
		values.forEach(function (item, i) {
			var val = typeof item === 'object' ? item.value : item;
			var original = typeof item === 'object' ? item.original : item;
	
			var field = document.createElement('fieldset');
			field.style.cssText = 'border:1px solid #a2a9b1; padding:6px 10px; margin-bottom:6px;';
			var legend = document.createElement('legend');
			legend.textContent = 'Reference ' + (i + 1);
			field.appendChild(legend);
	
			var inner = document.createElement('div');
			inner.style.cssText = 'display:flex; gap:6px; align-items:flex-start;';
	
			var ta = document.createElement('textarea');
			ta.name = 'sa-ref-edit-' + i;
			ta.value = val;
			ta.setAttribute('data-original-content', original);
			ta.style.cssText = 'width:100%; box-sizing:border-box; font-family:monospace; font-size:0.85em; height:60px; resize:vertical;';
	
			var removeBtn = document.createElement('button');
			removeBtn.textContent = '−';
			removeBtn.name = 'sa-ref-remove-' + i;
			removeBtn.style.cssText = 'flex-shrink:0; width:28px;';
	
			inner.appendChild(ta);
			inner.appendChild(removeBtn);
			field.appendChild(inner);
			list.appendChild(field);
		});
	}

    // Initial render of ref list
	rebuildRefList(refs.map(function (ref) {
		return { value: ref, original: ref };
	}));
	
    // ── Wire up events ──

    $(document).off('.sourceassess-refeditor');

	$(document).on('click.sourceassess-refeditor', '[name^="sa-ref-remove-"]', function (e) {
		e.preventDefault();
		var idx = parseInt(this.name.replace('sa-ref-remove-', ''), 10);
		var values = readRefEdits();
		if (values.length <= 1) {
			mw.notify('SourceAssess: cannot remove the last reference.', { type: 'warn' });
			SourceAssess._fixNotifZ();
			return;
		}
		values.splice(idx, 1);
		rebuildRefList(values);
	});
	
	$(document).on('click.sourceassess-refeditor', '[name="sa-ref-add-new"]', function (e) {
		e.preventDefault();
		var values = readRefEdits();
		values.push({ value: '', original: '' });
		rebuildRefList(values);
	});
	
	// Reset handler:
	$(document).on('click.sourceassess-refeditor', '[name="sa-ref-reset"]', function (e) {
		e.preventDefault();
		if (!confirm('Reset references to the originally extracted list? Any edits will be lost.')) return;
		SourceAssess._refs = SourceAssess._originalRefs.slice();
		SourceAssess._assessments = SourceAssess._originalRefs.map(function (ref, i) {
			return SourceAssess._assessments[i] || { i: '', r: '', s: '', ij: '', rj: '', sj: '', '2': '', '2j': '', skipped: false };
		});
		rebuildRefList(SourceAssess._refs.map(function (ref) {
			return { value: ref, original: ref };
		}));
	});

	$(document).on('click.sourceassess-refeditor', '[name="sa-ref-editor-return"]', function (e) {
		e.preventDefault();
		
		// Build a map from original content to its assessment
		var contentToAssessment = {};
		SourceAssess._refs.forEach(function (ref, i) {
			contentToAssessment[ref] = SourceAssess._assessments[i];
		});
		
		// Read edited values and their original content from data attributes
		var newRefs = [];
		var newAssessments = [];
		var idx = 0;
		while (true) {
			var ta = document.querySelector('[name="sa-ref-edit-' + idx + '"]');
			if (!ta) break;
			var newVal = ta.value.trim();
			var originalContent = ta.getAttribute('data-original-content') || '';
			newRefs.push(newVal);
			// If content unchanged, carry forward the matching assessment
			// If content changed or new, use the assessment for that original slot or blank
			newAssessments.push(
				contentToAssessment[originalContent] ||
				contentToAssessment[newVal] ||
				{ i: '', r: '', s: '', ij: '', rj: '', sj: '', '2': '', '2j': '', skipped: false }
			);
			idx++;
		}

		SourceAssess._refs = newRefs;
		SourceAssess._assessments = newAssessments;

		if (SourceAssess._currentIndex >= newRefs.length) {
			SourceAssess._currentIndex = newRefs.length - 1;
		}
		
		win3.close();
		SourceAssess.displayWindow(SourceAssess._refs, SourceAssess._assessments);
	});

};

// ─── Summary / output window ──────────────────────────────────────────────────

SourceAssess.showSummary = function (refs, assessments, prevWin) {
	prevWin.close();
	
	// Determine whether to build ORGCRIT
	var isOrgcrit = SourceAssess._orgcrit || false;
	var tableTemplate = isOrgcrit ? 'ORGCRIT assess table' : 'source assess table';
	var entryTemplate = isOrgcrit ? 'ORGCRIT assess' : 'source assess';
	
	var currentUser = mw.config.get('wgUserName');
	
	// Build wikitext
	var lines = ['{{' + tableTemplate];
	
	if (!isOrgcrit) {
		lines.push('| startopen = yes');
		lines.push('| user = ' + currentUser);
	}

	lines.push('|');
	
	refs.forEach(function (ref, idx) {
		var a = assessments[idx];
		if (a.skipped) return;

		lines.push('{{' + entryTemplate);
		lines.push('| source =  ' + ref);
		lines.push('| i = ' + a.i + ' | ij = ' + a.ij);
		lines.push('| r = ' + a.r + ' | rj = ' + a.rj);
		lines.push('| s = ' + a.s + ' | sj = ' + a.sj);
		if (isOrgcrit) {
			lines.push('| 2 = ' + a['2'] + ' | 2j = ' + a['2j']);
		}
		lines.push('}}');
	});

	lines.push('<!-- Generated using [[User:Bobby Cohn/SourceAssess|SourceAssess]] -->');
	lines.push('}}');
	var wikicode = lines.join('\n');

	// ── New window ──
	var win2 = new Morebits.simpleWindow(800, 720);
	win2.setTitle('Source assessment table summary');
	win2.setScriptName('SourceAssess');
	win2.addFooterLink('Documentation', 'User:Bobby Cohn/SourceAssess');
	win2.addFooterLink('Leave feedback', 'User talk:Bobby Cohn');

	var form2 = new Morebits.quickForm(function () {});

	// ── Rendered preview (parsed via API) ──
	var previewSection = form2.append({
		type: 'div',
		label: 'Rendered preview',
		style: 'font-weight:bold; margin-bottom:4px;'
	});
	previewSection.append({
		type: 'div',
		id: 'sa-preview-loading',
		label: 'Loading preview…',
		style: 'font-style:italic; color:#555;'
	});
	previewSection.append({
		type: 'div',
		id: 'sa-preview-content',
		style: [
			'border:1px solid #a2a9b1',
			'border-radius:3px',
			'padding:8px 10px',
			'min-height:60px',
			'max-height:380px',
			'overflow-y:auto',
			'display:none'
		].join(';')
	});

	// ── Wikitext output ──
	var codeSection = form2.append({
		type: 'field',
		label: 'Wikitext'
	});
	codeSection.append({
		type: 'div',
		style: 'margin-bottom:8px;'
	}).append({
		type: 'button',
		label: 'Copy to clipboard',
		name: 'sa-copy'
	});
	codeSection.append({
		type: 'textarea',
		name: 'sa-output',
		value: wikicode,
		style: [
			'width:100%',
			'height:200px',
			'font-family:monospace',
			'font-size:0.82em',
			'white-space:pre',
			'max-height:120px',
			'resize:vertical'
		].join(';')
	});

	form2.append({
		type: 'button',
		label: '← Return to assessment',
		name: 'sa-return',
		style: 'margin-bottom:12px;'
	});
	
	var rendered2 = form2.render();
	win2.setContent(rendered2);
	win2.display();

	var dialog = document.querySelector('.morebits-dialog');
	if (dialog) {
		dialog.style.zIndex = '10000';
	}

	// Populate textarea (Morebits may not set value directly)
	var ta = document.querySelector('[name="sa-output"]');
	if (ta) ta.value = wikicode;

	// Copy button
	$(document).off('.sourceassess-summary');
	$(document).on('click.sourceassess-summary', '[name="sa-copy"]', function (e) {
		e.preventDefault();
		var el = document.querySelector('[name="sa-output"]');
		if (!el) return;
		el.select();
		try {
			document.execCommand('copy');
			mw.notify('SourceAssess: wikitext copied to clipboard.', { type: 'success' });
		} catch (err) {
			// Fallback for browsers blocking execCommand
			if (navigator.clipboard) {
				navigator.clipboard.writeText(el.value).then(function () {
					mw.notify('SourceAssess: wikitext copied to clipboard.', { type: 'success' });
				});
			}
		}
	});

	$(document).off('.sourceassess-return');
	$(document).on('click.sourceassess-return', '[name="sa-return"]', function (e) {
		e.preventDefault();
		win2.close();
		SourceAssess.displayWindow(SourceAssess._refs, SourceAssess._assessments);
	});

	// ── Fetch rendered preview ──
	var api = new mw.Api();
	api.post({
		action: 'parse',
		text: wikicode,
		contentmodel: 'wikitext',
		prop: 'text',
		disablelimitreport: true,
		formatversion: 2
	}).then(function (data) {
		var loading = document.getElementById('sa-preview-loading');
		var content = document.getElementById('sa-preview-content');
		if (loading) loading.style.display = 'none';
		if (content) {
			content.innerHTML = data.parse.text;
			content.style.display = 'block';
			// Make links open in new tab to avoid navigating away
			content.querySelectorAll('a').forEach(function (a) {
				a.setAttribute('target', '_blank');
				a.setAttribute('rel', 'noopener');
			});
		}
	}).catch(function () {
		var loading = document.getElementById('sa-preview-loading');
		if (loading) loading.textContent = '(Preview unavailable – check wikitext below.)';
	});
};

// </nowiki>