@instructure/canvas-rce
Version:
A component wrapping Canvas's usage of Tinymce
171 lines (165 loc) • 4.9 kB
JavaScript
/*
* Copyright (C) 2018 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import formatMessage from '../../../../format-message';
import { changeTag } from '../utils/dom';
/* Headings Sequence rule
* this rule is ensuring that heading tags (H1-H6) are layed out in sequential
* order for organizing your site.
*
* this rule only looks at H2-H6 headings. all other tags pass.
* this rule will walk 'up-down' the dom to find the heading tag that is
* laid out previous to the heading tag being checked.
* this rule will see if the heading tag number of the previous heading is
* one more than one less than it's own heading tag and will fail if so
* this rule will check to see if there is no previous heading tag and will
* fail the test if so
*/
const isHtag = elem => {
const allHTags = {
H1: true,
H2: true,
H3: true,
H4: true,
H5: true,
H6: true
};
return elem && allHTags[elem.tagName] === true;
};
// gets the H tag that is furthest down in the tree from elem(inclusive)
const getHighestOrderHForElem = elem => {
const allHForElem = Array.prototype.slice.call(elem.querySelectorAll('H1,H2,H3,H4,H5,H6'));
if (allHForElem.length > 0) {
return allHForElem.reverse()[0];
}
if (isHtag(elem)) {
return elem;
}
return undefined;
};
// gets all siblings of elem that come before the elem ordered by nearest to
// elem
const getPrevSiblings = elem => {
const ret = [];
if (!elem || !elem.parentElement || !elem.parentElement.children) {
return ret;
}
const sibs = elem.parentElement.children;
for (let i = 0; i < sibs.length; i++) {
if (sibs[i] === elem) {
break;
}
ret.unshift(sibs[i]);
}
return ret;
};
const searchPrevSiblings = elem => {
const sibs = getPrevSiblings(elem);
let ret;
for (let i = 0; i < sibs.length; i++) {
ret = getHighestOrderHForElem(sibs[i]);
if (ret) {
break;
}
}
return ret;
};
const _walkUpTree = elem => {
let ret;
if (!elem || elem.tagName === 'BODY') {
return undefined;
}
if (isHtag(elem)) {
return elem;
}
ret = searchPrevSiblings(elem);
if (!ret) {
ret = _walkUpTree(elem.parentElement);
}
return ret;
};
const walkUpTree = elem => {
let ret = searchPrevSiblings(elem);
if (!ret) {
ret = _walkUpTree(elem.parentElement);
}
return ret;
};
const getPriorHeading = elem => {
return walkUpTree(elem);
};
// a valid prior H tag is greater or equal to one less than current
const getValidHeadings = elem => {
const hNum = +elem.tagName.substring(1);
const ret = {};
for (let i = hNum - 1; i <= 6; i++) {
ret[`H${i}`] = true;
}
return ret;
};
export default {
id: 'headings-sequence',
test: elem => {
const testTags = {
H2: true,
H3: true,
H4: true,
H5: true,
H6: true
};
if (testTags[elem.tagName] !== true) {
return true;
}
const validHeadings = getValidHeadings(elem);
const priorHeading = getPriorHeading(elem);
if (priorHeading) {
return validHeadings[priorHeading.tagName];
}
return true;
},
data: _elem => {
return {
action: 'nothing'
};
},
form: () => [{
label: formatMessage('Action to take:'),
dataKey: 'action',
options: [['nothing', formatMessage('Leave as is')], ['elem', formatMessage('Fix heading hierarchy')], ['modify', formatMessage('Remove heading style')]]
}],
update: (elem, data) => {
if (!data || !data.action || data.action === 'nothing') {
return elem;
}
switch (data.action) {
case 'elem':
{
const priorH = getPriorHeading(elem);
const hIdx = priorH ? +priorH.tagName.substring(1) : 0;
return changeTag(elem, `H${hIdx + 1}`);
}
case 'modify':
{
return changeTag(elem, 'p');
}
}
},
message: () => formatMessage('Heading levels should not be skipped.'),
why: () => formatMessage('Sighted users browse web pages quickly, looking for large or bolded headings. Screen reader users rely on headers for contextual understanding. Headers should use the proper structure.'),
link: 'https://www.w3.org/TR/WCAG20-TECHS/G141.html',
linkText: () => formatMessage('Learn more about organizing page headings')
};