mirror of
https://github.com/discourse/discourse.git
synced 2025-04-25 02:14:28 +08:00
DEV: Refactor chat HTML decorating (#31309)
Uses the new `<DecoratedHtml` component, which takes care of the full decorate/render lifecycle. That means we can drop all the custom modifiers/debouncing which chat was doing. It also naturally adds support for `helper.renderGlimmer` in chat decorations. This should resolve a number of subtle bugs related to chat message decorations.
This commit is contained in:
parent
b2b9657a0b
commit
a0f681b256
@ -1,9 +1,13 @@
|
||||
<div class="chat-message-collapser">
|
||||
{{#if this.hasUploads}}
|
||||
{{html-safe @cooked}}
|
||||
<DecoratedHtml
|
||||
@html={{html-safe @cooked}}
|
||||
@decorate={{@decorate}}
|
||||
@className="chat-cooked"
|
||||
/>
|
||||
|
||||
<Collapser @header={{this.uploadsHeader}} @onToggle={{@onToggleCollapse}}>
|
||||
<div class="chat-uploads">
|
||||
<div class="chat-uploads" {{didInsert this.lightbox}}>
|
||||
{{#each @uploads as |upload|}}
|
||||
<ChatUpload @upload={{upload}} />
|
||||
{{/each}}
|
||||
@ -18,11 +22,19 @@
|
||||
<LazyVideo @videoAttributes={{cooked.videoAttributes}} />
|
||||
</div>
|
||||
{{else}}
|
||||
{{cooked.body}}
|
||||
<DecoratedHtml
|
||||
@html={{html-safe cooked.body}}
|
||||
@decorate={{@decorate}}
|
||||
@className="chat-cooked"
|
||||
/>
|
||||
{{/if}}
|
||||
</Collapser>
|
||||
{{else}}
|
||||
{{cooked.body}}
|
||||
<DecoratedHtml
|
||||
@html={{html-safe cooked.body}}
|
||||
@decorate={{@decorate}}
|
||||
@className="chat-cooked"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import domFromString from "discourse/lib/dom-from-string";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import lightbox from "../lib/lightbox";
|
||||
|
||||
export default class ChatMessageCollapser extends Component {
|
||||
@service siteSettings;
|
||||
@ -66,12 +68,17 @@ export default class ChatMessageCollapser extends Component {
|
||||
`<a target="_blank" class="chat-message-collapser-link" rel="noopener noreferrer" href="${link}">${title}</a>`
|
||||
);
|
||||
|
||||
acc.push({ header, body: e, videoAttributes, needsCollapser: true });
|
||||
acc.push({
|
||||
header,
|
||||
body: e.outerHTML,
|
||||
videoAttributes,
|
||||
needsCollapser: true,
|
||||
});
|
||||
} else {
|
||||
acc.push({ body: e, needsCollapser: false });
|
||||
acc.push({ body: e.outerHTML, needsCollapser: false });
|
||||
}
|
||||
} else {
|
||||
acc.push({ body: e, needsCollapser: false });
|
||||
acc.push({ body: e.outerHTML, needsCollapser: false });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
@ -88,9 +95,9 @@ export default class ChatMessageCollapser extends Component {
|
||||
const header = htmlSafe(
|
||||
`<a target="_blank" class="chat-message-collapser-link-small" rel="noopener noreferrer" href="${link}">${link}</a>`
|
||||
);
|
||||
acc.push({ header, body: e, needsCollapser: true });
|
||||
acc.push({ header, body: e.outerHTML, needsCollapser: true });
|
||||
} else {
|
||||
acc.push({ body: e, needsCollapser: false });
|
||||
acc.push({ body: e.outerHTML, needsCollapser: false });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
@ -106,9 +113,9 @@ export default class ChatMessageCollapser extends Component {
|
||||
alt || link
|
||||
}</a>`
|
||||
);
|
||||
acc.push({ header, body: e, needsCollapser: true });
|
||||
acc.push({ header, body: e.outerHTML, needsCollapser: true });
|
||||
} else {
|
||||
acc.push({ body: e, needsCollapser: false });
|
||||
acc.push({ body: e.outerHTML, needsCollapser: false });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
@ -125,13 +132,18 @@ export default class ChatMessageCollapser extends Component {
|
||||
const header = htmlSafe(
|
||||
`<a target="_blank" class="chat-message-collapser-link-small" rel="noopener noreferrer" href="${link}">${title}</a>`
|
||||
);
|
||||
acc.push({ header, body: e, needsCollapser: true });
|
||||
acc.push({ header, body: e.outerHTML, needsCollapser: true });
|
||||
} else {
|
||||
acc.push({ body: e, needsCollapser: false });
|
||||
acc.push({ body: e.outerHTML, needsCollapser: false });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
@action
|
||||
lightbox(element) {
|
||||
lightbox(element.querySelectorAll("img.chat-img-upload"));
|
||||
}
|
||||
}
|
||||
|
||||
function lazyVideoPredicate(e) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import DecoratedHtml from "discourse/components/decorated-html";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import { isCollapsible } from "discourse/plugins/chat/discourse/components/chat-message-collapser";
|
||||
import ChatMessageCollapser from "./chat-message-collapser";
|
||||
@ -18,11 +19,16 @@ export default class ChatMessageText extends Component {
|
||||
{{#if this.isCollapsible}}
|
||||
<ChatMessageCollapser
|
||||
@cooked={{@cooked}}
|
||||
@decorate={{@decorate}}
|
||||
@uploads={{@uploads}}
|
||||
@onToggleCollapse={{@onToggleCollapse}}
|
||||
/>
|
||||
{{else}}
|
||||
{{htmlSafe @cooked}}
|
||||
<DecoratedHtml
|
||||
@html={{htmlSafe @cooked}}
|
||||
@decorate={{@decorate}}
|
||||
@className=" chat-cooked"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.isEdited}}
|
||||
|
@ -5,8 +5,6 @@ import { concat, fn } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { getOwner } from "@ember/owner";
|
||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
||||
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
||||
import { cancel, schedule } from "@ember/runloop";
|
||||
import { service } from "@ember/service";
|
||||
@ -186,7 +184,6 @@ export default class ChatMessage extends Component {
|
||||
cancel(this._invitationSentTimer);
|
||||
cancel(this._disableMessageActionsHandler);
|
||||
cancel(this._makeMessageActiveHandler);
|
||||
cancel(this._debounceDecorateCookedMessageHandler);
|
||||
this.#teardownMentionedUsers();
|
||||
this.chat.activeMessage = null;
|
||||
}
|
||||
@ -207,36 +204,6 @@ export default class ChatMessage extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
didInsertMessage(element) {
|
||||
this.messageContainer = element;
|
||||
this.initMentionedUsers();
|
||||
this.decorateMentions(element);
|
||||
this.debounceDecorateCookedMessage();
|
||||
this.refreshStatusOnMentions();
|
||||
}
|
||||
|
||||
@action
|
||||
didUpdateMessageId() {
|
||||
this.debounceDecorateCookedMessage();
|
||||
}
|
||||
|
||||
@action
|
||||
didUpdateMessageVersion() {
|
||||
this.debounceDecorateCookedMessage();
|
||||
this.refreshStatusOnMentions();
|
||||
this.initMentionedUsers();
|
||||
}
|
||||
|
||||
debounceDecorateCookedMessage() {
|
||||
this._debounceDecorateCookedMessageHandler = discourseDebounce(
|
||||
this,
|
||||
this.decorateCookedMessage,
|
||||
this.args.message,
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
initMentionedUsers() {
|
||||
this.args.message.mentionedUsers.forEach((user) => {
|
||||
if (!user.statusManager.isTrackingStatus()) {
|
||||
@ -273,16 +240,18 @@ export default class ChatMessage extends Component {
|
||||
|
||||
mentions.forEach((mention) => {
|
||||
mention.classList.add(...classes);
|
||||
updateUserStatusOnMention(getOwner(this), mention, user.status);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
decorateCookedMessage(message) {
|
||||
schedule("afterRender", () => {
|
||||
_chatMessageDecorators.forEach((decorator) => {
|
||||
decorator.call(this, this.messageContainer, message.channel);
|
||||
});
|
||||
@bind
|
||||
decorateCookedMessage(element, helper) {
|
||||
this.messageContainer = element;
|
||||
this.initMentionedUsers();
|
||||
this.decorateMentions(element);
|
||||
_chatMessageDecorators.forEach((decorator) => {
|
||||
decorator(element, helper);
|
||||
});
|
||||
}
|
||||
|
||||
@ -598,9 +567,6 @@ export default class ChatMessage extends Component {
|
||||
}}
|
||||
data-id={{@message.id}}
|
||||
data-thread-id={{@message.thread.id}}
|
||||
{{didInsert this.didInsertMessage}}
|
||||
{{didUpdate this.didUpdateMessageId @message.id}}
|
||||
{{didUpdate this.didUpdateMessageVersion @message.version}}
|
||||
{{willDestroy this.willDestroyMessage}}
|
||||
{{on "mouseenter" this.onMouseEnter passive=true}}
|
||||
{{on "mouseleave" this.onMouseLeave passive=true}}
|
||||
@ -665,6 +631,7 @@ export default class ChatMessage extends Component {
|
||||
@cooked={{@message.cooked}}
|
||||
@uploads={{@message.uploads}}
|
||||
@edited={{@message.edited}}
|
||||
@decorate={{this.decorateCookedMessage}}
|
||||
>
|
||||
{{#if @message.reactions.length}}
|
||||
<div class="chat-message-reaction-list">
|
||||
|
@ -1,13 +1,11 @@
|
||||
import $ from "jquery";
|
||||
import { spinnerHTML } from "discourse/helpers/loading-spinner";
|
||||
import { decorateGithubOneboxBody } from "discourse/instance-initializers/onebox-decorators";
|
||||
import { samePrefix } from "discourse/lib/get-url";
|
||||
import { decorateHashtags } from "discourse/lib/hashtag-decorator";
|
||||
import highlightSyntax from "discourse/lib/highlight-syntax";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import lightbox from "../lib/lightbox";
|
||||
|
||||
export default {
|
||||
name: "chat-decorators",
|
||||
@ -68,7 +66,7 @@ export default {
|
||||
});
|
||||
api.decorateChatMessage(
|
||||
(element) =>
|
||||
this.lightbox(element.querySelectorAll("img:not(.emoji, .avatar)")),
|
||||
lightbox(element.querySelectorAll("img:not(.emoji, .avatar)")),
|
||||
{
|
||||
id: "lightbox",
|
||||
}
|
||||
@ -108,15 +106,9 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentUserTimezone) {
|
||||
dateTimeLinkEl.innerText = moment
|
||||
.tz(dateTimeRaw, this.currentUserTimezone)
|
||||
.format(i18n("dates.long_no_year"));
|
||||
} else {
|
||||
dateTimeLinkEl.innerText = moment(dateTimeRaw).format(
|
||||
i18n("dates.long_no_year")
|
||||
);
|
||||
}
|
||||
dateTimeLinkEl.innerText = moment(dateTimeRaw).format(
|
||||
i18n("dates.long_no_year")
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
@ -132,29 +124,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
lightbox(images) {
|
||||
loadScript("/javascripts/jquery.magnific-popup.min.js").then(function () {
|
||||
$(images).magnificPopup({
|
||||
type: "image",
|
||||
closeOnContentClick: false,
|
||||
mainClass: "mfp-zoom-in",
|
||||
tClose: i18n("lightbox.close"),
|
||||
tLoading: spinnerHTML,
|
||||
image: {
|
||||
verticalFit: true,
|
||||
},
|
||||
gallery: {
|
||||
enabled: true,
|
||||
},
|
||||
callbacks: {
|
||||
elementParse: (item) => {
|
||||
item.src = item.el[0].dataset.largeSrc || item.el[0].src;
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
initialize(container) {
|
||||
if (container.lookup("service:chat").userCanChat) {
|
||||
withPluginApi("0.8.42", (api) =>
|
||||
|
27
plugins/chat/assets/javascripts/discourse/lib/lightbox.js
Normal file
27
plugins/chat/assets/javascripts/discourse/lib/lightbox.js
Normal file
@ -0,0 +1,27 @@
|
||||
import $ from "jquery";
|
||||
import { spinnerHTML } from "discourse/helpers/loading-spinner";
|
||||
import loadScript from "discourse/lib/load-script";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default function lightbox(images) {
|
||||
loadScript("/javascripts/jquery.magnific-popup.min.js").then(function () {
|
||||
$(images).magnificPopup({
|
||||
type: "image",
|
||||
closeOnContentClick: false,
|
||||
mainClass: "mfp-zoom-in",
|
||||
tClose: i18n("lightbox.close"),
|
||||
tLoading: spinnerHTML,
|
||||
image: {
|
||||
verticalFit: true,
|
||||
},
|
||||
gallery: {
|
||||
enabled: true,
|
||||
},
|
||||
callbacks: {
|
||||
elementParse: (item) => {
|
||||
item.src = item.el[0].dataset.largeSrc || item.el[0].src;
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
@ -200,8 +200,7 @@ body.has-full-page-chat {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.chat-message-collapser,
|
||||
.chat-message-text {
|
||||
.chat-cooked {
|
||||
> p {
|
||||
margin: 0.5em 0 0.5em;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user