FIX: Add hashtag placeholder when chat message sent (#23287)

This commit fixes an issue from 2ecc8291e8
where the user sees an ugly plain #hashtag when sending a chat
message. Now, we add a basic placeholder that looks like the
cooked hashtag with a grey square, which is then filled in
once the "sent" message bus event for the message comes back,
and we do decorateCooked on the message to fill in the proper
hashtag details.
This commit is contained in:
Martin Brennan 2023-08-30 11:31:34 +10:00 committed by GitHub
parent 875a817410
commit bbd908ae09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 37 deletions

View File

@ -48,6 +48,22 @@ export function decorateHashtags(element, site) {
});
}
export function generatePlaceholderHashtagHTML(type, spanEl, data) {
// NOTE: When changing the HTML structure here, you must also change
// it in the hashtag-autocomplete markdown rule, and vice-versa.
const link = document.createElement("a");
link.classList.add("hashtag-cooked");
link.href = data.relative_url;
link.dataset.type = type;
link.dataset.id = data.id;
link.dataset.slug = data.slug;
const hashtagTypeClass = new getHashtagTypeClasses()[type];
link.innerHTML = `${hashtagTypeClass.generateIconHTML(
data
)}<span>${emojiUnescape(data.text)}</span>`;
spanEl.replaceWith(link);
}
/**
* Sets up a textarea using the jQuery autocomplete plugin, specifically
* to match on the hashtag (#) character for autocompletion of categories,
@ -243,19 +259,7 @@ function _findAndReplaceSeenHashtagPlaceholder(
// Replace raw span for the hashtag with a cooked one
const matchingSeenHashtag = seenHashtags[type]?.[slugRef];
if (matchingSeenHashtag) {
// NOTE: When changing the HTML structure here, you must also change
// it in the hashtag-autocomplete markdown rule, and vice-versa.
const link = document.createElement("a");
link.classList.add("hashtag-cooked");
link.href = matchingSeenHashtag.relative_url;
link.dataset.type = type;
link.dataset.id = matchingSeenHashtag.id;
link.dataset.slug = matchingSeenHashtag.slug;
const hashtagType = new getHashtagTypeClasses()[type];
link.innerHTML = `${hashtagType.generateIconHTML(
matchingSeenHashtag
)}<span>${emojiUnescape(matchingSeenHashtag.text)}</span>`;
hashtagSpan.replaceWith(link);
generatePlaceholderHashtagHTML(type, hashtagSpan, matchingSeenHashtag);
}
});
}

View File

@ -78,10 +78,24 @@ function addHashtag(buffer, matches, state) {
// Instead, the UI will dynamically replace these where hashtags
// are rendered, like within posts, using decorateCooked* APIs.
function addIconPlaceholder(buffer, state) {
const token = new state.Token("span_open", "span", 1);
let token = new state.Token("span_open", "span", 1);
token.block = false;
token.attrs = [["class", "hashtag-icon-placeholder"]];
buffer.push(token);
token = new state.Token("svg_open", "svg", 1);
token.block = false;
token.attrs = [["class", `fa d-icon d-icon-square-full svg-icon svg-node`]];
buffer.push(token);
token = new state.Token("use_open", "use", 1);
token.block = false;
token.attrs = [["href", "#square-full"]];
buffer.push(token);
buffer.push(new state.Token("use_close", "use", -1));
buffer.push(new state.Token("svg_close", "svg", -1));
buffer.push(new state.Token("span_close", "span", -1));
}
@ -99,6 +113,8 @@ export function setup(helper) {
"a.hashtag-cooked",
"span.hashtag-raw",
"span.hashtag-icon-placeholder",
"svg[class=fa d-icon d-icon-square-full svg-icon svg-node]",
"use[href=#square-full]",
"a[data-type]",
"a[data-slug]",
"a[data-ref]",

View File

@ -22,12 +22,26 @@ a.hashtag {
color: var(--primary);
}
.d-icon {
.hashtag-icon-placeholder {
width: 0.72em;
height: 0.72em;
display: inline-block;
color: var(--primary-medium);
}
.d-icon,
.hashtag-icon-placeholder {
font-size: var(--font-down-2);
margin: 0 0.33em 0 0.1em;
&.hashtag-missing {
color: var(--primary-medium);
&.d-icon-square-full {
width: 8px;
height: 10px;
margin-bottom: 0;
margin-right: 0.7em;
}
}
}

View File

@ -0,0 +1,36 @@
import getURL from "discourse-common/lib/get-url";
import { generatePlaceholderHashtagHTML } from "discourse/lib/hashtag-autocomplete";
const domParser = new DOMParser();
export default function transformAutolinks(cooked) {
const html = domParser.parseFromString(cooked, "text/html");
transformMentions(html);
transformHashtags(html);
return html.body.innerHTML;
}
function transformMentions(html) {
(html.querySelectorAll("span.mention") || []).forEach((mentionSpan) => {
let mentionLink = document.createElement("a");
let mentionText = document.createTextNode(mentionSpan.innerText);
mentionLink.classList.add("mention");
mentionLink.appendChild(mentionText);
mentionLink.href = getURL(`/u/${mentionSpan.innerText.substring(1)}`);
mentionSpan.replaceWith(mentionLink);
});
}
function transformHashtags(html) {
(html.querySelectorAll("span.hashtag-raw") || []).forEach((hashtagSpan) => {
// Doesn't matter what "type" of hashtag we use here, it will get replaced anyway,
// this is just for the placeholder HTML.
generatePlaceholderHashtagHTML("category", hashtagSpan, {
id: -1,
text: "...",
relative_url: "/",
slug: "",
icon: "square-full",
});
});
}

View File

@ -1,20 +0,0 @@
import getURL from "discourse-common/lib/get-url";
const domParser = new DOMParser();
export default function transformMentions(cooked) {
const html = domParser.parseFromString(cooked, "text/html");
transform(html);
return html.body.innerHTML;
}
function transform(html) {
(html.querySelectorAll("span.mention") || []).forEach((mentionSpan) => {
let mentionLink = document.createElement("a");
let mentionText = document.createTextNode(mentionSpan.innerText);
mentionLink.classList.add("mention");
mentionLink.appendChild(mentionText);
mentionLink.href = getURL(`/u/${mentionSpan.innerText.substring(1)}`);
mentionSpan.parentNode.replaceChild(mentionLink, mentionSpan);
});
}

View File

@ -5,7 +5,7 @@ import ChatMessageReaction from "discourse/plugins/chat/discourse/models/chat-me
import Bookmark from "discourse/models/bookmark";
import I18n from "I18n";
import { generateCookFunction } from "discourse/lib/text";
import transformMentions from "discourse/plugins/chat/discourse/lib/transform-mentions";
import transformAutolinks from "discourse/plugins/chat/discourse/lib/transform-auto-links";
import { getOwner } from "discourse-common/lib/get-owner";
import discourseLater from "discourse-common/lib/later";
@ -192,7 +192,7 @@ export default class ChatMessage {
} else {
const cookFunction = await generateCookFunction(markdownOptions);
ChatMessage.cookFunction = (raw) => {
return transformMentions(cookFunction(raw));
return transformAutolinks(cookFunction(raw));
};
this.cooked = ChatMessage.cookFunction(this.message);