2022-09-09 11:53:35 +00:00
|
|
|
const prefix = 'ProseMirror-prompt'
|
|
|
|
|
|
|
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
2022-10-09 08:33:28 +00:00
|
|
|
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-09 08:33:28 +00:00
|
|
|
const mouseOutside = (e: MouseEvent) => {
|
|
|
|
if (!wrapper.contains(e.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)
|
|
|
|
if (wrapper.parentNode) wrapper.remove()
|
|
|
|
}
|
2022-10-09 00:00:13 +00:00
|
|
|
|
2022-10-09 08:33:28 +00:00
|
|
|
const domFields = []
|
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
|
|
|
}
|
2022-10-09 08:33:28 +00:00
|
|
|
domFields.forEach((field) => {
|
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) => {
|
|
|
|
if (e.key === 'Escape') {
|
|
|
|
e.preventDefault()
|
|
|
|
close()
|
2022-10-09 00:00:13 +00:00
|
|
|
// eslint-disable-next-line unicorn/prefer-keyboard-event-key
|
|
|
|
} else if (e.keyCode === 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) {
|
2022-09-09 11:53:35 +00:00
|
|
|
e.preventDefault()
|
|
|
|
submit()
|
|
|
|
} else if (e.key === 'Tab') {
|
|
|
|
window.setTimeout(() => {
|
|
|
|
if (!wrapper.contains(document.activeElement)) close()
|
|
|
|
}, 500)
|
|
|
|
}
|
|
|
|
})
|
2022-10-09 00:00:13 +00:00
|
|
|
|
2022-10-09 08:33:28 +00:00
|
|
|
const inpel = form.elements[0] as HTMLInputElement
|
|
|
|
if (inpel) inpel.focus()
|
2022-09-09 11:53:35 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 08:33:28 +00:00
|
|
|
function getValues(fields, domFields) {
|
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-09 08:33:28 +00:00
|
|
|
function reportInvalid(dom: HTMLElement, message: string) {
|
2022-09-09 11:53:35 +00:00
|
|
|
const parent = dom.parentNode
|
|
|
|
const msg = parent.appendChild(document.createElement('div'))
|
|
|
|
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-09 00:00:13 +00:00
|
|
|
// eslint-disable-next-line unicorn/prefer-dom-node-remove
|
|
|
|
setTimeout(() => parent.removeChild(msg), 1500)
|
2022-09-09 11:53:35 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 08:33:28 +00:00
|
|
|
interface FieldOptions {
|
|
|
|
options: { value: string; label: string }[]
|
|
|
|
required: boolean
|
|
|
|
label: string
|
|
|
|
value: string
|
|
|
|
validateType: (v) => boolean
|
|
|
|
validate: (v) => boolean
|
|
|
|
read: (v) => string
|
|
|
|
clean: (v) => boolean
|
|
|
|
}
|
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
export class Field {
|
2022-10-09 08:33:28 +00:00
|
|
|
options: FieldOptions
|
|
|
|
constructor(options) {
|
2022-09-09 11:53:35 +00:00
|
|
|
this.options = options
|
|
|
|
}
|
|
|
|
|
2022-10-09 08:33:28 +00:00
|
|
|
read(dom) {
|
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-09 08:33:28 +00:00
|
|
|
validate(value) {
|
2022-09-09 11:53:35 +00:00
|
|
|
if (!value && this.options.required) return 'Required field'
|
|
|
|
return this.validateType(value) || (this.options.validate && this.options.validate(value))
|
|
|
|
}
|
|
|
|
|
2022-10-09 08:33:28 +00:00
|
|
|
clean(value) {
|
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-09 08:33:28 +00:00
|
|
|
this.options.options.forEach((o) => {
|
2022-10-09 00:00:13 +00:00
|
|
|
const opt = select.appendChild(document.createElement('option'))
|
|
|
|
opt.value = o.value
|
|
|
|
opt.selected = o.value === this.options.value
|
|
|
|
opt.label = o.label
|
|
|
|
})
|
|
|
|
return select
|
|
|
|
}
|
|
|
|
}
|