DEV: Fix cooked content rendering issues in the Glimmer Post Stream (#32670)

This commit is contained in:
Sérgio Saquetim 2025-05-12 16:23:08 -03:00 committed by GitHub
parent 0658773056
commit c2ae2e244c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 108 additions and 14 deletions

View File

@ -1,5 +1,4 @@
import Component from "@glimmer/component";
import { hasInternalComponentManager } from "@glimmer/manager";
import { untrack } from "@glimmer/validator";
import { htmlSafe, isHTMLSafe } from "@ember/template";
import { TrackedArray } from "@ember-compat/tracked-built-ins";
@ -86,7 +85,7 @@ class DecorateHtmlHelper {
return;
}
if (!hasInternalComponentManager(component)) {
if (component.name === "factory") {
deprecated(
"Invalid `component` passed to `helper.renderGlimmer` while using `api.decorateCookedElement` with the Glimmer Post Stream. `component` must be a valid Glimmer component. If using a template compiled via ember-cli-htmlbars, replace it with the `<template>...</template>` syntax. This call has been ignored to prevent errors.",
POST_STREAM_DEPRECATION_OPTIONS

View File

@ -63,14 +63,14 @@ export default class PostCookedHtml extends Component {
createDetachedElement: this.#createDetachedElement,
currentUser: this.currentUser,
helper,
renderNestedCookedContent: (
renderNestedPostCookedHtml: (
nestedElement,
cooked,
nestedPost,
extraDecorators
) => {
const nestedArguments = {
cooked,
post: this.args.post,
post: nestedPost,
streamElement: false,
highlightTerm: this.highlightTerm,
extraDecorators: [
...this.extraDecorators,

View File

@ -47,14 +47,14 @@ export default function (element, context) {
// cleanup code
return () => {
state.extractedMentions = [];
if (userStatusService.isEnabled) {
post?.mentioned_users?.forEach((user) => {
state.extractedMentions.forEach(({ user }) => {
user.statusManager?.stopTrackingStatus?.();
user.off?.("status-changed", element, _updateUserStatus);
});
}
state.extractedMentions = [];
};
}

View File

@ -116,7 +116,7 @@ function _updateQuoteElements(aside, desc, context) {
}
async function _toggleQuote(aside, context) {
const { createDetachedElement, data, renderNestedCookedContent, state } =
const { createDetachedElement, data, renderNestedPostCookedHtml, state } =
context;
if (state.expanding) {
@ -154,19 +154,20 @@ async function _toggleQuote(aside, context) {
const postId = parseInt(aside.dataset.post, 10);
try {
const result = await ajax(`/posts/by_number/${topicId}/${postId}`);
const quotedPost = await ajax(`/posts/by_number/${topicId}/${postId}`);
const post = data.post;
const quotedPosts = post.quoted || {};
quotedPosts[result.id] = result;
quotedPosts[quotedPost.id] = quotedPost;
post.set("quoted", quotedPosts);
const div = createDetachedElement("div");
div.classList.add("expanded-quote");
div.dataset.postId = result.id;
div.dataset.postId = quotedPost.id;
// inception
renderNestedCookedContent(div, result.cooked, (element) =>
renderNestedPostCookedHtml(div, quotedPost, (element) =>
// to highlight the quoted text inside the original post content
highlightHTML(element, originalText, {
matchCase: true,
})

View File

@ -644,6 +644,68 @@ import { i18n } from "discourse-i18n";
}
);
acceptance(
`Cooked quoted content (glimmer_post_stream_mode = ${postStreamMode})`,
function (needs) {
needs.settings({
glimmer_post_stream_mode: postStreamMode,
});
needs.pretender((server, helper) => {
server.get("/posts/by_number/280/3", () =>
helper.response(
200,
topicFixtures["/t/280/1.json"].post_stream.posts[2]
)
);
});
test("The quoted content is toggled correclty", async function (assert) {
await visit("/t/internationalization-localization/280");
assert
.dom(
"#post_5 .cooked .quote[data-topic='280'][data-post='3'] .quote-controls .quote-toggle"
)
.hasAttribute("aria-expanded", "false");
assert
.dom("#post_5 .quote[data-topic='280'][data-post='3']")
.includesText(
'So you could replace that lookup table with the "de" one to get German.'
)
.doesNotIncludeText(
"Yep, all strings are going through a lookup table.*"
);
await click(
"#post_5 .cooked .quote[data-topic='280'][data-post='3'] .quote-controls .quote-toggle"
);
assert
.dom(
"#post_5 .cooked .quote[data-topic='280'][data-post='3'] .quote-controls .quote-toggle"
)
.hasAttribute("aria-expanded", "true");
assert
.dom("#post_5 .quote[data-topic='280'][data-post='3']")
.includesText(
'So you could replace that lookup table with the "de" one to get German.'
)
.includesText("Yep, all strings are going through a lookup table.*");
await click(
"#post_5 .cooked .quote[data-topic='280'][data-post='3'] .quote-controls .quote-toggle"
);
assert
.dom(
"#post_5 .cooked .quote[data-topic='280'][data-post='3'] .quote-controls .quote-toggle"
)
.hasAttribute("aria-expanded", "false");
});
}
);
acceptance(
`Topic stats update automatically (glimmer_post_stream_mode = ${postStreamMode})`,
function (needs) {

View File

@ -2,9 +2,11 @@ import { tracked } from "@glimmer/tracking";
import { htmlSafe } from "@ember/template";
import { render, settled } from "@ember/test-helpers";
import { hbs } from "ember-cli-htmlbars";
import curryComponent from "ember-curry-component";
import { module, test } from "qunit";
import DecoratedHtml from "discourse/components/decorated-html";
import { withSilencedDeprecations } from "discourse/lib/deprecated";
import { getOwnerWithFallback } from "discourse/lib/get-owner";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
module("Integration | Component | <DecoratedHtml />", function (hooks) {
@ -51,6 +53,36 @@ module("Integration | Component | <DecoratedHtml />", function (hooks) {
assert.dom("#render-glimmer").hasText("Hello from Glimmer Component");
});
test("can decorate content with renderGlimmer using a curried componet", async function (assert) {
const state = new (class {
@tracked html = htmlSafe("<h1>Initial</h1>");
})();
const decorate = (element, helper) => {
element.innerHTML += "<div id='appended'>Appended</div>";
helper.renderGlimmer(
element,
curryComponent(
<template>
<div id="render-glimmer">Hello from {{@value}} Component</div>
</template>,
{ value: "Curried" },
getOwnerWithFallback(this)
)
);
};
await render(
<template>
<DecoratedHtml @html={{state.html}} @decorate={{decorate}} />
</template>
);
assert.dom("h1").hasText("Initial");
assert.dom("#appended").hasText("Appended");
assert.dom("#render-glimmer").hasText("Hello from Curried Component");
});
test("renderGlimmer is ignored if receives invalid arguments", async function (assert) {
const state = new (class {
@tracked html = htmlSafe("<h1>Initial</h1>");