Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/webpage/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Guild} from "./guild.js";
import {I18n} from "./i18n.js";
import {Dialog} from "./settings.js";
import {Contextmenu} from "./contextmenu.js";
import {setTextWithWrappedEmoji} from "./utils/utils.js";
const linkMenu = new Contextmenu<string, void>("copyLink", true);
linkMenu.addButton(
() => I18n.copyRegLink(),
Expand Down Expand Up @@ -629,6 +630,7 @@ class MarkDown {
mention.classList.add("mentionMD");
mention.contentEditable = "false";
mention.textContent = everyone ? "@everyone" : "@here";

appendcurrent();
span.appendChild(mention);
mention.setAttribute("real", everyone ? `@everyone` : "@here");
Expand Down Expand Up @@ -670,17 +672,21 @@ class MarkDown {
const role = this.channel.guild.roleids.get(id);
if (role) {
mention.textContent = `@${role.name}`;
mention.style.color = `var(--role-${role.id})`;
setTextWithWrappedEmoji(mention, mention.textContent || "");
mention.style.setProperty("--userbg", `var(--role-${role.id})`);
} else {
mention.textContent = I18n.guild.unknownRole();
setTextWithWrappedEmoji(mention, mention.textContent || "");
}
}
} else {
(async () => {
mention.textContent = I18n.userping.resolving();
setTextWithWrappedEmoji(mention, mention.textContent || "");
const user = await this.localuser?.getUser(id);
if (user) {
mention.textContent = `@${user.name}`;
setTextWithWrappedEmoji(mention, mention.textContent || "");
let guild: null | Guild = null;
if (this.channel) {
guild = this.channel.guild;
Expand All @@ -692,11 +698,13 @@ class MarkDown {
guild.resolveMember(user).then((member) => {
if (member) {
mention.textContent = `@${member.name}`;
setTextWithWrappedEmoji(mention, mention.textContent || "");
}
});
}
} else {
mention.textContent = I18n.userping.unknown();
setTextWithWrappedEmoji(mention, mention.textContent || "");
}
})();
}
Expand All @@ -705,6 +713,7 @@ class MarkDown {
const channel = this.localuser.channelids.get(id);
if (channel) {
mention.textContent = `#${channel.name}`;
setTextWithWrappedEmoji(mention, mention.textContent || "");
if (!keep && !stdsize) {
mention.onclick = (_) => {
if (!this.localuser) return;
Expand All @@ -713,6 +722,7 @@ class MarkDown {
}
} else {
mention.textContent = "#unknown";
setTextWithWrappedEmoji(mention, mention.textContent || "");
}
break;
}
Expand Down
4 changes: 3 additions & 1 deletion src/webpage/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {highMemberJSON, memberjson, presencejson} from "./jsontypes.js";
import {I18n} from "./i18n.js";
import {Dialog, Options, Settings} from "./settings.js";
import {CDNParams} from "./utils/cdnParams.js";
import {setTextWithWrappedEmoji} from "./utils/utils.js";

class Member extends SnowFlake {
static already = {};
Expand Down Expand Up @@ -34,6 +35,7 @@ class Member extends SnowFlake {
elms = new Set<WeakRef<HTMLElement>>();
subName(elm: HTMLElement) {
this.elms.add(new WeakRef(elm));
setTextWithWrappedEmoji(elm, this.name);
}
nameChange() {
for (const ref of this.elms) {
Expand All @@ -42,7 +44,7 @@ class Member extends SnowFlake {
this.elms.delete(ref);
continue;
}
elm.textContent = this.name;
setTextWithWrappedEmoji(elm, this.name);
}
}
commuicationDisabledLeft() {
Expand Down
3 changes: 2 additions & 1 deletion src/webpage/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {OptionsElement, Buttons, Dialog, ColorInput} from "./settings.js";
import {Contextmenu} from "./contextmenu.js";
import {Channel} from "./channel.js";
import {I18n} from "./i18n.js";
import {setTextWithWrappedEmoji} from "./utils/utils.js";

class Role extends SnowFlake {
permissions: Permissions;
Expand Down Expand Up @@ -647,7 +648,7 @@ class RoleList extends Buttons {
this.buttonMap.set(thing[0], button);
button.classList.add("SettingsButton");
const span = document.createElement("span");
span.textContent = thing[0];
setTextWithWrappedEmoji(span, thing[0]);
button.append(span);
span.classList.add("roleButtonStyle");
const role = this.guild.roleids.get(thing[1]) || this.guild.localuser.userMap.get(thing[1]);
Expand Down
3 changes: 3 additions & 0 deletions src/webpage/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2646,6 +2646,9 @@ span.instanceStatus {
object-fit: contain;
align-self: center;
}
span.emoji {
color: initial;
}
.userwrap {
display: flex;
align-items: baseline;
Expand Down
5 changes: 3 additions & 2 deletions src/webpage/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {Search} from "./search.js";
import {I18n} from "./i18n.js";
import {Hover} from "./hover.js";
import {Dialog, Float, Options} from "./settings.js";
import {createImg, removeAni, safeImg} from "./utils/utils.js";
import {createImg, removeAni, safeImg, setTextWithWrappedEmoji} from "./utils/utils.js";
import {Direct} from "./direct.js";
import {Permissions} from "./permissions.js";
import {Channel} from "./channel.js";
Expand Down Expand Up @@ -682,6 +682,7 @@ class User extends SnowFlake {
elms = new Set<WeakRef<HTMLElement>>();
subName(elm: HTMLElement) {
this.elms.add(new WeakRef(elm));
setTextWithWrappedEmoji(elm, this.name);
}
nameChange() {
this.getMembersSync().forEach((memb) => {
Expand All @@ -694,7 +695,7 @@ class User extends SnowFlake {
this.elms.delete(ref);
continue;
}
elm.textContent = this.name;
setTextWithWrappedEmoji(elm, this.name);
}
}

Expand Down
30 changes: 30 additions & 0 deletions src/webpage/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,36 @@ let instances:
}[]
| null = null;
await setTheme();
// Matches keycaps, flags, standard, skin-toned, and ZWJ emoji sequences.
const emojiGraphemeRegex = /^(?:[0-9#*]\uFE0F?\u20E3|\p{Regional_Indicator}{2}|\p{Extended_Pictographic}(?:\uFE0F|\uFE0E)?[\u{1F3FB}-\u{1F3FF}]?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|\uFE0E)?[\u{1F3FB}-\u{1F3FF}]?)*?)$/u;
export function setTextWithWrappedEmoji(target: HTMLElement, name: string) {
target.textContent = "";
// Fallback for browsers without grapheme segmentation support.
if (!("Segmenter" in Intl)) {
target.textContent = name;
return;
}
const segmenter = new Intl.Segmenter("und", {granularity: "grapheme"});
// Buffer plain text between emoji to keep fewer DOM nodes.
let textBuffer = "";
const flushTextBuffer = () => {
if (!textBuffer) return;
target.append(textBuffer);
textBuffer = "";
};
for (const {segment} of segmenter.segment(name)) {
if (emojiGraphemeRegex.test(segment)) {
flushTextBuffer();
const emoji = document.createElement("span");
emoji.classList.add("emoji");
emoji.textContent = segment;
target.append(emoji);
} else {
textBuffer += segment;
}
}
flushTextBuffer();
}
export async function setTheme(theme?: string) {
const prefs = await getPreferences();
document.body.className = (theme || prefs.theme) + "-theme";
Expand Down