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 { isEmpty } from "@ember/utils";
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 { inject as service } from "@ember/service";
import showModal from "discourse/lib/show-modal";
@ -389,6 +394,32 @@ export default Component.extend({
}
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", () => {
if (this._state !== "inDOM") {
return;

View File

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

View File

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

View File

@ -7,7 +7,8 @@ export function loadOneboxes(
topicId,
categoryId,
maxOneboxes,
refresh
refresh,
offline
) {
const oneboxes = {};
const inlineOneboxes = {};
@ -41,17 +42,22 @@ export function loadOneboxes(
}
});
let newBoxes = 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) {
_loadInlineOneboxes(inlineOneboxes, ajax, topicId, categoryId);
}
return newBoxes;
return Object.keys(oneboxes).length + Object.keys(inlineOneboxes).length;
}
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) => {
onebox.forEach((o) => {
onebox.forEach((elem) => {
load({
elem: o,
refresh,
elem,
ajax,
categoryId: categoryId,
topicId: topicId,
categoryId,
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",
"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":
"magnific-popup/1.1.0/jquery.magnific-popup.min.js",
"pikaday.js": "pikaday/1.8.0/pikaday.js",

View File

@ -49,7 +49,11 @@
</div>
<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}}
</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.
export function load({
elem,
refresh = true,
ajax,
synchronous = false,
categoryId,
topicId,
categoryId,
refresh = true,
offline = false,
synchronous = false,
}) {
const $elem = $(elem);
@ -134,6 +135,10 @@ export function load({
if (failed) {
return;
}
if (offline) {
return;
}
}
// 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) {
const attributes =
"img[data-orig-src], a[data-orig-href], source[data-orig-src]";
let shortUploadElements = scope.querySelectorAll(attributes);
let shortUploadElements = scope.querySelectorAll(SHORT_URL_ATTRIBUTES);
if (shortUploadElements.length > 0) {
_loadCachedShortUrls(shortUploadElements, siteSettings, opts);
shortUploadElements = scope.querySelectorAll(attributes);
shortUploadElements = scope.querySelectorAll(SHORT_URL_ATTRIBUTES);
if (shortUploadElements.length > 0) {
// this is carefully batched so we can do a leading debounce (trigger right away)
return discourseDebounce(

View File

@ -2184,6 +2184,8 @@ en:
max_allowed_message_recipients: "Maximum recipients allowed in a message."
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"
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."

View File

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

View File

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

View File

@ -14,6 +14,7 @@
"bootstrap": "v3.4.1",
"chart.js": "2.9.3",
"chartjs-plugin-datalabels": "^0.7.0",
"diffhtml": "^1.0.0-beta.18",
"eslint-config-discourse": "^1.1.8",
"handlebars": "^4.7.0",
"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"
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:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"