Run prettier on app/coffeescripts/ from object to util
This was achieved by turning the prettier rule on in .eslintrc.js then running: ./node_modules/eslint app/cofeescripts/<folder_name>/**/*.js on each folder from the start directory to the end directory listed above. Test Plan: - Automated tests pass refs COREFE-347 flag = none Change-Id: I60a6b87e1df6ef027c22784b78abfa084ac0706b Reviewed-on: https://gerrit.instructure.com/212258 Tested-by: Jenkins Reviewed-by: Ryan Shaw <ryan@instructure.com> QA-Review: Ryan Shaw <ryan@instructure.com> Product-Review: Ryan Shaw <ryan@instructure.com>
This commit is contained in:
parent
b3c801819b
commit
5643aa7693
|
@ -16,7 +16,7 @@
|
|||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// turns {foo: {bar: 1}} into {'foo[bar]': 1}
|
||||
export default function flatten (obj, options = {}, result = {}, prefix) {
|
||||
export default function flatten(obj, options = {}, result = {}, prefix) {
|
||||
for (let key in obj) {
|
||||
const value = obj[key]
|
||||
key = prefix ? `${prefix}[${key}]` : key
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
import _ from 'underscore'
|
||||
|
||||
// turns {'foo[bar]': 1} into {foo: {bar: 1}}
|
||||
export default function unflatten (obj) {
|
||||
export default function unflatten(obj) {
|
||||
return _(obj).reduce((newObj, val, key) => {
|
||||
let keys = key.split('][')
|
||||
let lastKey = keys.length - 1
|
||||
|
@ -31,7 +31,10 @@ export default function unflatten (obj) {
|
|||
|
||||
// Split first keys part into two parts on the [ and add them back onto
|
||||
// the beginning of the keys array.
|
||||
keys = keys.shift().split('[').concat(keys)
|
||||
keys = keys
|
||||
.shift()
|
||||
.split('[')
|
||||
.concat(keys)
|
||||
lastKey = keys.length - 1
|
||||
} else {
|
||||
// Basic 'foo' style key.
|
||||
|
@ -53,7 +56,8 @@ export default function unflatten (obj) {
|
|||
while (i <= lastKey) {
|
||||
key = keys[i] === '' ? cur.length : keys[i]
|
||||
|
||||
cur = cur[key] = i < lastKey ? cur[key] || (keys[i + 1] && isNaN(keys[i + 1]) ? {} : []) : val
|
||||
cur = cur[key] =
|
||||
i < lastKey ? cur[key] || (keys[i + 1] && isNaN(keys[i + 1]) ? {} : []) : val
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -29,26 +29,52 @@ import toUnderscore from '../str/underscore'
|
|||
// declare all used i18n keys here to cluttering up the logic
|
||||
const keys = {
|
||||
limited: {
|
||||
get TeacherEnrollment(){ return I18n.t('enrolled_as_limited_teacher', 'enrolled as a teacher with section-only access')},
|
||||
get TaEnrollment(){ return I18n.t('enrolled_as_limited_ta', 'enrolled as a TA with section-only access')},
|
||||
get ObserverEnrollment(){ return I18n.t('enrolled_as_limited_observer', 'enrolled as a observer with section-only access')},
|
||||
get DesignerEnrollment(){ return I18n.t('enrolled_as_limited_designer', 'enrolled as a designer with section-only access')},
|
||||
get StudentEnrollment(){ return I18n.t('enrolled_as_limited_student', 'enrolled as a student with section-only access')},
|
||||
get TeacherEnrollment() {
|
||||
return I18n.t('enrolled_as_limited_teacher', 'enrolled as a teacher with section-only access')
|
||||
},
|
||||
get TaEnrollment() {
|
||||
return I18n.t('enrolled_as_limited_ta', 'enrolled as a TA with section-only access')
|
||||
},
|
||||
get ObserverEnrollment() {
|
||||
return I18n.t(
|
||||
'enrolled_as_limited_observer',
|
||||
'enrolled as a observer with section-only access'
|
||||
)
|
||||
},
|
||||
get DesignerEnrollment() {
|
||||
return I18n.t(
|
||||
'enrolled_as_limited_designer',
|
||||
'enrolled as a designer with section-only access'
|
||||
)
|
||||
},
|
||||
get StudentEnrollment() {
|
||||
return I18n.t('enrolled_as_limited_student', 'enrolled as a student with section-only access')
|
||||
}
|
||||
},
|
||||
standard: {
|
||||
get TeacherEnrollment(){ return I18n.t('enrolled_as_teacher', 'enrolled as a teacher')},
|
||||
get TaEnrollment(){ return I18n.t('enrolled_as_ta', 'enrolled as a TA')},
|
||||
get ObserverEnrollment(){ return I18n.t('enrolled_as_observer', 'enrolled as a observer')},
|
||||
get DesignerEnrollment(){ return I18n.t('enrolled_as_designer', 'enrolled as a designer')},
|
||||
get StudentEnrollment(){ return I18n.t('enrolled_as_student', 'enrolled as a student')},
|
||||
},
|
||||
get TeacherEnrollment() {
|
||||
return I18n.t('enrolled_as_teacher', 'enrolled as a teacher')
|
||||
},
|
||||
get TaEnrollment() {
|
||||
return I18n.t('enrolled_as_ta', 'enrolled as a TA')
|
||||
},
|
||||
get ObserverEnrollment() {
|
||||
return I18n.t('enrolled_as_observer', 'enrolled as a observer')
|
||||
},
|
||||
get DesignerEnrollment() {
|
||||
return I18n.t('enrolled_as_designer', 'enrolled as a designer')
|
||||
},
|
||||
get StudentEnrollment() {
|
||||
return I18n.t('enrolled_as_student', 'enrolled as a student')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #
|
||||
// begin returned function here
|
||||
// @param {array} array of enrollments returned from /courses/:course_id/enrollments
|
||||
export default data =>
|
||||
_.map(data, (enrollment) => {
|
||||
_.map(data, enrollment => {
|
||||
const scope = enrollment.limit_privileges_to_course_section ? 'limited' : 'standard'
|
||||
|
||||
// add extra fields to enrollments
|
||||
|
|
|
@ -24,22 +24,30 @@ import '../jquery.rails_flash_notifications'
|
|||
$(() => {
|
||||
let resending = false
|
||||
|
||||
$('.re_send_confirmation_link').click(preventDefault(function () {
|
||||
const $this = $(this)
|
||||
const text = $this.text()
|
||||
$('.re_send_confirmation_link').click(
|
||||
preventDefault(function() {
|
||||
const $this = $(this)
|
||||
const text = $this.text()
|
||||
|
||||
if (resending) return
|
||||
resending = true
|
||||
$this.text(I18n.t('resending', 'resending...'))
|
||||
if (resending) return
|
||||
resending = true
|
||||
$this.text(I18n.t('resending', 'resending...'))
|
||||
|
||||
$.ajaxJSON($this.attr('href'), 'POST', {}, (data) => {
|
||||
resending = false
|
||||
$this.text(text)
|
||||
$.flashMessage(I18n.t('done_resending', 'Done! Message delivery may take a few minutes.'))
|
||||
}, (data) => {
|
||||
resending = false
|
||||
$this.text(text)
|
||||
$.flashError(I18n.t('failed_resending', 'Request failed. Try again.'))
|
||||
$.ajaxJSON(
|
||||
$this.attr('href'),
|
||||
'POST',
|
||||
{},
|
||||
data => {
|
||||
resending = false
|
||||
$this.text(text)
|
||||
$.flashMessage(I18n.t('done_resending', 'Done! Message delivery may take a few minutes.'))
|
||||
},
|
||||
data => {
|
||||
resending = false
|
||||
$this.text(text)
|
||||
$.flashError(I18n.t('failed_resending', 'Request failed. Try again.'))
|
||||
}
|
||||
)
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import eventManager from '../quizzes/log_auditing'
|
||||
import eventManager from './log_auditing'
|
||||
|
||||
export default function(unregister) {
|
||||
if (unregister) {
|
||||
|
|
|
@ -116,7 +116,7 @@ export default {
|
|||
'aria-selected': this.props.isSelected,
|
||||
draggable: !this.state.editing,
|
||||
ref: 'FolderChild',
|
||||
onDragStart: (event) => {
|
||||
onDragStart: event => {
|
||||
if (!this.props.isSelected) {
|
||||
this.props.toggleSelected()
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import PropTypes from 'prop-types'
|
|||
import BBTreeBrowserView from '../modules/BBTreeBrowserView'
|
||||
import RootFoldersFinder from '../../views/RootFoldersFinder'
|
||||
import customPropTypes from '../modules/customPropTypes'
|
||||
import filesEnv from '../../react_files/modules/filesEnv'
|
||||
import filesEnv from '../modules/filesEnv'
|
||||
import page from 'page'
|
||||
import '../../jquery.rails_flash_notifications'
|
||||
|
||||
|
|
|
@ -120,22 +120,22 @@ export default {
|
|||
},
|
||||
|
||||
/*
|
||||
* Returns true if all the models passed in have usage rights
|
||||
*/
|
||||
* Returns true if all the models passed in have usage rights
|
||||
*/
|
||||
usageRightsOnAll() {
|
||||
return this.props.models.every(model => model.get('usage_rights'))
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns true if all the models passed in are folders.
|
||||
*/
|
||||
* Returns true if all the models passed in are folders.
|
||||
*/
|
||||
allFolders() {
|
||||
return this.props.models.every(model => model instanceof Folder)
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns true if all the models passed in are folders.
|
||||
*/
|
||||
* Returns true if all the models passed in are folders.
|
||||
*/
|
||||
anyFolders() {
|
||||
return this.props.models.filter(model => model instanceof Folder).length
|
||||
},
|
||||
|
|
|
@ -54,8 +54,8 @@ export default {
|
|||
const errors = _.isArray(responseText.errors)
|
||||
? this.translateErrors(responseText.errors)
|
||||
: responseText.errors && responseText.errors.base
|
||||
? [{message: `${responseText.errors.base}, ${responseText.status}`}]
|
||||
: [{message}]
|
||||
? [{message: `${responseText.errors.base}, ${responseText.status}`}]
|
||||
: [{message}]
|
||||
|
||||
this.setState({errors})
|
||||
$.screenReaderFlashMessageExclusive(_.map(errors, error => error.message).join(' '))
|
||||
|
@ -68,14 +68,12 @@ export default {
|
|||
} else {
|
||||
return error
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
updateResults(props) {
|
||||
const oldUrl = this.state.collection.url
|
||||
this.state.collection.url = `${window.location.origin}/api/v1/${this.props.contextType}/${
|
||||
this.props.contextId
|
||||
}/files`
|
||||
this.state.collection.url = `${window.location.origin}/api/v1/${this.props.contextType}/${this.props.contextId}/files`
|
||||
updateAPIQuerySortParams(this.state.collection, this.props.query)
|
||||
|
||||
if (this.state.collection.url === oldUrl && this.state.collection.models.length > 0) {
|
||||
|
|
|
@ -110,7 +110,7 @@ export default {
|
|||
}
|
||||
}
|
||||
)
|
||||
.done(this.clearSelectedItems);
|
||||
.done(this.clearSelectedItems)
|
||||
}
|
||||
}
|
||||
// @clearSelectedItems()
|
||||
|
|
|
@ -52,7 +52,7 @@ export default class BaseUploader {
|
|||
onPreflightComplete = data => {
|
||||
this.uploadData = data
|
||||
return this._actualUpload()
|
||||
};
|
||||
}
|
||||
|
||||
// kickoff / preflight upload process
|
||||
upload() {
|
||||
|
@ -79,14 +79,14 @@ export default class BaseUploader {
|
|||
.catch(this.deferred.reject)
|
||||
}
|
||||
|
||||
onUploadPosted = event => {};
|
||||
onUploadPosted = event => {}
|
||||
|
||||
// should be implemented in extensions
|
||||
|
||||
trackProgress = e => {
|
||||
this.progress = e.loaded / e.total
|
||||
return this.onProgress(this.progress, this.file)
|
||||
};
|
||||
}
|
||||
|
||||
getProgress() {
|
||||
return this.progress
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import _ from 'underscore'
|
||||
import UploadQueue from '../modules/UploadQueue'
|
||||
import UploadQueue from './UploadQueue'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
/*
|
||||
|
|
|
@ -24,7 +24,7 @@ export default class FileUploader extends BaseUploader {
|
|||
onUploadPosted = fileJson => {
|
||||
const file = this.addFileToCollection(fileJson)
|
||||
return this.deferred.resolve(file)
|
||||
};
|
||||
}
|
||||
|
||||
addFileToCollection = attrs => {
|
||||
const uploadedFile = new BBFile(attrs, 'no/url/needed/') // we've already done the upload, no preflight needed
|
||||
|
@ -39,5 +39,5 @@ export default class FileUploader extends BaseUploader {
|
|||
}
|
||||
}
|
||||
return uploadedFile
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,11 +54,11 @@ export default class ZipUploader extends BaseUploader {
|
|||
this.uploadData = data.pre_attachment
|
||||
this.contentMigrationId = data.id
|
||||
return this._actualUpload()
|
||||
};
|
||||
}
|
||||
|
||||
onUploadPosted = () => {
|
||||
return this.getContentMigration()
|
||||
};
|
||||
}
|
||||
|
||||
// get the content migration when ready and use progress api to pull migration progress
|
||||
getContentMigration = () => {
|
||||
|
@ -71,7 +71,7 @@ export default class ZipUploader extends BaseUploader {
|
|||
return this.pullMigrationProgress(results.progress_url)
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
pullMigrationProgress = url => {
|
||||
return $.getJSON(url).then(results => {
|
||||
|
@ -86,7 +86,7 @@ export default class ZipUploader extends BaseUploader {
|
|||
return this.onMigrationComplete()
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
onMigrationComplete() {
|
||||
// reload to get new files to appear
|
||||
|
@ -98,7 +98,7 @@ export default class ZipUploader extends BaseUploader {
|
|||
trackProgress = e => {
|
||||
this.progress = e.loaded / e.total
|
||||
return this.onProgress(this.progress, this.file)
|
||||
};
|
||||
}
|
||||
|
||||
// migration progress is [0..100]
|
||||
trackMigrationProgress(value) {
|
||||
|
|
|
@ -90,5 +90,5 @@ export default function deleteStuff(filesAndFolders, args) {
|
|||
if (args && args.returnFocusTo) {
|
||||
$(args.returnFocusTo).focus()
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
|
|
@ -98,5 +98,5 @@ export default function downloadStuffAsAZip(filesAndFolders, {contextType, conte
|
|||
.always(() => {
|
||||
$(window).off('beforeunload', promptBeforeLeaving)
|
||||
$progressIndicator.remove()
|
||||
});
|
||||
})
|
||||
}
|
||||
|
|
|
@ -22,11 +22,11 @@ import File from '../../models/File'
|
|||
import ModuleFile from '../../models/ModuleFile'
|
||||
|
||||
/*
|
||||
* Sets usage rights on the models in memory based on the response from the
|
||||
* API.
|
||||
* apiData - the response from the API
|
||||
* models - an array containing the files/folders to update
|
||||
*/
|
||||
* Sets usage rights on the models in memory based on the response from the
|
||||
* API.
|
||||
* apiData - the response from the API
|
||||
* models - an array containing the files/folders to update
|
||||
*/
|
||||
// Grab the ids affected from the apiData
|
||||
export default function updateModelsUsageRights(apiData, models) {
|
||||
const affectedIds = apiData && apiData.file_ids
|
||||
|
|
|
@ -15,6 +15,6 @@
|
|||
// 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/>.
|
||||
|
||||
export default function rEscape (string) {
|
||||
export default function rEscape(string) {
|
||||
return string.replace(/[\\\^\$\*\+\?\.\(\)\|\{\}\[\]]/g, '\\$&')
|
||||
}
|
||||
|
|
|
@ -1,5 +1,25 @@
|
|||
/*
|
||||
* Copyright (C) 2019 - 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 TextHelper from '../TextHelper'
|
||||
|
||||
test('truncateText', () => {
|
||||
expect(TextHelper.truncateText('this is longer than 30 characters')).toBe('this is longer than 30...')
|
||||
expect(TextHelper.truncateText('this is longer than 30 characters')).toBe(
|
||||
'this is longer than 30...'
|
||||
)
|
||||
})
|
||||
|
|
|
@ -24,24 +24,26 @@ const apiUserContent = {
|
|||
/*
|
||||
xsslint safeString.identifier mathml
|
||||
*/
|
||||
translateMathmlForScreenreaders ($equationImage) {
|
||||
translateMathmlForScreenreaders($equationImage) {
|
||||
// note, it is safe to treat the x-canvaslms-safe-mathml as html because it
|
||||
// only ever gets put there by us (in Api::Html::Content::apply_mathml).
|
||||
// Any user content that gets sent to the server will have the
|
||||
// x-canvaslms-safe-mathml attribute stripped out.
|
||||
const mathml = $('<div/>').html($equationImage.attr('x-canvaslms-safe-mathml')).html()
|
||||
const mathml = $('<div/>')
|
||||
.html($equationImage.attr('x-canvaslms-safe-mathml'))
|
||||
.html()
|
||||
const mathmlSpan = $('<span class="hidden-readable"></span>')
|
||||
mathmlSpan.html(mathml)
|
||||
return mathmlSpan
|
||||
},
|
||||
|
||||
toMediaCommentLink (node) {
|
||||
toMediaCommentLink(node) {
|
||||
const $link = $(
|
||||
`<a
|
||||
id='media_comment_${htmlEscape($(node).data('media_comment_id'))}'
|
||||
data-media_comment_type='${htmlEscape($(node).data('media_comment_type'))}'
|
||||
class='instructure_inline_media_comment ${htmlEscape(node.nodeName.toLowerCase())}_comment'
|
||||
data-alt='${htmlEscape($(node).attr("data-alt"))}'
|
||||
data-alt='${htmlEscape($(node).attr('data-alt'))}'
|
||||
/>`
|
||||
)
|
||||
$link.html($(node).html())
|
||||
|
@ -53,12 +55,14 @@ const apiUserContent = {
|
|||
*/
|
||||
// use this method to process any user content fields returned in api responses
|
||||
// this is important to handle object/embed tags safely, and to properly display audio/video tags
|
||||
convert (html, options = {}) {
|
||||
convert(html, options = {}) {
|
||||
const $dummy = $('<div />').html(html)
|
||||
// finds any <video/audio class="instructure_inline_media_comment"> and turns them into media comment thumbnails
|
||||
$dummy.find('video.instructure_inline_media_comment,audio.instructure_inline_media_comment').replaceWith(function () {
|
||||
return apiUserContent.toMediaCommentLink(this)
|
||||
})
|
||||
$dummy
|
||||
.find('video.instructure_inline_media_comment,audio.instructure_inline_media_comment')
|
||||
.replaceWith(function() {
|
||||
return apiUserContent.toMediaCommentLink(this)
|
||||
})
|
||||
|
||||
// remove any embed tags inside an object tag, to avoid repeated translations
|
||||
$dummy.find('object.instructure_user_content:not(#kaltura_player) embed').remove()
|
||||
|
@ -71,50 +75,54 @@ const apiUserContent = {
|
|||
//
|
||||
// see the corresponding code in lib/user_content.rb for non-api user
|
||||
// content handling
|
||||
$dummy.find('object.instructure_user_content,embed.instructure_user_content').replaceWith(function () {
|
||||
const $this = $(this)
|
||||
if (!$this.data('uc_snippet') || !$this.data('uc_sig')) {
|
||||
return this
|
||||
}
|
||||
$dummy
|
||||
.find('object.instructure_user_content,embed.instructure_user_content')
|
||||
.replaceWith(function() {
|
||||
const $this = $(this)
|
||||
if (!$this.data('uc_snippet') || !$this.data('uc_sig')) {
|
||||
return this
|
||||
}
|
||||
|
||||
const uuid = _.uniqueId('uc_')
|
||||
let action = '/object_snippet'
|
||||
if (ENV.files_domain) {
|
||||
action = `//${ENV.files_domain}${action}`
|
||||
}
|
||||
const $form = $(
|
||||
`<form
|
||||
const uuid = _.uniqueId('uc_')
|
||||
let action = '/object_snippet'
|
||||
if (ENV.files_domain) {
|
||||
action = `//${ENV.files_domain}${action}`
|
||||
}
|
||||
const $form = $(
|
||||
`<form
|
||||
action='${htmlEscape(action)}'
|
||||
method='post'
|
||||
class='user_content_post_form'
|
||||
target='${htmlEscape(uuid)}'
|
||||
id='form-${htmlEscape(uuid)}'
|
||||
/>`
|
||||
)
|
||||
$form.append(
|
||||
$("<input type='hidden'/>").attr({
|
||||
name: 'object_data',
|
||||
value: $this.data('uc_snippet'),
|
||||
})
|
||||
)
|
||||
$form.append(
|
||||
$("<input type='hidden'/>").attr({
|
||||
name: 's',
|
||||
value: $this.data('uc_sig'),
|
||||
})
|
||||
)
|
||||
$('body').append($form)
|
||||
setTimeout(() => $form.submit(), 0)
|
||||
return $(
|
||||
`<iframe
|
||||
)
|
||||
$form.append(
|
||||
$("<input type='hidden'/>").attr({
|
||||
name: 'object_data',
|
||||
value: $this.data('uc_snippet')
|
||||
})
|
||||
)
|
||||
$form.append(
|
||||
$("<input type='hidden'/>").attr({
|
||||
name: 's',
|
||||
value: $this.data('uc_sig')
|
||||
})
|
||||
)
|
||||
$('body').append($form)
|
||||
setTimeout(() => $form.submit(), 0)
|
||||
return $(
|
||||
`<iframe
|
||||
class='user_content_iframe'
|
||||
name='${htmlEscape(uuid)}'
|
||||
style='width: ${htmlEscape($this.data('uc_width'))}; height: ${htmlEscape($this.data('uc_height'))};'
|
||||
style='width: ${htmlEscape($this.data('uc_width'))}; height: ${htmlEscape(
|
||||
$this.data('uc_height')
|
||||
)};'
|
||||
frameborder='0'
|
||||
title='${htmlEscape(I18n.t('User Content'))}'
|
||||
/>`
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
$dummy.find('img.equation_image').each((index, equationImage) => {
|
||||
const $equationImage = $(equationImage)
|
||||
|
|
|
@ -21,11 +21,11 @@ const formatter = {
|
|||
}
|
||||
|
||||
// see also lib/i18n/lolcalize.rb
|
||||
function letThereBeLols (str) {
|
||||
function letThereBeLols(str) {
|
||||
// don't want to mangle placeholders, wrappers, etc.
|
||||
const pattern = /(\s*%h?\{[^\}]+\}\s*|\s*[\n\\`\*_\{\}\[\]\(\)\#\+\-!]+\s*|^\s+)/
|
||||
|
||||
const result = str.split(pattern).map((token) => {
|
||||
const result = str.split(pattern).map(token => {
|
||||
if (token.match(pattern)) return token
|
||||
let s = ''
|
||||
// same as for i in [0...token.length] in coffeescript
|
||||
|
@ -40,7 +40,7 @@ function letThereBeLols (str) {
|
|||
return result.join('')
|
||||
}
|
||||
|
||||
export default function i18nLolcalize (strOrObj) {
|
||||
export default function i18nLolcalize(strOrObj) {
|
||||
if (typeof strOrObj === 'string') return letThereBeLols(strOrObj)
|
||||
|
||||
const result = {}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import pluralize from 'str/pluralize'
|
||||
|
||||
export default function splitAssetString (assetString = '', toPlural = true) {
|
||||
export default function splitAssetString(assetString = '', toPlural = true) {
|
||||
const match = assetString.match(/(.*)_(\d+)$/)
|
||||
if (match) {
|
||||
const contextType = toPlural ? pluralize(match[1]) : match[1]
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
// @param {String} string - the string to convert to underscores
|
||||
//
|
||||
// @return String
|
||||
export default function underscore (string) {
|
||||
export default function underscore(string) {
|
||||
if (typeof string !== 'string' || string === '') return string
|
||||
return string
|
||||
.replace(/([A-Z])/g, '_$1')
|
||||
|
|
|
@ -22,7 +22,7 @@ import $ from 'jquery'
|
|||
import AvatarDialogView from '../views/profiles/AvatarDialogView'
|
||||
|
||||
export default class AvatarWidget {
|
||||
constructor (el) {
|
||||
constructor(el) {
|
||||
this.$el = $(el)
|
||||
this._attachEvents()
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export default class AvatarWidget {
|
|||
// Internal: Add click event to @$el to open widget.
|
||||
//
|
||||
// Returns nothing.
|
||||
_attachEvents () {
|
||||
_attachEvents() {
|
||||
return this.$el.on('click', this._openAvatarDialog)
|
||||
}
|
||||
|
||||
|
@ -47,5 +47,5 @@ export default class AvatarWidget {
|
|||
this.avatarDialog = new AvatarDialogView()
|
||||
}
|
||||
return this.avatarDialog.show()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import jQuery from 'jquery'
|
|||
import 'jquery.ajaxJSON'
|
||||
|
||||
export default class BackoffPoller {
|
||||
constructor (url, handler, opts = {}) {
|
||||
constructor(url, handler, opts = {}) {
|
||||
this.url = url
|
||||
this.handler = handler
|
||||
this.baseInterval = opts.baseInterval != null ? opts.baseInterval : 1000
|
||||
|
@ -38,7 +38,7 @@ export default class BackoffPoller {
|
|||
this.initialDelay = opts.initialDelay != null ? opts.initialDelay : true
|
||||
}
|
||||
|
||||
start () {
|
||||
start() {
|
||||
if (this.running) {
|
||||
this.reset()
|
||||
} else {
|
||||
|
@ -47,19 +47,19 @@ export default class BackoffPoller {
|
|||
return this
|
||||
}
|
||||
|
||||
then (callback) {
|
||||
(this.callbacks || (this.callbacks = [])).push(callback)
|
||||
then(callback) {
|
||||
;(this.callbacks || (this.callbacks = [])).push(callback)
|
||||
}
|
||||
|
||||
reset () {
|
||||
reset() {
|
||||
this.nextInterval = this.baseInterval
|
||||
this.attempts = 0
|
||||
}
|
||||
|
||||
stop (success = false) {
|
||||
stop(success = false) {
|
||||
if (this.running) clearTimeout(this.running)
|
||||
delete this.running
|
||||
if (success && this.callbacks) this.callbacks.forEach((callback) => callback())
|
||||
if (success && this.callbacks) this.callbacks.forEach(callback => callback())
|
||||
delete this.callbacks
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ export default class BackoffPoller {
|
|||
return jQuery.ajaxJSON(this.url, 'GET', {}, this.handle, (data, xhr) =>
|
||||
this.handleErrors ? this.handle(data, xhr) : this.stop()
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
handle = (data, xhr) => {
|
||||
switch (this.handler(data, xhr)) {
|
||||
|
@ -82,9 +82,9 @@ export default class BackoffPoller {
|
|||
default:
|
||||
return this.stop()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
nextPoll (reset = false) {
|
||||
nextPoll(reset = false) {
|
||||
if (reset) {
|
||||
this.reset()
|
||||
if (!this.initialDelay) return this.poll()
|
||||
|
|
|
@ -23,7 +23,7 @@ xsslint xssable.receiver.whitelist builder
|
|||
*/
|
||||
|
||||
export default {
|
||||
fromCanvas (canvas, type = 'image/jpeg') {
|
||||
fromCanvas(canvas, type = 'image/jpeg') {
|
||||
const url = canvas.toDataURL(type)
|
||||
const binary = atob(url.split(',')[1])
|
||||
const codes = _.map(binary, char => char.charCodeAt(0))
|
||||
|
@ -31,11 +31,11 @@ export default {
|
|||
return this._newBlob(data, type)
|
||||
},
|
||||
|
||||
fromXHR (response, type = 'image/jpeg') {
|
||||
fromXHR(response, type = 'image/jpeg') {
|
||||
return this._newBlob(response, type)
|
||||
},
|
||||
|
||||
_newBlob (src, type) {
|
||||
_newBlob(src, type) {
|
||||
const builder = this._blobBuilder()
|
||||
if (builder) {
|
||||
builder.append(src)
|
||||
|
@ -45,10 +45,14 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
_blobBuilder () {
|
||||
_blobBuilder() {
|
||||
if (typeof window.Blob === 'function') return null
|
||||
|
||||
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder
|
||||
window.BlobBuilder =
|
||||
window.BlobBuilder ||
|
||||
window.WebKitBlobBuilder ||
|
||||
window.MozBlobBuilder ||
|
||||
window.MSBlobBuilder
|
||||
if (typeof window.BlobBuilder === 'undefined') return null
|
||||
return new window.BlobBuilder()
|
||||
}
|
||||
|
|
|
@ -25,42 +25,71 @@ import DateHelper from 'jsx/shared/helpers/dateHelper'
|
|||
const DATE_RANGE_ERRORS = {
|
||||
due_at: {
|
||||
start_range: {
|
||||
get section(){ return I18n.t('Due date cannot be before section start')},
|
||||
get course(){ return I18n.t('Due date cannot be before course start')},
|
||||
get term(){ return I18n.t('Due date cannot be before term start')},
|
||||
get section() {
|
||||
return I18n.t('Due date cannot be before section start')
|
||||
},
|
||||
get course() {
|
||||
return I18n.t('Due date cannot be before course start')
|
||||
},
|
||||
get term() {
|
||||
return I18n.t('Due date cannot be before term start')
|
||||
}
|
||||
},
|
||||
end_range: {
|
||||
get section(){ return I18n.t('Due date cannot be after section end')},
|
||||
get course(){ return I18n.t('Due date cannot be after course end')},
|
||||
get term(){ return I18n.t('Due date cannot be after term end')},
|
||||
},
|
||||
get section() {
|
||||
return I18n.t('Due date cannot be after section end')
|
||||
},
|
||||
get course() {
|
||||
return I18n.t('Due date cannot be after course end')
|
||||
},
|
||||
get term() {
|
||||
return I18n.t('Due date cannot be after term end')
|
||||
}
|
||||
}
|
||||
},
|
||||
unlock_at: {
|
||||
start_range: {
|
||||
get section(){ return I18n.t('Unlock date cannot be before section start')},
|
||||
get course(){ return I18n.t('Unlock date cannot be before course start')},
|
||||
get term(){ return I18n.t('Unlock date cannot be before term start')},
|
||||
get section() {
|
||||
return I18n.t('Unlock date cannot be before section start')
|
||||
},
|
||||
get course() {
|
||||
return I18n.t('Unlock date cannot be before course start')
|
||||
},
|
||||
get term() {
|
||||
return I18n.t('Unlock date cannot be before term start')
|
||||
}
|
||||
},
|
||||
end_range: {
|
||||
get due(){ return I18n.t('Unlock date cannot be after due date')},
|
||||
get lock(){ return I18n.t('Unlock date cannot be after lock date')},
|
||||
},
|
||||
get due() {
|
||||
return I18n.t('Unlock date cannot be after due date')
|
||||
},
|
||||
get lock() {
|
||||
return I18n.t('Unlock date cannot be after lock date')
|
||||
}
|
||||
}
|
||||
},
|
||||
lock_at: {
|
||||
start_range: {
|
||||
get due(){ return I18n.t('Lock date cannot be before due date')},
|
||||
get due() {
|
||||
return I18n.t('Lock date cannot be before due date')
|
||||
}
|
||||
},
|
||||
end_range: {
|
||||
get section(){ return I18n.t('Lock date cannot be after section end')},
|
||||
get course(){ return I18n.t('Lock date cannot be after course end')},
|
||||
get term(){ return I18n.t('Lock date cannot be after term end')},
|
||||
},
|
||||
},
|
||||
get section() {
|
||||
return I18n.t('Lock date cannot be after section end')
|
||||
},
|
||||
get course() {
|
||||
return I18n.t('Lock date cannot be after course end')
|
||||
},
|
||||
get term() {
|
||||
return I18n.t('Lock date cannot be after term end')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class DateValidator {
|
||||
|
||||
constructor (params) {
|
||||
constructor(params) {
|
||||
this.dateRange = params.date_range
|
||||
this.data = params.data
|
||||
this.forIndividualStudents = params.forIndividualStudents
|
||||
|
@ -70,7 +99,7 @@ export default class DateValidator {
|
|||
this.dueDateRequired = params.postToSIS && ENV.DUE_DATE_REQUIRED_FOR_ACCOUNT
|
||||
}
|
||||
|
||||
validateDatetimes () {
|
||||
validateDatetimes() {
|
||||
const lockAt = this.data.lock_at
|
||||
const unlockAt = this.data.unlock_at
|
||||
const dueAt = this.data.due_at
|
||||
|
@ -78,15 +107,19 @@ export default class DateValidator {
|
|||
const currentDateRange = section ? this.getSectionRange(section) : this.dateRange
|
||||
const datetimesToValidate = []
|
||||
|
||||
if (currentDateRange.start_at && currentDateRange.start_at.date && !this.forIndividualStudents) {
|
||||
if (
|
||||
currentDateRange.start_at &&
|
||||
currentDateRange.start_at.date &&
|
||||
!this.forIndividualStudents
|
||||
) {
|
||||
datetimesToValidate.push({
|
||||
date: currentDateRange.start_at.date,
|
||||
validationDates: {
|
||||
due_at: dueAt,
|
||||
unlock_at: unlockAt,
|
||||
unlock_at: unlockAt
|
||||
},
|
||||
range: 'start_range',
|
||||
type: currentDateRange.start_at.date_context,
|
||||
type: currentDateRange.start_at.date_context
|
||||
})
|
||||
}
|
||||
if (currentDateRange.end_at && currentDateRange.end_at.date && !this.forIndividualStudents) {
|
||||
|
@ -94,42 +127,42 @@ export default class DateValidator {
|
|||
date: currentDateRange.end_at.date,
|
||||
validationDates: {
|
||||
due_at: dueAt,
|
||||
lock_at: lockAt,
|
||||
lock_at: lockAt
|
||||
},
|
||||
range: 'end_range',
|
||||
type: currentDateRange.end_at.date_context,
|
||||
type: currentDateRange.end_at.date_context
|
||||
})
|
||||
}
|
||||
if (dueAt) {
|
||||
datetimesToValidate.push({
|
||||
date: dueAt,
|
||||
validationDates: {
|
||||
lock_at: lockAt,
|
||||
lock_at: lockAt
|
||||
},
|
||||
range: 'start_range',
|
||||
type: 'due',
|
||||
type: 'due'
|
||||
})
|
||||
datetimesToValidate.push({
|
||||
date: dueAt,
|
||||
validationDates: {
|
||||
unlock_at: unlockAt,
|
||||
unlock_at: unlockAt
|
||||
},
|
||||
range: 'end_range',
|
||||
type: 'due',
|
||||
type: 'due'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.dueDateRequired) {
|
||||
datetimesToValidate.push({
|
||||
date: dueAt,
|
||||
dueDateRequired: this.dueDateRequired,
|
||||
dueDateRequired: this.dueDateRequired
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasGradingPeriods && !this.userIsAdmin && this.data.persisted === false) {
|
||||
datetimesToValidate.push({
|
||||
date: dueAt,
|
||||
range: 'grading_period_range',
|
||||
range: 'grading_period_range'
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -137,36 +170,36 @@ export default class DateValidator {
|
|||
datetimesToValidate.push({
|
||||
date: lockAt,
|
||||
validationDates: {
|
||||
unlock_at: unlockAt,
|
||||
unlock_at: unlockAt
|
||||
},
|
||||
range: 'end_range',
|
||||
type: 'lock',
|
||||
type: 'lock'
|
||||
})
|
||||
}
|
||||
const errs = {}
|
||||
return this._validateDatetimeSequences(datetimesToValidate, errs)
|
||||
}
|
||||
|
||||
getSectionRange (section) {
|
||||
getSectionRange(section) {
|
||||
if (!section.override_course_and_term_dates) return this.dateRange
|
||||
|
||||
if (section.start_at) {
|
||||
this.dateRange.start_at = {
|
||||
date: section.start_at,
|
||||
date_context: 'section',
|
||||
date_context: 'section'
|
||||
}
|
||||
}
|
||||
if (section.end_at) {
|
||||
this.dateRange.end_at = {
|
||||
date: section.end_at,
|
||||
date_context: 'section',
|
||||
date_context: 'section'
|
||||
}
|
||||
}
|
||||
|
||||
return this.dateRange
|
||||
}
|
||||
|
||||
_validateMultipleGradingPeriods (date, errs) {
|
||||
_validateMultipleGradingPeriods(date, errs) {
|
||||
const helper = new GradingPeriodsHelper(this.gradingPeriods)
|
||||
const dueAt = date === null ? null : new Date(this._formatDatetime(date))
|
||||
if (!helper.isDateInClosedGradingPeriod(dueAt)) return
|
||||
|
@ -175,15 +208,15 @@ export default class DateValidator {
|
|||
if (earliestDate) {
|
||||
const formatted = DateHelper.formatDateForDisplay(earliestDate)
|
||||
errs.due_at = I18n.t('Please enter a due date on or after %{earliestDate}', {
|
||||
earliestDate: formatted,
|
||||
earliestDate: formatted
|
||||
})
|
||||
} else {
|
||||
errs.due_at = I18n.t('Due date cannot fall in a closed grading period')
|
||||
}
|
||||
}
|
||||
|
||||
_validateDatetimeSequences (datetimesToValidate, errs) {
|
||||
datetimesToValidate.forEach((datetimeSet) => {
|
||||
_validateDatetimeSequences(datetimesToValidate, errs) {
|
||||
datetimesToValidate.forEach(datetimeSet => {
|
||||
if (datetimeSet.dueDateRequired && !datetimeSet.date) {
|
||||
errs.due_at = I18n.t('Please add a due date')
|
||||
}
|
||||
|
@ -193,14 +226,20 @@ export default class DateValidator {
|
|||
switch (datetimeSet.range) {
|
||||
case 'start_range':
|
||||
_.each(datetimeSet.validationDates, (validationDate, dateType) => {
|
||||
if (validationDate && this._formatDatetime(datetimeSet.date) > this._formatDatetime(validationDate)) {
|
||||
if (
|
||||
validationDate &&
|
||||
this._formatDatetime(datetimeSet.date) > this._formatDatetime(validationDate)
|
||||
) {
|
||||
errs[dateType] = DATE_RANGE_ERRORS[dateType][datetimeSet.range][datetimeSet.type]
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'end_range':
|
||||
_.each(datetimeSet.validationDates, (validationDate, dateType) => {
|
||||
if (validationDate && this._formatDatetime(datetimeSet.date) < this._formatDatetime(validationDate)) {
|
||||
if (
|
||||
validationDate &&
|
||||
this._formatDatetime(datetimeSet.date) < this._formatDatetime(validationDate)
|
||||
) {
|
||||
errs[dateType] = DATE_RANGE_ERRORS[dateType][datetimeSet.range][datetimeSet.type]
|
||||
}
|
||||
})
|
||||
|
@ -211,7 +250,7 @@ export default class DateValidator {
|
|||
return errs
|
||||
}
|
||||
|
||||
_formatDatetime (date) {
|
||||
_formatDatetime(date) {
|
||||
return tz.format(tz.parse(date), '%F %R')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,14 @@
|
|||
|
||||
import _ from 'underscore'
|
||||
|
||||
export default function numberCompare (a, b, options = {}) {
|
||||
return !_.isNumber(a) ?
|
||||
(!_.isNumber(b) ? 0 : 1)
|
||||
: !_.isNumber(b) ?
|
||||
-1 :
|
||||
options.descending ? b - a : a - b
|
||||
export default function numberCompare(a, b, options = {}) {
|
||||
return !_.isNumber(a)
|
||||
? !_.isNumber(b)
|
||||
? 0
|
||||
: 1
|
||||
: !_.isNumber(b)
|
||||
? -1
|
||||
: options.descending
|
||||
? b - a
|
||||
: a - b
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ export default class PandaPubPoller {
|
|||
// wrap your normal poll method. It is passed another function that should
|
||||
// be called when the poll is complete.
|
||||
|
||||
constructor (pollInterval, rarePollInterval, pollCB) {
|
||||
constructor(pollInterval, rarePollInterval, pollCB) {
|
||||
this.pollInterval = pollInterval
|
||||
this.rarePollInterval = rarePollInterval
|
||||
this.pollCB = pollCB
|
||||
|
@ -55,7 +55,7 @@ export default class PandaPubPoller {
|
|||
// Set the function to call when data is received via the streaming
|
||||
// channel.
|
||||
|
||||
setOnData = (streamingCB) => {
|
||||
setOnData = streamingCB => {
|
||||
this.streamingCB = streamingCB
|
||||
}
|
||||
|
||||
|
@ -82,8 +82,7 @@ export default class PandaPubPoller {
|
|||
//
|
||||
// @api private
|
||||
|
||||
startTimeout = () => this.timeout = setTimeout(this.considerPoll, this.pollInterval)
|
||||
|
||||
startTimeout = () => (this.timeout = setTimeout(this.considerPoll, this.pollInterval))
|
||||
|
||||
// Stop the timeout
|
||||
//
|
||||
|
@ -126,7 +125,7 @@ export default class PandaPubPoller {
|
|||
// don't attempt to subscribe until we get a channel and a token
|
||||
if (!this.channel || !this.token) return
|
||||
|
||||
this.subscription = pandapub.subscribe(this.channel, this.token, (message) => {
|
||||
this.subscription = pandapub.subscribe(this.channel, this.token, message => {
|
||||
this.lastUpdate = Date.now()
|
||||
this.streamingCB(message)
|
||||
})
|
||||
|
|
|
@ -21,7 +21,7 @@ import $ from 'jquery'
|
|||
// it will be passed the position cordinates and a feedback object which,
|
||||
// among other things, tells you where it positioned it relative to the target. we use it to add some
|
||||
// css classes that handle putting the pointer triangle (aka: caret) back to the trigger.
|
||||
function using (position, feedback) {
|
||||
function using(position, feedback) {
|
||||
if (position.top < 0) position.top = 0
|
||||
return $(this)
|
||||
.css(position)
|
||||
|
@ -32,63 +32,61 @@ let idCounter = 0
|
|||
const activePopovers = []
|
||||
|
||||
function trapFocus(element) {
|
||||
const focusableEls = element.querySelectorAll('a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select');
|
||||
const firstFocusableEl = focusableEls[0];
|
||||
const lastFocusableEl = focusableEls[focusableEls.length - 1];
|
||||
const KEYCODE_TAB = 9;
|
||||
const focusableEls = element.querySelectorAll(
|
||||
'a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select'
|
||||
)
|
||||
const firstFocusableEl = focusableEls[0]
|
||||
const lastFocusableEl = focusableEls[focusableEls.length - 1]
|
||||
const KEYCODE_TAB = 9
|
||||
|
||||
element.addEventListener('keydown', e => {
|
||||
const isTabPressed = (e.key === 'Tab' || e.keyCode === KEYCODE_TAB);
|
||||
if (!isTabPressed) {
|
||||
return;
|
||||
const isTabPressed = e.key === 'Tab' || e.keyCode === KEYCODE_TAB
|
||||
if (!isTabPressed) {
|
||||
return
|
||||
}
|
||||
if (e.shiftKey) {
|
||||
/* shift + tab */ if (document.activeElement === firstFocusableEl) {
|
||||
lastFocusableEl.focus()
|
||||
e.preventDefault()
|
||||
}
|
||||
if ( e.shiftKey ) /* shift + tab */ {
|
||||
if (document.activeElement === firstFocusableEl) {
|
||||
lastFocusableEl.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else /* tab */ {
|
||||
if (document.activeElement === lastFocusableEl) {
|
||||
setTimeout(() => {
|
||||
firstFocusableEl.focus();
|
||||
})
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
} /* tab */ else if (document.activeElement === lastFocusableEl) {
|
||||
setTimeout(() => {
|
||||
firstFocusableEl.focus()
|
||||
})
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export default class Popover {
|
||||
|
||||
ignoreOutsideClickSelector = '.ui-dialog'
|
||||
|
||||
constructor (triggerEvent, content, options = {}) {
|
||||
constructor(triggerEvent, content, options = {}) {
|
||||
this.content = content
|
||||
this.options = options
|
||||
this.trigger = $(triggerEvent.currentTarget)
|
||||
this.triggerAction = triggerEvent.type
|
||||
this.focusTrapped = false
|
||||
this.el = $(this.content).addClass('carat-bottom').data('popover', this).keydown((event) => {
|
||||
// if the user hits the escape key, reset the focus to what it was.
|
||||
if (event.keyCode === $.ui.keyCode.ESCAPE) this.hide()
|
||||
this.el = $(this.content)
|
||||
.addClass('carat-bottom')
|
||||
.data('popover', this)
|
||||
.keydown(event => {
|
||||
// if the user hits the escape key, reset the focus to what it was.
|
||||
if (event.keyCode === $.ui.keyCode.ESCAPE) this.hide()
|
||||
|
||||
// If the user tabs or shift-tabs away, close.
|
||||
if (event.keyCode !== $.ui.keyCode.TAB) return
|
||||
// If the user tabs or shift-tabs away, close.
|
||||
if (event.keyCode !== $.ui.keyCode.TAB) return
|
||||
|
||||
const tabbables = $(':tabbable', this.el)
|
||||
const index = $.inArray(event.target, tabbables)
|
||||
if (index === -1) return
|
||||
const tabbables = $(':tabbable', this.el)
|
||||
const index = $.inArray(event.target, tabbables)
|
||||
if (index === -1) return
|
||||
|
||||
if (event.shiftKey) {
|
||||
if (!this.focusTrapped && index === 0) this.hide()
|
||||
} else {
|
||||
if (!this.focusTrapped && index === tabbables.length - 1) this.hide()
|
||||
}
|
||||
})
|
||||
if (event.shiftKey) {
|
||||
if (!this.focusTrapped && index === 0) this.hide()
|
||||
} else if (!this.focusTrapped && index === tabbables.length - 1) this.hide()
|
||||
})
|
||||
|
||||
this.el.delegate('.popover_close', 'keyclick click', (event) => {
|
||||
this.el.delegate('.popover_close', 'keyclick click', event => {
|
||||
event.preventDefault()
|
||||
this.hide()
|
||||
})
|
||||
|
@ -96,12 +94,12 @@ export default class Popover {
|
|||
this.show(triggerEvent)
|
||||
}
|
||||
|
||||
trapFocus (element) {
|
||||
trapFocus(element) {
|
||||
this.focusTrapped = true
|
||||
trapFocus(element)
|
||||
}
|
||||
|
||||
show (triggerEvent) {
|
||||
show(triggerEvent) {
|
||||
// when the popover is open, we don't want SR users to be able to navigate to the flash messages
|
||||
let popoverToHide
|
||||
$.screenReaderFlashMessageExclusive('')
|
||||
|
@ -113,7 +111,7 @@ export default class Popover {
|
|||
const id = `popover-${idCounter++}`
|
||||
this.trigger.attr({
|
||||
'aria-expanded': true,
|
||||
'aria-controls': id,
|
||||
'aria-controls': id
|
||||
})
|
||||
this.previousTarget = triggerEvent.currentTarget
|
||||
|
||||
|
@ -123,8 +121,18 @@ export default class Popover {
|
|||
.show()
|
||||
this.position()
|
||||
if (triggerEvent.type !== 'mouseenter') {
|
||||
this.el.find(':tabbable').first().focus()
|
||||
setTimeout(() => this.el.find(':tabbable').first().focus(), 100)
|
||||
this.el
|
||||
.find(':tabbable')
|
||||
.first()
|
||||
.focus()
|
||||
setTimeout(
|
||||
() =>
|
||||
this.el
|
||||
.find(':tabbable')
|
||||
.first()
|
||||
.focus(),
|
||||
100
|
||||
)
|
||||
}
|
||||
|
||||
document.querySelector('#application').setAttribute('aria-hidden', 'true')
|
||||
|
@ -136,14 +144,19 @@ export default class Popover {
|
|||
const actualOffset = triggerEvent.pageX - this.trigger.offset().left
|
||||
const leftBound = Math.max(0, this.trigger.width() / 2 - this.el.width() / 2) + 20
|
||||
const rightBound = this.trigger.width() - leftBound
|
||||
const caratOffset = Math.min(Math.max(leftBound, actualOffset), rightBound) + differenceInOffset + additionalOffset
|
||||
$('<span class="ui-menu-carat"><span /></span>').css('left', caratOffset).prependTo(this.el)
|
||||
const caratOffset =
|
||||
Math.min(Math.max(leftBound, actualOffset), rightBound) +
|
||||
differenceInOffset +
|
||||
additionalOffset
|
||||
$('<span class="ui-menu-carat"><span /></span>')
|
||||
.css('left', caratOffset)
|
||||
.prependTo(this.el)
|
||||
|
||||
this.positionInterval = setInterval(this.position, 200)
|
||||
$(window).click(this.outsideClickHandler)
|
||||
}
|
||||
|
||||
hide () {
|
||||
hide() {
|
||||
// remove this from the activePopovers array
|
||||
for (let index = 0; index < activePopovers.length; index++) {
|
||||
const popover = activePopovers[index]
|
||||
|
@ -164,23 +177,27 @@ export default class Popover {
|
|||
}
|
||||
|
||||
// uses a fat arrow so that it has a unique guid per-instance for jquery event unbinding
|
||||
outsideClickHandler = (event) => {
|
||||
if (!$(event.target).closest(this.el.add(this.trigger).add(this.ignoreOutsideClickSelector)).length) {
|
||||
outsideClickHandler = event => {
|
||||
if (
|
||||
!$(event.target).closest(this.el.add(this.trigger).add(this.ignoreOutsideClickSelector))
|
||||
.length
|
||||
) {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
position = () => this.el.position({
|
||||
my: `center ${this.options.verticalSide === 'bottom' ? 'top' : 'bottom'}`,
|
||||
at: `center ${this.options.verticalSide || 'top'}`,
|
||||
of: this.trigger,
|
||||
offset: `0px ${this.offsetPx()}px`,
|
||||
within: 'body',
|
||||
collision: `flipfit ${this.options.verticalSide ? 'none' : 'flipfit'}`,
|
||||
using,
|
||||
})
|
||||
position = () =>
|
||||
this.el.position({
|
||||
my: `center ${this.options.verticalSide === 'bottom' ? 'top' : 'bottom'}`,
|
||||
at: `center ${this.options.verticalSide || 'top'}`,
|
||||
of: this.trigger,
|
||||
offset: `0px ${this.offsetPx()}px`,
|
||||
within: 'body',
|
||||
collision: `flipfit ${this.options.verticalSide ? 'none' : 'flipfit'}`,
|
||||
using
|
||||
})
|
||||
|
||||
offsetPx () {
|
||||
offsetPx() {
|
||||
const offset = this.options.verticalSide === 'bottom' ? 10 : -10
|
||||
if (this.options.invertOffset) {
|
||||
return offset * -1
|
||||
|
@ -189,7 +206,7 @@ export default class Popover {
|
|||
}
|
||||
}
|
||||
|
||||
restoreFocus () {
|
||||
restoreFocus() {
|
||||
// set focus back to the previously focused item.
|
||||
if (this.previousTarget && $(this.previousTarget).is(':visible')) {
|
||||
this.previousTarget.focus()
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default class SisValidationHelper {
|
||||
constructor (params) {
|
||||
constructor(params) {
|
||||
this.postToSIS = params.postToSIS
|
||||
this.allDates = params.allDates
|
||||
this.dueDateRequired = params.dueDateRequired
|
||||
|
@ -26,7 +26,7 @@ export default class SisValidationHelper {
|
|||
this.maxNameLength = params.maxNameLength
|
||||
}
|
||||
|
||||
nameTooLong () {
|
||||
nameTooLong() {
|
||||
if (!this.postToSIS) return false
|
||||
if (this.maxNameLengthRequired) {
|
||||
return this.nameLengthComparison()
|
||||
|
@ -35,26 +35,28 @@ export default class SisValidationHelper {
|
|||
}
|
||||
}
|
||||
|
||||
nameLengthComparison () {
|
||||
nameLengthComparison() {
|
||||
return this.modelName.length > this.maxNameLength
|
||||
}
|
||||
|
||||
dueAtNotValid (date) {
|
||||
if(!date) return true
|
||||
return (date.dueAt === null || date.dueAt === undefined || date.dueAt === '')
|
||||
dueAtNotValid(date) {
|
||||
if (!date) return true
|
||||
return date.dueAt === null || date.dueAt === undefined || date.dueAt === ''
|
||||
}
|
||||
|
||||
dueDateMissingDifferentiated () {
|
||||
dueDateMissingDifferentiated() {
|
||||
if (!this.allDates) return false
|
||||
return (this.allDates.map(this.dueAtNotValid).indexOf(true) !== -1)
|
||||
return this.allDates.map(this.dueAtNotValid).indexOf(true) !== -1
|
||||
}
|
||||
|
||||
baseDueDateMissing () {
|
||||
return ((!this.allDates || this.allDates.length === 0) && !this.dueDate)
|
||||
baseDueDateMissing() {
|
||||
return (!this.allDates || this.allDates.length === 0) && !this.dueDate
|
||||
}
|
||||
|
||||
dueDateMissing () {
|
||||
dueDateMissing() {
|
||||
if (!this.postToSIS) return false
|
||||
return this.dueDateRequired && (this.baseDueDateMissing() || this.dueDateMissingDifferentiated())
|
||||
return (
|
||||
this.dueDateRequired && (this.baseDueDateMissing() || this.dueDateMissingDifferentiated())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
import $ from 'jquery'
|
||||
import I18n from 'i18n!site'
|
||||
|
||||
export default function addPrivacyLinkToDialog ($dialog) {
|
||||
export default function addPrivacyLinkToDialog($dialog) {
|
||||
if (!(ENV.ACCOUNT && ENV.ACCOUNT.privacy_policy_url)) return
|
||||
|
||||
const $privacy = $('<a>', {
|
||||
|
|
|
@ -20,8 +20,7 @@ import {isRTL} from 'jsx/shared/helpers/rtlHelper'
|
|||
const loadedStylesheets = {}
|
||||
|
||||
const brandableCss = {
|
||||
|
||||
getCssVariant () {
|
||||
getCssVariant() {
|
||||
const variant = window.ENV.use_responsive_layout ? 'responsive_layout' : 'new_styles'
|
||||
const contrast = window.ENV.use_high_contrast ? 'high_contrast' : 'normal_contrast'
|
||||
const rtl = isRTL() ? '_rtl' : ''
|
||||
|
@ -31,10 +30,8 @@ const brandableCss = {
|
|||
// combinedChecksum should be like '09f833ef7a'
|
||||
// and includesNoVariables should be true if this bundle does not
|
||||
// "@include" variables.scss, brand_variables.scss or variant_variables.scss
|
||||
urlFor (bundleName, {combinedChecksum, includesNoVariables}) {
|
||||
const brandAndVariant = includesNoVariables
|
||||
? 'no_variables'
|
||||
: brandableCss.getCssVariant()
|
||||
urlFor(bundleName, {combinedChecksum, includesNoVariables}) {
|
||||
const brandAndVariant = includesNoVariables ? 'no_variables' : brandableCss.getCssVariant()
|
||||
|
||||
return [
|
||||
window.ENV.ASSET_HOST || '',
|
||||
|
@ -46,7 +43,7 @@ const brandableCss = {
|
|||
},
|
||||
|
||||
// bundleName should be something like 'jst/foo'
|
||||
loadStylesheet (bundleName, opts) {
|
||||
loadStylesheet(bundleName, opts) {
|
||||
if (bundleName in loadedStylesheets) return
|
||||
|
||||
const linkElement = document.createElement('link')
|
||||
|
|
|
@ -18,17 +18,17 @@
|
|||
import $ from 'jquery'
|
||||
import _ from 'underscore'
|
||||
import h from 'str/htmlEscape'
|
||||
import listWithOthers from '../util/listWithOthers'
|
||||
import listWithOthers from './listWithOthers'
|
||||
import 'jquery.instructure_misc_helpers'
|
||||
|
||||
function prepare (context, filters) {
|
||||
function prepare(context, filters) {
|
||||
context = _.clone(context)
|
||||
context.activeFilter = _.includes(filters, `${context.type}_${context.id}`)
|
||||
context.sortBy = `${context.activeFilter ? 0 : 1}_${context.name.toLowerCase()}`
|
||||
return context
|
||||
}
|
||||
|
||||
function format (context, linkToContexts) {
|
||||
function format(context, linkToContexts) {
|
||||
let html = h(context.name)
|
||||
if (context.activeFilter) {
|
||||
html = `<span class='active-filter'>${html}</span>`
|
||||
|
@ -43,7 +43,7 @@ function format (context, linkToContexts) {
|
|||
// and a map of possible contexts by type,
|
||||
// return an html sentence/list of the contexts (maybe links, etc., see
|
||||
// options)
|
||||
export default function contextList (contextMap, allContexts, options = {}) {
|
||||
export default function contextList(contextMap, allContexts, options = {}) {
|
||||
const filters = options.filters != null ? options.filters : []
|
||||
let contexts = []
|
||||
for (const type in contextMap) {
|
||||
|
|
|
@ -15,14 +15,14 @@
|
|||
// 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/>.
|
||||
|
||||
export default function coupleTimeFields ($start, $end, $date) {
|
||||
export default function coupleTimeFields($start, $end, $date) {
|
||||
// construct blur callback that couples them in order so that $start can
|
||||
// never be less than $end
|
||||
function blur ($blurred) {
|
||||
function blur($blurred) {
|
||||
if ($date && $blurred === $date) {
|
||||
const date = $date.data('unfudged-date')
|
||||
if (date) {
|
||||
[$start, $end].forEach((el) => {
|
||||
;[$start, $end].forEach(el => {
|
||||
const instance = el.data('instance')
|
||||
if (instance) instance.setDate(date)
|
||||
})
|
||||
|
|
|
@ -25,7 +25,7 @@ xsslint safeString.identifier i
|
|||
*/
|
||||
|
||||
const builders = {
|
||||
year (options, htmlOptions) {
|
||||
year(options, htmlOptions) {
|
||||
const step = options.startYear < options.endYear ? 1 : -1
|
||||
const $result = $('<select />', htmlOptions)
|
||||
if (options.includeBlank) $result.append('<option />')
|
||||
|
@ -36,7 +36,7 @@ const builders = {
|
|||
}
|
||||
return $result
|
||||
},
|
||||
month (options, htmlOptions, dateSettings) {
|
||||
month(options, htmlOptions, dateSettings) {
|
||||
const months = dateSettings.month_names
|
||||
const $result = $('<select />', htmlOptions)
|
||||
if (options.includeBlank) $result.append('<option />')
|
||||
|
@ -45,7 +45,7 @@ const builders = {
|
|||
}
|
||||
return $result
|
||||
},
|
||||
day (options, htmlOptions) {
|
||||
day(options, htmlOptions) {
|
||||
const $result = $('<select />', htmlOptions)
|
||||
if (options.includeBlank) $result.append('<option />')
|
||||
for (let i = 1; i <= 31; i++) {
|
||||
|
@ -57,7 +57,7 @@ const builders = {
|
|||
|
||||
// generates something like rails' date_select/select_date
|
||||
// TODO: feature parity
|
||||
export default function dateSelect (name, options, htmlOptions = _.clone(options)) {
|
||||
export default function dateSelect(name, options, htmlOptions = _.clone(options)) {
|
||||
const validOptions = ['type', 'startYear', 'endYear', 'includeBlank', 'order']
|
||||
validOptions.forEach(opt => delete htmlOptions[opt])
|
||||
|
||||
|
@ -88,7 +88,11 @@ export default function dateSelect (name, options, htmlOptions = _.clone(options
|
|||
|
||||
const $result = $('<span>')
|
||||
// in coffeescript: for i in [0...options.order.length]
|
||||
for (let i = 0, end = options.order.length, asc = end >= 0; asc ? i < end : i > end; asc ? i++ : i--) {
|
||||
for (
|
||||
let i = 0, end = options.order.length, asc = end >= 0;
|
||||
asc ? i < end : i > end;
|
||||
asc ? i++ : i--
|
||||
) {
|
||||
const type = options.order[i]
|
||||
const tName = name.replace(/(\]?)$/, `(${position[type]}i)$1`)
|
||||
const html = builders[type](options, {name: tName, ...htmlOptions}, dateSettings)
|
||||
|
|
|
@ -23,10 +23,10 @@ import unflatten from '../object/unflatten'
|
|||
const coerceTypes = {
|
||||
true: true,
|
||||
false: false,
|
||||
null: null,
|
||||
null: null
|
||||
}
|
||||
|
||||
export default function deparam (params, coerce) {
|
||||
export default function deparam(params, coerce) {
|
||||
// shortcut for just deparam'ing the current querystring
|
||||
if (!params || typeof params === 'boolean') {
|
||||
const currentQueryString = window.location.search
|
||||
|
@ -35,24 +35,28 @@ export default function deparam (params, coerce) {
|
|||
}
|
||||
|
||||
const obj = {}
|
||||
params.replace(/\+/g, ' ').split('&').forEach((param) => {
|
||||
let [key, val] = param.split('=')
|
||||
key = decodeURIComponent(key)
|
||||
val = decodeURIComponent(val)
|
||||
params
|
||||
.replace(/\+/g, ' ')
|
||||
.split('&')
|
||||
.forEach(param => {
|
||||
let [key, val] = param.split('=')
|
||||
key = decodeURIComponent(key)
|
||||
val = decodeURIComponent(val)
|
||||
|
||||
// coerce values.
|
||||
if (coerce) {
|
||||
val = val && !isNaN(val)
|
||||
? +val // number
|
||||
: val === 'undefined'
|
||||
// coerce values.
|
||||
if (coerce) {
|
||||
val =
|
||||
val && !isNaN(val)
|
||||
? +val // number
|
||||
: val === 'undefined'
|
||||
? undefined // undefined
|
||||
: coerceTypes[val] !== undefined
|
||||
? coerceTypes[val] // true, false, null
|
||||
: val // string
|
||||
}
|
||||
? coerceTypes[val] // true, false, null
|
||||
: val // string
|
||||
}
|
||||
|
||||
obj[key] = val
|
||||
})
|
||||
obj[key] = val
|
||||
})
|
||||
|
||||
return unflatten(obj)
|
||||
}
|
||||
|
|
|
@ -35,6 +35,6 @@ const types = {
|
|||
}
|
||||
}
|
||||
|
||||
export default function enrollmentName (type) {
|
||||
export default function enrollmentName(type) {
|
||||
return types[type] || type
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import I18n from 'i18nObj'
|
|||
const units = ['byte', 'bytes', 'KB', 'MB', 'GB', 'TB']
|
||||
|
||||
// converts bytes into a nice representation with unit. e.g. 13661855 -> 13.7 MB, 825399 -> 825 KB, 1396 -> 1 KB
|
||||
export default function friendlyBytes (value) {
|
||||
export default function friendlyBytes(value) {
|
||||
let resInt, resValue
|
||||
const bytes = parseInt(value, 10)
|
||||
if (bytes.toString() === 'NaN') return '--'
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
// 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/>.
|
||||
|
||||
export default function generateUUID () {
|
||||
export default function generateUUID() {
|
||||
let now = Date.now()
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
|
||||
const r = ((now + Math.random() * 16) % 16) | 0
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, char => {
|
||||
const r = (now + Math.random() * 16) % 16 | 0
|
||||
now = Math.floor(now / 16)
|
||||
const newChar = char === 'x' ? r : (r & 0x7) | 0x8
|
||||
return newChar.toString(16)
|
||||
|
|
|
@ -15,8 +15,13 @@
|
|||
// 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/>.
|
||||
|
||||
export default function groupHasSubmissions (group) {
|
||||
return group.get('has_submission') || group.users().models.reduce((hasSubmission, user) => {
|
||||
return hasSubmission || (user.has('group_submissions') && user.get('group_submissions').length > 0)
|
||||
}, false)
|
||||
export default function groupHasSubmissions(group) {
|
||||
return (
|
||||
group.get('has_submission') ||
|
||||
group.users().models.reduce((hasSubmission, user) => {
|
||||
return (
|
||||
hasSubmission || (user.has('group_submissions') && user.get('group_submissions').length > 0)
|
||||
)
|
||||
}, false)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ class KalturaAnalytics {
|
|||
data['event:eventTimestamp'] = new Date().getTime()
|
||||
|
||||
return this.queueApiCall(this.apiUrl + $.param(data))
|
||||
};
|
||||
}
|
||||
|
||||
// kaltura expects a persistent analytic session token for the user
|
||||
// this generates a simple session id for analytic purposes
|
||||
|
@ -67,7 +67,7 @@ class KalturaAnalytics {
|
|||
).replace(/\./g, '')
|
||||
return $.cookie('kaltura_analytic_tracker', this.kaSession, {path: '/'})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// pulls the kaltura domain from the plugin settins and sets up the base
|
||||
// url for sending analytics events
|
||||
|
@ -80,7 +80,7 @@ class KalturaAnalytics {
|
|||
}
|
||||
|
||||
return (this.apiUrl = `${domain}/api_v3/index.php?`)
|
||||
};
|
||||
}
|
||||
|
||||
// Since the analytic call is a cross-domain call, set the url in an iFrame
|
||||
setupApiIframes = count => {
|
||||
|
@ -95,16 +95,16 @@ class KalturaAnalytics {
|
|||
// iframe, so just send them every 4 seconds
|
||||
const queue = []
|
||||
const f = ((iframe, queue) =>
|
||||
(function() {
|
||||
function() {
|
||||
let url
|
||||
if ((url = queue.shift())) {
|
||||
return (iframe.src = url)
|
||||
}
|
||||
}))(iframe, queue)
|
||||
})(iframe, queue)
|
||||
this.iframes[i] = {iframe, queue, pinger: _.throttle(f, 4000)}
|
||||
}
|
||||
return this.iframes
|
||||
};
|
||||
}
|
||||
|
||||
queueApiCall = url => {
|
||||
if (!this.iframes) {
|
||||
|
@ -114,7 +114,7 @@ class KalturaAnalytics {
|
|||
this.iframes[this.qIndex].pinger()
|
||||
this.qIndex = (this.qIndex + 1) % this.iframes.length
|
||||
return this.qIndex
|
||||
};
|
||||
}
|
||||
|
||||
// Adds event listenrs to the mediaElement player
|
||||
//
|
||||
|
@ -208,7 +208,7 @@ class KalturaAnalytics {
|
|||
},
|
||||
false
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default function(mediaId, mediaElement, pluginSettings) {
|
||||
|
|
|
@ -20,14 +20,15 @@ import $ from 'jquery'
|
|||
import h from 'str/htmlEscape'
|
||||
import 'jquery.instructure_misc_helpers'
|
||||
|
||||
export default function listWithOthers (strings, cutoff = 2) {
|
||||
export default function listWithOthers(strings, cutoff = 2) {
|
||||
if (strings.length > cutoff) {
|
||||
strings = strings.slice(0, cutoff).concat([strings.slice(cutoff, strings.length)])
|
||||
}
|
||||
return $.toSentence(strings.map(strOrArray =>
|
||||
typeof strOrArray === 'string' || strOrArray instanceof h.SafeString
|
||||
? `<span>${h(strOrArray)}</span>`
|
||||
: `
|
||||
return $.toSentence(
|
||||
strings.map(strOrArray =>
|
||||
typeof strOrArray === 'string' || strOrArray instanceof h.SafeString
|
||||
? `<span>${h(strOrArray)}</span>`
|
||||
: `
|
||||
<span class='others'>
|
||||
${h(I18n.t('other', 'other', {count: strOrArray.length}))}
|
||||
<span>
|
||||
|
@ -39,4 +40,3 @@ export default function listWithOthers (strings, cutoff = 2) {
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import $ from 'jquery'
|
|||
import 'jquery.ajaxJSON'
|
||||
|
||||
export default {
|
||||
toggle (button) {
|
||||
toggle(button) {
|
||||
const data = $(button).data.bind($(button))
|
||||
return $.ajaxJSON(data('url'), data('isChecked') ? 'DELETE' : 'PUT', {}, () => {
|
||||
data('isChecked', !data('isChecked'))
|
||||
|
|
|
@ -27,7 +27,9 @@ import I18n from 'i18n!mimeClass'
|
|||
// (and app/stylesheets/components/deprecated/_fancy_links.scss if it is still being used)
|
||||
const mimeClasses = {
|
||||
audio: {
|
||||
get displayName(){return I18n.t('Audio')},
|
||||
get displayName() {
|
||||
return I18n.t('Audio')
|
||||
},
|
||||
mimeTypes: [
|
||||
'audio/x-mpegurl',
|
||||
'audio/x-pn-realaudio',
|
||||
|
@ -40,7 +42,9 @@ const mimeClasses = {
|
|||
]
|
||||
},
|
||||
code: {
|
||||
get displayName(){return I18n.t('Source code')},
|
||||
get displayName() {
|
||||
return I18n.t('Source code')
|
||||
},
|
||||
mimeTypes: [
|
||||
'text/xml',
|
||||
'text/css',
|
||||
|
@ -51,7 +55,9 @@ const mimeClasses = {
|
|||
]
|
||||
},
|
||||
doc: {
|
||||
get displayName(){return I18n.t('Text document')},
|
||||
get displayName() {
|
||||
return I18n.t('Text document')
|
||||
},
|
||||
mimeTypes: [
|
||||
'application/x-docx',
|
||||
'text/rtf',
|
||||
|
@ -62,50 +68,48 @@ const mimeClasses = {
|
|||
]
|
||||
},
|
||||
flash: {
|
||||
get displayName(){return I18n.t('Flash')},
|
||||
mimeTypes: [
|
||||
'application/x-shockwave-flash'
|
||||
]
|
||||
get displayName() {
|
||||
return I18n.t('Flash')
|
||||
},
|
||||
mimeTypes: ['application/x-shockwave-flash']
|
||||
},
|
||||
html: {
|
||||
get displayName(){return I18n.t('Web page')},
|
||||
mimeTypes: [
|
||||
'text/html',
|
||||
'application/xhtml+xml'
|
||||
]
|
||||
get displayName() {
|
||||
return I18n.t('Web page')
|
||||
},
|
||||
mimeTypes: ['text/html', 'application/xhtml+xml']
|
||||
},
|
||||
image: {
|
||||
get displayName(){return I18n.t('Image')},
|
||||
mimeTypes: [
|
||||
'image/png',
|
||||
'image/x-psd',
|
||||
'image/gif',
|
||||
'image/pjpeg',
|
||||
'image/jpeg'
|
||||
]
|
||||
get displayName() {
|
||||
return I18n.t('Image')
|
||||
},
|
||||
mimeTypes: ['image/png', 'image/x-psd', 'image/gif', 'image/pjpeg', 'image/jpeg']
|
||||
},
|
||||
ppt: {
|
||||
get displayName(){return I18n.t('Presentation')},
|
||||
get displayName() {
|
||||
return I18n.t('Presentation')
|
||||
},
|
||||
mimeTypes: [
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'application/vnd.ms-powerpoint'
|
||||
]
|
||||
},
|
||||
pdf: {
|
||||
get displayName(){return I18n.t('PDF')},
|
||||
mimeTypes: [
|
||||
'application/pdf'
|
||||
]
|
||||
get displayName() {
|
||||
return I18n.t('PDF')
|
||||
},
|
||||
mimeTypes: ['application/pdf']
|
||||
},
|
||||
text: {
|
||||
get displayName(){return I18n.t('Plain text')},
|
||||
mimeTypes: [
|
||||
'text',
|
||||
'text/plain'
|
||||
]
|
||||
get displayName() {
|
||||
return I18n.t('Plain text')
|
||||
},
|
||||
mimeTypes: ['text', 'text/plain']
|
||||
},
|
||||
video: {
|
||||
get displayName(){return I18n.t('Video')},
|
||||
get displayName() {
|
||||
return I18n.t('Video')
|
||||
},
|
||||
mimeTypes: [
|
||||
'video/mp4',
|
||||
'video/x-ms-asf',
|
||||
|
@ -118,7 +122,9 @@ const mimeClasses = {
|
|||
]
|
||||
},
|
||||
xls: {
|
||||
get displayName(){return I18n.t('Spreadsheet')},
|
||||
get displayName() {
|
||||
return I18n.t('Spreadsheet')
|
||||
},
|
||||
mimeTypes: [
|
||||
'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'text/csv',
|
||||
|
@ -127,7 +133,9 @@ const mimeClasses = {
|
|||
]
|
||||
},
|
||||
zip: {
|
||||
get displayName(){return I18n.t('Archive')},
|
||||
get displayName() {
|
||||
return I18n.t('Archive')
|
||||
},
|
||||
mimeTypes: [
|
||||
'application/x-rar-compressed',
|
||||
'application/x-zip-compressed',
|
||||
|
@ -138,17 +146,17 @@ const mimeClasses = {
|
|||
}
|
||||
}
|
||||
|
||||
export default function mimeClass (contentType) {
|
||||
export default function mimeClass(contentType) {
|
||||
return mimeClass.mimeClasses[contentType] || 'file'
|
||||
}
|
||||
|
||||
mimeClass.displayName = function (contentType) {
|
||||
mimeClass.displayName = function(contentType) {
|
||||
const found = mimeClasses[mimeClass(contentType)]
|
||||
return found && found.displayName || I18n.t('Unknown')
|
||||
return (found && found.displayName) || I18n.t('Unknown')
|
||||
}
|
||||
|
||||
mimeClass.mimeClasses = {}
|
||||
for (const cls in mimeClasses) {
|
||||
const value = mimeClasses[cls]
|
||||
value.mimeTypes.forEach(mimeType => mimeClass.mimeClasses[mimeType] = cls)
|
||||
value.mimeTypes.forEach(mimeType => (mimeClass.mimeClasses[mimeType] = cls))
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
import I18n from 'i18nObj'
|
||||
|
||||
export default {
|
||||
strings (x, y) {
|
||||
strings(x, y) {
|
||||
let locale = I18n.locale || 'en-US'
|
||||
const locale_map = {zh_Hant: 'zh-Hant'}
|
||||
locale = locale_map[locale] || locale
|
||||
|
@ -27,19 +27,19 @@ export default {
|
|||
return x.localeCompare(y, locale, {
|
||||
sensitivity: 'variant',
|
||||
ignorePunctuation: false,
|
||||
numeric: true,
|
||||
numeric: true
|
||||
})
|
||||
},
|
||||
|
||||
by (f) {
|
||||
by(f) {
|
||||
return (x, y) => this.strings(f(x), f(y))
|
||||
},
|
||||
|
||||
byKey (key) {
|
||||
byKey(key) {
|
||||
return this.by(x => x[key])
|
||||
},
|
||||
|
||||
byGet (key) {
|
||||
byGet(key) {
|
||||
return this.by(x => x.get(key))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
import $ from 'jquery'
|
||||
import 'jquery.disableWhileLoading'
|
||||
|
||||
export default function newCourseForm () {
|
||||
export default function newCourseForm() {
|
||||
const changeEvents = 'change keyup input'
|
||||
function showCourseCodeIfNeeded () {
|
||||
function showCourseCodeIfNeeded() {
|
||||
if ($nameInput.val().trim().length > 20) {
|
||||
$nameInput.unbind(changeEvents, showCourseCodeIfNeeded)
|
||||
$('#course_code_wrapper').slideDown('fast')
|
||||
|
@ -29,7 +29,7 @@ export default function newCourseForm () {
|
|||
|
||||
const $nameInput = $('#new_course_form [name="course[name]"]')
|
||||
$nameInput.bind(changeEvents, showCourseCodeIfNeeded)
|
||||
$('#new_course_form').submit(function () {
|
||||
$('#new_course_form').submit(function() {
|
||||
return $(this).disableWhileLoading($.Deferred())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -16,13 +16,15 @@
|
|||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// taken from http://jsfiddle.net/Mottie/xcqpF/1/light/
|
||||
export default function rgb2hex (rgb) {
|
||||
export default function rgb2hex(rgb) {
|
||||
rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i)
|
||||
if (rgb && rgb.length === 4) {
|
||||
return '#' +
|
||||
('0' + parseInt(rgb[1],10).toString(16)).slice(-2) +
|
||||
('0' + parseInt(rgb[2],10).toString(16)).slice(-2) +
|
||||
('0' + parseInt(rgb[3],10).toString(16)).slice(-2)
|
||||
return (
|
||||
'#' +
|
||||
('0' + parseInt(rgb[1], 10).toString(16)).slice(-2) +
|
||||
('0' + parseInt(rgb[2], 10).toString(16)).slice(-2) +
|
||||
('0' + parseInt(rgb[3], 10).toString(16)).slice(-2)
|
||||
)
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// rounds a number to m digits
|
||||
export default function round (n, digits = 0) {
|
||||
export default function round(n, digits = 0) {
|
||||
if (typeof n !== 'number' && !(n instanceof Number)) {
|
||||
n = parseFloat(n)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
const {floor} = Math
|
||||
|
||||
const pad = function (duration) {
|
||||
const pad = function(duration) {
|
||||
const padding = duration >= 0 && duration < 10 ? '0' : ''
|
||||
return padding + duration.toFixed()
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ const pad = function (duration) {
|
|||
// 84 seconds => 01:24
|
||||
// 7230 seconds => 02:00:30
|
||||
// 7530 seconds => 02:05:30
|
||||
export default function (seconds) {
|
||||
export default function(seconds) {
|
||||
if (seconds > 3600) {
|
||||
const hh = floor(seconds / 3600)
|
||||
const mm = floor((seconds - hh * 3600) / 60)
|
||||
|
|
|
@ -22,22 +22,18 @@ import tz from 'timezone'
|
|||
import htmlEscape from 'str/htmlEscape'
|
||||
import 'jquery.instructure_date_and_time'
|
||||
|
||||
|
||||
export default function semanticDateRange (startISO, endISO) {
|
||||
export default function semanticDateRange(startISO, endISO) {
|
||||
if (!startISO) {
|
||||
return (
|
||||
`<span class="date-range date-range-no-date">
|
||||
return `<span class="date-range date-range-no-date">
|
||||
${htmlEscape(I18n.t('no_date', 'No Date'))}
|
||||
</span>`
|
||||
)
|
||||
}
|
||||
|
||||
const startAt = tz.parse(startISO)
|
||||
const endAt = tz.parse(endISO)
|
||||
if (+startAt !== +endAt) {
|
||||
if (!$.sameDate(startAt, endAt)) {
|
||||
return (
|
||||
`<span class="date-range">
|
||||
return `<span class="date-range">
|
||||
<time datetime='${startAt.toISOString()}'>
|
||||
${$.datetimeString(startAt)}
|
||||
</time> -
|
||||
|
@ -45,10 +41,8 @@ export default function semanticDateRange (startISO, endISO) {
|
|||
${$.datetimeString(endAt)}
|
||||
</time>
|
||||
</span>`
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
`<span class="date-range">
|
||||
return `<span class="date-range">
|
||||
<time datetime='${startAt.toISOString()}'>
|
||||
${$.dateString(startAt)}, ${$.timeString(startAt)}
|
||||
</time> -
|
||||
|
@ -56,15 +50,12 @@ export default function semanticDateRange (startISO, endISO) {
|
|||
${$.timeString(endAt)}
|
||||
</time>
|
||||
</span>`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
`<span class="date-range">
|
||||
return `<span class="date-range">
|
||||
<time datetime='${startAt.toISOString()}'>
|
||||
${$.datetimeString(startAt)}
|
||||
</time>
|
||||
</span>`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,17 +18,16 @@
|
|||
import $ from 'jquery'
|
||||
import 'jqueryui/tooltip'
|
||||
|
||||
export default function vddTooltip () {
|
||||
export default function vddTooltip() {
|
||||
return $('.vdd_tooltip_link').tooltip({
|
||||
position: {my: 'center bottom', at: 'center top-10', collision: 'fit fit'},
|
||||
tooltipClass: 'center bottom vertical',
|
||||
content () {
|
||||
content() {
|
||||
const selector = $(this).data('tooltipSelector')
|
||||
try {
|
||||
const el = document.querySelector(selector)
|
||||
return $(el).html()
|
||||
} catch(err) {
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue