Benutzer:Schnark/js/diff.js
< Benutzer:Schnark | js
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
- Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
//Dokumentation unter [[Benutzer:Schnark/js/diff]] <nowiki>
/*global mediaWiki, OO, ve*/
/*global MouseEvent*/
(function ($, mw) {
"use strict";
//colors:
//base, lighten30%, lighten40%
function makeRevSliderStyle (oldColors, newColors) {
function fade (color, alpha) {
return color.replace(/^#(..)(..)(..)$/, function (all, r, g, b) {
return 'rgba(' + parseInt(r, 16) + ',' + parseInt(g, 16) + ',' + parseInt(b, 16) + ',' + alpha + ')';
});
}
//jscs:disable maximumLineLength
return '\n' +
'.revslider-schnark .mw-revslider-pointer-line .mw-revslider-upper-color {' +
'border-color: ' + newColors[0] + ';' +
'}' +
'.revslider-schnark .mw-revslider-pointer-line .mw-revslider-lower-color {' +
'border-color: ' + oldColors[0] + ';' +
'}' +
'.revslider-schnark .mw-revslider-revision-new .mw-revslider-revision-border-box {' +
'border-bottom-color: ' + newColors[0] + ';' +
'}' +
'.revslider-schnark .mw-revslider-revision-old .mw-revslider-revision-border-box {' +
'border-bottom-color: ' + oldColors[0] + ';' +
'}' +
'.revslider-schnark .mw-revslider-revision-hovered.mw-revslider-revision-wrapper-up .mw-revslider-pointer-ghost {' +
'background-color: ' + newColors[1] + ';' +
'border-color: ' + newColors[0] + ';' +
'}' +
'.revslider-schnark .mw-revslider-revision-hovered.mw-revslider-revision-wrapper-down .mw-revslider-pointer-ghost {' +
'background-color: ' + oldColors[1] + ';' +
'border-color: ' + oldColors[0] + ';' +
'}' +
'.revslider-schnark .mw-revslider-revision-wrapper-hovered .mw-revslider-revision-hovered.mw-revslider-revision-wrapper-up {' +
'background-color: ' + fade(newColors[0], 0.3) + ';' +
'}' +
'.revslider-schnark .mw-revslider-revision-wrapper-hovered .mw-revslider-revision-hovered.mw-revslider-revision-wrapper-down {' +
'background-color: ' + fade(oldColors[0], 0.3) + ';' +
'}' +
'.revslider-schnark .mw-revslider-pointer.mw-revslider-pointer-newer {' +
'border-color: ' + newColors[0] + ';' +
'background-color: ' + newColors[0] + ';' +
'background-image: linear-gradient(' + newColors[1] + ' 0, ' + newColors[0] + ' 100%);' +
'}' +
'.revslider-schnark .mw-revslider-pointer.mw-revslider-pointer-newer:hover {' +
'background-image: linear-gradient(' + newColors[2] + ' 0, ' + newColors[0] + ' 100%);' +
'}' +
'.revslider-schnark .mw-revslider-pointer.mw-revslider-pointer-older {' +
'border-color: ' + oldColors[0] + ';' +
'background-color: ' + oldColors[0] + ';' +
'background-image: linear-gradient(' + oldColors[1] + ' 0, ' + oldColors[0] + ' 100%);' +
'}' +
'.revslider-schnark .mw-revslider-pointer.mw-revslider-pointer-older:hover {' +
'background-image: linear-gradient(' + oldColors[2] + ' 0, ' + oldColors[0] + ' 100%);' +
'}' +
'.revslider-schnark .mw-revslider-pointer-container-newer .mw-revslider-slider-line {' +
'border-bottom-color: ' + fade(newColors[0], 0.5) + ';' +
'}' +
'.revslider-schnark .mw-revslider-pointer-container-newer:hover .mw-revslider-slider-line {' +
'border-bottom-color: ' + fade(newColors[0], 0.8) + ';' +
'}' +
'.revslider-schnark .mw-revslider-pointer-container-older .mw-revslider-slider-line {' +
'border-top-color: ' + fade(oldColors[0], 0.5) + ';' +
'}' +
'.revslider-schnark .mw-revslider-pointer-container-older:hover .mw-revslider-slider-line {' +
'border-top-color: ' + fade(oldColors[0], 0.8) + ';' +
'}';
//jscs:enable maximumLineLength
}
var diffEngineDeferred,
configPromise,
cachedRev = {},
origUri, mobileUri,
currentMode,
cachedInterfaceElements,
optionsPrefix = 'userjs-schnark-diff-',
version = '6.28',
l10n = {
en: {
'schnark-diff-switch-wikitext': 'Classical',
'schnark-diff-switch-schnark': 'Improved',
'schnark-diff-switch-config': 'Configuration for improved diff',
'schnark-diff-edit-diff-button': 'Δ',
'schnark-diff-edit-diff-button-tooltip': 'Improved diff',
'schnark-diff-config': 'Configuration for improved diff (v$1/$2)',
'schnark-diff-config-color-scheme': 'Color scheme: ',
'schnark-diff-config-color-scheme-classic': 'Classical (red/green)',
'schnark-diff-config-color-scheme-modern': 'Modern (yellow/blue)',
'schnark-diff-config-color-scheme-wiked': 'wikEd (yellow/blue, moves in gray)',
'schnark-diff-config-color-scheme-ve': 'VisualDiff (light red/green)',
'schnark-diff-config-moves': 'Show moved blocks: ',
'schnark-diff-config-moves-normal': 'normal',
'schnark-diff-config-moves-nested': 'nested',
'schnark-diff-config-moves-none': 'no',
'schnark-diff-config-moves-simple': 'simple',
'schnark-diff-config-char-diff': 'Number of rounds for diff on character level: ',
'schnark-diff-config-word-diff-qual': 'Quality of diffs on character level (0–100): ',
'schnark-diff-config-recursion': 'Number of recursions: ',
'schnark-diff-config-too-short': 'Length of a short word: ',
'schnark-diff-config-small-region': 'Length of a small region: ',
'schnark-diff-config-min-moved-length': 'Minimal length for a moved block: ',
'schnark-diff-config-save': 'Save settings',
'schnark-diff-config-reset': 'Use defaults'
},
'en-gb': {
'schnark-diff-config-color-scheme': 'Colour scheme: ',
'schnark-diff-config-color-scheme-wiked': 'wikEd (yellow/blue, moves in grey)'
},
de: {
'schnark-diff-switch-wikitext': 'Klassisch',
'schnark-diff-switch-schnark': 'Verbessert',
'schnark-diff-switch-config': 'Konfiguration für verbesserten Diff',
'schnark-diff-edit-diff-button-tooltip': 'Verbesserter Diff',
'schnark-diff-config': 'Konfiguration für verbesserten Diff (v$1/$2)',
'schnark-diff-config-color-scheme': 'Farbschema: ',
'schnark-diff-config-color-scheme-classic': 'Klassisch (rot/grün)',
'schnark-diff-config-color-scheme-modern': 'Modern (gelb/blau)',
'schnark-diff-config-color-scheme-wiked': 'wikEd (gelb/blau, Verschiebungen grau)',
'schnark-diff-config-color-scheme-ve': 'VisualDiff (hellrot/grün)',
'schnark-diff-config-moves': 'Anzeige von Verschiebungen: ',
'schnark-diff-config-moves-nested': 'verschachtelt',
'schnark-diff-config-moves-none': 'gar nicht',
'schnark-diff-config-moves-simple': 'einfach',
'schnark-diff-config-char-diff': 'Anzahl der Durchgänge für Diff auf Zeichenebene: ',
'schnark-diff-config-word-diff-qual': 'Qualität des Diffs auf Zeichenebene (0–100): ',
'schnark-diff-config-recursion': 'Anzahl der Rekursionen: ',
'schnark-diff-config-too-short': 'Länge eines kurzen Worts: ',
'schnark-diff-config-small-region': 'Größe eines kleinen Bereichs: ',
'schnark-diff-config-min-moved-length': 'Minimale Länge eines verschobenen Blocks: ',
'schnark-diff-config-save': 'Einstellungen speichern',
'schnark-diff-config-reset': 'Standardwerte verwenden'
},
'de-ch': {
'schnark-diff-config-small-region': 'Grösse eines kleinen Bereichs: '
}
},
colorSchemes = {
classic: {
additional: '.deletion-like, .insertion-like {' +
'color: inherit; background-color: transparent; border-top: thick solid;' +
'}' + '.deletion-like {' +
'border-color: #903;' +
'}' + '.insertion-like {' +
'border-color: #093;' +
'}' + makeRevSliderStyle(['#990033', '#ee0066', '#ff0055'], ['#009933', '#00ee66', '#00ff55'])
},
modern: {
ins: 'text-decoration: none; color: black; background-color: #a3d3ff;',
del: 'text-decoration: none; color: black; background-color: #ffe49c;',
movedIns: 'text-decoration: none; color: black; background-color: #bdedff;',
movedDel: 'text-decoration: none; color: black; background-color: #fffeb6;',
blocks: [['#000', '#ffc0e0'], ['#000', '#a0ffa0'], ['#000', '#ffd0a0'], ['#000', '#ffa0a0'],
['#000', '#a0a0ff'], ['#000', '#ffbbbb'], ['#000', '#c0ffa0'], ['#000', '#d8ffa0'], ['#000', '#b0a0d0']]
},
wiked: {
ins: 'text-decoration: none; color: black; background-color: #a3d3ff;',
del: 'text-decoration: none; color: black; background-color: #ffe49c;',
movedIns: 'text-decoration: none; color: black; background-color: #bdedff;',
movedDel: 'text-decoration: none; color: black; background-color: #fffeb6;',
movedFrom: 'color: #000; text-decoration: none; font-weight: bold; background-color: #ffe49c;',
movedTo: '#e8e8e8',
movedHover: 'color: #fff !important; background-color: #777 !important;',
blocks: [],
additional: '.enhanced-diff-moved-hover .enhanced-diff-equal {color: #fff; background-color: #777;}'
},
ve: {
ins: 'text-decoration: none; color: black; background-color: #7fd7c4;',
del: 'text-decoration: none; color: black; background-color: #e88e89;',
movedIns: 'text-decoration: none; color: black; background-color: #c8ccd1;',
movedDel: 'text-decoration: line-through; color: black; background-color: #c8ccd1;',
movedFrom: 'color: #000; text-decoration: none; font-weight: bold; background-color: #c8ccd1;',
movedTo: '#b6d4fb',
blocks: [],
omit: 'color: #72777d;',
additional: makeRevSliderStyle(['#d73c34', '#f0b7b4', '#f9e0de'], ['#39b79c', '#a6e3d6', '#cdefe8'])
}
}, currentColorScheme,
hasOwn = Object.prototype.hasOwnProperty;
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 formatVersion (v) {
if (typeof v === 'number' && Math.floor(v) === v) {
return String(v) + '.0';
} else {
return String(v);
}
}
function enableColorScheme (n, schnarkDiff) {
var backup;
if (n === currentColorScheme) {
return;
}
if (currentColorScheme) {
colorSchemes[currentColorScheme].disabled = true;
}
currentColorScheme = n;
if ($.isPlainObject(colorSchemes[n])) {
backup = schnarkDiff.style.get();
schnarkDiff.style.set(colorSchemes[n]);
colorSchemes[n] = mw.util.addCSS(schnarkDiff.getCSS());
schnarkDiff.style.set(backup);
} else {
colorSchemes[n].disabled = false;
}
}
function buildConfig (defaultConfig, currentConfig, libVersion) {
var fieldset, colorScheme, moves, charDiff, wordDiffQual, recursion, tooShort,
smallRegion, minMovedLength, buttonSave, buttonReset;
function set (config) {
colorScheme.getMenu().selectItemByData(config.colorScheme);
moves.getMenu().selectItemByData(config.nested ? -1 : config.indicateMoves);
charDiff.setValue(config.charDiff);
wordDiffQual.setValue(Math.round(config.wordDiffQual * 100));
recursion.setValue(config.recursion);
tooShort.setValue(config.tooShort);
smallRegion.setValue(config.smallRegion);
minMovedLength.setValue(config.minMovedLength);
}
function get () {
return {
colorScheme: colorScheme.getMenu().findSelectedItem().getData(),
indicateMoves: Math.abs(moves.getMenu().findSelectedItem().getData()),
nested: moves.getMenu().findSelectedItem().getData() < 0,
charDiff: Number(charDiff.getValue()),
wordDiffQual: wordDiffQual.getValue() / 100,
recursion: Number(recursion.getValue()),
tooShort: Number(tooShort.getValue()),
smallRegion: Number(smallRegion.getValue()),
minMovedLength: Number(minMovedLength.getValue())
};
}
colorScheme = new OO.ui.DropdownWidget({
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: 'classic',
label: mw.msg('schnark-diff-config-color-scheme-classic')
}),
new OO.ui.MenuOptionWidget({
data: 'modern',
label: mw.msg('schnark-diff-config-color-scheme-modern')
}),
new OO.ui.MenuOptionWidget({
data: 'wiked',
label: mw.msg('schnark-diff-config-color-scheme-wiked')
}),
new OO.ui.MenuOptionWidget({
data: 've',
label: mw.msg('schnark-diff-config-color-scheme-ve')
})
]
},
$overlay: true
});
moves = new OO.ui.DropdownWidget({
menu: {
items: [
new OO.ui.MenuOptionWidget({
data: 1,
label: mw.msg('schnark-diff-config-moves-normal')
}),
new OO.ui.MenuOptionWidget({
data: -1,
label: mw.msg('schnark-diff-config-moves-nested')
}),
new OO.ui.MenuOptionWidget({
data: 0,
label: mw.msg('schnark-diff-config-moves-none')
}),
new OO.ui.MenuOptionWidget({
data: 2,
label: mw.msg('schnark-diff-config-moves-simple')
})
]
},
$overlay: true
});
charDiff = new OO.ui.NumberInputWidget({
min: 0,
allowInteger: true
});
wordDiffQual = new OO.ui.NumberInputWidget({
min: 0,
max: 100
});
recursion = new OO.ui.NumberInputWidget({
min: 0,
allowInteger: true
});
tooShort = new OO.ui.NumberInputWidget({
min: 0,
allowInteger: true
});
smallRegion = new OO.ui.NumberInputWidget({
min: 0,
allowInteger: true
});
minMovedLength = new OO.ui.NumberInputWidget({
min: 0,
allowInteger: true
});
buttonSave = new OO.ui.ButtonWidget({
label: mw.msg('schnark-diff-config-save')
});
buttonReset = new OO.ui.ButtonWidget({
label: mw.msg('schnark-diff-config-reset')
});
buttonSave.on('click', saveConfig);
buttonReset.on('click', function () {
set(defaultConfig);
});
fieldset = new OO.ui.FieldsetLayout({
label: mw.msg('schnark-diff-config', formatVersion(version), formatVersion(libVersion))
});
fieldset.addItems([
new OO.ui.FieldLayout(colorScheme, {
label: mw.msg('schnark-diff-config-color-scheme')
}),
new OO.ui.FieldLayout(moves, {
label: mw.msg('schnark-diff-config-moves')
}),
new OO.ui.FieldLayout(charDiff, {
label: mw.msg('schnark-diff-config-char-diff')
}),
new OO.ui.FieldLayout(wordDiffQual, {
label: mw.msg('schnark-diff-config-word-diff-qual')
}),
new OO.ui.FieldLayout(recursion, {
label: mw.msg('schnark-diff-config-recursion')
}),
new OO.ui.FieldLayout(tooShort, {
label: mw.msg('schnark-diff-config-too-short')
}),
new OO.ui.FieldLayout(smallRegion, {
label: mw.msg('schnark-diff-config-small-region')
}),
new OO.ui.FieldLayout(minMovedLength, {
label: mw.msg('schnark-diff-config-min-moved-length')
}),
new OO.ui.FieldLayout(new OO.ui.Widget({
content: [
new OO.ui.HorizontalLayout({
items: [
buttonSave,
buttonReset
]
})
]
}), {
align: 'top',
label: null
})
]);
set(currentConfig);
return {
$div: $('<div>').append(fieldset.$element),
get: get,
defaultConfig: defaultConfig,
libVersion: libVersion
};
}
function applyLocalConfig (defaultConfig) {
var key, val, config = {};
for (key in defaultConfig) {
if (hasOwn.call(defaultConfig, key)) {
val = mw.user.options.get(optionsPrefix + key);
if (val === null) {
val = defaultConfig[key];
} else {
switch (typeof defaultConfig[key]) {
case 'number':
val = Number(val);
if (isNaN(val)) {
val = defaultConfig[key];
}
break;
case 'boolean':
val = val === 'true';
break;
}
}
config[key] = val;
}
}
return config;
}
function getDiffEngine () {
var load = true;
function callback (schnarkDiff) {
load = false;
mw.hook('userjs.load-script.diff-core').remove(callback);
schnarkDiff.config.set(
$('body').is('.rtl') ? {
movedLeft: '\u25b6',
movedRight: '\u25c0'
} : {
movedLeft: '\u25c0',
movedRight: '\u25b6'
}
);
diffEngineDeferred.resolve(schnarkDiff);
}
if (!diffEngineDeferred) {
diffEngineDeferred = $.Deferred();
mw.hook('userjs.load-script.diff-core').add(callback);
if (load) {
//</nowiki>[[Benutzer:Schnark/js/diff.js/core.js]]<nowiki>
mw.loader.load('https://de.wikipedia.org/w/index.php?title=Benutzer:Schnark/js/diff.js/core.js' +
'&action=raw&ctype=text/javascript');
}
}
return diffEngineDeferred.promise();
}
function getConfigPromise (forceNew) {
if (!configPromise) {
configPromise = getDiffEngine().then(function (schnarkDiff) {
var defaultConfig = schnarkDiff.config.get(['charDiff', 'wordDiffQual', 'recursion',
'tooShort', 'smallRegion', 'minMovedLength']);
defaultConfig.indicateMoves = 1;
defaultConfig.nested = true;
defaultConfig.colorScheme = 'classic';
return buildConfig(defaultConfig, applyLocalConfig(defaultConfig), schnarkDiff.version);
});
} else if (forceNew) {
configPromise = configPromise.then(function (config) {
return buildConfig(config.defaultConfig, config.get(), config.libVersion);
});
}
return configPromise;
}
function getDefaultConfig () {
return getConfigPromise().then(function (data) {
return data.defaultConfig;
});
}
function getConfig () {
return getConfigPromise().then(function (data) {
return data.get();
});
}
function getConfigPanel (forceNew) {
return getConfigPromise(forceNew).then(function (data) {
return data.$div;
});
}
function getRevision (id, section) {
var param;
switch (id) {
case 'edit':
case 'conflict':
return mw.loader.using('jquery.textSelection').then( //strange jscs issue
function () {
return $(id === 'edit' ? '#wpTextbox1' : '#wpTextbox2').textSelection('getContents');
}
);
case 've':
return ve.init.target.serialize(
ve.init.target.getSurface().getDom()
).then(
function (data) {
return data.content;
}
);
case 0:
return $.Deferred().resolve('').promise();
default:
if (section === 'new') {
return $.Deferred().resolve('').promise();
}
if (!cachedRev[id]) {
param = {
action: 'query',
prop: 'revisions',
revids: id,
rvprop: 'content',
rvslots: '*',
format: 'json',
formatversion: 2
};
if (section) {
param.rvsection = section;
}
cachedRev[id] = $.getJSON(mw.util.wikiScript('api'), param).then(function (json) {
try {
return json.query.pages[0].revisions[0].slots ||
json.query.pages[0].revisions[0].content;
} catch (e) {
return '';
}
}, function () {
return '';
});
}
return cachedRev[id];
}
}
function generateDiff (schnarkDiff, oldText, newText, nested) {
return schnarkDiff.htmlDiff(
(oldText || '').replace(/\s+$/, ''),
(newText || '').replace(/\s+$/, ''),
nested
);
}
function generateDiffs (schnarkDiff, oldContent, newContent, nested) {
var oldStr = typeof oldContent === 'string',
newStr = typeof newContent === 'string',
diffs = [];
if (oldStr || newStr) {
if (!oldStr) {
oldContent = oldContent.main.content;
}
if (!newStr) {
newContent = newContent.main.content;
}
return generateDiff(schnarkDiff, oldContent, newContent, nested);
}
diffs.push(generateDiff(schnarkDiff, oldContent.main.content, newContent.main.content, nested));
$.each(newContent, function (name, slot) {
var oldText;
if (name !== 'main') {
oldText = oldContent[name];
oldText = oldText ? oldText.content : '';
if (oldText !== slot.content) {
diffs.push(
mw.html.element('h3', {}, name) +
generateDiff(schnarkDiff, oldText, slot.content, nested)
);
}
}
});
$.each(oldContent, function (name, slot) {
if (name !== 'main' && !newContent[name]) {
diffs.push(
mw.html.element('h3', {}, name) +
generateDiff(schnarkDiff, slot.content, '', nested)
);
}
});
if (diffs.length > 1 && oldContent.main.content === newContent.main.content) {
diffs[0] = '';
}
return diffs.join('');
}
function getDiff (data) {
return $.when(
getDiffEngine(),
getRevision(data.oldId, data.section),
getRevision(data.newId, data.section),
getConfig()
).then(function (results) {
if (!Array.isArray(results)) { //change to the Promise.all way
results = arguments;
}
var schnarkDiff = results[0],
oldContent = results[1],
newContent = results[2],
config = results[3],
defaultConfig = schnarkDiff.config.get(),
nested = config.nested,
diffHtml, $div;
enableColorScheme(config.colorScheme, schnarkDiff);
delete config.nested;
delete config.colorScheme;
schnarkDiff.config.set(config);
diffHtml = generateDiffs(schnarkDiff, oldContent, newContent, nested);
$div = $('<div>').addClass('schnark-diff').html(diffHtml);
schnarkDiff.addEvents($div);
schnarkDiff.config.set(defaultConfig);
return $div;
});
}
function saveConfig () {
return $.when(
getDefaultConfig(),
getConfig()
).then(function (data) {
if (!Array.isArray(data)) { //change to the Promise.all way
data = arguments;
}
var defaultConfig = data[0], currentConfig = applyLocalConfig(defaultConfig), config = data[1], options = {}, key;
for (key in config) {
if (hasOwn.call(config, key)) {
if (config[key] === currentConfig[key]) {
continue;
}
if (config[key] === defaultConfig[key]) {
options[optionsPrefix + key] = null;
} else {
options[optionsPrefix + key] = String(config[key]);
}
}
}
mw.user.options.set(options);
return mw.loader.using('mediawiki.api').then(function () {
return (new mw.Api()).saveOptions(options);
});
});
}
function loadModules (diffPage) {
var deps = ['mediawiki.Uri', 'oojs-ui-core', 'oojs-ui-widgets',
'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-editing-advanced'];
if (diffPage) {
deps.push('ext.visualEditor.diffPage.init');
}
return mw.loader.using(deps);
}
function createVisualDiffButton () {
return new OO.ui.ButtonOptionWidget({data: 'visual', icon: 'eye', label: mw.msg('visualeditor-savedialog-review-visual')});
}
function createTraditionalDiffButton () {
return new OO.ui.ButtonOptionWidget({data: 'source', icon: 'wikiText', label: mw.msg('schnark-diff-switch-wikitext')});
}
function createSchnarkDiffButton () {
return new OO.ui.ButtonOptionWidget({data: 'schnark', icon: 'markup', label: mw.msg('schnark-diff-switch-schnark')});
}
function createSchnarkDiffConfigButton () {
return new OO.ui.ButtonOptionWidget({data: 'config', icon: 'settings', title: mw.msg('schnark-diff-switch-config')});
}
function createSwitcher (wikitext, visual) {
var buttons = [];
if (visual) {
buttons.push(createVisualDiffButton());
}
buttons.push(createTraditionalDiffButton());
if (!wikitext) {
buttons[buttons.length - 1].setDisabled(true);
}
buttons.push(createSchnarkDiffButton());
buttons.push(createSchnarkDiffConfigButton());
return new OO.ui.ButtonSelectWidget({
items: buttons,
classes: ['schnark-diff-review-button-select']
});
}
function makeEditDiffButton (showNew) {
var diffButton = OO.ui.infuse($('#wpDiffWidget')),
enhancedDiffButton = new OO.ui.ButtonWidget({
label: mw.msg('schnark-diff-edit-diff-button'),
title: mw.msg('schnark-diff-edit-diff-button-tooltip')
}),
group = new OO.ui.ButtonGroupWidget();
enhancedDiffButton.on('click', showNew);
enhancedDiffButton.$element.css('margin-top', '0.5em');
diffButton.$element.after(group.$element);
group.addItems([diffButton, enhancedDiffButton]);
}
function makeMouseupEvent () {
var evt;
try {
return new MouseEvent('mouseup');
} catch (e) {
//old browser
evt = document.createEvent('MouseEvents');
evt.initMouseEvent('mouseup', true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
return evt;
}
}
function getInterfaceElements () {
if (!cachedInterfaceElements) {
cachedInterfaceElements = {};
cachedInterfaceElements.$schnarkDiffContainer = $('<div>');
cachedInterfaceElements.$schnarkDiff = $('<div>');
cachedInterfaceElements.schnarkProgress = new OO.ui.ProgressBarWidget();
cachedInterfaceElements.$schnarkDiffContainer.append(
cachedInterfaceElements.schnarkProgress.$element.css({ //.ve-init-mw-diffPage-loading
clear: 'both',
margin: '2em auto'
}).addClass('oo-ui-element-hidden'),
cachedInterfaceElements.$schnarkDiff
);
cachedInterfaceElements.$configContainer = $('<div>');
cachedInterfaceElements.$config = $('<div>');
cachedInterfaceElements.configProgress = new OO.ui.ProgressBarWidget();
cachedInterfaceElements.$configContainer.append(
cachedInterfaceElements.configProgress.$element.css({ //.ve-init-mw-diffPage-loading
clear: 'both',
margin: '2em auto'
}).addClass('oo-ui-element-hidden'),
cachedInterfaceElements.$config
);
}
return cachedInterfaceElements;
}
function createInterface ($diff, isEdit) {
var oldId, newId, section,
reviewModeButtonSelect, $buttonSelectContainer,
$revSlider = $('.mw-revslider-container'),
$diffHeader, $diffBody,
interfaceElements,
hasVisual, hasDiff, $switcherContainer,
uri = new mw.Uri();
hasDiff = !!$diff;
if (isEdit) {
oldId = $('#wpTextbox2').length === 1 ? 'conflict' : mw.config.get('wgCurRevisionId');
newId = 'edit';
section = $('input[name="wpSection"]').val();
hasVisual = false;
} else {
if (
$diff.parent().find(
'#mw-rev-suppressed-no-diff, #mw-rev-deleted-no-diff,' +
'#mw-rev-suppressed-unhide-diff, #mw-rev-deleted-unhide-diff'
).length
) { //no real diff shown
return;
}
oldId = mw.config.get('wgDiffOldId');
newId = mw.config.get('wgDiffNewId');
$switcherContainer = $('.ve-init-mw-diffPage-diffMode').hide();
hasVisual = !!$switcherContainer.length;
}
interfaceElements = getInterfaceElements();
$('.schnark-diff-review-button-select').remove();
reviewModeButtonSelect = createSwitcher(hasDiff, hasVisual);
$buttonSelectContainer = $('<div>').css({ //.ve-init-mw-diffPage-diffMode
textAlign: $('body').is('.rtl') ? 'left' : 'right',
margin: '1em 0'
}).append(reviewModeButtonSelect.$element);
if (hasDiff) {
$diffHeader = $diff
.find('tr.diff-title').add(
$diff.find('td.diff-multi, td.diff-notice').parent()
);
$diffBody = $diff.find('tr').not($diffHeader);
$diff.before($buttonSelectContainer);
$diff.after(interfaceElements.$schnarkDiffContainer, interfaceElements.$configContainer);
} else {
$('#editform').before($buttonSelectContainer,
interfaceElements.$schnarkDiffContainer, interfaceElements.$configContainer);
}
function selectVisual () {
$switcherContainer.find('[role="button"]').eq(0)
.trigger($.Event('mousedown', {which: 1}))[0].dispatchEvent(makeMouseupEvent());
}
function unselectVisual () {
$switcherContainer.find('[role="button"]').eq(1)
.trigger($.Event('mousedown', {which: 1}))[0].dispatchEvent(makeMouseupEvent());
}
function hiddenSwitcherIsVisual () {
return $switcherContainer.find('.oo-ui-optionWidget-selected').index() === 0;
}
function storeDiffMode () {
mw.user.options.set(optionsPrefix + 'diffmode', currentMode);
mw.loader.using('mediawiki.api').then(function () {
new mw.Api().saveOption(optionsPrefix + 'diffmode', currentMode);
});
if (!isEdit && history.replaceState) {
uri.query.diffmode = currentMode;
history.replaceState('', document.title, uri);
}
}
reviewModeButtonSelect.on(isEdit ? 'choose' : 'select', function (item) {
//choose is fired even if nothing is changed,
//this is what we want to refresh our diff
currentMode = item.getData();
switch (currentMode) {
case 'schnark':
if (hasVisual && hiddenSwitcherIsVisual()) {
unselectVisual();
}
if (hasDiff) {
$diffBody.addClass('oo-ui-element-hidden');
$diffHeader.find('#mw-diff-otitle1').addClass('deletion-like');
$diffHeader.find('#mw-diff-ntitle1').addClass('insertion-like');
$diffHeader.removeClass('oo-ui-element-hidden');
}
interfaceElements.$schnarkDiffContainer.removeClass('oo-ui-element-hidden');
interfaceElements.$configContainer.addClass('oo-ui-element-hidden');
$revSlider.addClass('revslider-schnark');
if (interfaceElements.$schnarkDiff.data('diff-ids') !== oldId + '|' + newId) {
interfaceElements.$schnarkDiff.empty();
interfaceElements.schnarkProgress.$element.removeClass('oo-ui-element-hidden');
getDiff({
oldId: oldId,
newId: newId,
section: section
}).then(function ($d) {
mw.hook('userjs.schnark-diff').fire($d);
interfaceElements.schnarkProgress.$element.addClass('oo-ui-element-hidden');
interfaceElements.$schnarkDiff.append($d);
interfaceElements.$schnarkDiff.data('diff-ids', oldId + '|' + newId);
});
} else {
//Even if ids didn't change, the diff might, since
//a) it depends on the current config, and
//b) for edits the text might have changed, but
//in both cases the new diff will be generated fast,
//so don't show the progressbar, just replace the diff.
getDiff({
oldId: oldId,
newId: newId,
section: section
}).then(function ($d) {
mw.hook('userjs.schnark-diff').fire($d);
interfaceElements.$schnarkDiff.empty().append($d);
});
}
storeDiffMode();
break;
case 'config':
if (hasVisual && hiddenSwitcherIsVisual()) {
unselectVisual();
}
if (hasDiff) {
$diffBody.addClass('oo-ui-element-hidden');
$diffHeader.addClass('oo-ui-element-hidden');
}
interfaceElements.$schnarkDiffContainer.addClass('oo-ui-element-hidden');
interfaceElements.$configContainer.removeClass('oo-ui-element-hidden');
if (!interfaceElements.$config.hasClass('is-ready')) {
interfaceElements.$config.empty();
interfaceElements.configProgress.$element.removeClass('oo-ui-element-hidden');
getConfigPanel(true).then(function ($p) { //we need a new copy every time, see below
interfaceElements.configProgress.$element.addClass('oo-ui-element-hidden');
interfaceElements.$config.addClass('is-ready').append($p);
});
} else {
//Even if we built the config before, it could have been removed and re-attached.
//In this case, jQuery also removes event-handlers, so we have to replace the now
//unfunctional config panel with a fresh one. This will happen fast, so do it
//without progressbar.
getConfigPanel(true).then(function ($p) {
interfaceElements.$config.empty().append($p);
});
}
break;
default:
if (currentMode === 'source') {
$diffBody.removeClass('oo-ui-element-hidden');
}
$diffHeader.find('#mw-diff-otitle1').removeClass('deletion-like');
$diffHeader.find('#mw-diff-ntitle1').removeClass('insertion-like');
$diffHeader.removeClass('oo-ui-element-hidden');
interfaceElements.$schnarkDiffContainer.addClass('oo-ui-element-hidden');
interfaceElements.$configContainer.addClass('oo-ui-element-hidden');
$revSlider.removeClass('revslider-schnark');
if (hasVisual && hiddenSwitcherIsVisual() && currentMode === 'source') {
unselectVisual();
} else if (currentMode === 'visual') {
selectVisual();
}
storeDiffMode();
}
});
if (!currentMode) {
if (!hasDiff) {
currentMode = 'schnark';
} else {
currentMode =
(mobileUri || origUri || uri).query.diffmode ||
mw.user.options.get(optionsPrefix + 'diffmode') ||
'source';
if (currentMode === 'visual' && !hasVisual) {
currentMode = 'source';
}
}
}
if (isEdit) {
reviewModeButtonSelect.chooseItem(reviewModeButtonSelect.findItemFromData(currentMode));
} else {
reviewModeButtonSelect.selectItemByData(currentMode);
}
return reviewModeButtonSelect;
}
function initVe () {
function SaveDialogWithDiff () {
SaveDialogWithDiff.parent.apply(this, arguments);
}
//I have several scripts that extend the SaveDialog, so let's get the current one
OO.inheritClass(SaveDialogWithDiff, ve.ui.windowFactory.lookup('mwSave'));
SaveDialogWithDiff.prototype.setDiffAndReview = function () {
SaveDialogWithDiff.parent.prototype.setDiffAndReview.apply(this, arguments);
this.$reviewSchnarkDiff.append(new OO.ui.ProgressBarWidget().$element);
this.$reviewDiffConfig.append(new OO.ui.ProgressBarWidget().$element);
};
SaveDialogWithDiff.prototype.clearDiff = function () {
SaveDialogWithDiff.parent.prototype.clearDiff.apply(this, arguments);
this.$reviewSchnarkDiff.empty();
this.$reviewDiffConfig.children().detach();
};
SaveDialogWithDiff.prototype.initialize = function () {
SaveDialogWithDiff.parent.prototype.initialize.apply(this, arguments);
this.reviewModeButtonSelect.addItems([
createSchnarkDiffButton(),
createSchnarkDiffConfigButton()
]);
this.reviewModeButtonSelect.findItemFromData('source').setLabel(mw.msg('schnark-diff-switch-wikitext'));
this.$reviewVisualDiff.addClass('oo-ui-element-hidden'); //hiding is delayed, so sometimes 2 loading bars are visible,
this.$reviewWikitextDiff.addClass('oo-ui-element-hidden'); //but for some reason this doesn't happen without my script
this.$reviewSchnarkDiff = $('<div>').addClass('ve-ui-mwSaveDialog-viewer oo-ui-element-hidden');
this.$reviewDiffConfig = $('<div>').addClass('ve-ui-mwSaveDialog-viewer oo-ui-element-hidden');
this.reviewPanel.$element.find('.ve-ui-mwSaveDialog-actions').before(this.$reviewSchnarkDiff, this.$reviewDiffConfig);
};
SaveDialogWithDiff.prototype.updateReviewMode = function () {
var mode = this.reviewModeButtonSelect.findSelectedItem().getData(), surfaceMode;
if (!this.hasDiff) {
return;
}
this.$reviewSchnarkDiff.toggleClass('oo-ui-element-hidden', mode !== 'schnark');
this.$reviewDiffConfig.toggleClass('oo-ui-element-hidden', mode !== 'config');
if (mode === 'schnark' || mode === 'config') {
this.$reviewVisualDiff.toggleClass('oo-ui-element-hidden', true);
this.$reviewWikitextDiff.toggleClass('oo-ui-element-hidden', true);
if (mode === 'schnark') {
surfaceMode = ve.init.target.getSurface().getMode();
ve.userConfig('visualeditor-diffmode-' + surfaceMode, 'schnark');
getDiff({
oldId: mw.config.get('wgCurRevisionId'),
newId: 've',
section: surfaceMode === 'source' && ve.init.target.section
}).then(function ($div) {
mw.hook('userjs.schnark-diff').fire($div);
this.$reviewSchnarkDiff.empty().append($div);
this.updateSize();
}.bind(this));
} else {
getConfigPanel().then(function ($div) {
this.$reviewDiffConfig.children().replaceWith($div);
this.updateSize();
}.bind(this));
}
try {
if (!this.report) {
this.report = this.getActions().get({actions: 'report'})[0];
}
this.report.toggle(false);
} catch (e) {
mw.log.warn('Code for "report" broke, probably it can be removed.');
}
this.updateSize();
} else {
SaveDialogWithDiff.parent.prototype.updateReviewMode.apply(this, arguments);
}
};
ve.ui.windowFactory.register(SaveDialogWithDiff);
}
function initOwe () {
var switcher;
makeEditDiffButton(function () {
if (switcher) {
switcher.chooseItem(switcher.findItemFromData('schnark'));
} else {
switcher = createInterface(false, true);
}
switcher.scrollElementIntoView();
});
mw.hook('wikipage.diff').add(function ($diff) {
switcher = createInterface($diff, true);
});
}
function initDiff () {
mw.loader.using('mediawiki.Uri').then(function () {
var $mobileLink = $('#footer-places-mobileview a, #mobileview a');
//try to get Uri before VisualDiff changes it
origUri = new mw.Uri();
if ($mobileLink.length && $mobileLink.prop('href')) {
//dirty hack, but the mobile link has the parameters we want
//and doesn't change
mobileUri = new mw.Uri($mobileLink.prop('href'));
}
});
mw.hook('wikipage.diff').add(function ($diff) {
loadModules(!!$('.ve-init-mw-diffPage-diffMode').length).then(function () {
createInterface($diff);
});
});
}
function init () {
function initVeOnce () {
mw.hook('ve.activationComplete').remove(initVeOnce);
initVe();
}
initL10N(l10n);
mw.hook('ve.activationComplete').add(initVeOnce);
if (['edit', 'submit'].indexOf(mw.config.get('wgAction')) > -1) {
loadModules().then(initOwe);
} else {
initDiff();
}
}
$.when(mw.loader.using(['mediawiki.util', 'mediawiki.language', 'user.options']), $.ready).then(init);
})(jQuery, mediaWiki);
//</nowiki>