/
form.js
111 lines (103 loc) · 3.75 KB
/
form.js
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
/**
* @copyright 2022, the Converse.js contributors
* @license Mozilla Public License (MPLv2)
* @description This is the form utilities module.
*/
import { toStanza } from 'strophe.js';
/**
* @param {string} name
* @param {string|string[]} value
*/
const tplXformField = (name, value) => `<field var="${name}">${value}</field>`;
/** @param {string} value */
const tplXformValue = (value) => `<value>${value}</value>`;
/**
* @param {HTMLSelectElement} select
* @return {string[]}
*/
export function getSelectValues (select) {
const result = [];
const options = select?.options;
for (let i = 0, iLen = options.length; i < iLen; i++) {
const opt = options[i];
if (opt.selected) {
result.push(opt.value || opt.text);
}
}
return result;
}
/**
* Takes an HTML DOM and turns it into an XForm field.
* @param {HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement} field - the field to convert
* @return {Element}
*/
export function webForm2xForm (field) {
const name = field.getAttribute('name');
if (!name) {
return null; // See #1924
}
let value;
if (field.getAttribute('type') === 'checkbox') {
value = /** @type {HTMLInputElement} */ (field).checked && '1' || '0';
} else if (field.tagName == 'TEXTAREA') {
value = field.value.split('\n').filter((s) => s.trim());
} else if (field.tagName == 'SELECT') {
value = getSelectValues(/** @type {HTMLSelectElement} */ (field));
} else {
value = field.value;
}
return toStanza(tplXformField(name, Array.isArray(value) ? value.map(tplXformValue) : tplXformValue(value)));
}
/**
* Returns the current word being written in the input element
* @method u#getCurrentWord
* @param {HTMLInputElement} input - The HTMLElement in which text is being entered
* @param {number} [index] - An optional rightmost boundary index. If given, the text
* value of the input element will only be considered up until this index.
* @param {string|RegExp} [delineator] - An optional string delineator to
* differentiate between words.
*/
export function getCurrentWord (input, index, delineator) {
if (!index) {
index = input.selectionEnd || undefined;
}
let [word] = input.value.slice(0, index).split(/\s/).slice(-1);
if (delineator) {
[word] = word.split(delineator).slice(-1);
}
return word;
}
/**
* @param {string} s
*/
export function isMentionBoundary (s) {
return s !== '@' && RegExp(`(\\p{Z}|\\p{P})`, 'u').test(s);
}
/**
* @param {HTMLInputElement} input - The HTMLElement in which text is being entered
* @param {string} new_value
*/
export function replaceCurrentWord (input, new_value) {
const caret = input.selectionEnd || undefined;
const current_word = input.value.slice(0, caret).split(/\s/).pop();
const value = input.value;
const mention_boundary = isMentionBoundary(current_word[0]) ? current_word[0] : '';
input.value = value.slice(0, caret - current_word.length) + mention_boundary + `${new_value} ` + value.slice(caret);
const selection_end = caret - current_word.length + new_value.length + 1;
input.selectionEnd = mention_boundary ? selection_end + 1 : selection_end;
}
/**
* @param {HTMLTextAreaElement} textarea
*/
export function placeCaretAtEnd (textarea) {
if (textarea !== document.activeElement) {
textarea.focus();
}
// Double the length because Opera is inconsistent about whether a carriage return is one character or two.
const len = textarea.value.length * 2;
// Timeout seems to be required for Blink
setTimeout(() => textarea.setSelectionRange(len, len), 1);
// Scroll to the bottom, in case we're in a tall textarea
// (Necessary for Firefox and Chrome)
textarea.scrollTop = 999999;
}