aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/ProsemirrorCopy/prompt.js
blob: b9068195fa61c44e4a32220bb806b720a94e0114 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
const prefix = "ProseMirror-prompt"

export function openPrompt(options) {
    let wrapper = document.body.appendChild(document.createElement("div"))
    wrapper.className = prefix
    wrapper.style.zIndex = 1000;
    wrapper.style.width = 250;
    wrapper.style.textAlign = "center";

    let mouseOutside = e => { if (!wrapper.contains(e.target)) close() }
    setTimeout(() => window.addEventListener("mousedown", mouseOutside), 50)
    let close = () => {
        window.removeEventListener("mousedown", mouseOutside)
        if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper)
    }

    let domFields = []
    for (let name in options.fields) domFields.push(options.fields[name].render())

    let submitButton = document.createElement("button")
    submitButton.type = "submit"
    submitButton.className = prefix + "-submit"
    submitButton.textContent = "OK"
    let cancelButton = document.createElement("button")
    cancelButton.type = "button"
    cancelButton.className = prefix + "-cancel"
    cancelButton.textContent = "Cancel"
    cancelButton.addEventListener("click", close)

    let form = wrapper.appendChild(document.createElement("form"))
    let title = document.createElement("h5")
    title.style.marginBottom = 15
    title.style.marginTop = 10
    if (options.title) form.appendChild(title).textContent = options.title
    domFields.forEach(field => {
        form.appendChild(document.createElement("div")).appendChild(field)
    })
    let b = document.createElement("div");
    b.style.marginTop = 15;
    let buttons = form.appendChild(b)
    // buttons.className = prefix + "-buttons"
    buttons.appendChild(submitButton)
    buttons.appendChild(document.createTextNode(" "))
    buttons.appendChild(cancelButton)

    let box = wrapper.getBoundingClientRect()
    wrapper.style.top = options.flyout_top + "px"
    wrapper.style.left = options.flyout_left + "px"

    let submit = () => {
        let params = getValues(options.fields, domFields)
        if (params) {
            close()
            options.callback(params)
        }
    }

    form.addEventListener("submit", e => {
        e.preventDefault()
        submit()
    })

    form.addEventListener("keydown", e => {
        if (e.keyCode == 27) {
            e.preventDefault()
            close()
        } else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) {
            e.preventDefault()
            submit()
        } else if (e.keyCode == 9) {
            window.setTimeout(() => {
                if (!wrapper.contains(document.activeElement)) close()
            }, 500)
        }
    })

    let input = form.elements[0]
    if (input) input.focus()
}

function getValues(fields, domFields) {
    let result = Object.create(null), i = 0
    for (let name in fields) {
        let field = fields[name], dom = domFields[i++]
        let value = field.read(dom), bad = field.validate(value)
        if (bad) {
            reportInvalid(dom, bad)
            return null
        }
        result[name] = field.clean(value)
    }
    return result
}

function reportInvalid(dom, message) {
    // FIXME this is awful and needs a lot more work
    let parent = dom.parentNode
    let 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
    setTimeout(() => parent.removeChild(msg), 1500)
}

// ::- The type of field that `FieldPrompt` expects to be passed to it.
export class Field {
    // :: (Object)
    // Create a field with the given options. Options support by all
    // field types are:
    //
    // **`value`**`: ?any`
    //   : The starting value for the field.
    //
    // **`label`**`: string`
    //   : The label for the field.
    //
    // **`required`**`: ?bool`
    //   : Whether the field is required.
    //
    // **`validate`**`: ?(any) → ?string`
    //   : A function to validate the given value. Should return an
    //     error message if it is not valid.
    constructor(options) { this.options = options }

    // render:: (state: EditorState, props: Object) → dom.Node
    // Render the field to the DOM. Should be implemented by all subclasses.

    // :: (dom.Node) → any
    // Read the field's value from its DOM node.
    read(dom) { return dom.value }

    // :: (any) → ?string
    // A field-type-specific validation function.
    validateType(_value) { }

    validate(value) {
        if (!value && this.options.required)
            return "Required field"
        return this.validateType(value) || (this.options.validate && this.options.validate(value))
    }

    clean(value) {
        return this.options.clean ? this.options.clean(value) : value
    }
}

// ::- A field class for single-line text fields.
export class TextField extends Field {
    render() {
        let input = document.createElement("input")
        input.type = "text"
        input.placeholder = this.options.label
        input.value = this.options.value || ""
        input.autocomplete = "off"
        input.style.marginBottom = 4
        input.style.border = "1px solid black"
        input.style.padding = "4px 4px"
        return input
    }
}


// ::- A field class for dropdown fields based on a plain `<select>`
// tag. Expects an option `options`, which should be an array of
// `{value: string, label: string}` objects, or a function taking a
// `ProseMirror` instance and returning such an array.
export class SelectField extends Field {
    render() {
        let select = document.createElement("select")
        this.options.options.forEach(o => {
            let opt = select.appendChild(document.createElement("option"))
            opt.value = o.value
            opt.selected = o.value == this.options.value
            opt.label = o.label
        })
        return select
    }
}