AutorÃa | Ultima modificación | Ver Log |
{"version":3,"file":"checker.min.js","sources":["../src/checker.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/*\n * @package tiny_accessibilitychecker\n * @copyright 2022, Stevani Andolo <stevani@hotmail.com.au>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport
{getString, getStrings} from 'core/str';\nimport {component} from './common';\nimport Modal from 'core/modal';\nimport * as ModalEvents from 'core/modal_events';\nimport ColorBase from './colorbase';\nimport {getPlaceholderSelectors} from 'editor_tiny/options';\n\n/**\n * @typedef ProblemDetail\n * @type {object}\n * @param {string} description The description of the problem\n * @param {ProblemNode[]} problemNodes The list of affected nodes\n */\n\n/**\n * @typedef ProblemNode\n * @type {object}\n * @param {string} nodeName The node name for the affected node\n * @param {string} nodeIndex The indexd of the node\n * @param {string} text A description of the issue\n * @param {string} src The source of the image\n */\n\nexport default class {\n\n constructor(editor) {\n this.editor = editor;\n this.colorBase = new ColorBase();\n this.modal = null;\n this.placeholderSelectors = null;\n const placeholders = getPlaceholderSelectors(this.editor);\n if (placeholders.leng
th) {\n this.placeholderSelectors = placeholders.join(', ');\n }\n }\n\n destroy() {\n delete this.editor;\n delete this.colorBase;\n\n this.modal.destroy();\n delete this.modal;\n }\n\n async displayDialogue() {\n this.modal = await Modal.create({\n large: true,\n title: getString('pluginname', component),\n body: this.getDialogueContent(),\n show: true,\n });\n\n // Destroy the class when hiding the modal.\n this.modal.getRoot().on(ModalEvents.hidden, () => this.destroy());\n\n this.modal.getRoot()[0].addEventListener('click', (event) => {\n const faultLink = event.target.closest('[data-action=\"highlightfault\"]');\n if (!faultLink) {\n return;\n }\n\n event.preventDefault();\n\n const nodeName = faultLink.dataset.nodeName;\n let selectedNode = null;\n if (nodeName) {\n
if (nodeName.includes(',') || nodeName === 'body') {\n selectedNode = this.editor.dom.select('body')[0];\n } else {\n const nodeIndex = faultLink.dataset.nodeIndex ?? 0;\n selectedNode = this.editor.dom.select(nodeName)[nodeIndex];\n }\n }\n\n if (selectedNode && selectedNode.nodeName.toUpperCase() !== 'BODY') {\n this.selectAndScroll(selectedNode);\n }\n\n this.modal.hide();\n });\n }\n\n async getAllWarningStrings() {\n const keys = [\n 'emptytext',\n 'entiredocument',\n 'imagesmissingalt',\n 'needsmorecontrast',\n 'needsmoreheadings',\n 'tablesmissingcaption',\n 'tablesmissingheaders',\n 'tableswithmergedcells',\n ];\n\n const stringValues = await getStrings(keys.map((key) => ({key, component})));\n return new Map(keys.map((key, i
ndex) => ([key, stringValues[index]])));\n }\n\n /**\n * Return the dialogue content.\n *\n * @return {Promise<Array>} A template promise containing the rendered dialogue content.\n */\n async getDialogueContent() {\n const langStrings = await this.getAllWarningStrings();\n\n // Translate langstrings into real strings.\n const warnings = this.getWarnings().map((warning) => {\n if (warning.description) {\n if (warning.description.type === 'langstring') {\n warning.description = langStrings.get(warning.description.value);\n } else {\n warning.description = warning.description.value;\n }\n }\n\n warning.nodeData = warning.nodeData.map((problemNode) => {\n if (problemNode.text) {\n if (problemNode.text.type === 'langstring') {\n problemNode.text = langStrings.get(problemNode.text.value);\n
} else {\n problemNode.text = problemNode.text.value;\n }\n }\n\n return problemNode;\n });\n\n return warning;\n });\n\n return Templates.render('tiny_accessibilitychecker/warning_content', {\n warnings\n });\n }\n\n /**\n * Set the selection and scroll to the selected element.\n *\n * @param {node} node\n */\n selectAndScroll(node) {\n this.editor.selection.select(node).scrollIntoView({\n behavior: 'smooth',\n block: 'nearest'\n });\n }\n\n /**\n * Find all problems with the content editable region.\n *\n * @return {ProblemDetail[]} A complete list of all warnings and problems.\n */\n getWarnings() {\n const warnings = [];\n\n // Check Images with no alt text or dodgy alt text.\n warnings.push(this.createWarnings('imagesmissingalt', this.checkImage(), true));\n
warnings.push(this.createWarnings('needsmorecontrast', this.checkOtherElements(), false));\n\n // Check for no headings.\n if (this.editor.getContent({format: 'text'}).length > 1000 && this.editor.dom.select('h3,h4,h5').length < 1) {\n warnings.push(this.createWarnings('needsmoreheadings', [this.editor], false));\n }\n\n // Check for tables with no captions.\n warnings.push(this.createWarnings('tablesmissingcaption', this.checkTableCaption(), false));\n\n // Check for tables with merged cells.\n warnings.push(this.createWarnings('tableswithmergedcells', this.checkTableMergedCells(), false));\n\n // Check for tables with no row/col headers.\n warnings.push(this.createWarnings('tablesmissingheaders', this.checkTableHeaders(), false));\n\n return warnings.filter((warning) => warning.nodeData.length > 0);\n }\n\n /**\n * Generate the data that describes the issues found.\n *\n * @param {String} description Desc
ription of this failure.\n * @param {HTMLElement[]} nodes An array of failing nodes.\n * @param {boolean} isImageType Whether the warnings are related to image type checks\n * @return {ProblemDetail[]} A set of problem details\n */\n createWarnings(description, nodes, isImageType) {\n const getTextValue = (node) => {\n if (node === this.editor) {\n return {\n type: 'langstring',\n value: 'entiredocument',\n };\n }\n\n const emptyStringValue = {\n type: 'langstring',\n value: 'emptytext',\n };\n if ('innerText' in node) {\n const value = node.innerText.trim();\n return value.length ? {type: 'raw', value} : emptyStringValue;\n } else if ('textContent' in node) {\n const value = node.textContent.trim();\n return value.length ? {type: 'raw', value} : emptyStringValue
;\n }\n\n return {type: 'raw', value: node.nodeName};\n };\n\n const getEventualNode = (node) => {\n if (node !== this.editor) {\n return node;\n }\n const childNodes = node.dom.select('body')[0].childNodes;\n if (childNodes.length) {\n return document.body;\n } else {\n return childNodes;\n }\n };\n\n const warning = {\n description: {\n type: 'langstring',\n value: description,\n },\n nodeData: [],\n };\n\n warning.nodeData = [...nodes].filter((node) => {\n // If the failed node is a placeholder element. We should remove it from the list.\n if (node !== this.editor && this.placeholderSelectors) {\n return node.matches(this.placeholderSelectors) === false;\n }\n\n return node;\n }).map((node) => {\n
const describedNode = getEventualNode(node);\n\n // Find the index of the node within the type of node.\n // This is used to select the correct node when the user selects it.\n const nodeIndex = this.editor.dom.select(describedNode.nodeName).indexOf(describedNode);\n const warning = {\n src: null,\n text: null,\n nodeName: describedNode.nodeName,\n nodeIndex,\n };\n\n if (isImageType) {\n warning.src = node.getAttribute('src');\n } else {\n warning.text = getTextValue(node);\n }\n\n return warning;\n });\n\n return warning;\n }\n\n /**\n * Check accessiblity issue only for img type.\n *\n * @return {Node} A complete list of all warnings and problems.\n */\n checkImage() {\n const problemNodes = [];\n this.editor.dom.select('img').forEach((img) => {\n const a
lt = img.getAttribute('alt');\n if (!alt && img.getAttribute('role') !== 'presentation') {\n problemNodes.push(img);\n }\n });\n return problemNodes;\n }\n\n /**\n * Look for any table without a caption.\n *\n * @return {Node} A complete list of all warnings and problems.\n */\n checkTableCaption() {\n const problemNodes = [];\n this.editor.dom.select('table').forEach((table) => {\n const caption = table.querySelector('caption');\n if (!caption?.textContent.trim()) {\n problemNodes.push(table);\n }\n });\n\n return problemNodes;\n }\n\n /**\n * Check accessiblity issue for not img and table only.\n *\n * @return {Node} A complete list of all warnings and problems.\n * @private\n */\n checkOtherElements() {\n const problemNodes = [];\n\n const getRatio = (lum1, lum2) => {\n // Algorithm from \"http://www.w3
.org/TR/WCAG20-GENERAL/G18.html\".\n if (lum1 > lum2) {\n return (lum1 + 0.05) / (lum2 + 0.05);\n } else {\n return (lum2 + 0.05) / (lum1 + 0.05);\n }\n };\n\n this.editor.dom.select('body *')\n .filter((node) => node.hasChildNodes() && node.childNodes[0].nodeValue !== null)\n .forEach((node) => {\n const foreground = this.colorBase.fromArray(\n this.getComputedBackgroundColor(\n node,\n window.getComputedStyle(node, null).getPropertyValue('color')\n ),\n this.colorBase.TYPES.RGBA\n );\n const background = this.colorBase.fromArray(\n this.getComputedBackgroundColor(\n node\n ),\n this.colorBase.TYPES.RGBA\n );\n\n const lum1 = this.getLuminanceFromCssColor(f
oreground);\n const lum2 = this.getLuminanceFromCssColor(background);\n const ratio = getRatio(lum1, lum2);\n\n if (ratio <= 4.5) {\n window.console.log(`\n Contrast ratio is too low: ${ratio}\n Colour 1: ${foreground}\n Colour 2: ${background}\n Luminance 1: ${lum1}\n Luminance 2: ${lum2}\n `);\n\n // We only want the highest node with dodgy contrast reported.\n if (!problemNodes.find((existingProblemNode) => existingProblemNode.contains(node))) {\n problemNodes.push(node);\n }\n }\n });\n return problemNodes;\n }\n\n /**\n * Check accessiblity issue only for table with merged cells.\n *\n * @return {Node} A complete list of all warnings and problems.\n * @private\n */\n ch
eckTableMergedCells() {\n const problemNodes = [];\n this.editor.dom.select('table').forEach((table) => {\n const rowcolspan = table.querySelectorAll('[colspan], [rowspan]');\n if (rowcolspan.length) {\n problemNodes.push(table);\n }\n });\n return problemNodes;\n }\n\n /**\n * Check accessiblity issue only for table with no headers.\n *\n * @return {Node} A complete list of all warnings and problems.\n * @private\n */\n checkTableHeaders() {\n const problemNodes = [];\n\n this.editor.dom.select('table').forEach((table) => {\n if (table.querySelector('tr').querySelector('td')) {\n // The first row has a non-header cell, so all rows must have at least one header.\n const missingHeader = [...table.querySelectorAll('tr')].some((row) => {\n const header = row.querySelector('th');\n if (!header) {\n
return true;\n }\n\n if (!header.textContent.trim()) {\n return true;\n }\n\n return false;\n });\n if (missingHeader) {\n // At least one row is missing the header, or it is empty.\n problemNodes.push(table);\n }\n } else {\n // Every header must have some content.\n if ([...table.querySelectorAll('tr th')].some((header) => !header.textContent.trim())) {\n problemNodes.push(table);\n }\n }\n });\n return problemNodes;\n }\n\n /**\n * Convert a CSS color to a luminance value.\n *\n * @param {String} colortext The Hex value for the colour\n * @return {Number} The luminance value.\n * @private\n */\n getLuminanceFromCssColor(colortext) {\n if (colortext === 'transparent') {\n col
ortext = '#ffffff';\n }\n const color = this.colorBase.toArray(this.colorBase.toRGB(colortext));\n\n // Algorithm from \"http://www.w3.org/TR/WCAG20-GENERAL/G18.html\".\n const part1 = (a) => {\n a = parseInt(a, 10) / 255.0;\n if (a <= 0.03928) {\n a = a / 12.92;\n } else {\n a = Math.pow(((a + 0.055) / 1.055), 2.4);\n }\n return a;\n };\n\n const r1 = part1(color[0]);\n const g1 = part1(color[1]);\n const b1 = part1(color[2]);\n\n return 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1;\n }\n\n /**\n * Get the computed RGB converted to full alpha value, considering the node hierarchy.\n *\n * @param {Node} node\n * @param {String} color The initial colour. If not specified, fetches the backgroundColor from the node.\n * @return {Array} Colour in Array form (RGBA)\n * @private\n */\n getComputedBackgroundColor(node, color) {\n i
f (!node.parentNode) {\n // This is the document node and has no colour.\n // We cannot use window.getComputedStyle on the document.\n // If we got here, then the document has no background colour. Fall back to white.\n return this.colorBase.toArray('rgba(255, 255, 255, 1)');\n }\n color = color ? color : window.getComputedStyle(node, null).getPropertyValue('background-color');\n\n if (color.toLowerCase() === 'rgba(0, 0, 0, 0)' || color.toLowerCase() === 'transparent') {\n color = 'rgba(1, 1, 1, 0)';\n }\n\n // Convert the colour to its constituent parts in RGBA format, then fetch the alpha.\n const colorParts = this.colorBase.toArray(color);\n const alpha = colorParts[3];\n\n if (alpha === 1) {\n // If the alpha of the background is already 1, then the parent background colour does not change anything.\n return colorParts;\n }\n\n // Fetch the computed background
colour of the parent and use it to calculate the RGB of this item.\n const parentColor = this.getComputedBackgroundColor(node.parentNode);\n return [\n // RGB = (alpha * R|G|B) + (1 - alpha * solid parent colour).\n (1 - alpha) * parentColor[0] + alpha * colorParts[0],\n (1 - alpha) * parentColor[1] + alpha * colorParts[1],\n (1 - alpha) * parentColor[2] + alpha * colorParts[2],\n // We always return a colour with full alpha.\n 1\n ];\n }\n}\n"],"names":["constructor","editor","colorBase","ColorBase","modal","placeholderSelectors","placeholders","this","length","join","destroy","Modal","create","large","title","component","body","getDialogueContent","show","getRoot","on","ModalEvents","hidden","addEventListener","event","faultLink","target","closest","preventDefault","nodeName","dataset","selectedNode","includes","dom","select","nodeIndex","toUpperCase","selectAndScroll","hide","keys","stringValues","map","key","Map","
index","langStrings","getAllWarningStrings","warnings","getWarnings","warning","description","type","get","value","nodeData","problemNode","text","Templates","render","node","selection","scrollIntoView","behavior","block","push","createWarnings","checkImage","checkOtherElements","getContent","format","checkTableCaption","checkTableMergedCells","checkTableHeaders","filter","nodes","isImageType","getTextValue","emptyStringValue","innerText","trim","textContent","getEventualNode","childNodes","document","matches","describedNode","indexOf","src","getAttribute","problemNodes","forEach","img","table","caption","querySelector","hasChildNodes","nodeValue","foreground","fromArray","getComputedBackgroundColor","window","getComputedStyle","getPropertyValue","TYPES","RGBA","background","lum1","getLuminanceFromCssColor","lum2","ratio","getRatio","console","log","find","existingProblemNode","contains","querySelectorAll","some","row","header","colortext","color","toArray","toRGB","part1","a","parseInt","Math","pow","parent
Node","toLowerCase","colorParts","alpha","parentColor"],"mappings":"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"}