webapp/src/components/Editor/prosemirror/extension/prompt.ts

156 lines
4.4 KiB
TypeScript
Raw Normal View History

2022-09-09 11:53:35 +00:00
const prefix = 'ProseMirror-prompt'
2022-10-21 10:17:38 +00:00
// eslint-disable-next-line sonarjs/cognitive-complexity
export function openPrompt(options) {
2022-09-09 11:53:35 +00:00
const wrapper = document.body.appendChild(document.createElement('div'))
wrapper.className = prefix
2022-10-19 17:26:07 +00:00
2022-10-21 10:17:38 +00:00
const mouseOutside = (ev: MouseEvent) => {
if (!wrapper.contains(ev.target as Node)) close()
2022-09-09 11:53:35 +00:00
}
2022-10-09 00:00:13 +00:00
setTimeout(() => window.addEventListener('mousedown', mouseOutside), 50)
2022-09-09 11:53:35 +00:00
const close = () => {
window.removeEventListener('mousedown', mouseOutside)
2022-10-21 10:17:38 +00:00
if (wrapper.parentNode) wrapper.remove()
2022-09-09 11:53:35 +00:00
}
2022-10-09 00:00:13 +00:00
const domFields: HTMLElement[] = []
2022-10-09 00:00:13 +00:00
options.fields.forEach((name) => {
domFields.push(options.fields[name].render())
})
const submitButton = document.createElement('button')
2022-09-09 11:53:35 +00:00
submitButton.type = 'submit'
submitButton.className = prefix + '-submit'
submitButton.textContent = 'OK'
2022-10-09 00:00:13 +00:00
const cancelButton = document.createElement('button')
2022-09-09 11:53:35 +00:00
cancelButton.type = 'button'
cancelButton.className = prefix + '-cancel'
cancelButton.textContent = 'Cancel'
cancelButton.addEventListener('click', close)
2022-10-09 00:00:13 +00:00
const form = wrapper.appendChild(document.createElement('form'))
2022-09-09 11:53:35 +00:00
if (options.title) {
2022-10-09 00:00:13 +00:00
form.appendChild(document.createElement('h5')).textContent = options.title
2022-09-09 11:53:35 +00:00
}
domFields.forEach((field: HTMLElement) => {
2022-10-09 00:00:13 +00:00
form.appendChild(document.createElement('div')).appendChild(field)
})
const buttons = form.appendChild(document.createElement('div'))
2022-09-09 11:53:35 +00:00
buttons.className = prefix + '-buttons'
2022-10-09 00:00:13 +00:00
buttons.appendChild(submitButton)
buttons.appendChild(document.createTextNode(' '))
buttons.appendChild(cancelButton)
const box = wrapper.getBoundingClientRect()
2022-09-09 11:53:35 +00:00
wrapper.style.top = (window.innerHeight - box.height) / 2 + 'px'
wrapper.style.left = (window.innerWidth - box.width) / 2 + 'px'
2022-10-09 00:00:13 +00:00
2022-09-09 11:53:35 +00:00
const submit = () => {
const params = getValues(options.fields, domFields)
if (params) {
close()
options.callback(params)
}
}
2022-10-09 00:00:13 +00:00
2022-09-09 11:53:35 +00:00
form.addEventListener('submit', (e) => {
e.preventDefault()
submit()
})
2022-10-09 00:00:13 +00:00
2022-09-09 11:53:35 +00:00
form.addEventListener('keydown', (e) => {
2022-10-21 10:17:38 +00:00
if (e.key === 'Escape') {
2022-09-09 11:53:35 +00:00
e.preventDefault()
close()
2022-10-21 10:17:38 +00:00
} else if (e.key === 'Enter' && !(e.ctrlKey || e.metaKey || e.shiftKey)) {
2022-09-09 11:53:35 +00:00
e.preventDefault()
submit()
2022-10-21 10:17:38 +00:00
} else if (e.key === 'Tab') {
2022-09-09 11:53:35 +00:00
window.setTimeout(() => {
if (!wrapper.contains(document.activeElement)) close()
}, 500)
}
})
2022-10-09 00:00:13 +00:00
2022-10-21 10:17:38 +00:00
const input = form.elements[0] as HTMLInputElement
2022-10-19 17:26:07 +00:00
if (input) input.focus()
2022-09-09 11:53:35 +00:00
}
function getValues(fields: any, domFields: HTMLElement[]) {
2022-09-09 11:53:35 +00:00
const result = Object.create(null)
let i = 0
fields.forEarch((name) => {
const field = fields[name]
const dom = domFields[i++]
const value = field.read(dom)
const bad = field.validate(value)
if (bad) {
reportInvalid(dom, bad)
return null
}
result[name] = field.clean(value)
})
return result
}
2022-10-21 10:17:38 +00:00
function reportInvalid(dom: HTMLElement, message: string) {
const msg: HTMLElement = dom.parentNode.appendChild(document.createElement('div'))
2022-09-09 11:53:35 +00:00
msg.style.left = dom.offsetLeft + dom.offsetWidth + 2 + 'px'
msg.style.top = dom.offsetTop - 5 + 'px'
msg.className = 'ProseMirror-invalid'
msg.textContent = message
2022-10-21 10:17:38 +00:00
setTimeout(msg.remove, 1500)
2022-09-09 11:53:35 +00:00
}
export class Field {
2022-10-19 17:26:07 +00:00
options: any
constructor(options: any) {
2022-09-09 11:53:35 +00:00
this.options = options
}
2022-10-19 17:26:07 +00:00
read(dom: any) {
2022-09-09 11:53:35 +00:00
return dom.value
}
// :: (any) → ?string
// A field-type-specific validation function.
validateType(_value) {
return typeof _value === typeof ''
}
2022-10-19 17:26:07 +00:00
validate(value: any) {
2022-09-09 11:53:35 +00:00
if (!value && this.options.required) return 'Required field'
2022-10-19 17:26:07 +00:00
2022-09-09 11:53:35 +00:00
return this.validateType(value) || (this.options.validate && this.options.validate(value))
}
2022-10-19 17:26:07 +00:00
clean(value: any) {
2022-09-09 11:53:35 +00:00
return this.options.clean ? this.options.clean(value) : value
}
}
export class TextField extends Field {
render() {
const input: HTMLInputElement = document.createElement('input')
2022-10-09 00:00:13 +00:00
2022-09-09 11:53:35 +00:00
input.type = 'text'
input.placeholder = this.options.label
input.value = this.options.value || ''
input.autocomplete = 'off'
return input
}
}
2022-10-09 00:00:13 +00:00
export class SelectField extends Field {
render() {
const select = document.createElement('select')
2022-10-19 17:26:07 +00:00
this.options.options.forEach((o: { value: string; label: string }) => {
2022-10-09 00:00:13 +00:00
const opt = select.appendChild(document.createElement('option'))
opt.value = o.value
2022-10-21 10:17:38 +00:00
opt.selected = o.value === this.options.value
2022-10-09 00:00:13 +00:00
opt.label = o.label
})
return select
}
}