Channel + User Mentions

This commit is contained in:
ADAMJR 2021-11-12 14:40:18 +00:00
parent a5b7628768
commit cd38bec601
6 changed files with 55 additions and 51 deletions

View File

@ -1,8 +1,10 @@
# Accord - Like Discord but cooler.
Custom Frontend and Backend that is similar to Discord.
> Built with React, Redux, and Node.js.
> Built with React TS, Redux, and Node.js.
Accord is an app, similar to Discord, but cooler.
**Please star this repo to support development.**
<a href="https://ibb.co/kgndDwd"><img src="https://i.ibb.co/N6h4NJ4/Screenshot-from-2021-08-31-16-09-41.png" alt="Screenshot-from-2021-08-31-16-09-41" border="0" /></a>
<a href="https://ibb.co/st2q2B0"><img src="https://i.ibb.co/fQ2H2ch/Screenshot-from-2021-08-30-11-55-01.png" alt="Screenshot-from-2021-08-30-11-55-01" border="0" /></a>

View File

@ -53,10 +53,7 @@ const MessageContent: FunctionComponent<MessageContentProps> = ({ message }) =>
const messageHTML =
((message.content)
? format(mentions
.tagsToHTML(
striptags(mentions
.stripFormat(message.content), mentions.tags)))
? format(mentions.tagsToHTML(striptags(mentions.stripFormat(message.content), mentions.tags)))
: ''
) + ((message.updatedAt && message.content)
? `<span

View File

@ -12,6 +12,7 @@ describe('mention-service', () => {
},
entities: {
channels: [{ id: '246688207138279430', name: 'general', guildId: '246688207148279429' }],
roles: [{ id: '246688207138279435', name: '@everyone', guildId: '246688207148279429' }],
users: [{ id: '246688207138279428', username: 'Adam', discriminator: 1 }],
}
} as any;
@ -21,11 +22,17 @@ describe('mention-service', () => {
};
test(fn('formatOriginal'), () => {
given('@Adam#0001').expect('<@246688207138279428>');
given('@nobody#0000').expect('@nobody#0000');
given('hi @Adam#0001').expect('hi <@246688207138279428>');
given('@Adam#0001 @Adam#0001').expect('<@246688207138279428> <@246688207138279428>');
given('@Adam#0001@Adam#0001').expect('<@246688207138279428><@246688207138279428>');
given('#general').expect('<#246688207138279430>');
given('#non-existent-channel').expect('#non-existent-channel');
given('let\'s talk in #general').expect('let\'s talk in <#246688207138279430>');
given('#general #general').expect('<#246688207138279430> <#246688207138279430>');
given('#general#general').expect('<#246688207138279430><#246688207138279430>');
});
});
export {}

View File

@ -1,7 +1,6 @@
import { getChannel, getChannelByName } from '../store/channels';
import { getGuildUsers } from '../store/guilds';
import { getRole, getRoleByName } from '../store/roles';
import { getUser, getUserByTag } from '../store/users';
import patterns from '../types/patterns';
export class MentionService {
public readonly tags = ['mention'];
@ -14,57 +13,55 @@ export class MentionService {
private readonly patterns = {
formatted: {
channel: /<#(\d{18})>/gm,
role: /<&(\d{18})>/gm,
user: /<@(\d{18})>/gm,
},
original: {
channel: /#([A-Za-z\-\d]{2,32})/gm,
role: /@((.*){2,32})/gm,
user: /@([A-Za-z\d\-\_ ]{2,32}#\d{4})/gm,
},
tag: /<mention type="(user|everyone|someone|here)" id="(\d{18})" \/>/,
tag: /<mention type="(channel|role|user)" id="(\d{18})" \/>/,
};
constructor(private state: Store.AppState) {}
// messageBox.onInput -> formatted mentions appear fancy in message box
public formatOriginal(content: string) {
const guildId = this.state.ui.activeGuild!.id;
return content
// .replace(this.patterns.original.role, (_, tag) => {
// const user = getUserByTag(tag)(this.state);
// return `<@${user.id}>`;
// })
.replace(this.patterns.original.user, (_, tag) => {
const user = getUserByTag(tag)(this.state);
return (user) ? `<@${user.id}>` : '';
.replace(this.patterns.original.channel, (og, name) => {
const channel = getChannelByName(guildId, name)(this.state);
return (channel) ? `<#${channel?.id}>` : og;
})
.replace(this.patterns.original.user, (og, tag) => {
const user = getUserByTag(tag)(this.state);
return (user) ? `<@${user.id}>` : og;
})
.replace(this.patterns.original.channel, (_, name) => {
const channel = getChannelByName(this.state.ui.activeGuild!.id, name)(this.state);
return (channel) ? `<#${channel?.id}>` : '';
});
}
// public stripFormat(content: string) {
// return content
// .replace(this.patterns.formatted.user, `<mention type="user" id="$1" />`)
// }
// public tagsToHTML(content: string) {
// return content.replace(this.patterns., (_, type, id) => {
// const tag = {
// user: {
// onClick: `events.emit('openUserProfile', '${id}')`,
// text: `@${this.getUserTag(id)}`,
// }
// };
public stripFormat(content: string) {
return content
.replace(this.patterns.formatted.channel, `<mention type="channel" id="$1" />`)
.replace(this.patterns.formatted.user, `<mention type="user" id="$1" />`);
}
// return `<a
// data-id="${id}"
// class="font-extrabold cursor-pointer"
// onclick="${tag[type].onClick}">${tag[type].text}</a>`;
// });
// }
public tagsToHTML(content: string) {
return content.replace(this.patterns.tag, (_, type, id) => {
const guildId = this.state.ui.activeGuild!.id;
const tag = {
channel: {
onClick: `window.location.href = '/channels/${guildId}/${id}'`,
text: `#${getChannel(id)(this.state)?.name}`,
},
user: {
onClick: `events.emit('openUserProfile', '${id}')`,
text: `@${this.tag(getUser(id)(this.state))}`,
},
};
private getUserTag(userId: string) {
const user = getUser(userId)(this.state);
return this.tag(user);
return `<a
data-id="${id}"
class="font-extrabold cursor-pointer hover:underline"
onclick="${tag[type].onClick}">${tag[type].text}</a>`;
});
}
private tag(user: Entity.User) {

View File

@ -33,6 +33,11 @@ export const getRole = (id: string) => createSelector<Store.AppState, Entity.Rol
roles => roles.find(r => r.id === id),
);
export const getRoleByName = (guildId: string, name: string) => createSelector<Store.AppState, Entity.Role[], Entity.Role | undefined>(
state => state.entities.roles,
roles => roles.find(r => r.guildId === guildId && r.name === name),
);
export const getRoles = (ids: string[]) => createSelector<Store.AppState, Entity.Role[], Entity.Role[]>(
state => state.entities.roles,
roles => roles.filter(r => ids.includes(r.id)),

View File

@ -83,14 +83,10 @@ export const getUser = (id: string) =>
);
export const getUserByTag = (tag: string) =>
createSelector<Store.AppState, Entity.User[], Entity.User>(
createSelector<Store.AppState, Entity.User[], Entity.User | undefined>(
state => state.entities.users,
users => {
const [username, discrim] = tag.split('#');
return users.find(u => u.username === username && u.discriminator === +discrim) ?? {
avatarURL: '/avatars/unknown.png',
discriminator: 0,
username: 'Unknown',
} as Entity.User;
return users.find(u => u.username === username && u.discriminator === +discrim);
}
);