Справица:Solutions.js
Пређи на навигацију
Пређи на претрагу
/* eslint-disable max-statements */
(function() {
'use strict';
var $body = $('body');
var questions = [];
var allSolutionsShown = false;
var CORRECT_SRC = 'https://upload.wikimedia.org/wikipedia/en/thumb/f/fb/Yes_check.svg/18px-Yes_check.svg.png';
var INCORRECT_SRC = 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/X_mark.svg/18px-X_mark.svg.png';
var staticId = 0;
var questionIdMap = {};
var interactiveMode = null;
function createButton(msg) {
return $('<span>', {
'class': [
'oo-ui-buttonElement',
'oo-ui-buttonElement-framed',
'oo-ui-flaggedElement-primary',
'oo-ui-flaggedElement-progressive',
'oo-ui-labelElement',
'oo-ui-widget-enabled'
].join(' ')
}).append(
$('<button>', {
'class': 'oo-ui-buttonElement-button',
'tabindex': '0'
}).append(
$('<span>', {
'class': 'oo-ui-labelElement-label label',
'text': mw.message('gadget-solutions-' + msg).plain()
})
)
);
}
function createCorrectImage(correct) {
// TODO: alt
return $('<img>', {
'class': 'correct',
'src': correct ?
CORRECT_SRC :
INCORRECT_SRC
});
}
function Question($elem) {
this.id = staticId++;
questionIdMap[this.id] = this;
this.$elem = $elem.addClass('loaded');
this.state = allSolutionsShown;
if (allSolutionsShown) {
this.$butt = createButton('hide');
this.show();
} else {
this.$butt = createButton('show');
this.hide();
}
this.appendButton();
}
Question.prototype.isAttached = function() {
return $.contains(document, this.$elem[0]);
};
Question.prototype.show = function() {
this.$butt
.off()
.click(this.hide.bind(this))
.find('.label')
.text(mw.message('gadget-solutions-hide').plain());
if (this.explanation) {
this.explanation.show();
}
};
Question.prototype.hide = function() {
this.$butt
.off()
.click(this.show.bind(this))
.find('.label')
.text(mw.message('gadget-solutions-show').plain());
if (this.explanation) {
this.explanation.hide();
}
};
Question.prototype.appendButton = function() {
this.$elem.after(this.$butt);
};
function Text($elem) {
this.solution = $elem.text().trim();
this.$input = $('<input>', {
type: 'text'
});
Question.call(this, $elem);
}
OO.inheritClass(Text, Question);
Text.prototype.show = function() {
Question.prototype.show.call(this);
this.$elem.addClass('spoiler show');
var answer = this.$input.val().trim();
this.$input.val('');
this.$elem.text(this.solution);
if (answer) {
this.$elem.prepend(createCorrectImage(this.solution === answer));
}
};
Text.prototype.hide = function() {
Question.prototype.hide.call(this);
this.$elem
.removeClass('spoiler')
.empty()
.append(this.$input);
};
function Choice($elem, inputType) {
this.inputType = inputType;
Question.call(this, $elem);
}
OO.inheritClass(Choice, Question);
Choice.prototype.show = function() {
Question.prototype.show.call(this);
this.$elem.addClass('show');
this.$elem.find('li').each(function(_, el) {
var $li = $(el);
var isCorrect = $li.find('.solution').length === 1;
var $input = $li.find('input');
var isChecked = $input.prop('checked');
$input.remove();
$li.html($li.find('label').html());
if (isChecked) {
$li.prepend(createCorrectImage(isCorrect === isChecked));
}
});
};
Choice.prototype.hide = function() {
Question.prototype.hide.call(this);
this.$elem.removeClass('show');
this.$elem.find('li').each(function(index, el) {
var $li = $(el);
var id = 'solution-' + this.id + '-' + index;
$li.find('img.correct').remove();
$li
.html($('<label>', {
'for': id,
'html': $li.html()
}))
.prepend($('<input>', {
id: id,
name: 'solution-' + this.id,
type: this.inputType
}));
}.bind(this));
};
function Select($elem) {
var options = $elem.attr('data-options');
if (options) {
this.options = options
.split(',')
.map(function(opt) {
return opt.trim();
});
this.$select = $('<select>')
.append(this.options.map(function(opt) {
return $('<option>', {
text: opt
});
}));
}
Question.call(this, $elem);
}
OO.inheritClass(Select, Question);
Select.prototype.show = function() {
Question.prototype.show.call(this);
this.$elem.find('> ul > li, > ol > li, td').each(function(_, el) {
var $li = $(el);
var images = [];
$li.find('select').each(function(__, el2) {
var $select = $(el2);
var selected = $select.prop('selectedIndex');
var answer = $select.attr('data-answer');
var options = $select
.find('option')
.map(function(___, opt) {
return $(opt).text();
}).toArray();
var correct = options.indexOf(answer);
$select.replaceWith($('<span>', {
'data-options': options.join(','),
'class': 'spoiler show',
'text': answer
}));
images.push(createCorrectImage(selected === correct));
});
$li.prepend(images);
});
};
Select.prototype.hide = function() {
Question.prototype.hide.call(this);
var $thisSelect = this.$select;
this.$elem.find('> ul > li, > ol > li, td').each(function(_, el) {
var $li = $(el);
$li.find('img.correct').remove();
$li.find('.spoiler').each(function(__, el2) {
var $spoiler = $(el2);
var $oldSelect = $thisSelect;
if (!$oldSelect) {
// Each list item has different options.
$oldSelect = $('<select>').append(
$spoiler
.attr('data-options')
.split(',')
.map(function(opt) {
return $('<option>', {
text: opt.trim()
});
})
);
}
$spoiler.replaceWith(
$oldSelect
.clone()
.attr('data-answer', $spoiler.text())
);
});
});
};
function Explanation($elem) {
Question.call(this, $elem);
questionIdMap[this.id - 1].explanation = this;
}
OO.inheritClass(Explanation, Question);
Explanation.prototype.appendButton = $.noop;
Explanation.prototype.show = function() {
Question.prototype.show.call(this);
this.$elem.addClass('show');
};
Explanation.prototype.hide = function() {
Question.prototype.hide.call(this);
this.$elem.removeClass('show');
};
function Plain($elem) {
Question.call(this, $elem);
}
OO.inheritClass(Plain, Question);
Plain.prototype.show = function() {
Question.prototype.show.call(this);
this.$elem.addClass('show');
};
Plain.prototype.hide = function() {
Question.prototype.hide.call(this);
this.$elem.removeClass('show');
};
function createQuestion(_, elem) {
var $elem = $(elem);
var type = $elem.attr('data-solution');
if (!interactiveMode) {
if (type === 'explanation') {
return new Explanation($elem);
}
return new Plain($elem);
}
switch (type) {
case 'text': return new Text($elem);
case 'single': return new Choice($elem, 'radio');
case 'multiple': return new Choice($elem, 'checkbox');
case 'select': return new Select($elem);
case 'explanation': return new Explanation($elem);
default: return new Plain($elem);
}
}
function toggleAll() {
var $label = $(this).find('.label');
allSolutionsShown = !allSolutionsShown;
if (allSolutionsShown) {
$label.text(mw.message('gadget-solutions-hide-all').plain());
} else {
$label.text(mw.message('gadget-solutions-show-all').plain());
}
questions.forEach(function(q) {
if (allSolutionsShown) {
q.show();
} else {
q.hide();
}
});
}
function checkInteractiveMode() {
if (interactiveMode !== null) {
// We already checked for interactive mode.
return;
}
var interactiveModeLS = mw.storage.get('solutions-interactive');
if (interactiveModeLS) {
// The interactive mode has been set.
interactiveMode = interactiveModeLS === 'yes';
} else {
mw.storage.set('solutions-interactive', 'yes');
interactiveMode = true;
mw.notification.notify(mw.message('gadget-solutions-interactive-notification'), {
type: 'info'
});
}
}
function toggleInteractive() {
mw.storage.set('solutions-interactive', interactiveMode ? 'no' : 'yes');
window.location.reload();
}
function hook($content) {
if ($content.find('[data-solution]').length) {
// Only check for interactive mode if we're on a page where it can be used.
checkInteractiveMode();
}
$content
// TODO: unhide -> show
.find('.unhide-solutions:not(.loaded)')
.addClass('loaded')
.append([
createButton(
allSolutionsShown ?
'hide-all' :
'show-all'
).click(toggleAll),
createButton(
interactiveMode ?
'disable-interactive' :
'enable-interactive'
).click(toggleInteractive)
]);
questions = questions.filter(function(q) {
var ret = q.isAttached();
if (!ret) {
delete questionIdMap[q.id];
}
return q.isAttached();
}).concat(
$content.find('[data-solution]:not(.loaded)')
.map(createQuestion)
.toArray()
);
}
mw.hook('wikipage.content').add(hook);
})();