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

Replace :shortnames: with emojis (#390)

Fixes #1
Silly 7 éve
szülő
commit
d485dd0bb5

+ 56 - 17
src/directives/compose_area.ts

@@ -82,7 +82,11 @@ export default [
                     composeDiv[0].innerText = scope.initialData.draft;
                 }
 
-                let caretPosition: {from?: number, to?: number} = null;
+                let caretPosition: {
+                    from?: number,
+                    to?: number,
+                    fromBytes?: number,
+                    toBytes?: number } = null;
 
                 /**
                  * Stop propagation of click events and hold htmlElement of the emojipicker
@@ -269,19 +273,33 @@ export default [
                         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 <br>, make it fully empty.
                         // See also: https://stackoverflow.com/q/14638887/284318
                         let text = getText(false);
                         if (text === '\n') {
                             composeDiv[0].innerText = '';
+                        } else if (ev.keyCode === 190) {
+                            // A ':' is pressed, try to parse
+                            let currentWord = stringService.getWord(text, caretPosition.fromBytes, [':']);
+                            if (currentWord.length > 2
+                                && currentWord.substr(0, 1) === ':') {
+                                let unicodeEmoji = emojione.shortnameToUnicode(currentWord);
+                                if (unicodeEmoji && unicodeEmoji !== currentWord) {
+                                    return insertEmoji(unicodeEmoji,
+                                        caretPosition.from - currentWord.length,
+                                        caretPosition.to);
+                                }
+                            }
                         }
 
-                        // Update typing information
-                        if (composeAreaIsEmpty()) {
+                        // Update typing information (use text instead method)
+                        if (text.trim().length === 0) {
                             stopTyping();
                         } else {
                             startTyping();
@@ -483,7 +501,10 @@ export default [
                 // Emoji is chosen
                 function onEmojiChosen(ev: MouseEvent): void {
                     ev.stopPropagation();
-                    const emoji = this.textContent; // Unicode character
+                    insertEmoji (this.textContent);
+                }
+
+                function insertEmoji(emoji, posFrom = null, posTo = null): void {
                     const formatted = ($filter('emojify') as any)(emoji, true, true);
 
                     // In Chrome in right-to-left mode, our content editable
@@ -520,25 +541,29 @@ export default [
                     }
 
                     if (caretPosition !== null) {
-                        currentHTML = currentHTML.substr(0, caretPosition.from)
+                        posFrom = null === posFrom ? caretPosition.from : posFrom;
+                        posTo = null === posTo ? caretPosition.to : posTo;
+                        currentHTML = currentHTML.substr(0, posFrom)
                             + formatted
-                            + currentHTML.substr(caretPosition.to);
+                            + currentHTML.substr(posTo);
 
                         // change caret position
                         caretPosition.from += formatted.length - 1;
-                        caretPosition.to = caretPosition.from;
+                        caretPosition.fromBytes++;
                     } else {
                         // insert at the end of line
+                        posFrom = currentHTML.length;
                         currentHTML += formatted;
                         caretPosition = {
                             from: currentHTML.length,
-                            to: currentHTML.length,
                         };
                     }
+                    caretPosition.to = caretPosition.from;
+                    caretPosition.toBytes = caretPosition.fromBytes;
 
                     contentElement.innerHTML = currentHTML;
                     cleanupComposeContent();
-                    setCaretPosition(caretPosition.from);
+                    setCaretPosition(posFrom);
 
                     // Update the draft text
                     scope.onTyping(getText());
@@ -597,31 +622,42 @@ export default [
                 }
 
                 // return the html code position of the container element
-                function getHTMLPosition(offset: number, container: Node) {
+                function getPositions(offset: number, container: Node): {html: number, text: number} {
                     let pos = null;
+                    let textPos = null;
+
                     if (composeDiv[0].contains(container)) {
                         let selectedElement;
                         if (container === composeDiv[0]) {
                             if (offset === 0) {
-                                return 0;
+                                return {
+                                    html: 0, text: 0,
+                                };
                             }
                             selectedElement = composeDiv[0].childNodes[offset - 1];
                             pos = 0;
+                            textPos = 0;
                         } else {
                             selectedElement =  container.previousSibling;
                             pos = offset;
+                            textPos = offset;
                         }
 
                         while (selectedElement !== null) {
                             if (selectedElement.nodeType === Node.TEXT_NODE) {
                                 pos += selectedElement.textContent.length;
+                                textPos += selectedElement.textContent.length;
                             } else {
                                 pos += getOuterHtml(selectedElement).length;
+                                textPos += 1;
                             }
                             selectedElement = selectedElement.previousSibling;
                         }
                     }
-                    return pos;
+                    return {
+                        html: pos,
+                        text: textPos,
+                    };
                 }
 
                 // Update the current caret position or selection
@@ -631,11 +667,15 @@ export default [
                         const selection = window.getSelection();
                         if (selection.rangeCount) {
                             const range = selection.getRangeAt(0);
-                            let from = getHTMLPosition(range.startOffset, range.startContainer);
-                            if (from !== null && from >= 0) {
+                            let from = getPositions(range.startOffset, range.startContainer);
+                            if (from !== null && from.html >= 0) {
+                                const to = getPositions(range.endOffset, range.endContainer);
+
                                 caretPosition = {
-                                    from: from,
-                                    to: getHTMLPosition(range.endOffset, range.endContainer),
+                                    from: from.html,
+                                    to: to.html,
+                                    fromBytes: from.text,
+                                    toBytes: to.text,
                                 };
                             }
                         }
@@ -677,7 +717,6 @@ export default [
                         if (pos < size) {
                             // use this node
                             rangeAt(node, offset);
-                            this.stop = true;
                         } else if (i === composeDiv[0].childNodes.length - 1) {
                             rangeAt(node);
                         }

+ 36 - 1
src/services/string.ts

@@ -33,7 +33,7 @@ export class StringService {
             if (currentChunkSize + length > byteLength) {
                 let appendNewChunk = true;
                 if (lastSeparator > -1) {
-                    // check if sepeator in offset
+                    // check if separator in offset
                     if (currentChunkSize - lastSeparator <= offset
                         && chunks.length >= 1) {
                         // create new chunk with existing data
@@ -58,4 +58,39 @@ export class StringService {
         });
         return chunks;
     }
+    public getWord(input: string, pos: number, additionalSeparators: string[] = null): string {
+        if (input !== null && input.length > 0) {
+            let chars = [...input];
+            let charFound = false;
+            let realPos = Math.min(pos, chars.length) - 1;
+
+            let wordChars = new Array(realPos);
+            for (let n = realPos; n >= 0; n--) {
+                let realChar = chars[n].trim();
+                if (realChar === '') {
+                    // Abort
+                    if (charFound === false) {
+                        continue;
+                    } else {
+                        break;
+                    }
+                } else if (additionalSeparators !== null) {
+                    if (additionalSeparators.indexOf(chars[n]) > -1) {
+                        // append char
+                        wordChars[n] = realChar;
+                        if (charFound === false) {
+                            continue;
+                        } else {
+                            break;
+                        }
+                    }
+                }
+
+                wordChars[n] = realChar;
+                charFound = true;
+            }
+            return wordChars.join('');
+        }
+        return '';
+    }
 }

+ 1 - 0
src/types/emojione.d.ts

@@ -29,5 +29,6 @@ declare var emojione: {
     toShort: (str: string) => string,
     toImage: (str: string) => string,
     shortnameToImage: (str: string) => string,
+    shortnameToUnicode: (str: string) => string,
     unicodeToImage: (str: string) => string,
 };

+ 56 - 0
tests/service/string.js

@@ -0,0 +1,56 @@
+describe('StringService', function() {
+
+    let $service;
+
+    // Ignoring page reload request
+    beforeAll(() => window.onbeforeunload = () => null);
+
+    beforeEach(function() {
+
+        module('3ema.services');
+
+        // Inject the service
+        inject(function(StringService) {
+            $service = StringService;
+        });
+
+    });
+
+    it('parse null string', () => {
+        expect($service.getWord(null, 1)).toEqual('');
+    });
+
+    it('parse empty string', () => {
+        expect($service.getWord('', 1)).toEqual('');
+    });
+
+    it('parse string (spaces)', () => {
+        expect($service.getWord('When the man comes around.', 12)).toEqual('man');
+        expect($service.getWord('When the man comes around.', 13)).toEqual('man');
+        expect($service.getWord('When the man        comes around.', 16)).toEqual('man');
+    });
+
+    it('parse string (newline)', () => {
+        expect($service.getWord("When\nthe\nman\ncomes\naround.", 12)).toEqual('man');
+        expect($service.getWord("When\nthe\nman\ncomes\naround.", 13)).toEqual('man');
+        expect($service.getWord("When\nthe\nman\n\n\n\n\n\n\n\ncomes\naround.", 16)).toEqual('man');
+    });
+
+    it('parse string (newline/spaces)', () => {
+        expect($service.getWord("When the\nman comes around.", 12)).toEqual('man');
+        expect($service.getWord("When the\nman \ncomes around.", 13)).toEqual('man');
+        expect($service.getWord("When the\nman \n \n \n \ncomes around.", 16)).toEqual('man');
+    });
+
+    it('parse string (special character)', () => {
+        expect($service.getWord('When the :man: comes around.', 15)).toEqual(':man:');
+    });
+
+    it('parse string (with emoji (2 chars))', () => {
+        expect($service.getWord('this 😄 is a :smile: face', 19)).toEqual(':smile:');
+    });
+
+    it('parse string (additional separators)', () => {
+        expect($service.getWord('When the spider:man: comes around.', 20, [':'])).toEqual(':man:');
+    });
+});

+ 1 - 0
tests/testsuite.html

@@ -26,6 +26,7 @@
         <script src="service/qrcode.js"></script>
         <script src="service/uri.js"></script>
         <script src="service/webclient.js"></script>
+        <script src="service/string.js"></script>
         <script src="helpers.js"></script>
     </head>
     <body>