From f13a7226dbdd0f7716e780c2164f6ca2e8f47524 Mon Sep 17 00:00:00 2001 From: OsamaSayegh Date: Thu, 12 Jul 2018 07:13:52 +0300 Subject: [PATCH] FIX: display tables in posts history diff (#6032) --- .../discourse/controllers/history.js.es6 | 25 +++++---- .../javascripts/discourse/lib/text.js.es6 | 30 ++++++++--- .../engines/discourse-markdown-it.js.es6 | 4 ++ .../controllers/history-test.js.es6 | 51 ++++++++++++++++++- 4 files changed, 92 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/history.js.es6 b/app/assets/javascripts/discourse/controllers/history.js.es6 index 4635b03c634..1a38a3daaa6 100644 --- a/app/assets/javascripts/discourse/controllers/history.js.es6 +++ b/app/assets/javascripts/discourse/controllers/history.js.es6 @@ -2,9 +2,8 @@ import ModalFunctionality from "discourse/mixins/modal-functionality"; import { categoryBadgeHTML } from "discourse/helpers/category-link"; import computed from "ember-addons/ember-computed-decorators"; import { propertyGreaterThan, propertyLessThan } from "discourse/lib/computed"; -import { on } from "ember-addons/ember-computed-decorators"; -import { default as WhiteLister } from "pretty-text/white-lister"; -import { sanitize } from "pretty-text/sanitizer"; +import { on, observes } from "ember-addons/ember-computed-decorators"; +import { sanitizeAsync } from "discourse/lib/text"; function customTagArray(fieldName) { return function() { @@ -246,15 +245,23 @@ export default Ember.Controller.extend(ModalFunctionality, { return this.get("model.title_changes." + viewMode); }, - @computed("viewMode", "model.body_changes") - bodyDiff(viewMode) { + @observes("viewMode", "model.body_changes") + bodyDiffChanged() { + const viewMode = this.get("viewMode"); const html = this.get(`model.body_changes.${viewMode}`); if (viewMode === "side_by_side_markdown") { - return html; + this.set("bodyDiff", html); } else { - const whiteLister = new WhiteLister({ features: { editHistory: true } }); - whiteLister.whiteListFeature("editHistory", { custom: () => true }); - return sanitize(html, whiteLister); + const opts = { + features: { editHistory: true }, + whiteListed: { + editHistory: { custom: (tag, attr) => attr === "class" } + } + }; + + return sanitizeAsync(html, opts).then(result => + this.set("bodyDiff", result) + ); } }, diff --git a/app/assets/javascripts/discourse/lib/text.js.es6 b/app/assets/javascripts/discourse/lib/text.js.es6 index b42bd37b7ef..a143cd72554 100644 --- a/app/assets/javascripts/discourse/lib/text.js.es6 +++ b/app/assets/javascripts/discourse/lib/text.js.es6 @@ -25,25 +25,39 @@ function getOpts(opts) { // Use this to easily create a pretty text instance with proper options export function cook(text, options) { - return new Handlebars.SafeString(new PrettyText(getOpts(options)).cook(text)); + return new Handlebars.SafeString(createPrettyText(options).cook(text)); } // everything should eventually move to async API and this should be renamed // cook export function cookAsync(text, options) { - if (Discourse.MarkdownItURL) { - return loadScript(Discourse.MarkdownItURL) - .then(() => cook(text, options)) - .catch(e => Ember.Logger.error(e)); - } else { - return Ember.RSVP.Promise.resolve(cook(text)); - } + return loadMarkdownIt().then(() => cook(text, options)); } export function sanitize(text, options) { return textSanitize(text, new WhiteLister(options)); } +export function sanitizeAsync(text, options) { + return new loadMarkdownIt().then(() => { + return createPrettyText(options).sanitize(text); + }); +} + +function loadMarkdownIt() { + if (Discourse.MarkdownItURL) { + return loadScript(Discourse.MarkdownItURL).catch(e => + Ember.Logger.error(e) + ); + } else { + return Ember.RSVP.Promise.resolve(); + } +} + +function createPrettyText(options) { + return new PrettyText(getOpts(options)); +} + function emojiOptions() { const siteSettings = Discourse.__container__.lookup("site-settings:main"); if (!siteSettings.enable_emoji) { diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6 index 243b8e7db24..5a77beb33a3 100644 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6 +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6 @@ -210,6 +210,10 @@ export function setup(opts, siteSettings, state) { } }); + Object.entries(state.whiteListed || {}).forEach(entry => { + whiteListed.push(entry); + }); + optionCallbacks.forEach(([, callback]) => { callback(opts, siteSettings, state); }); diff --git a/test/javascripts/controllers/history-test.js.es6 b/test/javascripts/controllers/history-test.js.es6 index c2eca9962a5..a97d1fec0c6 100644 --- a/test/javascripts/controllers/history-test.js.es6 +++ b/test/javascripts/controllers/history-test.js.es6 @@ -22,10 +22,59 @@ QUnit.test("displayEdit", function(assert) { ); HistoryController.set("model.current_revision", 2); - assert.equal( HistoryController.get("displayEdit"), false, "it should only display the edit button on the latest revision" ); + + const html = `
+

" width="276" height="183">

+
+ + + + + + + + + + + + + +
ColumnTest
OsamaTesting
`; + + const expectedOutput = `
+

" width="276" height="183">

+
+ + + + + + + + + + + + + +
ColumnTest
OsamaTesting
`; + + HistoryController.setProperties({ + viewMode: "side_by_side", + model: { + body_changes: { + side_by_side: html + } + } + }); + + HistoryController.bodyDiffChanged().then(() => { + const output = HistoryController.get("bodyDiff"); + assert.equal(output, expectedOutput, "it keeps safe HTML"); + }); });