FIX: Do not cook icon with hashtags (#21676)

This commit makes some fundamental changes to how hashtag cooking and
icon generation works in the new experimental hashtag autocomplete mode.
Previously we cooked the appropriate SVG icon with the cooked hashtag,
though this has proved inflexible especially for theming purposes.

Instead, we now cook a data-ID attribute with the hashtag and add a new
span as an icon placeholder. This is replaced on the client side with an
icon (or a square span in the case of categories) on the client side via
the decorateCooked API for posts and chat messages.

This client side logic uses the generated hashtag, category, and channel
CSS classes added in a previous commit.

This is missing changes to the sidebar to use the new generated CSS
classes and also colors and the split square for categories in the
hashtag autocomplete menu -- I will tackle this in a separate PR so it
is clearer.
This commit is contained in:
Martin Brennan
2023-05-23 09:33:55 +02:00
committed by GitHub
parent ecb9a27e55
commit 0b3cf83e3c
36 changed files with 235 additions and 102 deletions

View File

@ -1,4 +1,5 @@
import { decorateGithubOneboxBody } from "discourse/initializers/onebox-decorators";
import { replaceHashtagIconPlaceholder } from "discourse/lib/hashtag-autocomplete";
import { withPluginApi } from "discourse/lib/plugin-api";
import highlightSyntax from "discourse/lib/highlight-syntax";
import I18n from "I18n";
@ -12,6 +13,7 @@ export default {
initializeWithPluginApi(api, container) {
const siteSettings = container.lookup("service:site-settings");
const site = container.lookup("service:site");
api.decorateChatMessage((element) => decorateGithubOneboxBody(element), {
id: "onebox-github-body",
});
@ -70,6 +72,11 @@ export default {
id: "lightbox",
}
);
api.decorateChatMessage(
(element) => replaceHashtagIconPlaceholder(element, site),
{ id: "hashtagIcons" }
);
},
_getScrollParent(node, maxParentSelector) {

View File

@ -28,7 +28,7 @@ export default {
}
withPluginApi("0.12.1", (api) => {
api.registerHashtagType("channel", ChannelHashtagType);
api.registerHashtagType("channel", new ChannelHashtagType(container));
api.registerChatComposerButton({
id: "chat-upload-btn",

View File

@ -1,4 +1,5 @@
import HashtagTypeBase from "discourse/lib/hashtag-types/base";
import { iconHTML } from "discourse-common/lib/icon-library";
import { inject as service } from "@ember/service";
export default class ChannelHashtagType extends HashtagTypeBase {
@ -17,9 +18,15 @@ export default class ChannelHashtagType extends HashtagTypeBase {
}
}
generateColorCssClasses(model) {
generateColorCssClasses(channel) {
return [
`.hashtag-color--${this.type}-${model.id} { color: var(--category-${model.chatable.id}-color); }`,
`.hashtag-cooked .d-icon.hashtag-color--${this.type}-${channel.id} { color: var(--category-${channel.chatable.id}-color); }`,
];
}
generateIconHTML(hashtag) {
return iconHTML(hashtag.icon, {
class: `hashtag-color--${this.type}-${hashtag.id}`,
});
}
}

View File

@ -18,6 +18,7 @@ module Chat
item.icon = icon
item.relative_url = channel.relative_url
item.type = "channel"
item.id = channel.id
end
end

View File

@ -195,7 +195,7 @@ describe Chat::ChannelArchiveService do
expect(@channel_archive.reload.complete?).to eq(true)
pm_topic = Topic.private_messages.last
expect(pm_topic.first_post.cooked).to include(
"<a class=\"hashtag-cooked\" href=\"#{channel.relative_url}\" data-type=\"channel\" data-slug=\"#{channel.slug}\" data-ref=\"#{channel.slug}::channel\"><svg class=\"fa d-icon d-icon-comment svg-icon svg-node\"><use href=\"#comment\"></use></svg><span>#{channel.title(user)}</span></a>",
"<a class=\"hashtag-cooked\" href=\"#{channel.relative_url}\" data-type=\"channel\" data-slug=\"#{channel.slug}\" data-id=\"#{channel.id}\" data-ref=\"#{channel.slug}::channel\"><span class=\"hashtag-icon-placeholder\"></span><span>#{channel.title(user)}</span></a>",
)
end
end

View File

@ -41,6 +41,7 @@ RSpec.describe Chat::ChannelHashtagDataSource do
text: "Zany Things",
description: "Just weird stuff",
icon: "comment",
id: channel1.id,
type: "channel",
ref: nil,
slug: "random",
@ -60,6 +61,7 @@ RSpec.describe Chat::ChannelHashtagDataSource do
text: "Secret Stuff",
description: nil,
icon: "comment",
id: channel2.id,
type: "channel",
ref: nil,
slug: "secret",
@ -94,6 +96,7 @@ RSpec.describe Chat::ChannelHashtagDataSource do
text: "Zany Things",
description: "Just weird stuff",
icon: "comment",
id: channel1.id,
type: "channel",
ref: nil,
slug: "random",
@ -109,6 +112,7 @@ RSpec.describe Chat::ChannelHashtagDataSource do
text: "Zany Things",
description: "Just weird stuff",
icon: "comment",
id: channel1.id,
type: "channel",
ref: nil,
slug: "random",
@ -127,6 +131,7 @@ RSpec.describe Chat::ChannelHashtagDataSource do
text: "Secret Stuff",
description: nil,
icon: "comment",
id: channel2.id,
type: "channel",
ref: nil,
slug: "secret",

View File

@ -259,7 +259,7 @@ describe Chat::Message do
cooked = described_class.cook("##{category.slug}", user_id: user.id)
expect(cooked).to eq(
"<p><a class=\"hashtag-cooked\" href=\"#{category.url}\" data-type=\"category\" data-slug=\"#{category.slug}\"><svg class=\"fa d-icon d-icon-folder svg-icon svg-node\"><use href=\"#folder\"></use></svg><span>#{category.name}</span></a></p>",
"<p><a class=\"hashtag-cooked\" href=\"#{category.url}\" data-type=\"category\" data-slug=\"#{category.slug}\" data-id=\"#{category.id}\"><span class=\"hashtag-icon-placeholder\"></span><span>#{category.name}</span></a></p>",
)
end

View File

@ -71,13 +71,13 @@ describe "Using #hashtag autocompletion to search for and lookup channels",
cooked_hashtags = page.all(".hashtag-cooked", count: 3)
expect(cooked_hashtags[0]["outerHTML"]).to eq(<<~HTML.chomp)
<a class=\"hashtag-cooked\" href=\"#{channel2.relative_url}\" data-type=\"channel\" data-slug=\"random\"><svg class=\"fa d-icon d-icon-comment svg-icon svg-node\"><use href=\"#comment\"></use></svg><span>Random</span></a>
<a class=\"hashtag-cooked\" href=\"#{channel2.relative_url}\" data-type=\"channel\" data-slug=\"random\" data-id=\"#{channel2.id}\"><svg class=\"fa d-icon d-icon-comment svg-icon hashtag-color--channel-#{channel2.id} svg-string\" xmlns=\"http://www.w3.org/2000/svg\"><use href=\"#comment\"></use></svg><span>Random</span></a>
HTML
expect(cooked_hashtags[1]["outerHTML"]).to eq(<<~HTML.chomp)
<a class=\"hashtag-cooked\" href=\"#{category.url}\" data-type=\"category\" data-slug=\"raspberry-beret\"><svg class=\"fa d-icon d-icon-folder svg-icon svg-node\"><use href=\"#folder\"></use></svg><span>Raspberry</span></a>
<a class=\"hashtag-cooked\" href=\"#{category.url}\" data-type=\"category\" data-slug=\"raspberry-beret\" data-id="#{category.id}"><span class=\"hashtag-category-badge hashtag-color--category-#{category.id}\"></span><span>Raspberry</span></a>
HTML
expect(cooked_hashtags[2]["outerHTML"]).to eq(<<~HTML.chomp)
<a class=\"hashtag-cooked\" href=\"#{tag.url}\" data-type=\"tag\" data-slug=\"razed\"><svg class=\"fa d-icon d-icon-tag svg-icon svg-node\"><use href=\"#tag\"></use></svg><span>razed</span></a>
<a class=\"hashtag-cooked\" href=\"#{tag.url}\" data-type=\"tag\" data-slug=\"razed\" data-id="#{tag.id}"><svg class=\"fa d-icon d-icon-tag svg-icon hashtag-color--tag-#{tag.id} svg-string\" xmlns=\"http://www.w3.org/2000/svg\"><use href=\"#tag\"></use></svg><span>razed</span></a>
HTML
end
end

View File

@ -59,7 +59,7 @@ acceptance("Chat | Hashtag CSS Generator", function (needs) {
assert.equal(
cssTag.innerHTML,
".hashtag-color--category-1 {\n background: linear-gradient(90deg, var(--category-1-color) 50%, var(--category-1-color) 50%);\n}\n.hashtag-color--category-2 {\n background: linear-gradient(90deg, var(--category-2-color) 50%, var(--category-2-color) 50%);\n}\n.hashtag-color--category-4 {\n background: linear-gradient(90deg, var(--category-4-color) 50%, var(--category-1-color) 50%);\n}\n.hashtag-color--channel-44 { color: var(--category-1-color); }\n.hashtag-color--channel-74 { color: var(--category-2-color); }\n.hashtag-color--channel-88 { color: var(--category-4-color); }"
".hashtag-color--category-1 {\n background: linear-gradient(90deg, var(--category-1-color) 50%, var(--category-1-color) 50%);\n}\n.hashtag-color--category-2 {\n background: linear-gradient(90deg, var(--category-2-color) 50%, var(--category-2-color) 50%);\n}\n.hashtag-color--category-4 {\n background: linear-gradient(90deg, var(--category-4-color) 50%, var(--category-1-color) 50%);\n}\n.hashtag-cooked .d-icon.hashtag-color--channel-44 { color: var(--category-1-color); }\n.hashtag-cooked .d-icon.hashtag-color--channel-74 { color: var(--category-2-color); }\n.hashtag-cooked .d-icon.hashtag-color--channel-88 { color: var(--category-4-color); }"
);
});
});