Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Strg+F5
//Dokumentation unter [[Benutzer:Schnark/js/veCode]] <nowiki>
/*global mediaWiki, OO, ve*/
(function ($, mw) {
"use strict";

var l10n = {
	en: {
		'schnark-vecode-label': 'Edit code separately',
		'schnark-vecode-title': 'Edit code',
		'schnark-vecode-owe': 'Code (traditional)'
	},
	de: {
		'schnark-vecode-label': 'Code separat bearbeiten',
		'schnark-vecode-title': 'Code bearbeiten',
		'schnark-vecode-owe': 'Quelltext (traditionell)'
	}
}, availableModels = {
	javascript: {mime: 'text/javascript', module: 'javascript', ace: 'javascript', geshi: 'javascript'},
	css: {mime: 'text/css', module: 'css', ace: 'css', geshi: 'css'},
	'sanitized-css': {mime: 'text/css', module: 'css', ace: 'css', geshi: 'css'}
	//Scribunto: {mime: 'text/plain', module: ???, ace: 'lua', geshi: 'lua'}
};

function initL10N (l10n) {
	var i, chain = mw.language.getFallbackLanguageChain();
	for (i = chain.length - 1; i >= 0; i--) {
		if (chain[i] in l10n) {
			mw.messages.set(l10n[chain[i]]);
		}
	}
}

function define (model) {
	/*jshint evil:true*/

	//Change CodeMirrorAction and ArticleTarget to depend on current mime type
	ve.ui.CodeMirrorAction.prototype.toggle = eval('(' +
		ve.ui.CodeMirrorAction.prototype.toggle.toString().replace(
			/(['"]?)mode\1\s*:\s*(['"])text\/mediawiki\2/,
			'mode:"' + availableModels[model].mime + '"'
		) +
	')');

	ve.init.mw.ArticleTarget.prototype.submit = eval('(' +
		ve.init.mw.ArticleTarget.prototype.submit.toString().replace(
			/(['"]?)format\1\s*:\s*(['"])text\/x-wiki\2/,
			'format:"' + availableModels[model].mime + '"'
		).replace(
			/(['"]?)model\1\s*:\s*(['"])wikitext\2/,
			'model:"' + model + '"'
		) +
	')');

	//Target for editing code
	function CodeTarget (config) {
		config.modes = ['source']; //VE thinks no mode is available
		CodeTarget.parent.call(this, config);
	}

	OO.inheritClass(CodeTarget, ve.init.mw.DesktopArticleTarget);

	CodeTarget.static.name = 'code';

	//Remove useless stuff, add our own dialog
	CodeTarget.static.toolbarGroups = [
		{name: 'history', include: [{ group: 'history'}]},
		{name: 'aceDialog', include: ['aceDialog']},
		{name: 'specialCharacter', include: ['specialCharacter']}
	].concat( CodeTarget.static.toolbarGroups.filter( function ( group ) {
		return group.align === 'after';
	} ) );

	CodeTarget.prototype.getDocToSave = function () {
		var doc = CodeTarget.parent.prototype.getDocToSave.apply(this, arguments);
		if (this.addSyntaxhighlightTags) {
			doc =
				'<syntaxhighlight lang="' + availableModels[model].geshi + '">' +
				doc +
				'</syntaxhighlight>';
		}
		return doc;
	};

	CodeTarget.prototype.onSaveDialogPreview = function () {
		var code = this.getDocToSave();
		switch (model) {
		case 'javascript':
			$.globalEval(code);
			break;
		case 'css':
			if (this.previousStyleTag) {
				$(this.previousStyleTag).remove();
			}
			this.previousStyleTag = mw.util.addCSS(code).ownerNode;
		}
		this.addSyntaxhighlightTags = true;
		CodeTarget.parent.prototype.onSaveDialogPreview.apply(this, arguments);
		this.addSyntaxhighlightTags = false;
	};

	CodeTarget.prototype.saveComplete = function (html, categoriesHtml, newid, isRedirect) {
		var newUrlParams;
		//skip one level
		ve.init.mw.DesktopArticleTarget.parent.prototype.saveComplete.apply(this, arguments);
		//always reload
		this.teardown().then(function () {
			newUrlParams = newid === undefined ? {} :
				{venotify: this.restoring ? 'restored' : (this.pageExists ? 'saved' : 'created')};
			if (isRedirect) {
				newUrlParams.redirect = 'no';
			}
			location.href = this.viewUri.extend(newUrlParams);
		}.bind(this));
	};

	ve.init.mw.targetFactory.register(CodeTarget);

	//Varinat of SaveDialog to exclude VisualDiff
	function CodeSaveDialog () {
		CodeSaveDialog.parent.apply(this, arguments);
	}

	OO.inheritClass(CodeSaveDialog, ve.ui.windowFactory.lookup('mwSave'));

	CodeSaveDialog.prototype.initialize = function () {
		if (ve.userConfig('visualeditor-diffmode-source') === 'visual') {
			ve.userConfig('visualeditor-diffmode-source', 'source');
		}
		CodeSaveDialog.parent.prototype.initialize.apply(this, arguments);
		this.reviewModeButtonSelect.findItemFromData('visual').setDisabled(true);
	};

	ve.ui.windowFactory.register(CodeSaveDialog);

	//Add utility method to MWAceEditorWidget
	ve.ui.MWAceEditorWidget.prototype.withAceEditor = function (callback) {
		this.loadingPromise.then(function () {
			callback(this.editor);
		}.bind(this));
		return this;
	};

	//Dialog, command, and tool to edit with Ace
	function AceDialog () {
		AceDialog.parent.apply(this, arguments);
	}

	OO.inheritClass(AceDialog, OO.ui.ProcessDialog);

	AceDialog.static.name = 'aceDialog';
	AceDialog.static.title = mw.msg('schnark-vecode-title');
	AceDialog.static.size = 'full';
	AceDialog.static.actions = [
		{
			label: OO.ui.deferMsg('visualeditor-dialog-action-cancel'),
			flags: ['safe', 'close'],
			modes: ['readonly', 'edit']
		},
		{
			action: 'done',
			label: OO.ui.deferMsg('visualeditor-dialog-action-done'),
			flags: ['progressive', 'primary'],
			modes: ['edit']
		}
	];

	AceDialog.prototype.initialize = function () {
		AceDialog.parent.prototype.initialize.apply(this, arguments);
		this.panel = new OO.ui.PanelLayout({
			padded: true
		});
		this.input = new ve.ui.MWAceEditorWidget({
			limit: 1,
			minRows: 5,
			maxRows: Math.floor((window.innerHeight - 100) / 21), //FIXME
			autosize: true,
			autocomplete: 'live'
		});
		this.input.$element.css('max-width', 'none');
		//this.input.connect(this, {resize: 'updateSize'});
		this.panel.$element.append(this.input.$element);
		this.$body.append(this.panel.$element);
	};

	AceDialog.prototype.getSetupProcess = function () {
		return AceDialog.parent.prototype.getSetupProcess.apply(this, arguments).first(function () {
			this.input.setup();
			this.input
				.setLanguage(availableModels[model].ace)
				.withAceEditor(function (editor) {
					var session = editor.getSession();
					editor.setOptions({
						enableSnippets: true
					});
					session.setUseSoftTabs(false);
					mw.hook('codeEditor.configure').fire(session);
					//TODO Toolbar, statusbar
					//see Extension CodeEditor > jquery.codeEditor.js > updateStatusBar
					//etc., e.g. editor.setShowInvisibles(true);
				});
		}, this).next(function () {
			var readOnly = this.manager.surface.isReadOnly();
			if (readOnly) {
				this.input.setReadOnly(true);
			}
			this.actions.setMode(readOnly ? 'readonly' : 'edit');
			this.actions.setAbilities({done: false});
		}, this);
	};

	AceDialog.prototype.onFirstChange = function () {
		this.actions.setAbilities({done: true});
		this.input.disconnect(this, {change: 'onFirstChange'});
	};

	AceDialog.prototype.getTeardownProcess = function () {
		return AceDialog.parent.prototype.getTeardownProcess.apply(this, arguments).first(function () {
			this.input.teardown();
		}, this);
	};

	AceDialog.prototype.getReadyProcess = function () {
		return AceDialog.parent.prototype.getReadyProcess.apply(this, arguments)
			.next(function () {
				this.input.setValue(this.manager.surface.getDom());
				this.input.clearUndoStack();
				this.input.focus();
				this.input.connect(this, {change: 'onFirstChange'});
			}, this);
	};

	AceDialog.prototype.getActionProcess = function (action) {
		return AceDialog.parent.prototype.getActionProcess.apply(this, arguments).next(function () {
			var surfaceModel;
			if (action === 'done') {
				surfaceModel = this.manager.surface.getModel();
				surfaceModel.setLinearSelection(
					new ve.Range(0, surfaceModel.getDocument().data.getLength())
				);
				surfaceModel.getFragment().insertContent(this.input.getValue()).collapseToStart().select();
				this.close({action: 'done'});
			}
		}, this);
	};

	ve.ui.windowFactory.register(AceDialog);

	ve.ui.commandRegistry.register(
		new ve.ui.Command('aceDialog', 'window', 'open', {args: ['aceDialog']})
	);

	function AceTool () {
		AceTool.parent.apply(this, arguments);
	}

	OO.inheritClass(AceTool, ve.ui.WindowTool);
	AceTool.static.name = 'aceDialog';
	AceTool.static.group = 'dialog';
	AceTool.static.icon = 'markup';
	AceTool.static.title = mw.msg('schnark-vecode-label');
	AceTool.static.displayBothIconAndLabel = true;
	AceTool.static.autoAddToCatchall = false;
	AceTool.static.autoAddToGroup = false;
	AceTool.static.commandName = 'aceDialog';

	ve.ui.toolFactory.register(AceTool);

	//Tool to switch to OWE
	function EditModeOweTool () {
		EditModeOweTool.parent.apply(this, arguments);
	}

	OO.inheritClass(EditModeOweTool, mw.libs.ve.MWEditModeTool);

	EditModeOweTool.static.editMode = 'owe';
	EditModeOweTool.static.name = 'editModeOwe';
	EditModeOweTool.static.icon = 'markup';
	EditModeOweTool.static.title = mw.msg('schnark-vecode-owe');

	EditModeOweTool.prototype.switch = function () {
		//Actually nobody really cares whether the content is modified or not,
		//so just claim it is withouth checking
		this.toolbar.getTarget().switchToWikitextEditor(false, true, true);
	};

	ve.ui.toolFactory.register(EditModeOweTool);
}

function editVe (model) {
	mw.loader.using([
		'mediawiki.language',
		'mediawiki.util',
		'ext.visualEditor.desktopArticleTarget',
		'ext.visualEditor.articleTarget',
		'ext.visualEditor.mwwikitext',
		'ext.CodeMirror.visualEditor',
		'ext.CodeMirror.lib.mode.' + availableModels[model].module
	]).then(function () {
		initL10N(l10n);
		define(model);
		mw.config.get('wgVisualEditorConfig').contentModels[model] = 'code';
		mw.libs.ve.activateVe('source');
	});
}

function enableVe () {
	$('html').removeClass('ve-not-available').addClass('ve-available');
	$('#ca-edit, #ca-viewsource').on('click', function (e) {
		if (!(e && e.which && e.which === 1 && !(e.shiftKey || e.altKey || e.ctrlKey || e.metaKey))) {
			return;
		}
		e.preventDefault();
		editVe(mw.config.get('wgPageContentModel'));
	});
}

if (availableModels[mw.config.get('wgPageContentModel')]) {
	enableVe();
}

})(jQuery, mediaWiki);
//</nowiki>