@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
106 lines (88 loc) • 3.49 kB
JavaScript
import sortBy from 'lodash/sortBy'
import setFromArray from '../../../../util/setFromArray'
export default class EditStemController {
constructor(props) {
this.props = props
}
updateProps = props => {
this.props = props
}
textForStemItem = stemItem => {
if (stemItem.type !== 'blank') {
return stemItem.value
}
const matchingData = this.props.scoringData.value.find(sd => {
return sd.id === stemItem.blankId
})
return matchingData.scoringData.blankText
}
// ===========
// ACTIONS
// ===========
insertTextAtCursor(text, e) {
if (window.getSelection) {
const sel = window.getSelection()
if (sel.getRangeAt && sel.rangeCount) {
const range = sel.getRangeAt(0)
range.deleteContents()
const newNode = document.createTextNode(text)
range.insertNode(newNode)
// This merges adjacent text nodes, as we just inserted a new one.
// Necessary to prevent weird selection errors from happening on some
// browsers, since they'll otherwise disagree on where the selection begins.
e.target.normalize()
const newRange = sel.getRangeAt(0)
// We want to extend the selection to a text node rather than an element
// to prevent more browser compatibility issues.
const newContainer =
newRange.startContainer.nodeType === Node.ELEMENT_NODE
? newRange.startContainer.childNodes[0]
: newRange.startContainer
try {
sel.extend(newContainer, newRange.startOffset + text.length)
} catch {
// Safari errors out inexplicably on above line, so this is a workaround.
// All other methods I tried fail on at least one other browser.
newRange.setEnd(newContainer, newRange.endOffset + text.length)
sel.addRange(newRange)
}
sel.collapseToEnd()
}
} else if (document.selection && document.selection.createRange) {
document.selection.createRange().text = text
}
}
handlePaste = e => {
e.stopPropagation()
e.preventDefault()
const clipboardData = e.clipboardData || window.clipboardData
const pastedData = clipboardData.getData('Text')
this.insertTextAtCursor(pastedData, e)
}
// If a new blank was added, we should focus on it. If a blank was deleted,
// we need to focus on the textual stem item that the blank's text was merged
// into, and report the correct cursor position to focus on.
getFocusData(prevProps) {
const prevIds = setFromArray(prevProps.stemItems.map(item => item.id))
const currIds = setFromArray(this.props.stemItems.map(item => item.id))
const newItems = this.props.stemItems.filter(item => !prevIds.has(item.id))
const deletedItems = sortBy(
prevProps.stemItems.filter(item => !currIds.has(item.id)),
['position'],
)
if (newItems.length === 0 && deletedItems.length === 0) {
return null
}
const newBlank = newItems.find(item => item.type === 'blank')
if (newBlank) {
return {stemItem: newBlank, cursorPosition: null}
}
const lastDeletedItem = deletedItems[deletedItems.length - 1]
const sortedItems = sortBy(this.props.stemItems, ['position'])
const modifiedItem = sortedItems[deletedItems[0].position - 1]
return {
stemItem: modifiedItem || sortedItems[0],
cursorPosition: modifiedItem.value.length - lastDeletedItem.value.length,
}
}
}