s without any line break in between
// (except for the first line, which stays plain text)
//
// Thus, for Safari, we need to detect
s and insert a newline.
// tslint:disable-next-line: prefer-for-of (see #98)
for (let i = 0; i < parentNode.childNodes.length; i++) {
const node = parentNode.childNodes[i] as HTMLElement;
switch (node.nodeType) {
case Node.TEXT_NODE:
// Append text, but strip leading and trailing newlines
text += node.nodeValue.replace(/(^[\r\n]*|[\r\n]*$)/g, '');
break;
case Node.ELEMENT_NODE:
const tag = node.tagName.toLowerCase();
if (tag === 'div') {
text += '\n';
visitChildNodes(node);
break;
} else if (tag === 'img') {
text += (node as HTMLImageElement).alt;
break;
} else if (tag === 'br') {
text += '\n';
break;
} else if (tag === 'span' && node.hasAttribute('text')) {
text += node.getAttributeNode('text').value;
break;
}
default:
$log.warn(logTag, 'Unhandled node:', node);
}
}
};
visitChildNodes(composeDiv[0]);
return trim ? text.trim() : text;
}
// Determine whether field is empty
function composeAreaIsEmpty() {
return getText().length === 0;
}
// Submit the text from the compose area.
//
// Emoji images are converted to their alt text in this process.
function submitText(): Promise
{
const text = getText();
return new Promise((resolve, reject) => {
const submitTexts = (strings: string[]) => {
const messages: threema.TextMessageData[] = [];
for (const piece of strings) {
messages.push({
text: piece,
});
}
scope.submit('text', messages)
.then(resolve)
.catch(reject);
};
const fullText = text.trim().replace(/\r/g, '');
if (fullText.length > scope.maxTextLength) {
const pieces: string[] = stringService.byteChunk(fullText, scope.maxTextLength, 50);
const confirm = $mdDialog.confirm()
.title($translate.instant('messenger.MESSAGE_TOO_LONG_SPLIT_SUBJECT'))
.textContent($translate.instant('messenger.MESSAGE_TOO_LONG_SPLIT_BODY', {
max: scope.maxTextLength,
count: pieces.length,
}))
.ok($translate.instant('common.YES'))
.cancel($translate.instant('common.NO'));
$mdDialog.show(confirm).then(function() {
submitTexts(pieces);
}, () => {
reject();
});
} else {
submitTexts([fullText]);
}
});
}
function sendText(): boolean {
if (!composeAreaIsEmpty()) {
submitText().then(() => {
// Clear compose div
composeDiv[0].innerText = '';
composeDiv[0].focus();
// Send stopTyping event
stopTyping();
// Clear draft
scope.onTyping('');
updateView();
}).catch(() => {
// do nothing
$log.warn(logTag, 'Failed to submit text');
});
return true;
}
return false;
}
// Handle typing events
function onKeyDown(ev: KeyboardEvent): void {
// If enter is pressed, prevent default event from being dispatched
if (!ev.shiftKey && ev.key === 'Enter') {
ev.preventDefault();
}
// If the keydown is handled and aborted outside
if (scope.onKeyDown && scope.onKeyDown(ev) !== true) {
ev.preventDefault();
return;
}
// At link time, the element is not yet evaluated.
// Therefore add following code to end of event loop.
$timeout(() => {
// Shift + enter to insert a newline. Enter to send.
if (!ev.shiftKey && ev.key === 'Enter') {
if (sendText()) {
return;
}
}
updateView();
}, 0);
}
function onKeyUp(ev: KeyboardEvent): void {
// At link time, the element is not yet evaluated.
// Therefore add following code to end of event loop.
$timeout(() => {
// If the compose area contains only a single
, make it fully empty.
// See also: https://stackoverflow.com/q/14638887/284318
const text = getText(false);
if (text === '\n') {
composeDiv[0].innerText = '';
} else if (ev.keyCode === 190 && caretPosition !== null) {
// A ':' is pressed, try to parse
const currentWord = stringService.getWord(text, caretPosition.fromBytes, [':']);
if (currentWord.realLength > 2
&& currentWord.word.substr(0, 1) === ':') {
const unicodeEmoji = emojione.shortnameToUnicode(currentWord.word);
if (unicodeEmoji && unicodeEmoji !== currentWord.word) {
return insertEmoji(unicodeEmoji,
caretPosition.from - currentWord.realLength,
caretPosition.to);
}
}
}
// Update typing information (use text instead method)
if (text.trim().length === 0 || caretPosition === null) {
stopTyping();
scope.onTyping('');
} else {
startTyping();
scope.onTyping(text.trim(), stringService.getWord(text, caretPosition.from));
}
updateView();
}, 0);
}
// Function to fetch file contents
// Resolve to ArrayBuffer or reject to ErrorEvent.
function fetchFileListContents(fileList: FileList): Promise