FEATURE: Use diffhtml to update composer preview ()

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
app/assets/javascripts
config
lib/tasks
package.json
public/javascripts
diffhtml.min.js
diffhtml/1.0.0-beta.18
yarn.lock

@ -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;

@ -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

@ -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");
}
});
}

@ -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++;
});
});
}

@ -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",

@ -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>

@ -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

@ -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(

@ -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."

@ -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

@ -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

@ -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

@ -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"