Benutzer:Schnark/js/veHint.js
aus Wikipedia, der freien Enzyklopädie
< 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
- Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
- Opera: Strg+F5
//Dokumentation unter [[Benutzer:Schnark/js/veHint]] <nowiki>
/*global mediaWiki, OO, ve*/
(function ($, mw) {
"use strict";
var l10n = {
en: {
'schnark-ve-problems-total-count': 'All problems: $1',
'schnark-ve-problems-find-prev': 'Find previous problem',
'schnark-ve-problems-find-next': 'Find next problem',
'schnark-ve-problems-replace': 'Fix problem',
'schnark-ve-problems-replace-all': 'Fix all problems',
'schnark-ve-problems-enable-all': 'Enable all problems',
'schnark-ve-problems-disable-all': 'Disable all problems',
'schnark-ve-problems-done': 'Done',
'schnark-ve-problems-title': 'Find problems'
},
de: {
'schnark-ve-problems-total-count': 'Alle Probleme: $1',
'schnark-ve-problems-find-prev': 'Finde vorheriges Problem',
'schnark-ve-problems-find-next': 'Finde nächstes Problem',
'schnark-ve-problems-replace': 'Behebe Problem',
'schnark-ve-problems-replace-all': 'Behebe alle Probleme',
'schnark-ve-problems-enable-all': 'Aktiviere alle Probleme',
'schnark-ve-problems-disable-all': 'Deaktiviere alle Probleme',
'schnark-ve-problems-done': 'Fertig',
'schnark-ve-problems-title': 'Finde Probleme'
}
}, SOURCE = 1, VISUAL = 2, problemsForProjects = {};
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 addToToolbar (tool) {
var i, include;
try {
include = ve.init.mw.DesktopArticleTarget.static.actionGroups[1].include;
i = include.indexOf('findAndReplace');
if (i === -1) {
include.push(tool);
} else {
include.splice(i + 1, 0, tool);
}
} catch (e) {
mw.log.warn('veHint.js: addToToolbar failed: ' + e);
}
}
function init (problems) {
//virtual indent
var css =
'.schnark-ve-problems-current-problem-highlight {' +
'background-color: #fc3;' +
'}' +
'.schnark-multicol-layout {' +
'padding: 0 0.5em;' +
'max-height: 8em;' +
'overflow: auto;' +
'}' +
'.schnark-multicol-layout .oo-ui-fieldLayout {' +
'width: 20em;' +
'height: 2.5em;' +
'float: left;' +
'}';
mw.util.addCSS(css);
function MulticolLayout () {
MulticolLayout.parent.apply(this, arguments);
this.$element.addClass('schnark-multicol-layout');
this.$group.prepend($('<span>')); //don't remove the margin-top from the first widget
}
OO.inheritClass(MulticolLayout, OO.ui.FieldsetLayout);
function FindProblemsDialog () {
FindProblemsDialog.parent.apply(this, arguments);
}
OO.inheritClass(FindProblemsDialog, ve.ui.FindAndReplaceDialog);
FindProblemsDialog.static.name = 'findProblems';
FindProblemsDialog.static.padded = false; //strange issue with padding + overflow
//TODO besser lösen, ohne padding sieht es blöd aus
FindProblemsDialog.static.problemsVisual = [];
FindProblemsDialog.static.problemsSource = [];
FindProblemsDialog.static.addProblem = function (desc, re, fix, mode) {
var problem = {desc: desc, rawRe: re, re: new RegExp('^' + re + '$')};
if (fix !== false) {
problem.replace = fix;
}
if (mode === VISUAL || mode === VISUAL + SOURCE) {
FindProblemsDialog.static.problemsVisual.push(
$.extend({index: FindProblemsDialog.static.problemsVisual.length}, problem)
);
}
if (mode === SOURCE || mode === VISUAL + SOURCE) {
FindProblemsDialog.static.problemsSource.push(
$.extend({index: FindProblemsDialog.static.problemsSource.length}, problem)
);
}
};
FindProblemsDialog.prototype.initialize = function () {
var problemGroup, navigateGroup, replaceGroup, onoffGroup, doneButton, $problemRow, $navRow;
ve.ui.FindAndReplaceDialog.parent.prototype.initialize.apply(this, arguments); //eine Hierarchiestufe überspringen
this.surface = null;
this.invalidRegex = false;
this.$findResults = $('<div>').addClass('ve-ui-findAndReplaceDialog-findResults');
this.initialFragment = null;
this.startOffset = 0;
this.fragments = [];
this.results = 0;
this.renderedResultsCache = {};
this.renderedFragments = new ve.Range();
this.replacing = false;
this.focusedIndex = 0;
this.query = null;
this.problemsForIndexCache = null;
if (ve.init.target.getSurface().getMode() === 'source') {
this.constructor.static.problems = this.constructor.static.problemsSource;
} else {
this.constructor.static.problems = this.constructor.static.problemsVisual;
}
this.problemSwitches = this.constructor.static.problems.map(function (problem, index) {
var id = 'schnark-ve-problems-problem-' + index,
toggleSwitch = new OO.ui.CheckboxInputWidget({
selected: true
}), fieldLayout = new OO.ui.FieldLayout(toggleSwitch, {
align: 'inline',
label: new OO.ui.HtmlSnippet(
'<span id="' + id + '">' + problem.desc + '<span class="count"></span></span>'
)
});
return {toggleSwitch: toggleSwitch, fieldLayout: fieldLayout};
});
this.previousButton = new OO.ui.ButtonWidget({
icon: 'previous',
title: ve.msg('schnark-ve-problems-find-prev'),
tabIndex: 1
});
this.nextButton = new OO.ui.ButtonWidget({
icon: 'next',
title: ve.msg('schnark-ve-problems-find-next'),
tabIndex: 1
});
this.replaceButton = new OO.ui.ButtonWidget({
label: ve.msg('schnark-ve-problems-replace'),
tabIndex: 1
});
this.replaceAllButton = new OO.ui.ButtonWidget({
label: ve.msg('schnark-ve-problems-replace-all'),
tabIndex: 1
});
this.enableAllButton = new OO.ui.ButtonWidget({
label: ve.msg('schnark-ve-problems-enable-all'),
tabIndex: 1
});
this.disableAllButton = new OO.ui.ButtonWidget({
label: ve.msg('schnark-ve-problems-disable-all'),
tabIndex: 1
});
problemGroup = new MulticolLayout({
items: this.problemSwitches.map(function (item) {
return item.fieldLayout;
}),
label: new OO.ui.HtmlSnippet(
ve.msg('schnark-ve-problems-total-count', '<span id="schnark-problems-totalcount">0</span>')
)
});
navigateGroup = new OO.ui.ButtonGroupWidget({
classes: ['ve-ui-findAndReplaceDialog-cell'],
items: [
this.previousButton,
this.nextButton
]
});
replaceGroup = new OO.ui.ButtonGroupWidget({
classes: ['ve-ui-findAndReplaceDialog-cell'],
items: [
this.replaceButton,
this.replaceAllButton
]
});
onoffGroup = new OO.ui.ButtonGroupWidget({
classes: ['ve-ui-findAndReplaceDialog-cell'],
items: [
this.enableAllButton,
this.disableAllButton
]
});
doneButton = new OO.ui.ButtonWidget({
classes: ['ve-ui-findAndReplaceDialog-cell'],
label: ve.msg('schnark-ve-problems-done'),
tabIndex: 1
});
$problemRow = $('<div>');
$navRow = $('<div>').addClass('ve-ui-findAndReplaceDialog-row').css('padding', '0 0.5em');
this.onWindowScrollThrottled = ve.throttle(this.onWindowScroll.bind(this), 250);
this.updateFragmentsThrottled = ve.throttle(this.updateFragments.bind(this), 250);
this.renderFragmentsThrottled = ve.throttle(this.renderFragments.bind(this), 250);
this.problemSwitches.forEach(function (item) {
item.toggleSwitch.connect(this, {change: 'onProblemsChange'});
}, this);
this.nextButton.connect(this, {click: 'findNext'});
this.previousButton.connect(this, {click: 'findPrevious'});
this.replaceButton.connect(this, {click: 'onReplaceButtonClick'});
this.replaceAllButton.connect(this, {click: 'onReplaceAllButtonClick'});
this.enableAllButton.connect(this, {click: 'onEnableAllButtonClick'});
this.disableAllButton.connect(this, {click: 'onDisableAllButtonClick'});
doneButton.connect(this, {click: 'close'});
this.tabIndexScope = new ve.ui.TabIndexScope({
root: this.$element
});
this.$content.addClass('ve-ui-findAndReplaceDialog-content');
this.$body
.append(
$navRow.append(navigateGroup.$element, replaceGroup.$element, onoffGroup.$element, doneButton.$element),
$problemRow.append(problemGroup.$element)
);
};
FindProblemsDialog.prototype.onProblemsChange = function () {
this.updateFragments();
this.clearRenderedResultsCache();
this.renderFragments();
this.highlightFocused(true);
};
FindProblemsDialog.prototype.onEnableAllButtonClick = function () {
this.problemSwitches.forEach(function (item) {
item.toggleSwitch.setSelected(true);
}, this);
};
FindProblemsDialog.prototype.onDisableAllButtonClick = function () {
this.problemSwitches.forEach(function (item) {
item.toggleSwitch.setSelected(false);
}, this);
};
FindProblemsDialog.prototype.getProblemSelectionStates = function () {
return this.problemSwitches.map(function (item) {
return item.toggleSwitch.isSelected();
});
};
FindProblemsDialog.prototype.updateFragments = function () {
var i, l, startIndex,
surfaceModel = this.surface.getModel(),
documentModel = surfaceModel.getDocument(),
isReadOnly = surfaceModel.isReadOnly(),
ranges = [],
selectionStates = this.getProblemSelectionStates(),
problemCounts, countHtml;
this.query = this.constructor.static.problems.filter(function (el, index) {
return selectionStates[index];
}).map(function (problem) {
return '(?:' + problem.rawRe + ')';
}).join('|');
this.fragments = [];
this.problemsForIndexCache = null;
if (this.query) {
this.query = new RegExp(this.query, 'g');
ranges = documentModel.findText(this.query, {
noOverlaps: true
});
for (i = 0, l = ranges.length; i < l; i++) {
this.fragments.push(surfaceModel.getLinearFragment(ranges[i], true, true));
if (startIndex === undefined && ranges[i].start >= this.startOffset) {
startIndex = this.fragments.length - 1;
}
}
}
this.results = this.fragments.length;
this.focusedIndex = startIndex || 0;
this.nextButton.setDisabled(!this.results);
this.previousButton.setDisabled(!this.results);
this.replaceAllButton.setDisabled(!this.results || isReadOnly);
problemCounts = this.getCounts();
this.$element.find('#schnark-problems-totalcount').html(this.results);
for (i = 0; i < problemCounts.length; i++) {
if (selectionStates[i]) {
countHtml = ' (' + problemCounts[i] + ')';
} else {
countHtml = '';
}
this.$element.find('#schnark-ve-problems-problem-' + i + ' .count').html(countHtml);
}
};
FindProblemsDialog.prototype.getCounts = function () {
var i, problemCounts = [];
function add (problem) {
problemCounts[problem.index]++;
}
for (i = 0; i < this.constructor.static.problems.length; i++) {
problemCounts.push(0);
}
for (i = 0; i < this.fragments.length; i++) {
this.getProblemsForIndex(i).list.forEach(add);
}
return problemCounts;
};
FindProblemsDialog.prototype.createProblemsForIndexCache = function () {
var i;
this.problemsForIndexCache = [];
for (i = 0; i < this.fragments.length; i++) {
this.problemsForIndexCache.push(this.createProblemsForIndex(i));
}
};
FindProblemsDialog.prototype.createProblemsForIndex = function (index) {
var text = this.fragments[index].getText(true).replace(/\n/g, '\uFFFC'),
selectionStates = this.getProblemSelectionStates(),
fixProblem = false,
list = this.constructor.static.problems.filter(function (problem, index) {
//FIXME teilweise sprechen auch falsche REs an
var matches = selectionStates[index] && text.search(problem.re) === 0;
if (text.indexOf('\uFFFC') === -1 && matches && fixProblem === false && problem.replace !== undefined) {
fixProblem = index;
}
return matches;
});
return {list: list, fixProblem: fixProblem};
};
FindProblemsDialog.prototype.getProblemsForIndex = function (index) {
if (!this.problemsForIndexCache) {
this.createProblemsForIndexCache();
}
return this.problemsForIndexCache[index] || {list: [], fixProblem: false};
};
FindProblemsDialog.prototype.highlightFocused = function () {
this.findText = {setLabel: function () {}};
FindProblemsDialog.parent.prototype.highlightFocused.apply(this, arguments);
this.$element.find('.schnark-ve-problems-current-problem-highlight')
.removeClass('schnark-ve-problems-current-problem-highlight');
var currentProblems = this.getProblemsForIndex(this.focusedIndex);
currentProblems.list.forEach(function (problem) {
this.$element.find('#schnark-ve-problems-problem-' + problem.index)
.addClass('schnark-ve-problems-current-problem-highlight')[0]
.scrollIntoView(false);
}, this);
this.replaceButton.setDisabled(
currentProblems.fixProblem === false || this.surface.getModel().isReadOnly()
);
};
FindProblemsDialog.prototype.findFirst = function () {
var initialFragment = this.surface.getModel().getFragment(null, true);
this.startOffset = ve.getProp(
initialFragment.getSelection().getRanges(initialFragment.getDocument()),
0, 'start'
) || 0;
this.onProblemsChange();
};
FindProblemsDialog.prototype.focus = function () {};
FindProblemsDialog.prototype.replace = function (index) {
var fix = this.getProblemsForIndex(index).fixProblem;
if (fix === false) {
return;
}
fix = this.constructor.static.problems[fix];
this.replacing = true;
this.fragments[index].insertContent(
this.fragments[index].getText().replace(fix.re, fix.replace),
true
);
setTimeout(function () {
this.replacing = false;
}.bind(this));
};
ve.ui.windowFactory.register(FindProblemsDialog);
ve.ui.commandRegistry.register(
new ve.ui.Command(
'findProblems', 'window', 'open', {args: ['findProblems', null, 'findFirst']}
)
);
function FindProblemsTool () {
FindProblemsTool.parent.apply(this, arguments);
}
OO.inheritClass(FindProblemsTool, ve.ui.FindAndReplaceTool);
FindProblemsTool.static.name = 'findProblems';
FindProblemsTool.static.icon = 'alert';
FindProblemsTool.static.title = ve.msg('schnark-ve-problems-title');
FindProblemsTool.static.commandName = 'findProblems';
ve.ui.toolFactory.register(FindProblemsTool);
addToToolbar('findProblems');
ve.ui.triggerRegistry.register(
'findProblems', {mac: new ve.ui.Trigger('cmd+p'), pc: new ve.ui.Trigger('ctrl+p')}
);
problems(FindProblemsDialog.static.addProblem);
//virtual outdent
}
problemsForProjects.dewiki = function (add) {
function allowCapital (re) {
var first = re.charAt(0),
firstCapital = first.toUpperCase();
if (first === firstCapital) {
return re;
}
return '[' + first + firstCapital + ']' + re.slice(1);
}
var typo = { //FIXME erweitern
'aberufen': 'abgerufen',
'Addresse': 'Adresse',
'anderere': 'andere',
'andereren': 'anderen',
'andererer': 'anderer',
'Gallerie': 'Galerie',
'Standart': 'Standard'
}, words = [ //evt. weitere aus wikilint übernehmen
'bedauerlicherweise',
'bedeutendste',
'bekannte',
'bekannteste',
'berühmteste',
'derzeit',
'einzigste',
'ich',
'in der Regel',
'in den letzten Jahren',
'sogar',
'wichtigste',
'zum Glück',
'zurzeit',
'Insider'
], abbr = [
'z. B.',
'bzw.',
'd. h.',
'jährl.',
'u. a.',
'hl.',
'engl.',
'ital.',
'span.',
'lat.',
'ca.',
'sog.'
], months = [
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember'
],
opening = '([„«', closing = ')]“»', digitPunct = '.,', punct = ':;?!', object = '\uFFFC';
add('Wortdopplung', '\\b(\\S+)\\b(\\W+)\\b\\1\\b', '$1$2', SOURCE + VISUAL);
add('Plenk', '\\s+([' + mw.util.escapeRegExp(digitPunct + punct + closing + '/') +
'])|([' + mw.util.escapeRegExp(opening + '/') + '])\\s+', '$1$2', SOURCE + VISUAL);
add('Klemp',
'([' + mw.util.escapeRegExp(punct) + '])([^ ' + mw.util.escapeRegExp(closing) + object + '])|' +
'(,)([^ \\d' + mw.util.escapeRegExp(closing) + object + '])|' +
'(\\.)([^ \\-\\da-z' + mw.util.escapeRegExp(closing) + object + '])',
'$1$3$5 $2$4$6', VISUAL);
add('Mehrfache Leerzeichen', ' {2,}', ' ', VISUAL);
add('Überflüssige Leerzeichen', ' +$', '', VISUAL + SOURCE);
add('Kleinschreibung', '([.!?]\\s+)([a-zäöü])', function (all, pre, char) {
return pre + char.toUpperCase();
}, SOURCE + VISUAL);
add('Tippfehler', '\\b(?:' + Object.keys(typo).map(mw.util.escapeRegExp).join('|') + ')\\b', function (word) {
return typo[word];
}, SOURCE + VISUAL);
add('Zahlenformat (4-stellig)', '\\b(\\d)\\.(\\d{3})\\b', '$1$2', VISUAL + SOURCE);
add('Zahlenformat', ' \\d{5,}\\b', function (number) {
function group (n) {
if (n.length <= 3) {
return n;
}
return group(n.slice(0, -3)) + '.' + n.slice(-3);
}
return ' ' + group(number.slice(1));
}, SOURCE + VISUAL);
add('Unerwünschte Wörter', '\\b(?:' + words.map(mw.util.escapeRegExp).map(allowCapital).join('|') + ')\\b',
false, SOURCE + VISUAL);
add('Abkürzung', '\\b(?:' + abbr.map(mw.util.escapeRegExp).map(allowCapital).join('|') + ')', false, SOURCE + VISUAL);
add('Langer Satz', '(?:\\b\\S+\\b[ ,]){50,}', false, SOURCE + VISUAL);
add('Datum', '\\b([0-3]?[0-9])\\.\\s*([01]?[0-9])\\.\\s*', function (all, day, month) {
return String(Number(day)) + '. ' + months[Number(month) - 1] + ' ';
}, SOURCE + VISUAL);
add('Gedankenstrich', ' - ', ' – ', SOURCE + VISUAL);
add('Bis-Strich zwischen Jahren', '([^-0-9][12][0-9]{3}) *- *([12][0-9]{3}[^-0-9])', '$1–$2', SOURCE + VISUAL);
add('Bis-Strich zwischen Seiten', '\\b(Sp?\\.|Seiten?|Spalten?) *(\\d+) *- *', '$1 $2–', SOURCE + VISUAL);
add('Bis-Strich mit Leerzeichen', '(\\d) +(–) +(\\d)', '$1$2$3', SOURCE + VISUAL);
add('Auslassungspunkte', '\\.\\.\\.', '…', SOURCE + VISUAL);
add('Anführungszeichen', '"([^"\\n]{0,100})"', '„$1“', VISUAL);
add('Apostroph', '\'', '’', VISUAL);
add('Zusammenstoßende Links', '\\]\\]\\[\\[', false, SOURCE);
add('Weblinks', '^\\s*(?:[eE]xterner?|[eE]xternal)?\\s*(?:[wW]eblink|[lL]inks?|[wW]ebseiten?|[wW]ebsites?)\\s*$',
'Weblinks', SOURCE + VISUAL);
};
problemsForProjects['*'] = function (add) {
mw.log.warn('veHint.js: No config for this project!');
add('Placeholder', '^.*$', false, SOURCE + VISUAL);
};
//'oojs-ui.styles.icons-movement' nicht aufgeführt, da diese Icons auch vom beerbten Suchdialog verwendet werden
mw.loader.using([
'mediawiki.language', 'mediawiki.util',
'ext.visualEditor.core', 'ext.visualEditor.desktopArticleTarget', 'oojs-ui.styles.icons-alerts'
]).then(function () {
initL10N(l10n);
init(problemsForProjects[mw.config.get('wgDBname')] || problemsForProjects['*']);
mw.hook('userjs.script-ready.veHint').fire();
});
})(jQuery, mediaWiki);
//</nowiki>