From 21986200d7cd6f6cbaf1b64f939c6d5071e7a51e Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Mon, 13 Feb 2023 08:48:05 +0000 Subject: [PATCH] MDL-77212 tiny_accessibilitychecker: remove unused lang string fetch. --- .../tiny/plugins/accessibilitychecker/amd/build/checker.min.js | 2 +- .../plugins/accessibilitychecker/amd/build/checker.min.js.map | 2 +- lib/editor/tiny/plugins/accessibilitychecker/amd/src/checker.js | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/editor/tiny/plugins/accessibilitychecker/amd/build/checker.min.js b/lib/editor/tiny/plugins/accessibilitychecker/amd/build/checker.min.js index 835356f341d..ba758239f7f 100644 --- a/lib/editor/tiny/plugins/accessibilitychecker/amd/build/checker.min.js +++ b/lib/editor/tiny/plugins/accessibilitychecker/amd/build/checker.min.js @@ -3,6 +3,6 @@ define("tiny_accessibilitychecker/checker",["exports","core/templates","core/str * @package tiny_accessibilitychecker * @copyright 2022, Stevani Andolo * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),Modal=_interopRequireWildcard(Modal),ModalEvents=_interopRequireWildcard(ModalEvents),_colorbase=_interopRequireDefault(_colorbase);return _exports.default=class{constructor(editor){this.editor=editor,this.colorBase=new _colorbase.default,this.modal=null,this.placeholderSelectors=null;const placeholders=(0,_options.getPlaceholderSelectors)(this.editor);placeholders.length&&(this.placeholderSelectors=placeholders.join(", "))}destroy(){delete this.editor,delete this.colorBase,this.modal.destroy(),delete this.modal}async displayDialogue(){this.modal=await Modal.create({type:Modal.types.DEFAULT,large:!0,title:(0,_str.get_string)("pluginname",_common.component),body:this.getDialogueContent()}),this.modal.getRoot().on(ModalEvents.hidden,(()=>this.destroy())),this.modal.getRoot()[0].addEventListener("click",(event=>{const faultLink=event.target.closest('[data-action="highlightfault"]');if(!faultLink)return;event.preventDefault();const nodeName=faultLink.dataset.nodeName;let selectedNode=null;if(nodeName)if(nodeName.includes(",")||"body"===nodeName)selectedNode=this.editor.dom.select("body")[0];else{var _faultLink$dataset$no;const nodeIndex=null!==(_faultLink$dataset$no=faultLink.dataset.nodeIndex)&&void 0!==_faultLink$dataset$no?_faultLink$dataset$no:0;selectedNode=this.editor.dom.select(nodeName)[nodeIndex]}selectedNode&&"BODY"!==selectedNode.nodeName.toUpperCase()&&this.selectAndScroll(selectedNode),this.modal.hide()})),this.modal.show()}async getAllWarningStrings(){const keys=["emptytext","entiredocument","imagesmissingalt","needsmorecontrast","needsmoreheadings","nowarnings","nowarningsonselected","tablesmissingcaption","tablesmissingheaders","tableswithmergedcells"],stringValues=await(0,_str.get_strings)(keys.map((key=>({key:key,component:_common.component}))));return new Map(keys.map(((key,index)=>[key,stringValues[index]])))}async getDialogueContent(){const langStrings=await this.getAllWarningStrings(),warnings=this.getWarnings().map((warning=>(warning.description&&("langstring"===warning.description.type?warning.description=langStrings.get(warning.description.value):warning.description=warning.description.value),warning.nodeData=warning.nodeData.map((problemNode=>(problemNode.text&&("langstring"===problemNode.text.type?problemNode.text=langStrings.get(problemNode.text.value):problemNode.text=problemNode.text.value),problemNode))),warning)));return _templates.default.render("tiny_accessibilitychecker/warning_content",{warnings:warnings})}selectAndScroll(node){this.editor.selection.select(node).scrollIntoView({behavior:"smooth",block:"nearest"})}getWarnings(){const warnings=[];return warnings.push(this.createWarnings("imagesmissingalt",this.checkImage(),!0)),warnings.push(this.createWarnings("needsmorecontrast",this.checkOtherElements(),!1)),this.editor.getContent({format:"text"}).length>1e3&&this.editor.dom.select("h3,h4,h5").length<1&&warnings.push(this.createWarnings("needsmoreheadings",[this.editor],!1)),warnings.push(this.createWarnings("tablesmissingcaption",this.checkTableCaption(),!1)),warnings.push(this.createWarnings("tableswithmergedcells",this.checkTableMergedCells(),!1)),warnings.push(this.createWarnings("tablesmissingheaders",this.checkTableHeaders(),!1)),warnings.filter((warning=>warning.nodeData.length>0))}createWarnings(description,nodes,isImageType){const getTextValue=node=>{if(node===this.editor)return{type:"langstring",value:"entiredocument"};const emptyStringValue={type:"langstring",value:"emptytext"};if("innerText"in node){const value=node.innerText.trim();return value.length?{type:"raw",value:value}:emptyStringValue}if("textContent"in node){const value=node.textContent.trim();return value.length?{type:"raw",value:value}:emptyStringValue}return{type:"raw",value:node.nodeName}},getEventualNode=node=>{if(node!==this.editor)return node;const childNodes=node.dom.select("body")[0].childNodes;return childNodes.length?document.body:childNodes},warning={description:{type:"langstring",value:description},nodeData:[]};return warning.nodeData=[...nodes].filter((node=>node!==this.editor&&this.placeholderSelectors?!1===node.matches(this.placeholderSelectors):node)).map((node=>{const describedNode=getEventualNode(node),nodeIndex=this.editor.dom.select(describedNode.nodeName).indexOf(describedNode),warning={src:null,text:null,nodeName:describedNode.nodeName,nodeIndex:nodeIndex};return isImageType?warning.src=node.getAttribute("src"):warning.text=getTextValue(node),warning})),warning}checkImage(){const problemNodes=[];return this.editor.dom.select("img").forEach((img=>{img.getAttribute("alt")||"presentation"===img.getAttribute("role")||problemNodes.push(img)})),problemNodes}checkTableCaption(){const problemNodes=[];return this.editor.dom.select("table").forEach((table=>{const caption=table.querySelector("caption");null!=caption&&caption.textContent.trim()||problemNodes.push(table)})),problemNodes}checkOtherElements(){const problemNodes=[];return this.editor.dom.select("body > *").filter((node=>{var _node$textContent;return null===(_node$textContent=node.textContent)||void 0===_node$textContent?void 0:_node$textContent.trim().length})).forEach((node=>{const foreground=this.colorBase.fromArray(this.getComputedBackgroundColor(node,window.getComputedStyle(node,null).getPropertyValue("color")),this.colorBase.TYPES.RGBA),background=this.colorBase.fromArray(this.getComputedBackgroundColor(node),this.colorBase.TYPES.RGBA),lum1=this.getLuminanceFromCssColor(foreground),lum2=this.getLuminanceFromCssColor(background),ratio=((lum1,lum2)=>lum1>lum2?(lum1+.05)/(lum2+.05):(lum2+.05)/(lum1+.05))(lum1,lum2);ratio<=4.5&&(window.console.log("\n Contrast ratio is too low: ".concat(ratio,"\n Colour 1: ").concat(foreground,"\n Colour 2: ").concat(background,"\n Luminance 1: ").concat(lum1,"\n Luminance 2: ").concat(lum2,"\n ")),problemNodes.find((existingProblemNode=>existingProblemNode.contains(node)))||problemNodes.push(node))})),problemNodes}checkTableMergedCells(){const problemNodes=[];return this.editor.dom.select("table").forEach((table=>{table.querySelectorAll("[colspan], [rowspan]").length&&problemNodes.push(table)})),problemNodes}checkTableHeaders(){const problemNodes=[];return this.editor.dom.select("table").forEach((table=>{if(table.querySelector("tr").querySelector("td")){[...table.querySelectorAll("tr")].some((row=>{const header=row.querySelector("th");return!header||!header.textContent.trim()}))&&problemNodes.push(table)}else[...table.querySelectorAll("tr th")].some((header=>!header.textContent.trim()))&&problemNodes.push(table)})),problemNodes}getLuminanceFromCssColor(colortext){"transparent"===colortext&&(colortext="#ffffff");const color=this.colorBase.toArray(this.colorBase.toRGB(colortext)),part1=a=>((a=parseInt(a,10)/255)<=.03928?a/=12.92:a=Math.pow((a+.055)/1.055,2.4),a);return.2126*part1(color[0])+.7152*part1(color[1])+.0722*part1(color[2])}getComputedBackgroundColor(node,color){if(!node.parentNode)return this.colorBase.toArray("rgba(255, 255, 255, 1)");"rgba(0, 0, 0, 0)"!==(color=color||window.getComputedStyle(node,null).getPropertyValue("background-color")).toLowerCase()&&"transparent"!==color.toLowerCase()||(color="rgba(1, 1, 1, 0)");const colorParts=this.colorBase.toArray(color),alpha=colorParts[3];if(1===alpha)return colorParts;const parentColor=this.getComputedBackgroundColor(node.parentNode);return[(1-alpha)*parentColor[0]+alpha*colorParts[0],(1-alpha)*parentColor[1]+alpha*colorParts[1],(1-alpha)*parentColor[2]+alpha*colorParts[2],1]}},_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),Modal=_interopRequireWildcard(Modal),ModalEvents=_interopRequireWildcard(ModalEvents),_colorbase=_interopRequireDefault(_colorbase);return _exports.default=class{constructor(editor){this.editor=editor,this.colorBase=new _colorbase.default,this.modal=null,this.placeholderSelectors=null;const placeholders=(0,_options.getPlaceholderSelectors)(this.editor);placeholders.length&&(this.placeholderSelectors=placeholders.join(", "))}destroy(){delete this.editor,delete this.colorBase,this.modal.destroy(),delete this.modal}async displayDialogue(){this.modal=await Modal.create({type:Modal.types.DEFAULT,large:!0,title:(0,_str.get_string)("pluginname",_common.component),body:this.getDialogueContent()}),this.modal.getRoot().on(ModalEvents.hidden,(()=>this.destroy())),this.modal.getRoot()[0].addEventListener("click",(event=>{const faultLink=event.target.closest('[data-action="highlightfault"]');if(!faultLink)return;event.preventDefault();const nodeName=faultLink.dataset.nodeName;let selectedNode=null;if(nodeName)if(nodeName.includes(",")||"body"===nodeName)selectedNode=this.editor.dom.select("body")[0];else{var _faultLink$dataset$no;const nodeIndex=null!==(_faultLink$dataset$no=faultLink.dataset.nodeIndex)&&void 0!==_faultLink$dataset$no?_faultLink$dataset$no:0;selectedNode=this.editor.dom.select(nodeName)[nodeIndex]}selectedNode&&"BODY"!==selectedNode.nodeName.toUpperCase()&&this.selectAndScroll(selectedNode),this.modal.hide()})),this.modal.show()}async getAllWarningStrings(){const keys=["emptytext","entiredocument","imagesmissingalt","needsmorecontrast","needsmoreheadings","tablesmissingcaption","tablesmissingheaders","tableswithmergedcells"],stringValues=await(0,_str.get_strings)(keys.map((key=>({key:key,component:_common.component}))));return new Map(keys.map(((key,index)=>[key,stringValues[index]])))}async getDialogueContent(){const langStrings=await this.getAllWarningStrings(),warnings=this.getWarnings().map((warning=>(warning.description&&("langstring"===warning.description.type?warning.description=langStrings.get(warning.description.value):warning.description=warning.description.value),warning.nodeData=warning.nodeData.map((problemNode=>(problemNode.text&&("langstring"===problemNode.text.type?problemNode.text=langStrings.get(problemNode.text.value):problemNode.text=problemNode.text.value),problemNode))),warning)));return _templates.default.render("tiny_accessibilitychecker/warning_content",{warnings:warnings})}selectAndScroll(node){this.editor.selection.select(node).scrollIntoView({behavior:"smooth",block:"nearest"})}getWarnings(){const warnings=[];return warnings.push(this.createWarnings("imagesmissingalt",this.checkImage(),!0)),warnings.push(this.createWarnings("needsmorecontrast",this.checkOtherElements(),!1)),this.editor.getContent({format:"text"}).length>1e3&&this.editor.dom.select("h3,h4,h5").length<1&&warnings.push(this.createWarnings("needsmoreheadings",[this.editor],!1)),warnings.push(this.createWarnings("tablesmissingcaption",this.checkTableCaption(),!1)),warnings.push(this.createWarnings("tableswithmergedcells",this.checkTableMergedCells(),!1)),warnings.push(this.createWarnings("tablesmissingheaders",this.checkTableHeaders(),!1)),warnings.filter((warning=>warning.nodeData.length>0))}createWarnings(description,nodes,isImageType){const getTextValue=node=>{if(node===this.editor)return{type:"langstring",value:"entiredocument"};const emptyStringValue={type:"langstring",value:"emptytext"};if("innerText"in node){const value=node.innerText.trim();return value.length?{type:"raw",value:value}:emptyStringValue}if("textContent"in node){const value=node.textContent.trim();return value.length?{type:"raw",value:value}:emptyStringValue}return{type:"raw",value:node.nodeName}},getEventualNode=node=>{if(node!==this.editor)return node;const childNodes=node.dom.select("body")[0].childNodes;return childNodes.length?document.body:childNodes},warning={description:{type:"langstring",value:description},nodeData:[]};return warning.nodeData=[...nodes].filter((node=>node!==this.editor&&this.placeholderSelectors?!1===node.matches(this.placeholderSelectors):node)).map((node=>{const describedNode=getEventualNode(node),nodeIndex=this.editor.dom.select(describedNode.nodeName).indexOf(describedNode),warning={src:null,text:null,nodeName:describedNode.nodeName,nodeIndex:nodeIndex};return isImageType?warning.src=node.getAttribute("src"):warning.text=getTextValue(node),warning})),warning}checkImage(){const problemNodes=[];return this.editor.dom.select("img").forEach((img=>{img.getAttribute("alt")||"presentation"===img.getAttribute("role")||problemNodes.push(img)})),problemNodes}checkTableCaption(){const problemNodes=[];return this.editor.dom.select("table").forEach((table=>{const caption=table.querySelector("caption");null!=caption&&caption.textContent.trim()||problemNodes.push(table)})),problemNodes}checkOtherElements(){const problemNodes=[];return this.editor.dom.select("body > *").filter((node=>{var _node$textContent;return null===(_node$textContent=node.textContent)||void 0===_node$textContent?void 0:_node$textContent.trim().length})).forEach((node=>{const foreground=this.colorBase.fromArray(this.getComputedBackgroundColor(node,window.getComputedStyle(node,null).getPropertyValue("color")),this.colorBase.TYPES.RGBA),background=this.colorBase.fromArray(this.getComputedBackgroundColor(node),this.colorBase.TYPES.RGBA),lum1=this.getLuminanceFromCssColor(foreground),lum2=this.getLuminanceFromCssColor(background),ratio=((lum1,lum2)=>lum1>lum2?(lum1+.05)/(lum2+.05):(lum2+.05)/(lum1+.05))(lum1,lum2);ratio<=4.5&&(window.console.log("\n Contrast ratio is too low: ".concat(ratio,"\n Colour 1: ").concat(foreground,"\n Colour 2: ").concat(background,"\n Luminance 1: ").concat(lum1,"\n Luminance 2: ").concat(lum2,"\n ")),problemNodes.find((existingProblemNode=>existingProblemNode.contains(node)))||problemNodes.push(node))})),problemNodes}checkTableMergedCells(){const problemNodes=[];return this.editor.dom.select("table").forEach((table=>{table.querySelectorAll("[colspan], [rowspan]").length&&problemNodes.push(table)})),problemNodes}checkTableHeaders(){const problemNodes=[];return this.editor.dom.select("table").forEach((table=>{if(table.querySelector("tr").querySelector("td")){[...table.querySelectorAll("tr")].some((row=>{const header=row.querySelector("th");return!header||!header.textContent.trim()}))&&problemNodes.push(table)}else[...table.querySelectorAll("tr th")].some((header=>!header.textContent.trim()))&&problemNodes.push(table)})),problemNodes}getLuminanceFromCssColor(colortext){"transparent"===colortext&&(colortext="#ffffff");const color=this.colorBase.toArray(this.colorBase.toRGB(colortext)),part1=a=>((a=parseInt(a,10)/255)<=.03928?a/=12.92:a=Math.pow((a+.055)/1.055,2.4),a);return.2126*part1(color[0])+.7152*part1(color[1])+.0722*part1(color[2])}getComputedBackgroundColor(node,color){if(!node.parentNode)return this.colorBase.toArray("rgba(255, 255, 255, 1)");"rgba(0, 0, 0, 0)"!==(color=color||window.getComputedStyle(node,null).getPropertyValue("background-color")).toLowerCase()&&"transparent"!==color.toLowerCase()||(color="rgba(1, 1, 1, 0)");const colorParts=this.colorBase.toArray(color),alpha=colorParts[3];if(1===alpha)return colorParts;const parentColor=this.getComputedBackgroundColor(node.parentNode);return[(1-alpha)*parentColor[0]+alpha*colorParts[0],(1-alpha)*parentColor[1]+alpha*colorParts[1],(1-alpha)*parentColor[2]+alpha*colorParts[2],1]}},_exports.default})); //# sourceMappingURL=checker.min.js.map \ No newline at end of file diff --git a/lib/editor/tiny/plugins/accessibilitychecker/amd/build/checker.min.js.map b/lib/editor/tiny/plugins/accessibilitychecker/amd/build/checker.min.js.map index 7c746f9d4e2..8909b432f6b 100644 --- a/lib/editor/tiny/plugins/accessibilitychecker/amd/build/checker.min.js.map +++ b/lib/editor/tiny/plugins/accessibilitychecker/amd/build/checker.min.js.map @@ -1 +1 @@ -{"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 .\n\n/*\n * @package tiny_accessibilitychecker\n * @copyright 2022, Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {component} from './common';\nimport * as Modal from 'core/modal_factory';\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.length) {\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 type: Modal.types.DEFAULT,\n large: true,\n title: getString('pluginname', component),\n body: this.getDialogueContent()\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 this.modal.show();\n }\n\n async getAllWarningStrings() {\n const keys = [\n 'emptytext',\n 'entiredocument',\n 'imagesmissingalt',\n 'needsmorecontrast',\n 'needsmoreheadings',\n 'nowarnings',\n 'nowarningsonselected',\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, index) => ([key, stringValues[index]])));\n }\n\n /**\n * Return the dialogue content.\n *\n * @return {Promise} 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 Description 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 alt = 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 this.editor.dom.select('body > *')\n .filter((node) => node.textContent?.trim().length)\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(foreground);\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 checkTableMergedCells() {\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 colortext = '#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 if (!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","type","types","DEFAULT","large","title","component","body","getDialogueContent","getRoot","on","ModalEvents","hidden","addEventListener","event","faultLink","target","closest","preventDefault","nodeName","dataset","selectedNode","includes","dom","select","nodeIndex","toUpperCase","selectAndScroll","hide","show","keys","stringValues","map","key","Map","index","langStrings","getAllWarningStrings","warnings","getWarnings","warning","description","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","_node$textContent","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","parentNode","toLowerCase","colorParts","alpha","parentColor"],"mappings":"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"} \ No newline at end of file +{"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 .\n\n/*\n * @package tiny_accessibilitychecker\n * @copyright 2022, Stevani Andolo \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {component} from './common';\nimport * as Modal from 'core/modal_factory';\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.length) {\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 type: Modal.types.DEFAULT,\n large: true,\n title: getString('pluginname', component),\n body: this.getDialogueContent()\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 this.modal.show();\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, index) => ([key, stringValues[index]])));\n }\n\n /**\n * Return the dialogue content.\n *\n * @return {Promise} 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 Description 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 alt = 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 this.editor.dom.select('body > *')\n .filter((node) => node.textContent?.trim().length)\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(foreground);\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 checkTableMergedCells() {\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 colortext = '#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 if (!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","type","types","DEFAULT","large","title","component","body","getDialogueContent","getRoot","on","ModalEvents","hidden","addEventListener","event","faultLink","target","closest","preventDefault","nodeName","dataset","selectedNode","includes","dom","select","nodeIndex","toUpperCase","selectAndScroll","hide","show","keys","stringValues","map","key","Map","index","langStrings","getAllWarningStrings","warnings","getWarnings","warning","description","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","_node$textContent","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","parentNode","toLowerCase","colorParts","alpha","parentColor"],"mappings":"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"} \ No newline at end of file diff --git a/lib/editor/tiny/plugins/accessibilitychecker/amd/src/checker.js b/lib/editor/tiny/plugins/accessibilitychecker/amd/src/checker.js index 0b3078d46c1..126c814c3e2 100644 --- a/lib/editor/tiny/plugins/accessibilitychecker/amd/src/checker.js +++ b/lib/editor/tiny/plugins/accessibilitychecker/amd/src/checker.js @@ -111,8 +111,6 @@ export default class { 'imagesmissingalt', 'needsmorecontrast', 'needsmoreheadings', - 'nowarnings', - 'nowarningsonselected', 'tablesmissingcaption', 'tablesmissingheaders', 'tableswithmergedcells',