1
0

Add AccessifyHTML5 and details polyfill as external files.

Technically being third-party libraries, these should be included
in our combined plugins js file. Once that file is being generated,
we can remove these to from serendipity_editor.js.tpl.

References 
This commit is contained in:
Matthias Mees
2014-06-25 10:33:54 +02:00
parent 8386c25037
commit 7d127738ca
2 changed files with 302 additions and 0 deletions
templates/2k11/admin/js

@ -0,0 +1,151 @@
/*
* Accessifyhtml5.js
*
* Source: https://github.com/yatil/accessifyhtml5.js
*/
var AccessifyHTML5 = function (defaults, more_fixes) {
"use strict";
var fixes = {
'article' : {'role': 'article' },
'aside' : {'role': 'complementary' },
'nav' : {'role': 'navigation' },
'main' : {'role': 'main' },
'output' : {'aria-live': 'polite' },
'section' : {'role': 'region' },
'[required]': {'aria-required': 'true' }
},
result = { ok:[], warn:[], fail:[] },
error = result.fail,
fix, elems, attr, value, key, obj, i, mo, by_match, el_label,
ATTR_SECURE = new RegExp("aria-[a-z]+|role|tabindex|title|alt|data-[\\w-]+|lang|"
+ "style|maxlength|placeholder|pattern|required|type|target|accesskey|longdesc"),
ID_PREFIX = "acfy-id-",
n_label = 0,
Doc = document;
if (Doc.querySelectorAll) {
if (defaults) {
if (defaults.header) {
fixes[defaults.header] = {
'role': 'banner'
};
}
if (defaults.footer) {
fixes[defaults.footer] = {
'role': 'contentinfo'
};
}
if (defaults.main) {
fixes[defaults.main] = {
'role': 'main'
};
fixes.main = {
'role': ''
};
}
}
// Either replace fixes...
if (more_fixes && more_fixes._CONFIG_
&& more_fixes._CONFIG_.ignore_defaults) {
fixes = more_fixes;
} else {
// ..Or concatenate - the default.
for (mo in more_fixes) {
fixes[mo] = more_fixes[mo];
}
}
for (fix in fixes) {
if (fix.match(/^_(CONFIG|[A-Z]+)_/)) {
continue; // Silently ignore.
}
if (fixes.hasOwnProperty(fix)) {
//Question: should we catch and report (or ignore) bad selector syntax?
try {
elems = Doc.querySelectorAll(fix);
} catch (ex) {
error.push({ sel:fix, attr:null, val:null,
msg:"Invalid syntax for `document.querySelectorAll` function", ex:ex });
}
obj = fixes[fix];
if (!elems || elems.length < 1) {
result.warn.push({ sel:fix, attr:null, val:null, msg:"Not found" });
}
for (i = 0; i < elems.length; i++) {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
attr = key;
value = obj[key];
if (attr.match(/_?note/)) { // Ignore notes/comments.
continue;
}
if (!attr.match(ATTR_SECURE)) {
error.push({ sel:fix, attr:attr, val:null, msg:"Attribute not allowed",
re:ATTR_SECURE });
continue;
}
if (!(typeof value).match(/string|number|boolean/)) {
error.push({ sel:fix, attr:attr, val:value, msg:"Value-type not allowed" });
continue;
}
// Connect up 'aria-labelledby'. //Question: do we accept poor spelling/ variations?
by_match = attr.match(/(describ|label)l?edby/);
if (by_match) {
try {
el_label = Doc.querySelector(value); //Not: elems[i].querySel()
} catch (ex) {
error.push({ sel:fix, attr:attr, val:value,
msg:"Invalid selector syntax (2) - see 'val'", ex:ex });
}
if (! el_label) {
error.push({ sel:fix, attr:attr, val:value,
msg:"Labelledby ref not found - see 'val'" });
continue;
}
if (! el_label.id) {
el_label.id = ID_PREFIX + n_label;
}
value = el_label.id;
attr = "aria-" + ("label" === by_match[1] ? "labelledby" : "describedby");
n_label++;
}
if (!elems[i].hasAttribute(attr)) {
elems[i].setAttribute(attr, value);
result.ok.push({ sel:fix, attr:attr, val:value, msg:"Added" });
}
else {
result.warn.push({ sel:fix, attr:attr, val:value, msg:"Already present, skipped" });
}
}
}
} //End: for (i..elems..i++)
}
} //End: for (fix in fixes)
}
result.input = fixes;
return result;
};

@ -0,0 +1,151 @@
/*! http://mths.be/details v0.1.0 by @mathias | includes http://mths.be/noselect v1.0.3 */
;(function(document, $) {
var proto = $.fn,
details,
// :'(
isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]',
// Feature test for native `<details>` support
isDetailsSupported = (function(doc) {
var el = doc.createElement('details'),
fake,
root,
diff;
if (!('open' in el)) {
return false;
}
root = doc.body || (function() {
var de = doc.documentElement;
fake = true;
return de.insertBefore(doc.createElement('body'), de.firstElementChild || de.firstChild);
}());
el.innerHTML = '<summary>a</summary>b';
el.style.display = 'block';
root.appendChild(el);
diff = el.offsetHeight;
el.open = true;
diff = diff != el.offsetHeight;
root.removeChild(el);
if (fake) {
root.parentNode.removeChild(root);
}
return diff;
}(document)),
toggleOpen = function($details, $detailsSummary, $detailsNotSummary, toggle) {
var isOpen = $details.prop('open'),
close = isOpen && toggle || !isOpen && !toggle;
if (close) {
$details.removeClass('open').prop('open', false).triggerHandler('close.details');
$detailsSummary.attr('aria-expanded', false);
$detailsNotSummary.hide();
} else {
$details.addClass('open').prop('open', true).triggerHandler('open.details');
$detailsSummary.attr('aria-expanded', true);
$detailsNotSummary.show();
}
};
/* http://mths.be/noselect v1.0.3 */
proto.noSelect = function() {
// Since the string 'none' is used three times, storing it in a variable gives better results after minification
var none = 'none';
// onselectstart and ondragstart for WebKit & IE
// onmousedown for WebKit & Opera
return this.bind('selectstart dragstart mousedown', function() {
return false;
}).css({
'MozUserSelect': none,
'msUserSelect': none,
'webkitUserSelect': none,
'userSelect': none
});
};
// Execute the fallback only if theres no native `details` support
if (isDetailsSupported) {
details = proto.details = function() {
return this.each(function() {
var $details = $(this),
$summary = $('summary', $details).first();
$summary.attr({
'role': 'button',
'aria-expanded': $details.prop('open')
}).on('click', function() {
// the value of the `open` property is the old value
var close = $details.prop('open');
$summary.attr('aria-expanded', !close);
$details.triggerHandler((close ? 'close' : 'open') + '.details');
});
});
};
details.support = isDetailsSupported;
} else {
details = proto.details = function() {
// Loop through all `details` elements
return this.each(function() {
// Store a reference to the current `details` element in a variable
var $details = $(this),
// Store a reference to the `summary` element of the current `details` element (if any) in a variable
$detailsSummary = $('summary', $details).first(),
// Do the same for the info within the `details` element
$detailsNotSummary = $details.children(':not(summary)'),
// This will be used later to look for direct child text nodes
$detailsNotSummaryContents = $details.contents(':not(summary)');
// If there is no `summary` in the current `details` element…
if (!$detailsSummary.length) {
// …create one with default text
$detailsSummary = $('<summary>').text('Details').prependTo($details);
}
// Look for direct child text nodes
if ($detailsNotSummary.length != $detailsNotSummaryContents.length) {
// Wrap child text nodes in a `span` element
$detailsNotSummaryContents.filter(function() {
// Only keep the node in the collection if its a text node containing more than only whitespace
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#space-character
return this.nodeType == 3 && /[^ \t\n\f\r]/.test(this.data);
}).wrap('<span>');
// There are now no direct child text nodes anymore — theyre wrapped in `span` elements
$detailsNotSummary = $details.children(':not(summary)');
}
// Hide content unless theres an `open` attribute
$details.prop('open', typeof $details.attr('open') == 'string');
toggleOpen($details, $detailsSummary, $detailsNotSummary);
// Add `role=button` and set the `tabindex` of the `summary` element to `0` to make it keyboard accessible
$detailsSummary.attr('role', 'button').noSelect().prop('tabIndex', 0).on('click', function() {
// Focus on the `summary` element
$detailsSummary.focus();
// Toggle the `open` and `aria-expanded` attributes and the `open` property of the `details` element and display the additional info
toggleOpen($details, $detailsSummary, $detailsNotSummary, true);
}).keyup(function(event) {
if (32 == event.keyCode || (13 == event.keyCode && !isOpera)) {
// Space or Enter is pressed — trigger the `click` event on the `summary` element
// Opera already seems to trigger the `click` event when Enter is pressed
event.preventDefault();
$detailsSummary.click();
}
});
});
};
details.support = isDetailsSupported;
}
}(document, jQuery));