Benutzer:Schnark/js/vorleser.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
- Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
- Opera: Strg+F5
//Dokumentation unter [[Benutzer:Schnark/js/vorleser]] <nowiki>
/*global mediaWiki*/
(function ($, mw) {
"use strict";
//mw.user.options.set('userjs-speechsynthesis-polyfill', 'http://localhost/testwiki2/polyfill-incl.js');
var icons = {
//jscs:disable maximumLineLength
//Icons by Google
play: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M0 0h48v48H0z" fill="none"/><path d="M20 33l12-9-12-9v18zm4-29C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16z"/></svg>',
pause: '<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M0 0h48v48H0z" fill="none"/><path d="M18 32h4V16h-4v16zm6-28C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 36c-8.82 0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16zm2-8h4V16h-4v16z"/></svg>'
}, l10n = {
en: {
'schnark-vorleser-enable': 'Enable reading',
'schnark-vorleser-disable': 'Disable reading',
'schnark-vorleser-tooltip': 'When reading out is enabled, you can start and pause it with the key s or the button shown, or by clicking the place from which on should be read.'
},
de: {
'schnark-vorleser-enable': 'Vorlesefunktion aktivieren',
'schnark-vorleser-disable': 'Vorlesefunktion deaktivieren',
'schnark-vorleser-tooltip': 'Wenn die Vorlesefunktion aktiviert ist, kann sie mit der Taste s oder die eingeblendete Schaltfläche gestartet und pausiert werden, oder mit einem Klick auf die Stelle, ab der vorgelesen werden soll.'
}
//jscs:enable maximumLineLength
};
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 getFunctions (speechSynthesis, SpeechSynthesisUtterance) {
//virtual indent
var getVoicesCache, otherVoiceCache = {};
function getOtherVoiceUncached (lang) {
if (!getVoicesCache) {
getVoicesCache = speechSynthesis.getVoices();
}
var list = $.grep(getVoicesCache, function (data) {
return data.lang === lang && !data['default'];
});
return (list[0] && list[0].voiceURI) || '';
}
function getOtherVoice (lang) {
if (!(lang in otherVoiceCache)) {
otherVoiceCache[lang] = getOtherVoiceUncached(lang);
}
return otherVoiceCache[lang];
}
function speakText (text, data) {
var utterance = new SpeechSynthesisUtterance(mw.html.escape(text));
if (data.lang) {
utterance.lang = data.lang;
}
if (data.voice) {
utterance.voiceURI = getOtherVoice(data.lang);
}
if (data.volume) {
utterance.volume = data.volume;
}
if (data.rate) {
utterance.rate = data.rate;
}
if (data.pitch) {
utterance.pitch = data.pitch;
}
if (data.callback) {
utterance.onend = function () {
data.callback(true);
};
utterance.onerror = function () {
data.callback(false);
};
}
speechSynthesis.speak(utterance);
}
function Tree (element, data) {
this.element = element;
this.data = $.extend({}, data, Tree.getData(element));
this.content = Tree.getContent(element);
}
Tree.getData = function (element) {
var lang, name, ret = {element: element};
if (!element.getAttribute) {
return ret;
}
lang = element.getAttribute('lang') || '';
name = element.nodeName.toUpperCase();
if (lang) {
ret.lang = lang;
}
if (['H1', 'H2', 'H3', 'H4', 'H5', 'IMG'].indexOf(name) !== -1) {
ret.voice = true;
}
if (name === 'SMALL') {
ret.volume = 0.7;
}
if (['U', 'B', 'STRONG'].indexOf(name) !== -1) {
ret.rate = 0.7;
ret.pitch = 0.7;
}
if (['I', 'EM'].indexOf(name) !== -1) {
ret.pitch = 1.3;
}
if (window.getComputedStyle(element, null).display === 'none') {
ret.silent = true;
}
return ret;
};
Tree.getContent = function (element) {
var before, after, ret;
try {
//TODO counter etc.
before = window.getComputedStyle(element, '::before').content.replace(/[^"]*(?:"([^"]*)")?[^"]*/g, '$1');
after = window.getComputedStyle(element, '::after').content.replace(/[^"]*(?:"([^"]*)")?[^"]*/g, '$1');
} catch (e) {
//#TEXT node etc.
}
switch (element.nodeName.toUpperCase()) {
case '#TEXT': ret = [element.data]; break;
case 'IMG': ret = [element.getAttribute('alt') || '']; break;
default: ret = $.makeArray(element.childNodes);
}
if (before) {
ret.unshift(before);
}
if (after) {
ret.push(after);
}
return ret;
};
Tree.getResult = function (text) {
return {text: text};
};
Tree.prototype.getNext = function () {
var next;
while (1) {
if (this.current) {
next = this.current.getNext();
if (next) {
return next;
}
this.current = false;
}
if (this.content.length === 0) {
return false;
}
next = this.content.shift();
if (typeof next === 'string') {
next = next.trim();
if (next && !this.data.silent) {
return $.extend(Tree.getResult(next), this.data);
} else {
continue;
}
}
this.current = new Tree(next, this.data);
}
};
function parents (element) {
var ret = [element];
while (ret[0].parentNode) {
ret.unshift(ret[0].parentNode);
}
return ret;
}
function commonParentIndex (p1, p2) {
var i = 0;
while (p1[i] === p2[i]) {
i++;
}
return i - 1;
}
function comesBefore (a, b, p1) {
var p2, c, i, j;
//p1 = parents(a);
p2 = parents(b);
i = commonParentIndex(p1, p2);
if (i === p1.length - 1) {
return true;
}
if (i === p2.length - 1) {
return false;
}
c = p1[i].childNodes;
for (j = 0; j < c.length; j++) {
if (c[j] === p1[i + 1]) {
return true;
}
if (c[j] === p2[i + 1]) {
return false;
}
}
}
function read (element, from) {
/*jshint boss: true*/
var tree = new Tree(element, {}), data, start = false, fromParents;
if (!from) {
start = true;
} else {
fromParents = parents(from);
}
while (data = tree.getNext()) {
if (!start && comesBefore(from, data.element, fromParents)) {
start = true;
}
if (data.text && start) {
speakText(data.text, data);
}
}
}
return {
read: read,
cancel: speechSynthesis.cancel.bind(speechSynthesis),
pause: speechSynthesis.pause.bind(speechSynthesis),
resume: speechSynthesis.resume.bind(speechSynthesis)
};
//virtual outdent
}
function manage (f) {
var content = document.getElementById('content');
return {
readAll: function () {
f.cancel();
f.resume();
f.read(content);
},
readFrom: function (el) {
f.cancel();
f.resume();
f.read(content, el);
},
pause: function () {
f.pause();
},
resume: function () {
f.resume();
},
cancel: function () {
f.cancel();
f.resume();
}
};
}
function initEvents (type, f) {
var $body = $('body'), $content = $('#content'), $icon;
$body.off('.speechSynthesisTest');
$content.off('.speechSynthesisTest');
$('#speechSynthesisTestIcon').remove();
if (type === 'off') {
return;
}
function toggle () {
f[type === 'reading' ? 'pause' : type === 'pausing' ? 'resume' : 'readAll']();
initEvents(type === 'reading' ? 'pausing' : 'reading', f);
}
if (type !== 'pausing') {
$content.on('click.speechSynthesisTest', '*', function (e) {
if (this === e.target) {
initEvents('reading', f);
f.readFrom(this);
}
});
}
$icon = $('<img>');
$icon.attr('id', 'speechSynthesisTestIcon');
$icon.attr('src', 'data:image/svg+xml,' + encodeURI(icons[type === 'reading' ? 'pause' : 'play']));
$icon.attr('style', 'position: fixed; width: 48px; height: 48px; top: 2px; left: 2px;');
$icon.on('click', toggle);
$icon.appendTo($body);
$body.on('keydown.speechSynthesisTest', function (e) {
if (e.which === 83) { //s
e.preventDefault();
e.stopPropagation();
toggle();
}
});
}
function switchOn ($el, f) {
initEvents('nothing', f);
$el.text(mw.msg('schnark-vorleser-enable')).off('click').on('click', function (e) {
e.preventDefault();
switchOff($el, f);
});
}
function switchOff ($el, f) {
f.cancel();
initEvents('off', f);
$el.text(mw.msg('schnark-vorleser-enable')).off('click').on('click', function (e) {
e.preventDefault();
switchOn($el, f);
});
}
function init () {
var sS = window.polyfillSpeechSynthesis || window.speechSynthesis,
SSU = window.polyfillSpeechSynthesisUtterance || window.SpeechSynthesisUtterance,
f, el;
if (!sS || !SSU) {
return;
}
f = manage(getFunctions(sS, SSU));
mw.loader.using(['mediawiki.language', 'mediawiki.util'], function () {
initL10N(l10n);
$(function () {
el = mw.util.addPortletLink('p-tb', '#', '', 't-read-functions', mw.msg('schnark-vorleser-tooltip'));
switchOff($(el).find('a'), f);
});
});
}
if (mw.user.options.exists('userjs-speechsynthesis-polyfill')) {
$.ajax({
url: mw.user.options.get('userjs-speechsynthesis-polyfill'),
dataType: 'script', crossDomain: true,
cache: true, async: true
}).then(init);
} else {
init();
}
})(jQuery, mediaWiki);
//</nowiki>