Skip to content

Commit

Permalink
Merge pull request #982 from ucfopen/equal-access
Browse files Browse the repository at this point in the history
Adding Equal Access Scanner to Dev
  • Loading branch information
dmols authored Jan 31, 2025
2 parents f66fff2 + eddadac commit 3564afa
Show file tree
Hide file tree
Showing 21 changed files with 1,516 additions and 30 deletions.
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ ADMIN_LTI_NAME="UDOIT 3 Admin"
USE_DEVELOPMENT_AUTH="no"
VERSION_NUMBER="3.5.0"

# Define which accessibility checker to use
# Available options: "phpally", "equalaccess_local", "equalaccess_lambda"
ACCESSIBILITY_CHECKER="equalaccess_lambda"

# NOTE: When using a lambda function with equal access,
# you need to define the following in a separate .env.local:
# EQUALACCESS_AWS_ACCESS_KEY_ID=<access_key_id>
# EQUALACCESS_AWS_SECRET_ACCESS_KEY=<secret_access_key>
# EQUALACCESS_AWS_REGION=<region (e.g. us-east-1)>
# EQUALACCESS_AWS_SERVICE=<service (e.g. execute-api)>
# EQUALACCESS_AWS_HOST=abcdefghi.execute-api.us-east-1.amazonaws.com
# EQUALACCESS_CANONICAL_URI=endpoint/generate-accessibility-report


###> symfony/messenger ###
MESSENGER_TRANSPORT_DSN=doctrine://default
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- main
- 'stable/*'
- 'equal-access'

env:
REGISTRY: ghcr.io
Expand Down
5 changes: 4 additions & 1 deletion assets/js/Components/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,8 @@ export const issueRuleIds = [
"VideoEmbedCheck",
"VideoProvidesCaptions",
"VideosEmbeddedOrLinkedNeedCaptions",
"VideosHaveAutoGeneratedCaptions"
"VideosHaveAutoGeneratedCaptions",

"img_alt_misuse",
"text_contrast_sufficient",
]
11 changes: 10 additions & 1 deletion assets/js/Components/Forms/ContrastForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,12 @@ export default class ContrastForm extends React.Component {

if (element.style.backgroundColor) {
return Contrast.standardizeColor(element.style.backgroundColor)
}
}
else if (metadata.messageArgs) {
// TODO: check if 4th argument exists
// (Equal Access) text_contrast_sufficient: The 4th index in messageArgs is the background color
return metadata.messageArgs[4]
}
else {
return (metadata.backgroundColor) ? Contrast.standardizeColor(metadata.backgroundColor) : this.props.settings.backgroundColor
}
Expand All @@ -332,6 +337,10 @@ export default class ContrastForm extends React.Component {
if (element.style.color) {
return Contrast.standardizeColor(element.style.color)
}
else if (metadata.messageArgs) {
// (Equal Access) text_contrast_sufficient: The 3rd index in messageArgs is the foreground color
return metadata.messageArgs[3]
}
else {
return (metadata.color) ? Contrast.standardizeColor(metadata.color) : this.props.settings.textColor
}
Expand Down
151 changes: 151 additions & 0 deletions assets/js/Components/Forms/LabelForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React, { useEffect, useState } from 'react'
import { View } from '@instructure/ui-view'
import { TextInput } from '@instructure/ui-text-input'
import { Button } from '@instructure/ui-buttons'
import { IconCheckMarkLine } from '@instructure/ui-icons'
import { Checkbox } from '@instructure/ui-checkbox'
import { Spinner } from '@instructure/ui-spinner'
import * as Html from '../../Services/Html'

export default function LabelForm(props) {

let html = props.activeIssue.newHtml ? props.activeIssue.newHtml : props.activeIssue.sourceHtml

if (props.activeIssue.status === '1') {
html = props.activeIssue.newHtml
}

let element = Html.toElement(html)

const [textInputValue, setTextInputValue] = useState(element ? Html.getAttribute(element, "aria-label") : "")
// const [deleteLabel, setDeleteLabel] = useState(!element && (props.activeIssue.status === "1"))
const [textInputErrors, setTextInputErrors] = useState([])

let formErrors = []

useEffect(() => {
let html = props.activeIssue.newHtml ? props.activeIssue.newHtml : props.activeIssue.sourceHtml
if (props.activeIssue.status === 1) {
html = props.activeIssue.newHtml
}

let element = Html.toElement(html)
setTextInputValue(element ? Html.getAttribute(element, "aria-label") : "")
// setDeleteLabel(!element && props.activeIssue.status === 1)

formErrors = []

}, [props.activeIssue])

useEffect(() => {
handleHtmlUpdate()
}, [textInputValue])

const handleHtmlUpdate = () => {
let updatedElement = Html.toElement(html)

updatedElement = Html.setAttribute(updatedElement, "aria-label", textInputValue)

// if (deleteLabel) {
// updatedElement = Html.removeAttribute(updatedElement, "aria-label")
// }
// else {
// updatedElement = Html.setAttribute(updatedElement, "aria-label", textInputValue)
// }

let issue = props.activeIssue
issue.newHtml = Html.toString(updatedElement)
props.handleActiveIssue(issue)

}

const handleButton = () => {
formErrors = []

// if (!deleteLabel) {
// checkTextNotEmpty()
// }

checkTextNotEmpty()
checkLabelIsUnique()

if (formErrors.length > 0) {
setTextInputErrors(formErrors)
}
else {
props.handleIssueSave(props.activeIssue)
}
}

const handleInput = (event) => {
setTextInputValue(event.target.value)
// handleHtmlUpdate()
}

// const handleCheckbox = () => {
// setDeleteLabel(!deleteLabel)
// // handleHtmlUpdate()
// }

const checkTextNotEmpty = () => {
const text = textInputValue.trim().toLowerCase()
if (text === '') {
formErrors.push({ text: "Empty label text.", type: "error" })
}
}

const checkLabelIsUnique = () => {
// in the case of aria_*_label_unique, messageArgs (from metadata) should have the offending label (at the first index)
// i guess we could get it from the aria-label itself as well...
const issue = props.activeIssue
const metadata = issue.metadata ? JSON.parse(issue.metadata) : {}
const labelFromMessageArgs = metadata.messageArgs ? metadata.messageArgs[0] : null
const text = textInputValue.trim().toLowerCase()

if (labelFromMessageArgs) {
if (text == labelFromMessageArgs) {
formErrors.push({ text: "Cannot reuse label text.", type: "error" })
}
}

}

const pending = props.activeIssue && props.activeIssue.pending == "1"
const buttonLabel = pending ? "form.processing" : "form.submit"

return (
<View as='div' padding='x-small' >
<View>
<TextInput
renderLabel='New Label Text'
display='inline-block'
width='100%'
onChange={handleInput}
value={textInputValue}
id="textInputValue"
// disabled={deleteLabel}
messages={textInputErrors}
/>
</View>
{/* <View>
<View as='span' display='inline-block'>
<Checkbox
label='form.label.remove_label'
checked={deleteLabel}
onChange={handleCheckbox}
/>
</View>
</View> */}
<View as='div' margin='small 0'>
<Button
color='primary'
onClick={handleButton}
interaction={(!pending && props.activeIssue.status !== 2) ? 'enabled' : 'disabled'}
>
{('1' == pending) && <Spinner size="x-small" renderTitle={props.t(buttonLabel)} />}
{props.t(buttonLabel)}
</Button>
</View>
</View>
);
}
166 changes: 166 additions & 0 deletions assets/js/Components/Forms/QuoteForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import React, { useEffect, useState } from 'react'
import { View } from '@instructure/ui-view'
import { TextInput } from '@instructure/ui-text-input'
import { Button } from '@instructure/ui-buttons'
import { IconCheckMarkLine } from '@instructure/ui-icons'
import { Checkbox } from '@instructure/ui-checkbox'
import { Spinner } from '@instructure/ui-spinner'
import * as Html from '../../Services/Html'
import { SimpleSelect } from '@instructure/ui-simple-select'

// TODO: not finished

export default function QuoteForm(props) {

let html = props.activeIssue.newHtml ? props.activeIssue.newHtml : props.activeIssue.sourceHtml

if (props.activeIssue.status === '1') {
html = props.activeIssue.newHtml
}

let element = Html.toElement(html)

const [textInputValue, setTextInputValue] = useState(element ? Html.getAttribute(element, "aria-label") : "")
const [deleteQuotes, setRemoveQuotes] = useState(!element && (props.activeIssue.status === "1"))
const [textInputErrors, setTextInputErrors] = useState([])

let formErrors = []

useEffect(() => {
let html = props.activeIssue.newHtml ? props.activeIssue.newHtml : props.activeIssue.sourceHtml
if (props.activeIssue.status === 1) {
html = props.activeIssue.newHtml
}

let element = Html.toElement(html)
setTextInputValue(element ? Html.getAttribute(element, "aria-label") : "")
// setRemoveQuotes(!element && props.activeIssue.status === 1)

formErrors = []

}, [props.activeIssue])

useEffect(() => {
handleHtmlUpdate()
}, [textInputValue])

const handleHtmlUpdate = () => {
let updatedElement = Html.toElement(html)

updatedElement = Html.setAttribute(updatedElement, "aria-label", textInputValue)

// if (deleteQuotes) {
// updatedElement = Html.removeAttribute(updatedElement, "aria-label")
// }
// else {
// updatedElement = Html.setAttribute(updatedElement, "aria-label", textInputValue)
// }

let issue = props.activeIssue
issue.newHtml = Html.toString(updatedElement)
props.handleActiveIssue(issue)

}

const handleButton = () => {
formErrors = []

// if (!deleteQuotes) {
// checkTextNotEmpty()
// }

checkTextNotEmpty()
checkLabelIsUnique()

if (formErrors.length > 0) {
setTextInputErrors(formErrors)
}
else {
props.handleIssueSave(props.activeIssue)
}
}

const handleInput = (event) => {
setTextInputValue(event.target.value)
// handleHtmlUpdate()
}

const handleCheckbox = () => {
setRemoveQuotes(!deleteQuotes)
// handleHtmlUpdate()
}

const checkTextNotEmpty = () => {
const text = textInputValue.trim().toLowerCase()
if (text === '') {
formErrors.push({ text: "Empty label text.", type: "error" })
}
}

const checkLabelIsUnique = () => {
// in the case of aria_*_label_unique, messageArgs (from metadata) should have the offending label (at the first index)
// i guess we could get it from the aria-label itself as well...
const issue = props.activeIssue
const metadata = issue.metadata ? JSON.parse(issue.metadata) : {}
const labelFromMessageArgs = metadata.messageArgs ? metadata.messageArgs[0] : null
const text = textInputValue.trim().toLowerCase()

if (labelFromMessageArgs) {
if (text == labelFromMessageArgs) {
formErrors.push({ text: "Cannot reuse label text.", type: "error" })
}
}

}

const pending = props.activeIssue && props.activeIssue.pending == "1"
const buttonLabel = pending ? "form.processing" : "form.submit"

// TODO: use props.t (from en/es.json) to display text for renderLabel, etc
return (
<View as="div" padding="x-small">
<View as="div" margin="small 0">
<SimpleSelect
renderLabel="Select quotation style"
width="100%"
>
<SimpleSelect.Option
key="opt-empty"
id="opt-empty"
value=""
>
-- Choose --
</SimpleSelect.Option>

<SimpleSelect.Group renderLabel="Regular quotation">
<SimpleSelect.Option
key="1"
id="opt-1"
>

</SimpleSelect.Option>
</SimpleSelect.Group>
</SimpleSelect>
</View>
<View>
<View as='span' display='inline-block'>
<Checkbox
label='Remove quotes'
checked={deleteQuotes}
onChange={handleCheckbox}
/>
</View>
</View>
<View as='div' margin='small 0'>
<Button
color='primary'
onClick={handleButton}
interaction={(!pending && props.activeIssue.status !== 2) ? 'enabled' : 'disabled'}
>
{('1' == pending) && <Spinner size="x-small" renderTitle={props.t(buttonLabel)} />}
{props.t(buttonLabel)}
</Button>
</View>
</View>
);
}
Loading

0 comments on commit 3564afa

Please sign in to comment.