diff --git a/.storybook/main.js b/.storybook/main.js
index a92bc6aa51f..e89a4325f3c 100644
--- a/.storybook/main.js
+++ b/.storybook/main.js
@@ -61,6 +61,8 @@ module.exports = {
path.resolve(canvasDir, 'packages/jquery-sticky'),
path.resolve(canvasDir, 'packages/mathml'),
path.resolve(canvasDir, 'packages/defer-promise'),
+ path.resolve(canvasDir, 'packages/convert-case'),
+ path.resolve(canvasDir, 'packages/html-escape'),
path.resolve(canvasDir, 'packages/persistent-array'),
path.resolve(canvasDir, 'packages/slickgrid'),
path.resolve(canvasDir, 'packages/with-breakpoints'),
diff --git a/packages/convert-case/index.js b/packages/convert-case/index.ts
similarity index 78%
rename from packages/convert-case/index.js
rename to packages/convert-case/index.ts
index f8d47977cac..4fd78839e6e 100644
--- a/packages/convert-case/index.js
+++ b/packages/convert-case/index.ts
@@ -16,7 +16,7 @@
* with this program. If not, see .
*/
-function camelizeString(str, lowerFirst) {
+function camelizeString(str: string, lowerFirst: boolean = false) {
return (str || '').replace(/(?:^|[-_])(\w)/g, (_, c, index) => {
if (index === 0 && lowerFirst) {
return c ? c.toLowerCase() : ''
@@ -26,16 +26,17 @@ function camelizeString(str, lowerFirst) {
})
}
-function underscoreString(str) {
+function underscoreString(str: string) {
return str.replace(/([A-Z])/g, $1 => `_${$1.toLowerCase()}`)
}
// Convert all property keys in an object to camelCase
-export function camelize(props) {
- let prop
- const attrs = {}
+export function camelize(props: {[key: string]: any}) {
+ const attrs: {
+ [key: string]: any
+ } = {}
- for (prop in props) {
+ for (const prop in props) {
if (props.hasOwnProperty(prop)) {
attrs[camelizeString(prop, true)] = props[prop]
}
@@ -44,9 +45,11 @@ export function camelize(props) {
return attrs
}
-export function underscore(props) {
+export function underscore(props: {[key: string]: any}) {
let prop
- const attrs = {}
+ const attrs: {
+ [key: string]: any
+ } = {}
for (prop in props) {
if (props.hasOwnProperty(prop)) {
diff --git a/packages/convert-case/package.json b/packages/convert-case/package.json
index 234715cc951..ad94da27d1a 100644
--- a/packages/convert-case/package.json
+++ b/packages/convert-case/package.json
@@ -3,5 +3,10 @@
"private": true,
"version": "1.0.0",
"author": "neme",
- "main": "./index.js"
+ "main": "./index.ts",
+ "babel": {
+ "presets": [
+ "@babel/preset-typescript"
+ ]
+ }
}
diff --git a/packages/html-escape/index.js b/packages/html-escape/index.ts
similarity index 85%
rename from packages/html-escape/index.js
rename to packages/html-escape/index.ts
index 84f6579008e..31b9d7b054c 100644
--- a/packages/html-escape/index.js
+++ b/packages/html-escape/index.ts
@@ -16,10 +16,13 @@
* with this program. If not, see .
*/
+// eslint-disable-next-line import/no-extraneous-dependencies
import INST from 'browser-sniffer'
class SafeString {
- constructor(string) {
+ 'string': string
+
+ constructor(string: any) {
this.string = typeof string === 'string' ? string : `${string}`
}
@@ -36,10 +39,10 @@ const ENTITIES = {
"'": ''',
'/': '/',
'`': '`', // for old versions of IE
- '=': '=' // in case of unquoted attributes
-}
+ '=': '=', // in case of unquoted attributes
+} as const
-function htmlEscape(str) {
+export function htmlEscape(str: string): string {
// ideally we should wrap this in a SafeString, but this is how it has
// always worked :-/
return str.replace(/[&<>"'\/`=]/g, c => ENTITIES[c])
@@ -47,7 +50,7 @@ function htmlEscape(str) {
// Escapes HTML tags from string, or object string props of `strOrObject`.
// returns the new string, or the object with escaped properties
-export default function escape(strOrObject) {
+export default function escape(strOrObject: string | SafeString | Object) {
if (typeof strOrObject === 'string') {
return htmlEscape(strOrObject)
} else if (strOrObject instanceof SafeString) {
@@ -62,7 +65,7 @@ export default function escape(strOrObject) {
strOrObject[k] = escape(v)
}
}
- return strOrObject
+ return strOrObject as T
}
escape.SafeString = SafeString
@@ -78,7 +81,7 @@ const UNESCAPE_ENTITIES = Object.keys(ENTITIES).reduce((map, key) => {
const unescapeSource = `(?:${Object.keys(UNESCAPE_ENTITIES).join('|')})`
const UNESCAPE_REGEX = new RegExp(unescapeSource, 'g')
-function unescape(str) {
+function unescape(str: string) {
return str.replace(UNESCAPE_REGEX, match => UNESCAPE_ENTITIES[match])
}
diff --git a/packages/html-escape/package.json b/packages/html-escape/package.json
index 0b9a7dcf331..cac0f03bce5 100644
--- a/packages/html-escape/package.json
+++ b/packages/html-escape/package.json
@@ -3,5 +3,10 @@
"private": true,
"version": "1.0.0",
"author": "neme",
- "main": "./index.js"
+ "main": "./index.ts",
+ "babel": {
+ "presets": [
+ "@babel/preset-typescript"
+ ]
+ }
}
diff --git a/ui-build/webpack-for-karma/index.js b/ui-build/webpack-for-karma/index.js
index 0b4009831b2..e9792bf1a39 100644
--- a/ui-build/webpack-for-karma/index.js
+++ b/ui-build/webpack-for-karma/index.js
@@ -66,6 +66,8 @@ module.exports = {
path.join(canvasDir, 'packages/jquery-selectmenu'),
path.join(canvasDir, 'packages/mathml'),
path.join(canvasDir, 'packages/defer-promise'),
+ path.resolve(canvasDir, 'packages/convert-case'),
+ path.resolve(canvasDir, 'packages/html-escape'),
path.join(canvasDir, 'packages/persistent-array'),
path.join(canvasDir, 'packages/slickgrid'),
path.join(canvasDir, 'packages/with-breakpoints'),
diff --git a/ui-build/webpack/index.js b/ui-build/webpack/index.js
index 4660d79618b..6d9a58af0c5 100644
--- a/ui-build/webpack/index.js
+++ b/ui-build/webpack/index.js
@@ -263,6 +263,8 @@ module.exports = {
path.resolve(canvasDir, 'packages/jquery-sticky'),
path.resolve(canvasDir, 'packages/mathml'),
path.resolve(canvasDir, 'packages/defer-promise'),
+ path.resolve(canvasDir, 'packages/convert-case'),
+ path.resolve(canvasDir, 'packages/html-escape'),
path.resolve(canvasDir, 'packages/persistent-array'),
path.resolve(canvasDir, 'packages/slickgrid'),
path.resolve(canvasDir, 'packages/with-breakpoints'),
diff --git a/ui/api.d.ts b/ui/api.d.ts
index 33602a8cae9..e1e30444e97 100644
--- a/ui/api.d.ts
+++ b/ui/api.d.ts
@@ -63,18 +63,18 @@ export type Student = Readonly<{
avatar_url?: string
created_at: string
email: null | string
- first_name: string
group_ids: string[]
id: string
integration_id: null | string
- last_name: string
login_id: string
- name: string
short_name: string
sis_import_id: null | string
sis_user_id: null | string
}> & {
enrollments: Enrollment[]
+ first_name: string
+ last_name: string
+ name: string
} & Partial<{
computed_current_score: number
computed_final_score: number
diff --git a/ui/features/gradebook/react/default_gradebook/Gradebook.utils.ts b/ui/features/gradebook/react/default_gradebook/Gradebook.utils.ts
index b06bb78be1a..d716d3c6ee9 100644
--- a/ui/features/gradebook/react/default_gradebook/Gradebook.utils.ts
+++ b/ui/features/gradebook/react/default_gradebook/Gradebook.utils.ts
@@ -44,6 +44,7 @@ import type {
GradingPeriod,
Module,
Section,
+ Student,
StudentGroup,
StudentGroupCategory,
StudentGroupCategoryMap,
@@ -490,14 +491,14 @@ export function maxAssignmentCount(
}
// mutative
-export function escapeStudentContent(student) {
+export function escapeStudentContent(student: Student) {
const unescapedName = student.name
const unescapedSortableName = student.sortable_name
const unescapedFirstName = student.first_name
const unescapedLastName = student.last_name
// TODO: selectively escape fields
- const escapedStudent = htmlEscape(student)
+ const escapedStudent = htmlEscape(student)
escapedStudent.name = unescapedName
escapedStudent.sortable_name = unescapedSortableName
escapedStudent.first_name = unescapedFirstName
diff --git a/ui/features/gradebook/react/default_gradebook/apis/GradebookSettingsModalApi.ts b/ui/features/gradebook/react/default_gradebook/apis/GradebookSettingsModalApi.ts
index cc3858ad4d6..badb2b6c0db 100644
--- a/ui/features/gradebook/react/default_gradebook/apis/GradebookSettingsModalApi.ts
+++ b/ui/features/gradebook/react/default_gradebook/apis/GradebookSettingsModalApi.ts
@@ -84,5 +84,7 @@ export function updateCourseSettings(
}
) {
const url = `/api/v1/courses/${courseId}/settings`
- return axios.put(url, underscore(settings)).then(response => ({data: camelize(response.data)}))
+ return axios.put(url, underscore(settings)).then(response => ({
+ data: camelize<{allowFinalGradeOverride: boolean}>(response.data),
+ }))
}
diff --git a/ui/features/gradebook/react/default_gradebook/components/SubmissionTray.tsx b/ui/features/gradebook/react/default_gradebook/components/SubmissionTray.tsx
index d64b9a03b33..0f9a110fd9c 100644
--- a/ui/features/gradebook/react/default_gradebook/components/SubmissionTray.tsx
+++ b/ui/features/gradebook/react/default_gradebook/components/SubmissionTray.tsx
@@ -104,7 +104,7 @@ export type SubmissionTrayProps = {
isOpen: boolean
isFirstStudent: boolean
isLastStudent: boolean
- latePolicy: LatePolicyCamelized
+ latePolicy?: LatePolicyCamelized
locale: string
editSubmissionComment: (commentId: string | null) => void
onClose: () => void
diff --git a/ui/features/gradebook/react/default_gradebook/gradebook.d.ts b/ui/features/gradebook/react/default_gradebook/gradebook.d.ts
index 72407b205c7..bd64f589e6b 100644
--- a/ui/features/gradebook/react/default_gradebook/gradebook.d.ts
+++ b/ui/features/gradebook/react/default_gradebook/gradebook.d.ts
@@ -213,7 +213,7 @@ export type CourseContent = {
gradingSchemes: GradingScheme[]
gradingPeriodAssignments: GradingPeriodAssignmentMap
assignmentStudentVisibility: {[assignmentId: string]: null | StudentMap}
- latePolicy: LatePolicyCamelized
+ latePolicy?: LatePolicyCamelized
students: StudentDatastore
modulesById: ModuleMap
}
diff --git a/ui/features/gradebook/react/default_gradebook/initialState.ts b/ui/features/gradebook/react/default_gradebook/initialState.ts
index 053cbc7ea03..c0416eaf72d 100644
--- a/ui/features/gradebook/react/default_gradebook/initialState.ts
+++ b/ui/features/gradebook/react/default_gradebook/initialState.ts
@@ -24,6 +24,7 @@ import type {
GradebookOptions,
GradebookSettings,
InitialActionStates,
+ LatePolicyCamelized,
} from './gradebook.d'
import type {GridDisplaySettings, FilterColumnsOptions} from './grid.d'
import {camelize} from 'convert-case'
@@ -132,7 +133,9 @@ export function getInitialCourseContent(options: GradebookOptions): CourseConten
gradingSchemes: options.grading_schemes.map(camelize),
gradingPeriodAssignments: {},
assignmentStudentVisibility: {},
- latePolicy: options.late_policy ? camelize(options.late_policy) : undefined,
+ latePolicy: options.late_policy
+ ? camelize(options.late_policy)
+ : undefined,
students: new StudentDatastore({}, {}),
modulesById: {},
}
diff --git a/ui/features/speed_grader/jquery/speed_grader.utils.tsx b/ui/features/speed_grader/jquery/speed_grader.utils.tsx
index 367a9bc1df8..1896eff71d2 100644
--- a/ui/features/speed_grader/jquery/speed_grader.utils.tsx
+++ b/ui/features/speed_grader/jquery/speed_grader.utils.tsx
@@ -136,7 +136,7 @@ export const configureRecognition = (recognition, messages) => {
}
// xsslint safeString.function linebreak
- function linebreak(transcript) {
+ function linebreak(transcript: string) {
return htmlEscape(transcript).replace(/\n\n/g, '').replace(/\n/g, '
')
}
}
diff --git a/ui/imports.d.ts b/ui/imports.d.ts
index 7187fed51aa..2fc36f9581a 100644
--- a/ui/imports.d.ts
+++ b/ui/imports.d.ts
@@ -209,3 +209,15 @@ declare module '@instructure/ui-badge' {
}
}
}
+
+declare module 'convert-case' {
+ export function camelize(props: {[key: string]: unknown}): T
+ export function underscore(props: {[key: string]: unknown}): T
+}
+
+declare module 'html-escape' {
+ type Escapeable = string | number | {[key: string]: Escapeable}
+ export function escape(strOrObject: Escapeable): T
+ export function htmlEscape(str: string): string
+ export function unescape(str: string): string
+}
diff --git a/ui/shared/grading/SubmissionHelper.ts b/ui/shared/grading/SubmissionHelper.ts
index 03d5bbd84d2..2fbc0c38e96 100644
--- a/ui/shared/grading/SubmissionHelper.ts
+++ b/ui/shared/grading/SubmissionHelper.ts
@@ -18,7 +18,11 @@
import {camelize, underscore} from 'convert-case'
import {originalityReportSubmissionKey} from './originalityReportHelper'
-import type {PlagiarismData, SimilarityEntry} from '@canvas/grading/grading.d'
+import type {
+ PlagiarismData,
+ SimilarityEntry,
+ CamelizedSubmissionWithOriginalityReport,
+} from '@canvas/grading/grading.d'
export function isGraded(submission) {
// TODO: remove when we no longer camelize data in Gradebook
@@ -52,9 +56,9 @@ export function isHideable(submission) {
// - "pending" reports (reports still being processed)
// - scored reports, with higher scores (indicating more likely plagiarism) first
export function extractSimilarityInfo(submission) {
- const sub = camelize(submission)
+ const sub = camelize(submission) as CamelizedSubmissionWithOriginalityReport
let plagiarismData
- let type
+ let type: 'vericite' | 'turnitin' | 'originality_report' | null = null
if (sub.vericiteData?.provider === 'vericite') {
type = 'vericite'