211 lines
6.2 KiB
JavaScript
211 lines
6.2 KiB
JavaScript
/*
|
|
* Copyright (C) 2016 - 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 I18n from 'i18n!shared.flash_notices'
|
|
import $ from 'jquery'
|
|
import htmlEscape from 'str/htmlEscape'
|
|
import 'jquery.cookie'
|
|
|
|
function updateAriaLive({polite} = {polite: false}) {
|
|
if (this.screenreaderHolderReady()) {
|
|
const value = polite ? 'polite' : 'assertive'
|
|
$(this.screenreader_holder).attr('aria-live', value)
|
|
// instui FocusRegionManager throws aria-hidden on everything outside a Dialog when opened
|
|
// removing it here sees that it's done whenever screenreader alerts are displayed
|
|
$(this.screenreader_holder).removeAttr('aria-hidden')
|
|
}
|
|
}
|
|
|
|
class RailsFlashNotificationsHelper {
|
|
constructor() {
|
|
this.holder = null
|
|
this.screenreader_holder = null
|
|
}
|
|
|
|
initHolder() {
|
|
const $current_holders = $('#flash_message_holder')
|
|
|
|
if ($current_holders.length === 0) {
|
|
this.holder = null
|
|
} else {
|
|
this.holder = $current_holders[0]
|
|
|
|
$(this.holder).on('click', '.close_link', event => {
|
|
event.preventDefault()
|
|
})
|
|
|
|
$(this.holder).on('click', 'li', event => {
|
|
if ($(event.currentTarget).hasClass('no_close')) {
|
|
return
|
|
}
|
|
|
|
if ($(event.currentTarget).hasClass('unsupported_browser')) {
|
|
$.cookie('unsupported_browser_dismissed', true, {path: '/'})
|
|
}
|
|
|
|
$(event.currentTarget)
|
|
.stop(true, true)
|
|
.remove()
|
|
})
|
|
}
|
|
}
|
|
|
|
holderReady() {
|
|
return this.holder != null
|
|
}
|
|
|
|
createNode(type, content, timeout, cssOptions = {}, classes = '') {
|
|
if (this.holderReady()) {
|
|
const node = this.generateNodeHTML(type, content)
|
|
|
|
$(node)
|
|
.addClass(classes)
|
|
.appendTo($(this.holder))
|
|
.css({zIndex: 2, ...cssOptions})
|
|
.show('fast')
|
|
.delay(ENV.flashAlertTimeout || timeout || 7000)
|
|
.fadeOut('slow', function() {
|
|
$(this).remove()
|
|
})
|
|
}
|
|
}
|
|
|
|
generateNodeHTML(type, content) {
|
|
const icon = this.getIconType(type)
|
|
|
|
// See generateScreenreaderNodeHtml for SR features
|
|
return `
|
|
<li class="ic-flash-${htmlEscape(type)}" aria-hidden="true">
|
|
<div class="ic-flash__icon">
|
|
<i class="icon-${htmlEscape(icon)}"></i>
|
|
</div>
|
|
${this.escapeContent(content)}
|
|
<button type="button" class="Button Button--icon-action close_link" aria-label="${htmlEscape(
|
|
I18n.t('Close')
|
|
)}">
|
|
<i class="icon-x"></i>
|
|
</button>
|
|
</li>
|
|
`
|
|
}
|
|
|
|
getIconType(type) {
|
|
if (type === 'success') {
|
|
return 'check'
|
|
} else if (type === 'warning' || type === 'error') {
|
|
return 'warning'
|
|
} else {
|
|
return 'info'
|
|
}
|
|
}
|
|
|
|
initScreenreaderHolder() {
|
|
const $current_screenreader_holders = $('#flash_screenreader_holder')
|
|
|
|
if ($current_screenreader_holders.length === 0) {
|
|
this.screenreader_holder = null
|
|
} else {
|
|
this.screenreader_holder = $current_screenreader_holders[0]
|
|
this.setScreenreaderAttributes()
|
|
}
|
|
}
|
|
|
|
screenreaderHolderReady() {
|
|
return this.screenreader_holder != null
|
|
}
|
|
|
|
createScreenreaderNode(content, closable = true) {
|
|
if (this.screenreaderHolderReady()) {
|
|
updateAriaLive.call(this, {polite: false})
|
|
const node = $(this.generateScreenreaderNodeHTML(content, closable))
|
|
node.appendTo($(this.screenreader_holder))
|
|
|
|
window.setTimeout(() => {
|
|
// Accessibility attributes must be removed for the deletion of the node
|
|
// and then reapplied because JAWS/IE will not respect the
|
|
// "aria-relevant" attribute and read when the node is deleted if
|
|
// the attributes are in place
|
|
this.resetScreenreaderAttributes()
|
|
node.remove()
|
|
this.setScreenreaderAttributes()
|
|
}, 10000)
|
|
}
|
|
}
|
|
|
|
setScreenreaderAttributes() {
|
|
if (this.screenreaderHolderReady()) {
|
|
// These attributes are added for accessibility. However, adding them
|
|
// to the DOM at load causes some screenreaders to read "alert" when
|
|
// the page is loaded. That is why these attributes are added here.
|
|
$(this.screenreader_holder).attr('role', 'alert')
|
|
$(this.screenreader_holder).attr('aria-live', 'assertive')
|
|
$(this.screenreader_holder).attr('aria-relevant', 'additions')
|
|
$(this.screenreader_holder).attr('class', 'screenreader-only')
|
|
$(this.screenreader_holder).attr('aria-atomic', 'false')
|
|
}
|
|
}
|
|
|
|
resetScreenreaderAttributes() {
|
|
if (this.screenreaderHolderReady()) {
|
|
$(this.screenreader_holder).removeAttr('role')
|
|
$(this.screenreader_holder).removeAttr('aria-live')
|
|
$(this.screenreader_holder).removeAttr('aria-relevant')
|
|
$(this.screenreader_holder).removeAttr('class')
|
|
$(this.screenreader_holder).removeAttr('aria-atomic')
|
|
}
|
|
}
|
|
|
|
createScreenreaderNodeExclusive(content, polite = false) {
|
|
if (this.screenreaderHolderReady()) {
|
|
updateAriaLive.call(this, {polite})
|
|
this.screenreader_holder.innerHTML = ''
|
|
const node = $(this.generateScreenreaderNodeHTML(content, false))
|
|
node.appendTo($(this.screenreader_holder))
|
|
}
|
|
}
|
|
|
|
generateScreenreaderNodeHTML(content, closable) {
|
|
let closeContent
|
|
if (closable) {
|
|
closeContent = I18n.t('Close')
|
|
} else {
|
|
closeContent = ''
|
|
}
|
|
|
|
return `
|
|
<span>
|
|
${this.escapeContent(content)}
|
|
${htmlEscape(closeContent)}
|
|
</span>
|
|
`
|
|
}
|
|
|
|
/*
|
|
xsslint safeString.method escapeContent
|
|
*/
|
|
escapeContent(content) {
|
|
if (content.hasOwnProperty('html')) {
|
|
return content.html
|
|
} else {
|
|
return htmlEscape(content)
|
|
}
|
|
}
|
|
}
|
|
|
|
export default RailsFlashNotificationsHelper
|