Browse Source

Fix pasting of HTML code

Additionally, HTML markup will now be converted to ASCII counterparts
when pasting.
Danilo Bargen 8 years ago
parent
commit
c5310c5da9

+ 15 - 10
npm-shrinkwrap.json

@@ -12,6 +12,11 @@
       "from": "@types/angular-material@>=1.1.43 <1.2.0",
       "resolved": "https://registry.npmjs.org/@types/angular-material/-/angular-material-1.1.44.tgz"
     },
+    "@types/angular-sanitize": {
+      "version": "1.3.4",
+      "from": "@types/angular-sanitize@latest",
+      "resolved": "https://registry.npmjs.org/@types/angular-sanitize/-/angular-sanitize-1.3.4.tgz"
+    },
     "@types/angular-translate": {
       "version": "2.4.34",
       "from": "@types/angular-translate@>=2.4.34 <2.5.0",
@@ -47,6 +52,11 @@
       "from": "@types/webrtc@>=0.0.21 <0.1.0",
       "resolved": "https://registry.npmjs.org/@types/webrtc/-/webrtc-0.0.21.tgz"
     },
+    "JSONStream": {
+      "version": "1.3.0",
+      "from": "JSONStream@>=1.0.3 <2.0.0",
+      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.0.tgz"
+    },
     "abbrev": {
       "version": "1.0.9",
       "from": "abbrev@>=1.0.0 <2.0.0",
@@ -2268,11 +2278,6 @@
       "from": "jsonpointer@>=4.0.0 <5.0.0",
       "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz"
     },
-    "JSONStream": {
-      "version": "1.3.0",
-      "from": "JSONStream@>=1.0.3 <2.0.0",
-      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.0.tgz"
-    },
     "jsprim": {
       "version": "1.3.1",
       "from": "jsprim@>=1.2.2 <2.0.0",
@@ -3706,16 +3711,16 @@
         }
       }
     },
-    "string_decoder": {
-      "version": "0.10.31",
-      "from": "string_decoder@>=0.10.0 <0.11.0",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-    },
     "string-width": {
       "version": "1.0.2",
       "from": "string-width@>=1.0.1 <2.0.0",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz"
     },
+    "string_decoder": {
+      "version": "0.10.31",
+      "from": "string_decoder@>=0.10.0 <0.11.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+    },
     "stringstream": {
       "version": "0.0.5",
       "from": "stringstream@>=0.0.4 <0.1.0",

+ 1 - 0
package.json

@@ -27,6 +27,7 @@
   "dependencies": {
     "@types/angular": "~1.5.21",
     "@types/angular-material": "~1.1.43",
+    "@types/angular-sanitize": "~1.3.4",
     "@types/angular-translate": "~2.4.34",
     "@types/angular-ui-router": "~1.1.35",
     "@types/filesaver": "~0.0.30",

+ 14 - 7
src/directives/compose_area.ts

@@ -24,11 +24,13 @@ export default [
     '$timeout',
     '$translate',
     '$filter',
+    '$sanitize',
     '$log',
     function(browserService: threema.BrowserService,
              $window, $timeout: ng.ITimeoutService,
              $translate: ng.translate.ITranslateService,
              $filter: ng.IFilterService,
+             $sanitize: ng.sanitize.ISanitizeService,
              $log: ng.ILogService) {
         return {
             restrict: 'EA',
@@ -230,21 +232,26 @@ export default [
 
                 function applyFilters(text: string): string {
                     const emojify = $filter('emojify') as (a: string, b?: boolean) => string;
-                    return emojify(text, true);
+                    const parseNewLine = $filter('nlToBr') as (a: string, b?: boolean) => string;
+                    return parseNewLine(emojify(text, true), true);
                 }
 
                 // Handle pasting
-                // Source: http://stackoverflow.com/a/36846308/284318
                 function onPaste(ev: ClipboardEvent) {
                     ev.preventDefault();
                     const text = ev.clipboardData.getData('text/plain');
-                    let formatted = applyFilters(text);
 
-                    // replace newlines with html br
-                    const parseNewLine = $filter('writeNewLine') as (a: string, b?: boolean) => string;
-                    formatted = parseNewLine(formatted, true);
+                    // Apply filters (emojify, convert newline, etc)
+                    const formatted = applyFilters(text);
 
-                    document.execCommand('insertHTML', false, formatted);
+                    // Replace HTML formatting with ASCII counterparts
+                    const htmlToAsciiMarkup = $filter('htmlToAsciiMarkup') as (a: string) => string;
+
+                    // Sanitize
+                    const sanitized = $sanitize(htmlToAsciiMarkup(formatted));
+
+                    // Insert HTML
+                    document.execCommand('insertHTML', false, sanitized);
 
                     cleanupComposeContent();
                     updateView();

+ 1 - 1
src/directives/message_quote.ts

@@ -34,7 +34,7 @@ export default [
             template: `
                 <div class="message-quote-content" ng-style="{'border-color': ctrl.contact.color}">
                     <span class="message-name" ng-style="{'color': ctrl.contact.color}">{{ ctrl.contact.displayName }}</span>
-                    <span class="message-quote" ng-bind-html="ctrl.text | escapeHtml | writeNewLine | emojify | linkify | markify"></span>
+                    <span class="message-quote" ng-bind-html="ctrl.text | escapeHtml | nlToBr | emojify | linkify | markify"></span>
                 </div>
             `,
         };

+ 1 - 1
src/directives/message_text.ts

@@ -48,7 +48,7 @@ export default [
                 }
             }],
             template: `
-                <span threema-action ng-bind-html="ctrl.text | escapeHtml | writeNewLine: ctrl.multiLine | emojify | markify | linkify"></span>
+                <span threema-action ng-bind-html="ctrl.text | escapeHtml | nlToBr: ctrl.multiLine | emojify | markify | linkify"></span>
             `,
         };
     },

+ 39 - 2
src/filters.ts

@@ -32,14 +32,51 @@ angular.module('3ema.filters', [])
     };
     return (text) => (text !== undefined && text !== null ? text : '').replace(/[&<>"']/g, (m) => map[m]);
 })
-.filter('writeNewLine', function() {
+
+/**
+ * Convert newline characters with a <br> tag.
+ */
+.filter('nlToBr', function() {
     return (text, enabled: boolean) => {
         if (enabled) {
-            text = text.replace(/\n/g, '<br/>');
+            text = text.replace(/\n/g, '<br>');
         }
         return text;
     };
 })
+
+/**
+ * Replace formatting HTML with ASCII alternatives.
+ */
+.filter('htmlToAsciiMarkup', function() {
+    return (text) => {
+        let out = text
+
+        // Bold
+        .replace(/<b>/g, '*')
+        .replace(/<\/b>/g, '*')
+        .replace(/<strong>/g, '*')
+        .replace(/<\/strong>/g, '*')
+
+        // Italic
+        .replace(/<i>/g, '_')
+        .replace(/<\/i>/g, '_')
+        .replace(/<em>/g, '_')
+        .replace(/<\/em>/g, '_')
+
+        // Strikethrough
+        .replace(/<strike>/g, '~')
+        .replace(/<\/strike>/g, '~')
+        .replace(/<del>/g, '~')
+        .replace(/<\/del>/g, '~')
+        .replace(/<s>/g, '~')
+        .replace(/<\/s>/g, '~')
+
+        ;
+        return out;
+    };
+})
+
 /**
  * Convert links in text to <a> tags.
  */

+ 45 - 0
tests/filters.js

@@ -93,4 +93,49 @@ describe('Filters', function() {
         });
 
     });
+
+    describe('htmlToAsciiMarkup', function() {
+
+        this.testPatterns = (cases) => {
+            const filter = $filter('htmlToAsciiMarkup');
+            for (let testcase of cases) {
+                const input = testcase[0];
+                const expected = testcase[1];
+                expect(filter(input)).toEqual(expected);
+            };
+        };
+
+        it('converts bold text', () => {
+            this.testPatterns([
+                ['<b>bold</b>', '*bold*'],
+                ['<strong>bold</strong>', '*bold*'],
+                ['<b><b>bold</b></b>', '**bold**'],
+                ['<b><strong>bold</strong></b>', '**bold**'],
+            ]);
+        });
+
+        it('converts italic text', () => {
+            this.testPatterns([
+                ['<i>italic</i>', '_italic_'],
+                ['<em>italic</em>', '_italic_'],
+                ['<i><em>italic</em></i>', '__italic__'],
+            ]);
+        });
+
+        it('converts strikethrough text', () => {
+            this.testPatterns([
+                ['<strike>strikethrough</strike>', '~strikethrough~'],
+                ['<del>strikethrough</del>', '~strikethrough~'],
+                ['<s>strikethrough</s>', '~strikethrough~'],
+                ['<strike><del><s>strikethrough</s></del></strike>', '~~~strikethrough~~~'],
+            ]);
+        });
+
+        it('combination of all', () => {
+            this.testPatterns([
+                ['<b><em><del>foo</del></em></b>', '*_~foo~_*'],
+            ]);
+        });
+
+    });
 });