Forráskód Böngészése

Re-implement emoji shortcode processing

Danilo Bargen 6 éve
szülő
commit
bcb51a8b6d

+ 54 - 32
src/directives/compose_area.ts

@@ -310,6 +310,15 @@ export default [
                         return;
                     }
 
+                    // If a : is pressed, possibly insert emoji
+                    if (ev.key === ':') {
+                        const modified = onEmojiShortcodeKeyPressed(ev, ':', false);
+                        if (modified) {
+                            ev.preventDefault();
+                            return;
+                        }
+                    }
+
                     // At link time, the element is not yet evaluated.
                     // Therefore add following code to end of event loop.
                     $timeout(() => {
@@ -325,40 +334,23 @@ export default [
                 }
 
                 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 <br>, make it fully empty.
-                        // See also: https://stackoverflow.com/q/14638887/284318
-                        const text = composeArea.get_text(true);
-                        if (text === '\n') {
-                            composeDiv[0].innerText = '';
-                        // TODO
-                        // } else if ((ev.keyCode === 190 || ev.key === ':') && caretPosition !== null) {
-                        //    // A ':' is pressed, try to parse
-                        //    const currentWord = stringService.getWord(text, caretPosition.fromChar, [':']);
-                        //    if (currentWord.realLength > 2 && currentWord.word.substr(0, 1) === ':') {
-                        //        const trimmed = currentWord.word.substr(1, currentWord.word.length - 2);
-                        //        const unicodeEmoji = shortnameToUnicode(trimmed);
-                        //        if (unicodeEmoji !== null) {
-                        //            return insertEmoji(unicodeEmoji,
-                        //                caretPosition.from - currentWord.realLength,
-                        //                caretPosition.to);
-                        //        }
-                        //    }
-                        }
+                    // If the compose area contains only a single <br>, make it fully empty.
+                    // See also: https://stackoverflow.com/q/14638887/284318
+                    const text = composeArea.get_text(true);
+                    if (text === '\n') {
+                        composeDiv[0].innerText = '';
+                    }
 
-                        // Update typing information
-                        if (text.trim().length === 0) {
-                            stopTyping();
-                            scope.onTyping('');
-                        } else {
-                            startTyping();
-                            scope.onTyping(text.trim(), null/* TODO stringService.getWord(text, caretPosition.from)*/);
-                        }
+                    // Update typing information
+                    if (text.trim().length === 0) {
+                        stopTyping();
+                        scope.onTyping('');
+                    } else {
+                        startTyping();
+                        scope.onTyping(text.trim());
+                    }
 
-                        updateView();
-                    }, 0);
+                    updateView();
                 }
 
                 // Function to fetch file contents
@@ -597,6 +589,36 @@ export default [
                     img.ondragstart = () => false;
                 }
 
+                // The emoji shortcode trigger (:) was inserted. Return a boolean
+                // indicating whether the compose area contents were modified.
+                //
+                // The `alreadyProcessed` indicates whether the key has already
+                // been processed in the DOM (onKeyUp) or not (onKeyDown).
+                function onEmojiShortcodeKeyPressed(ev, trigger: string, alreadyProcessed: boolean): boolean {
+                    const word = composeArea.get_word_at_caret();
+                    if (word === undefined) {
+                        return false;
+                    }
+                    let before = word.before();
+                    const after = word.after();
+                    if (!alreadyProcessed) {
+                        before += trigger;
+                    }
+                    if (after.length === 0 && before.length > 2) {
+                        if (before.startsWith(trigger) && before.endsWith(trigger)) {
+                            const trimmed = before.substr(1, before.length - 2);
+                            const unicodeEmoji = shortnameToUnicode(trimmed);
+                            if (unicodeEmoji !== null) {
+                                composeArea.select_word_at_caret();
+                                composeArea.store_selection_range();
+                                insertSingleEmojiString(unicodeEmoji);
+                                return true;
+                            }
+                        }
+                    }
+                    return false;
+                }
+
                 // TODO
                 // function insertMention(mentionString, posFrom?: number, posTo?: number): void {
                 //     const mentionElement = ($filter('mentionify') as any)(mentionString) as string;

+ 1 - 27
src/partials/messenger.ts

@@ -735,35 +735,9 @@ class ConversationController {
      * In contrast to startTyping, this method is is always called, not just if
      * the text field is non-empty.
      */
-    public onTyping = (text: string, currentWord: threema.WordResult = null) => {
+    public onTyping = (text: string) => {
         // Update draft
         this.webClientService.setDraft(this.receiver, text);
-
-        /* Make mentions readonly for now
-        if (currentWord && currentWord.substr(0, 1) === '@') {
-            this.currentMentionFilterWord = currentWord.substr(1);
-            const query = this.currentMentionFilterWord.toLowerCase().trim();
-            const selectedMentionObject = this.getSelectedMention();
-            this.currentMentions = this.allMentions.filter((i) => {
-                if (query.length === 0) {
-                    return true;
-                }
-                return i.query.indexOf(query) >= 0;
-            });
-            // If only one mention is filtered, select them
-            if (this.currentMentions.length === 1) {
-                this.selectedMention = 0;
-            } else if (selectedMentionObject !== null) {
-                // Get the new position of the latest selected mention object
-                this.selectedMention = null;
-                this.selectedMention = this.currentMentions.findIndex((m) => {
-                    return m.identity === selectedMentionObject.identity;
-                });
-            }
-        } else {
-            this.currentMentionFilterWord = null;
-        }
-        */
     }
 
     public getSelectedMention = (): threema.Mention => {

+ 0 - 55
src/services/string.ts

@@ -58,59 +58,4 @@ export class StringService {
         });
         return chunks;
     }
-
-    /**
-     * Return the word below the cursor position.
-     *
-     * If the cursor is at the end of a word, the word to the left of it will
-     * be returned.
-     *
-     * If there is whitespace to the left of the cursor, then the returned
-     * `WordResult` object will include the trimmed word to the left of the
-     * cursor. The `realLength` includes the trimmed whitespace though.
-     */
-    public getWord(input: string, pos: number, additionalSeparators: string[] = null): threema.WordResult {
-        const result = {
-            word: null,
-            realLength: 0,
-        };
-        if (input !== null && input.length > 0) {
-            const chars = Array.from(input);
-            let charFound = false;
-            const realPos = Math.min(pos, chars.length) - 1;
-
-            if (realPos > 0) {
-                const wordChars = new Array(realPos);
-                for (let n = realPos; n >= 0; n--) {
-                    const realChar = chars[n].trim();
-                    if (realChar === '') {
-                        // Abort
-                        if (charFound === false) {
-                            result.realLength++;
-                            continue;
-                        } else {
-                            break;
-                        }
-                    } else if (additionalSeparators !== null) {
-                        if (additionalSeparators.indexOf(chars[n]) > -1) {
-                            // append char
-                            result.realLength++;
-                            wordChars[n] = realChar;
-                            if (charFound === false) {
-                                continue;
-                            } else {
-                                break;
-                            }
-                        }
-                    }
-                    result.realLength++;
-                    wordChars[n] = realChar;
-                    charFound = true;
-                }
-                result.word = wordChars.join('');
-            }
-
-        }
-        return result;
-    }
 }

+ 0 - 7
src/threema.d.ts

@@ -740,13 +740,6 @@ declare namespace threema {
         isAll: boolean;
     }
 
-    interface WordResult {
-        // The trimmed word
-        word: string;
-        // The length of the untrimmed word
-        realLength: number;
-    }
-
     interface WebClientServiceStopArguments {
         reason: DisconnectReason;
         send: boolean;

+ 0 - 87
tests/service/string.js

@@ -16,93 +16,6 @@ describe('StringService', function() {
 
     });
 
-    describe('getWord', function () {
-
-        it('parse null string', () => {
-            expect($service.getWord(null, 1)).toEqual(jasmine.objectContaining({
-                word: null,
-                realLength: 0
-            }));
-        });
-
-        it('parse empty string', () => {
-            expect($service.getWord('', 1)).toEqual(jasmine.objectContaining({
-                word: null,
-                realLength: 0
-            }));
-        });
-
-        it('parse string (spaces)', () => {
-            expect($service.getWord('When the man comes around.', 12)).toEqual(jasmine.objectContaining({
-                word: 'man',
-                realLength: 3
-            }));
-            expect($service.getWord('When the man comes around.', 13)).toEqual(jasmine.objectContaining({
-                word: 'man',
-                realLength: 4
-            }));
-            expect($service.getWord('When the man        comes around.', 16)).toEqual(jasmine.objectContaining({
-                word: 'man',
-                realLength: 7
-            }));
-        });
-
-        it('parse string (newline)', () => {
-            expect($service.getWord("When\nthe\nman\ncomes\naround.", 12)).toEqual(jasmine.objectContaining({
-                word: 'man',
-                realLength: 3
-            }));
-            expect($service.getWord("When\nthe\nman\ncomes\naround.", 13)).toEqual(jasmine.objectContaining({
-                word: 'man',
-                realLength: 4
-            }));
-            expect($service.getWord("When\nthe\nman\n\n\n\n\n\n\n\ncomes\naround.", 16)).toEqual(jasmine.objectContaining({
-                word: 'man',
-                realLength: 7
-            }));
-        });
-
-        it('parse string (newline/spaces)', () => {
-            expect($service.getWord("When the\nman comes around.", 12)).toEqual(jasmine.objectContaining({
-                word: 'man',
-                realLength: 3
-            }));
-            expect($service.getWord("When the\nman \ncomes around.", 13)).toEqual(jasmine.objectContaining({
-                word: 'man',
-                realLength: 4
-            }));
-            expect($service.getWord("When the\nman \n \n \n \ncomes around.", 16)).toEqual(jasmine.objectContaining({
-                word: 'man',
-                realLength: 7
-            }));
-        });
-
-        it('parse string (special character)', () => {
-            expect($service.getWord('When the :man: comes around.', 15)).toEqual(jasmine.objectContaining({
-                word: ':man:',
-                realLength: 6
-            }));
-            expect($service.getWord('When the :man: comes around.', 14)).toEqual(jasmine.objectContaining({
-                word: ':man:',
-                realLength: 5
-            }));
-        });
-
-        it('parse string (with emoji (2 chars))', () => {
-            expect($service.getWord('this 😄 is a :smile: face', 19)).toEqual(jasmine.objectContaining({
-                word: ':smile:',
-                realLength: 7
-            }));
-        });
-
-        it('parse string (additional separators)', () => {
-            expect($service.getWord('When the spider:man: comes around.', 20, [':'])).toEqual(jasmine.objectContaining({
-                word: ':man:',
-                realLength: 5
-            }));
-        });
-    });
-
     describe('byteChunkSplit', function() {
         this.testPatterns = (cases, size, offset) => {
             for (let testcase of cases) {