FEATURE: Use diffhtml to update composer preview (#11237)

Displaying videos, animated GIFs or any kind of rich content in preview
used to refresh on every keystroke, which could cause performance
problems.
This commit is contained in:
Bianca Nenciu 2021-02-18 16:07:26 +02:00 committed by GitHub
parent bddf94c0ab
commit 08acf51be0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 129 additions and 54 deletions

View File

@ -24,6 +24,11 @@ import { findRawTemplate } from "discourse-common/lib/raw-templates";
import { getRegister } from "discourse-common/lib/get-owner"; import { getRegister } from "discourse-common/lib/get-owner";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { isTesting } from "discourse-common/config/environment"; import { isTesting } from "discourse-common/config/environment";
import { linkSeenHashtags } from "discourse/lib/link-hashtags";
import { linkSeenMentions } from "discourse/lib/link-mentions";
import { loadOneboxes } from "discourse/lib/load-oneboxes";
import loadScript from "discourse/lib/load-script";
import { resolveCachedShortUrls } from "pretty-text/upload-short-url";
import { search as searchCategoryTag } from "discourse/lib/category-tag-search"; import { search as searchCategoryTag } from "discourse/lib/category-tag-search";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
@ -389,6 +394,32 @@ export default Component.extend({
} }
this.set("preview", cooked); this.set("preview", cooked);
if (this.siteSettings.enable_diffhtml_preview) {
const cookedElement = document.createElement("div");
cookedElement.innerHTML = cooked;
linkSeenHashtags($(cookedElement));
linkSeenMentions($(cookedElement), this.siteSettings);
resolveCachedShortUrls(this.siteSettings, cookedElement);
loadOneboxes(
cookedElement,
null,
null,
null,
this.siteSettings.max_oneboxes_per_post,
false,
true
);
loadScript("/javascripts/diffhtml.min.js").then(() => {
window.diff.innerHTML(
this.element.querySelector(".d-editor-preview"),
cookedElement.innerHTML
);
});
}
schedule("afterRender", () => { schedule("afterRender", () => {
if (this._state !== "inDOM") { if (this._state !== "inDOM") {
return; return;

View File

@ -1,7 +1,6 @@
import { TAG_HASHTAG_POSTFIX } from "discourse/lib/tag-hashtags"; import { TAG_HASHTAG_POSTFIX } from "discourse/lib/tag-hashtags";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { replaceSpan } from "discourse/lib/category-hashtags"; import { replaceSpan } from "discourse/lib/category-hashtags";
import { schedule } from "@ember/runloop";
const categoryHashtags = {}; const categoryHashtags = {};
const tagHashtags = {}; const tagHashtags = {};
@ -15,21 +14,19 @@ export function linkSeenHashtags($elem) {
const slugs = [...$hashtags.map((_, hashtag) => hashtag.innerText.substr(1))]; const slugs = [...$hashtags.map((_, hashtag) => hashtag.innerText.substr(1))];
schedule("afterRender", () => { $hashtags.each((index, hashtag) => {
$hashtags.each((index, hashtag) => { let slug = slugs[index];
let slug = slugs[index]; const hasTagSuffix = slug.endsWith(TAG_HASHTAG_POSTFIX);
const hasTagSuffix = slug.endsWith(TAG_HASHTAG_POSTFIX); if (hasTagSuffix) {
if (hasTagSuffix) { slug = slug.substr(0, slug.length - TAG_HASHTAG_POSTFIX.length);
slug = slug.substr(0, slug.length - TAG_HASHTAG_POSTFIX.length); }
}
const lowerSlug = slug.toLowerCase(); const lowerSlug = slug.toLowerCase();
if (categoryHashtags[lowerSlug] && !hasTagSuffix) { if (categoryHashtags[lowerSlug] && !hasTagSuffix) {
replaceSpan($(hashtag), slug, categoryHashtags[lowerSlug]); replaceSpan($(hashtag), slug, categoryHashtags[lowerSlug]);
} else if (tagHashtags[lowerSlug]) { } else if (tagHashtags[lowerSlug]) {
replaceSpan($(hashtag), slug, tagHashtags[lowerSlug]); replaceSpan($(hashtag), slug, tagHashtags[lowerSlug]);
} }
});
}); });
return slugs return slugs

View File

@ -1,7 +1,6 @@
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { formatUsername } from "discourse/lib/utilities"; import { formatUsername } from "discourse/lib/utilities";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { schedule } from "@ember/runloop";
import { userPath } from "discourse/lib/url"; import { userPath } from "discourse/lib/url";
let maxGroupMention; let maxGroupMention;
@ -42,23 +41,21 @@ const checked = {};
const cannotSee = []; const cannotSee = [];
function updateFound($mentions, usernames) { function updateFound($mentions, usernames) {
schedule("afterRender", function () { $mentions.each((i, e) => {
$mentions.each((i, e) => { const $e = $(e);
const $e = $(e); const username = usernames[i];
const username = usernames[i]; if (found[username.toLowerCase()]) {
if (found[username.toLowerCase()]) { replaceSpan($e, username, { cannot_see: cannotSee[username] });
replaceSpan($e, username, { cannot_see: cannotSee[username] }); } else if (mentionableGroups[username]) {
} else if (mentionableGroups[username]) { replaceSpan($e, username, {
replaceSpan($e, username, { group: true,
group: true, mentionable: mentionableGroups[username],
mentionable: mentionableGroups[username], });
}); } else if (foundGroups[username]) {
} else if (foundGroups[username]) { replaceSpan($e, username, { group: true });
replaceSpan($e, username, { group: true }); } else if (checked[username]) {
} else if (checked[username]) { $e.addClass("mention-tested");
$e.addClass("mention-tested"); }
}
});
}); });
} }

View File

@ -7,7 +7,8 @@ export function loadOneboxes(
topicId, topicId,
categoryId, categoryId,
maxOneboxes, maxOneboxes,
refresh refresh,
offline
) { ) {
const oneboxes = {}; const oneboxes = {};
const inlineOneboxes = {}; const inlineOneboxes = {};
@ -41,17 +42,22 @@ export function loadOneboxes(
} }
}); });
let newBoxes = 0;
if (Object.keys(oneboxes).length > 0) { if (Object.keys(oneboxes).length > 0) {
_loadOneboxes(oneboxes, ajax, newBoxes, topicId, categoryId, refresh); _loadOneboxes({
oneboxes,
ajax,
topicId,
categoryId,
refresh,
offline,
});
} }
if (Object.keys(inlineOneboxes).length > 0) { if (Object.keys(inlineOneboxes).length > 0) {
_loadInlineOneboxes(inlineOneboxes, ajax, topicId, categoryId); _loadInlineOneboxes(inlineOneboxes, ajax, topicId, categoryId);
} }
return newBoxes; return Object.keys(oneboxes).length + Object.keys(inlineOneboxes).length;
} }
function _loadInlineOneboxes(inline, ajax, topicId, categoryId) { function _loadInlineOneboxes(inline, ajax, topicId, categoryId) {
@ -61,18 +67,24 @@ function _loadInlineOneboxes(inline, ajax, topicId, categoryId) {
}); });
} }
function _loadOneboxes(oneboxes, ajax, count, topicId, categoryId, refresh) { function _loadOneboxes({
oneboxes,
ajax,
topicId,
categoryId,
refresh,
offline,
}) {
Object.values(oneboxes).forEach((onebox) => { Object.values(oneboxes).forEach((onebox) => {
onebox.forEach((o) => { onebox.forEach((elem) => {
load({ load({
elem: o, elem,
refresh,
ajax, ajax,
categoryId: categoryId, categoryId,
topicId: topicId, topicId,
refresh,
offline,
}); });
count++;
}); });
}); });
} }

View File

@ -6,6 +6,7 @@ export const PUBLIC_JS_VERSIONS = {
"Chart.min.js": "chart.js/2.9.3/Chart.min.js", "Chart.min.js": "chart.js/2.9.3/Chart.min.js",
"chartjs-plugin-datalabels.min.js": "chartjs-plugin-datalabels.min.js":
"chartjs-plugin-datalabels/0.7.0/chartjs-plugin-datalabels.min.js", "chartjs-plugin-datalabels/0.7.0/chartjs-plugin-datalabels.min.js",
"diffhtml.min.js": "diffhtml/1.0.0-beta.18/diffhtml.min.js",
"jquery.magnific-popup.min.js": "jquery.magnific-popup.min.js":
"magnific-popup/1.1.0/jquery.magnific-popup.min.js", "magnific-popup/1.1.0/jquery.magnific-popup.min.js",
"pikaday.js": "pikaday/1.8.0/pikaday.js", "pikaday.js": "pikaday/1.8.0/pikaday.js",

View File

@ -49,7 +49,11 @@
</div> </div>
<div class="d-editor-preview-wrapper {{if forcePreview "force-preview"}}"> <div class="d-editor-preview-wrapper {{if forcePreview "force-preview"}}">
<div class="d-editor-preview">{{html-safe preview}}</div> <div class="d-editor-preview">
{{#unless siteSettings.enable_diffhtml_preview}}
{{html-safe preview}}
{{/unless}}
</div>
{{plugin-outlet name="editor-preview" classNames="d-editor-plugin" args=outletArgs}} {{plugin-outlet name="editor-preview" classNames="d-editor-plugin" args=outletArgs}}
</div> </div>
</div> </div>

View File

@ -103,11 +103,12 @@ function loadNext(ajax) {
// It will insert a loading indicator and remove it when the loading is complete or fails. // It will insert a loading indicator and remove it when the loading is complete or fails.
export function load({ export function load({
elem, elem,
refresh = true,
ajax, ajax,
synchronous = false,
categoryId,
topicId, topicId,
categoryId,
refresh = true,
offline = false,
synchronous = false,
}) { }) {
const $elem = $(elem); const $elem = $(elem);
@ -134,6 +135,10 @@ export function load({
if (failed) { if (failed) {
return; return;
} }
if (offline) {
return;
}
} }
// Add the loading CSS class // Add the loading CSS class

View File

@ -173,15 +173,24 @@ function _loadShortUrls(uploads, ajax, siteSettings, opts) {
); );
} }
const SHORT_URL_ATTRIBUTES =
"img[data-orig-src], a[data-orig-href], source[data-orig-src]";
export function resolveCachedShortUrls(siteSettings, scope, opts) {
let shortUploadElements = scope.querySelectorAll(SHORT_URL_ATTRIBUTES);
if (shortUploadElements.length > 0) {
_loadCachedShortUrls(shortUploadElements, siteSettings, opts);
}
}
export function resolveAllShortUrls(ajax, siteSettings, scope, opts) { export function resolveAllShortUrls(ajax, siteSettings, scope, opts) {
const attributes = let shortUploadElements = scope.querySelectorAll(SHORT_URL_ATTRIBUTES);
"img[data-orig-src], a[data-orig-href], source[data-orig-src]";
let shortUploadElements = scope.querySelectorAll(attributes);
if (shortUploadElements.length > 0) { if (shortUploadElements.length > 0) {
_loadCachedShortUrls(shortUploadElements, siteSettings, opts); _loadCachedShortUrls(shortUploadElements, siteSettings, opts);
shortUploadElements = scope.querySelectorAll(attributes); shortUploadElements = scope.querySelectorAll(SHORT_URL_ATTRIBUTES);
if (shortUploadElements.length > 0) { if (shortUploadElements.length > 0) {
// this is carefully batched so we can do a leading debounce (trigger right away) // this is carefully batched so we can do a leading debounce (trigger right away)
return discourseDebounce( return discourseDebounce(

View File

@ -2184,6 +2184,8 @@ en:
max_allowed_message_recipients: "Maximum recipients allowed in a message." max_allowed_message_recipients: "Maximum recipients allowed in a message."
watched_words_regular_expressions: "Watched words are regular expressions." watched_words_regular_expressions: "Watched words are regular expressions."
enable_diffhtml_preview: "Experimental feature which uses diffHTML to sync preview instead of full re-render"
old_post_notice_days: "Days before post notice becomes old" old_post_notice_days: "Days before post notice becomes old"
new_user_notice_tl: "Minimum trust level required to see new user post notices." new_user_notice_tl: "Minimum trust level required to see new user post notices."
returning_user_notice_tl: "Minimum trust level required to see returning user post notices." returning_user_notice_tl: "Minimum trust level required to see returning user post notices."

View File

@ -976,6 +976,10 @@ posting:
hidden: true hidden: true
default: false default: false
client: true client: true
enable_diffhtml_preview:
hidden: true
default: false
client: true
old_post_notice_days: old_post_notice_days:
default: 14 default: 14
max: 36500 max: 36500

View File

@ -76,6 +76,9 @@ def dependencies
}, { }, {
source: 'chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.min.js', source: 'chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.min.js',
public: true public: true
}, {
source: 'diffhtml/dist/diffhtml.min.js',
public: true
}, { }, {
source: 'magnific-popup/dist/jquery.magnific-popup.min.js', source: 'magnific-popup/dist/jquery.magnific-popup.min.js',
public: true public: true

View File

@ -14,6 +14,7 @@
"bootstrap": "v3.4.1", "bootstrap": "v3.4.1",
"chart.js": "2.9.3", "chart.js": "2.9.3",
"chartjs-plugin-datalabels": "^0.7.0", "chartjs-plugin-datalabels": "^0.7.0",
"diffhtml": "^1.0.0-beta.18",
"eslint-config-discourse": "^1.1.8", "eslint-config-discourse": "^1.1.8",
"handlebars": "^4.7.0", "handlebars": "^4.7.0",
"highlight.js": "https://github.com/highlightjs/highlight.js", "highlight.js": "https://github.com/highlightjs/highlight.js",

1
public/javascripts/diffhtml.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -881,6 +881,13 @@ diff@^4.0.2:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
diffhtml@^1.0.0-beta.18:
version "1.0.0-beta.18"
resolved "https://registry.yarnpkg.com/diffhtml/-/diffhtml-1.0.0-beta.18.tgz#b5255f6eb9e358fa279b423ea7d168b061a9146d"
integrity sha512-DEsiAiSNxTE7vTpfRtT4SPm1cxQRahIbzvLXNKquOh95+BQOS24IGX6s7sJihYgk7XN6cYy2xqMwO+pEDWGtpg==
dependencies:
"@babel/plugin-proposal-class-properties" "^7.10.4"
dir-glob@^3.0.1: dir-glob@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"