Validate voice channel connect permissions (untested).
This commit is contained in:
parent
ea26910993
commit
4fae880e6d
@ -9,5 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Profanity filter: Text channel option to filter explicit words (disabled by default).
|
||||
- Customizable themes: Create, share, and customize UI themes.
|
||||
- Family Friendly Usernames: No profanity in usernames please.
|
||||
- Send Files Permission: Control who can send files/images in a text channel.
|
||||
- Invite Page: Join a server through `acrd.app/invites/<code>`.
|
||||
- Advanced Invite Management: View who created roles, and how many uses they have had.
|
||||
- Profanity Filter: Text channel option to filter explicit words (disabled by default).
|
||||
- Customizable Themes: Create, share, and customize UI themes.
|
||||
|
@ -11,6 +11,12 @@ export default class Themes extends DBWrapper<string, ThemeDocument> {
|
||||
throw new TypeError('Theme not found');
|
||||
return theme;
|
||||
}
|
||||
public async getByCode(code: string | undefined) {
|
||||
const theme = await Theme.findOne({ code });
|
||||
if (!theme)
|
||||
throw new TypeError('Theme not found');
|
||||
return theme;
|
||||
}
|
||||
|
||||
public async create(options: Partial<Entity.Theme>) {
|
||||
this.parse(options.styles!);
|
||||
|
@ -62,8 +62,8 @@ router.delete('/:id', updateUser, validateUser, async (req, res) => {
|
||||
res.status(201).json({ message: 'Deleted' });
|
||||
});
|
||||
|
||||
router.get('/:id/unlock', updateUser, validateUser, async (req, res) => {
|
||||
const theme = await deps.themes.get(req.params.id);
|
||||
router.get('/:code/unlock', updateUser, validateUser, async (req, res) => {
|
||||
const theme = await deps.themes.getByCode(req.params.code);
|
||||
const user: SelfUserDocument = res.locals.user;
|
||||
await deps.themes.unlock(theme.id, user);
|
||||
|
||||
|
@ -12,9 +12,11 @@ export default class implements WSEvent<'CHANNEL_JOIN'> {
|
||||
if (channel.type !== 'VOICE')
|
||||
throw new TypeError('You cannot join a non-voice channel');
|
||||
|
||||
await deps.wsGuard.validateCanInChannel(client, channelId, 'CONNECT');
|
||||
|
||||
const userId = ws.sessions.get(client.id);
|
||||
const user = await deps.users.getSelf(userId);
|
||||
const movedChannel = user.voice.channelId !== channelId;
|
||||
const movedChannel = (user.voice.channelId !== channelId);
|
||||
|
||||
if (user.voice.channelId && movedChannel)
|
||||
await deps.channelLeave.invoke(ws, client);
|
||||
@ -23,7 +25,6 @@ export default class implements WSEvent<'CHANNEL_JOIN'> {
|
||||
if (doesExist)
|
||||
throw new TypeError('User already connected to voice');
|
||||
|
||||
// TODO: perms - validate can join
|
||||
deps.voiceService.add(channelId, { userId });
|
||||
|
||||
await Promise.all([
|
||||
|
@ -11,12 +11,12 @@ export default class implements WSEvent<'CHANNEL_LEAVE'> {
|
||||
const userId = ws.sessions.get(client.id);
|
||||
const user = await deps.users.getSelf(userId);
|
||||
|
||||
await this.updateVoiceState(user);
|
||||
const oldChannel = await deps.channels.getSafely(user.voice.channelId);
|
||||
const channelLeaveAction = (oldChannel)
|
||||
? await this.handleExistingVC(oldChannel, userId, ws, client)
|
||||
: undefined;
|
||||
if (!oldChannel) return [];
|
||||
|
||||
await this.updateVoiceState(user);
|
||||
|
||||
var channelLeaveAction = await this.handleExistingVC(oldChannel, userId, ws, client);
|
||||
return [channelLeaveAction, {
|
||||
emit: 'VOICE_STATE_UPDATE' as const,
|
||||
to: [client.id],
|
||||
@ -34,9 +34,11 @@ export default class implements WSEvent<'CHANNEL_LEAVE'> {
|
||||
|
||||
// leave voice server
|
||||
deps.voiceService.remove(oldChannel.id, userId);
|
||||
await deps.channels.leaveVC(oldChannel, userId);
|
||||
|
||||
await client.leave(oldChannel.id);
|
||||
await Promise.all([
|
||||
client.leave(oldChannel.id),
|
||||
deps.channels.leaveVC(oldChannel, userId),
|
||||
]);
|
||||
|
||||
return {
|
||||
emit: 'CHANNEL_UPDATE' as const,
|
||||
|
@ -18,6 +18,9 @@ export default class implements WSEvent<'MESSAGE_CREATE'> {
|
||||
deps.users.getSelf(authorId),
|
||||
]);
|
||||
|
||||
if (attachmentURLs && attachmentURLs.length > 0)
|
||||
await deps.wsGuard.validateCanInChannel(client, channelId, 'SEND_FILES');
|
||||
|
||||
var message = await deps.messages.create(authorId, channelId, {
|
||||
attachmentURLs,
|
||||
content: this.filterContent(content, channel.filterProfanity),
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { faUpload } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import usePerms from '../../../hooks/use-perms';
|
||||
import { uploadFileAsMessage } from '../../../store/messages';
|
||||
|
||||
interface MessageBoxLeftSideProps {
|
||||
@ -12,17 +14,21 @@ interface MessageBoxLeftSideProps {
|
||||
const MessageBoxLeftSide: React.FunctionComponent<MessageBoxLeftSideProps> = (props) => {
|
||||
const channel = useSelector((s: Store.AppState) => s.ui.activeChannel)!;
|
||||
const dispatch = useDispatch();
|
||||
const perms = usePerms();
|
||||
|
||||
const uploadInput = React.createRef<HTMLInputElement>();
|
||||
const onChange: any = (e: Event) => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
dispatch(uploadFileAsMessage(channel.id, { content: props.content }, input.files![0]));
|
||||
dispatch(uploadFileAsMessage(channel.id, { content: props.content }, input.files![0]));
|
||||
}
|
||||
|
||||
const canSendFiles = perms.canInChannel('SEND_FILES', channel.guildId, channel.id);
|
||||
|
||||
return (!props.editingMessageId) ? (
|
||||
<div className="px-4">
|
||||
<div className={classNames('px-4')}>
|
||||
<div className="relative">
|
||||
<input
|
||||
disabled={!canSendFiles}
|
||||
ref={uploadInput}
|
||||
type="file"
|
||||
name="file"
|
||||
@ -30,9 +36,10 @@ const MessageBoxLeftSide: React.FunctionComponent<MessageBoxLeftSideProps> = (pr
|
||||
onChange={onChange}
|
||||
hidden />
|
||||
<FontAwesomeIcon
|
||||
color={canSendFiles ? '#ffffff' : 'var(--muted)'}
|
||||
icon={faUpload}
|
||||
onClick={() => uploadInput.current?.click()}
|
||||
className="cursor-pointer z-1" />
|
||||
className={classNames('cursor-pointer z-1')} />
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
|
@ -8,7 +8,7 @@ input:hover {
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border: 1px solid var(--link);
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
|
@ -74,7 +74,7 @@ const ChannelSettingsPerms: React.FunctionComponent = () => {
|
||||
overrideRoles.push(role);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 flex flex-col pt-14 px-10 pb-20 h-full mt-1">
|
||||
<div className="grid grid-cols-12 flex-col pt-14 px-10 pb-20 h-full mt-1">
|
||||
<div className="lg:col-span-3 col-span-12">
|
||||
<nav className="pr-10">
|
||||
{overrideRoles.map(r => (
|
||||
|
@ -20,11 +20,10 @@ const GuildSettingsInvites: React.FunctionComponent = () => {
|
||||
{invites.map(i => (
|
||||
<div className="w-full mb-3">
|
||||
<strong><code>{i.id}</code></strong>
|
||||
<span className="pl-5 secondary">
|
||||
<span>Used <code>{i.uses}</code> times</span>
|
||||
<span>Created by
|
||||
<span className="ml-4 secondary">
|
||||
<span className='ml-4'>Used <code>{i.uses}</code> times</span>
|
||||
<span className='ml-4'>Created by
|
||||
<Username
|
||||
className='pt-2 scale-75'
|
||||
size='sm'
|
||||
user={guildUsers.find(gu => gu.id == i.inviterId)}
|
||||
guild={guild} />
|
||||
|
@ -18,8 +18,8 @@ import Toggle from '../../inputs/toggle';
|
||||
import SaveChanges from '../../utils/save-changes';
|
||||
import TabLink from '../../utils/tab-link';
|
||||
import RolePermissions from './role-permissions';
|
||||
|
||||
const GuildSettingsRoles: React.FunctionComponent = () => {
|
||||
|
||||
const GuildSettingsRoles: React.FunctionComponent = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { handleSubmit, register, setValue, getValues } = useForm();
|
||||
const { guildId }: any = useParams();
|
||||
@ -62,20 +62,20 @@ const GuildSettingsRoles: React.FunctionComponent = () => {
|
||||
<span>Separate role on member list</span>
|
||||
<Toggle
|
||||
id="hoisted"
|
||||
className="float-right"
|
||||
checked={hoisted}
|
||||
{...register('hoisted')}
|
||||
onChange={() => {
|
||||
setHoisted(!hoisted);
|
||||
setValue('hoisted', !hoisted);
|
||||
}}
|
||||
className="float-right" />
|
||||
}} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<RolePermissions
|
||||
perms={perms}
|
||||
setPerms={setPerms}
|
||||
setRoleValue={setValue} />
|
||||
setRoleValue={setValue} />
|
||||
<NormalButton
|
||||
onClick={() => dispatch(deleteRole(guildId, activeRole!.id))}
|
||||
className="bg-danger float-right"
|
||||
@ -87,12 +87,12 @@ const GuildSettingsRoles: React.FunctionComponent = () => {
|
||||
const onSave = (e) => {
|
||||
const onUpdate = (payload) => dispatch(updateRole(guildId, activeRole!.id, payload));
|
||||
handleSubmit(onUpdate)(e);
|
||||
};
|
||||
};
|
||||
const byPosition = (a, b) => (a.position > b.position) ? -1 : 1;
|
||||
const selfIsHigher = (r) => permsService.memberIsHigher(guildId, [r.id]);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 flex flex-col pt-14 px-10 pb-20 h-full mt-1">
|
||||
<div className="grid grid-cols-12 flex-col pt-14 px-10 pb-20 h-full mt-1">
|
||||
<div className="lg:col-span-3 col-span-12">
|
||||
<nav className="pr-10">
|
||||
{roles.sort(byPosition).map(r => (
|
||||
@ -126,9 +126,9 @@ const GuildSettingsRoles: React.FunctionComponent = () => {
|
||||
setHoisted(activeRole!.hoisted);
|
||||
}}
|
||||
onSave={onSave}
|
||||
obj={getValues()} />
|
||||
obj={getValues()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default GuildSettingsRoles;
|
@ -20,12 +20,12 @@ const UserSettingsThemes: React.FunctionComponent = () => {
|
||||
const [themeId, setTab] = useState(selfUser.activeThemeId);
|
||||
const [addMode, enableAddMode] = useState(false);
|
||||
const [focusedInputId, setFocusedInputId] = useState('');
|
||||
|
||||
|
||||
const theme = getTheme(themeId, themes);
|
||||
useEffect(() => {
|
||||
if (!theme) setTab('default');
|
||||
}, [theme, themeId]);
|
||||
|
||||
|
||||
const refreshFocus = () => {
|
||||
if (!focusedInputId) return;
|
||||
|
||||
@ -45,8 +45,9 @@ const UserSettingsThemes: React.FunctionComponent = () => {
|
||||
}}
|
||||
title={t.name}>
|
||||
<SidebarIcon
|
||||
childClasses={classNames('bg-bg-secondary', {
|
||||
'border-2 border-primary h-[3.1rem]': t.id === themeId,
|
||||
childClasses={classNames('border-2 h-[3.1rem] bg-bg-secondary', {
|
||||
'border-primary': t.id === themeId,
|
||||
'border-transparent': t.id !== themeId,
|
||||
})}
|
||||
imageURL={t.iconURL}
|
||||
name={t.name}
|
||||
@ -64,13 +65,13 @@ const UserSettingsThemes: React.FunctionComponent = () => {
|
||||
const { register, setValue, handleSubmit } = useForm();
|
||||
const theme = themes.find(t => t.id === themeId);
|
||||
if (!theme) return null;
|
||||
|
||||
|
||||
const onApply = () => dispatch(updateSelf({ activeThemeId: themeId }));
|
||||
const onDelete = () => {
|
||||
const confirmation = window.confirm('Are you sure you want to delete this theme?');
|
||||
if (confirmation) dispatch(deleteTheme(theme.id));
|
||||
};
|
||||
const onSave = (e) => {
|
||||
const onSave = (e) => {
|
||||
const onUpdate = (payload) => dispatch(updateTheme(themeId, payload));
|
||||
handleSubmit(onUpdate)(e);
|
||||
};
|
||||
@ -84,7 +85,7 @@ const UserSettingsThemes: React.FunctionComponent = () => {
|
||||
<h1 className="text-3xl font-bold inline">Add Theme</h1>
|
||||
<p className="secondary">Add an existing theme with a shareable code.</p>
|
||||
</header>
|
||||
|
||||
|
||||
<div className="mb-10">
|
||||
<Input
|
||||
className="float-left w-1/3 mr-3 disabled"
|
||||
@ -115,23 +116,36 @@ const UserSettingsThemes: React.FunctionComponent = () => {
|
||||
};
|
||||
|
||||
if (addMode) return <AddTheme />;
|
||||
|
||||
|
||||
return (themeId) ? (
|
||||
<div className="px-5 ml-4">
|
||||
<header className="mb-5">
|
||||
<h1 className="text-3xl font-bold inline">{theme.name}</h1>
|
||||
</header>
|
||||
{/* <FileInput
|
||||
className="w-1/3"
|
||||
name="icon"
|
||||
label="Icon"
|
||||
options={{ value: theme.iconURL }}
|
||||
tooltip="An optional icon for your theme."
|
||||
onChange={(e) => {
|
||||
const file = e.currentTarget?.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
dispatch(uploadFile(file, ({ url }) => {
|
||||
dispatch(updateTheme(themeId, { iconURL: url }));
|
||||
}));
|
||||
}} /> */}
|
||||
|
||||
<form
|
||||
onChange={() => dispatch(openSaveChanges(true))}
|
||||
className="flex flex-col h-full mt-1 mb-5">
|
||||
<header className="mb-5">
|
||||
<h1 className="text-3xl font-bold inline">{theme.name}</h1>
|
||||
</header>
|
||||
|
||||
<div className="flex">
|
||||
<Input
|
||||
className="w-1/3 mr-5"
|
||||
label="Name"
|
||||
name="name"
|
||||
register={register}
|
||||
setFocusedInputId={setFocusedInputId}
|
||||
options={{ value: theme.name }} />
|
||||
<Input
|
||||
tooltip="The code that is used to share themes."
|
||||
@ -140,30 +154,22 @@ const UserSettingsThemes: React.FunctionComponent = () => {
|
||||
name="code"
|
||||
register={register}
|
||||
options={{ value: theme.code }}
|
||||
setFocusedInputId={setFocusedInputId}
|
||||
disabled />
|
||||
<FileInput
|
||||
className="w-1/3"
|
||||
name="iconURL"
|
||||
label="Icon"
|
||||
options={{ value: theme.iconURL }}
|
||||
tooltip="An optional icon for your theme."
|
||||
setFocusedInputId={setFocusedInputId}
|
||||
onChange={(e) => {
|
||||
const file = e.currentTarget?.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
dispatch(uploadFile(file, ({ url }) => {
|
||||
dispatch(updateTheme(themeId, { iconURL: url }));
|
||||
}));
|
||||
}} />
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
className="p-2 rounded bg-bg-secondary outline-none border-bg-tertiary hover:border w-1/2 mt-2"
|
||||
defaultValue={theme.styles}
|
||||
onFocus={(e) => setFocusedInputId?.(e.currentTarget.id)}
|
||||
{...register('styles', { value: theme.styles })} />
|
||||
<div className='mt-2'>
|
||||
<label
|
||||
htmlFor="styles"
|
||||
className="uppercase text-xs font-semibold">Styles</label>
|
||||
|
||||
<textarea
|
||||
id="styles"
|
||||
rows={20}
|
||||
className="p-2 rounded bg-bg-secondary outline-none w-full mt-2"
|
||||
defaultValue={theme.styles}
|
||||
onFocus={(e) => setFocusedInputId?.(e.currentTarget.id)}
|
||||
{...register('styles', { value: theme.styles })} />
|
||||
</div>
|
||||
|
||||
<SaveChanges
|
||||
onOpen={refreshFocus}
|
||||
@ -191,5 +197,5 @@ const UserSettingsThemes: React.FunctionComponent = () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default UserSettingsThemes;
|
@ -1,16 +1,16 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import CircleButton from '../utils/buttons/circle-button';
|
||||
|
||||
|
||||
const Navbar: React.FunctionComponent = () => {
|
||||
const user = useSelector((s: Store.AppState) => s.auth.user);
|
||||
|
||||
|
||||
return (
|
||||
<nav className="flex items-center justify-between h-15 p-4 px-8">
|
||||
<a className="logo">
|
||||
<span className="font-bold text-white">accord</span>
|
||||
<span className="text-gray-600">.</span>
|
||||
<span className="muted font-light">app</span>
|
||||
<span className="font-bold heading">acrd</span>
|
||||
<span className="normal">.</span>
|
||||
<span className="muted secondary">app</span>
|
||||
</a>
|
||||
<div>
|
||||
<Link to={user ? '/channels/@me' : '/login'}>
|
||||
@ -20,5 +20,5 @@ const Navbar: React.FunctionComponent = () => {
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default Navbar;
|
@ -7,7 +7,7 @@ import { getChannel, leaveVoiceChannel } from '../../../store/channels';
|
||||
import { getGuild } from '../../../store/guilds';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
|
||||
const SidebarFooter: React.FunctionComponent = () => {
|
||||
const dispatch = useDispatch();
|
||||
const user = useSelector((s: Store.AppState) => s.auth.user)!;
|
||||
@ -19,7 +19,7 @@ const SidebarFooter: React.FunctionComponent = () => {
|
||||
const ping = useSelector((s: Store.AppState) => s.meta.ping);
|
||||
|
||||
if (!channel || !guild) return null;
|
||||
|
||||
|
||||
return (
|
||||
<div id="sidebarFooter">
|
||||
<div className="justify-between flex items-center p-3 pr-4">
|
||||
@ -27,11 +27,11 @@ const SidebarFooter: React.FunctionComponent = () => {
|
||||
<FontAwesomeIcon
|
||||
icon={faSignal}
|
||||
className={classNames({
|
||||
'success': ping && ping < 100,
|
||||
'secondary': ping && ping >= 100 && ping < 200,
|
||||
'warning': ping && ping >= 200 && ping < 300,
|
||||
'danger': ping && ping >= 300,
|
||||
'muted': !ping,
|
||||
// 'success': ping && ping < 100,
|
||||
// 'secondary': ping && ping >= 100 && ping < 200,
|
||||
// 'warning': ping && ping >= 200 && ping < 300,
|
||||
// 'danger': ping && ping >= 300,
|
||||
'success': true,
|
||||
})} />
|
||||
<strong className="success ml-2">Voice Connected</strong>
|
||||
<div className="normal">{channel.name} / {guild.name}</div>
|
||||
@ -45,7 +45,7 @@ const SidebarFooter: React.FunctionComponent = () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="bg-bg-secondary-alt">
|
||||
<VoiceFooter />
|
||||
@ -62,5 +62,5 @@ const SidebarFooter: React.FunctionComponent = () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default SidebarFooter;
|
@ -9,6 +9,7 @@ export interface SidebarIconProps {
|
||||
to?: string;
|
||||
childClasses?: string;
|
||||
disableHoverEffect?: boolean;
|
||||
styles?: any;
|
||||
}
|
||||
|
||||
const SidebarIcon: React.FunctionComponent<SidebarIconProps> = (props) => {
|
||||
@ -25,9 +26,9 @@ const SidebarIcon: React.FunctionComponent<SidebarIconProps> = (props) => {
|
||||
|
||||
const Icon = () => (imageURL)
|
||||
? <Image
|
||||
className="h-12 w-12"
|
||||
src={imageURL}
|
||||
alt={name} />
|
||||
className="h-12 w-12"
|
||||
src={imageURL}
|
||||
alt={name} />
|
||||
: <span className="select-none flex items-center justify-center h-12 w-12">{getAbbr(name)}</span>;
|
||||
|
||||
const isActive = to && location.pathname.startsWith(to);
|
||||
|
@ -10,7 +10,9 @@ import { fetchInvite, getInvite } from '../../store/invites';
|
||||
import { joinGuild } from '../../store/members';
|
||||
import { getTag, getUser } from '../../store/users';
|
||||
import SidebarIcon from '../navigation/sidebar/sidebar-icon';
|
||||
import Username from '../user/username';
|
||||
import NormalButton from '../utils/buttons/normal-button';
|
||||
import FullParticles from '../utils/full-particles';
|
||||
import PageWrapper from './page-wrapper';
|
||||
|
||||
interface InvitePageProps { }
|
||||
@ -48,17 +50,19 @@ const InvitePage: React.FunctionComponent<InvitePageProps> = () => {
|
||||
size="2x" />
|
||||
);
|
||||
|
||||
if (!invite || !guild) return (
|
||||
<Wrapper>
|
||||
<NotFoundIcon />
|
||||
<h1 className="text-xl font-bold warning">Invite not found...</h1>
|
||||
<p className="lead">The invite either has expired, or never existed.</p>
|
||||
</Wrapper>
|
||||
);
|
||||
if (!invite || !guild)
|
||||
return (
|
||||
<Wrapper>
|
||||
<NotFoundIcon />
|
||||
<h1 className="text-xl font-bold warning">Invite not found...</h1>
|
||||
<p className="lead">The invite either has expired, or never existed.</p>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<h1 className="text-3xl font-bold">You have been invited to {guild.name}!</h1>
|
||||
<FullParticles />
|
||||
<h1 className="text-3xl font-bold text-center">You have been invited to {guild.name}!</h1>
|
||||
<div className="flex mt-5">
|
||||
<SidebarIcon
|
||||
name={guild.name}
|
||||
@ -66,11 +70,13 @@ const InvitePage: React.FunctionComponent<InvitePageProps> = () => {
|
||||
childClasses="bg-bg-tertiary w-24 h-24 pt-6 text-xl"
|
||||
disableHoverEffect />
|
||||
<div className="flex justify-around items-center w-full mx-5">
|
||||
<span>
|
||||
<strong className="heading">Members</strong>: <code className="muted">{members.length}</code>
|
||||
<span className='text-center'>
|
||||
<div className="heading font-bold text-center">Members</div>
|
||||
<code>{members.length}</code>
|
||||
</span>
|
||||
<span>
|
||||
<strong className="heading">Owner</strong>: <code className="muted">{getTag(ownerUser)}</code>
|
||||
<span className='text-center'>
|
||||
<div className="heading font-bold">Owned By</div>
|
||||
<Username user={ownerUser} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -80,9 +86,9 @@ const InvitePage: React.FunctionComponent<InvitePageProps> = () => {
|
||||
dispatch(joinGuild(inviteId));
|
||||
history.push(`/channels/${invite.guildId}`);
|
||||
}}
|
||||
className="bg-success light">Join :D</NormalButton>
|
||||
className="bg-success dark">Join</NormalButton>
|
||||
<Link to="/">
|
||||
<NormalButton className="bg-danger light">Nope :(</NormalButton>
|
||||
<NormalButton className="bg-danger light">Cancel</NormalButton>
|
||||
</Link>
|
||||
</div>
|
||||
</Wrapper>
|
||||
|
@ -6,28 +6,24 @@ import fetchEntities from '../../store/actions/fetch-entities';
|
||||
|
||||
const LoadingPage: React.FunctionComponent = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(ready());
|
||||
dispatch(fetchEntities());
|
||||
}, []);
|
||||
|
||||
const tips = [
|
||||
'Did Discord steal our idea?!?!',
|
||||
'ADAMJR, Stop refactoring code please. okthxbye.',
|
||||
'Sample Text.',
|
||||
'!(\'Hello World\')',
|
||||
'May work on a Tesla',
|
||||
'Please subscribe.',
|
||||
'Hi YouTube!',
|
||||
'accord.includes(\'VOICE_CHANNEL\') === true',
|
||||
'Like Discord, but less cringe.',
|
||||
'This message is officially dumb.',
|
||||
'This message is funny.',
|
||||
'Dear Bill Gates please buy my app okthxbye.',
|
||||
'Is coding the same as programming? :thinking:',
|
||||
'Is coding the same as programming? 🤔',
|
||||
'TypeError: There may be bugs.',
|
||||
'What is a Discord? :thinking:',
|
||||
'What\'s your Skype? Wait, actually I don\'t want your IP. Cya.',
|
||||
'Does anyone remember Skype?',
|
||||
'Started in 2020.',
|
||||
'Disclaimer: Not actually a Discord clone.',
|
||||
];
|
||||
@ -44,5 +40,5 @@ const LoadingPage: React.FunctionComponent = () => {
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default LoadingPage;
|
||||
|
@ -19,10 +19,10 @@ const PrivateRoute: React.FunctionComponent<RouteProps> = (props) => {
|
||||
const theme = themes.find(t => t.id === user.activeThemeId)
|
||||
?? themes.find(t => t.id === 'default');
|
||||
applyTheme(theme.styles);
|
||||
|
||||
|
||||
return (
|
||||
<Route {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default PrivateRoute;
|
@ -66,7 +66,7 @@ const FullParticles: React.FunctionComponent = () => {
|
||||
},
|
||||
"move": {
|
||||
"enable": true,
|
||||
"speed": 6,
|
||||
"speed": 3,
|
||||
"direction": "bottom",
|
||||
"random": false,
|
||||
"straight": false,
|
||||
|
@ -18,9 +18,10 @@ export class PermService {
|
||||
'MANAGE_INVITES': 'Ability to delete invites.',
|
||||
},
|
||||
text: {
|
||||
'SEND_MESSAGES': 'Ability to send messages in text channels.',
|
||||
'MANAGE_MESSAGES': `Ability to manage message other member's messages.`,
|
||||
'READ_MESSAGES': `Ability to read messages,`,
|
||||
'SEND_MESSAGES': 'Ability to send messages in text channels.',
|
||||
'SEND_FILES': `Ability to send files or images in messages.`,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -27,7 +27,7 @@ const slice = createSlice({
|
||||
creatorId: '177127942839676928',
|
||||
iconURL: '/images/themes/discord.svg',
|
||||
isFeatured: true,
|
||||
name: 'Discord (experimental)',
|
||||
name: 'Discord (built-in)',
|
||||
styles: discordTheme,
|
||||
}, {
|
||||
id: 'winter',
|
||||
@ -36,7 +36,7 @@ const slice = createSlice({
|
||||
creatorId: '177127942839676928',
|
||||
iconURL: '/images/themes/winter.svg',
|
||||
isFeatured: true,
|
||||
name: 'Winter (experimental)',
|
||||
name: 'Winter (built-in)',
|
||||
styles: winterTheme,
|
||||
}] as Store.AppState['entities']['themes'],
|
||||
reducers: {
|
||||
@ -75,7 +75,7 @@ export const createTheme = (theme: Partial<Entity.Theme>, callback?: (theme: Ent
|
||||
|
||||
export const unlockTheme = (id: string, callback?: (theme: Entity.Theme) => any) => (dispatch) => {
|
||||
dispatch(api.restCallBegan({
|
||||
url: `/themes/unlock/${id}`,
|
||||
url: `/themes/${id}/unlock`,
|
||||
headers: getHeaders(),
|
||||
callback,
|
||||
}));
|
||||
|
@ -24,48 +24,74 @@ textarea {
|
||||
:not(nav a)[href] {
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: var(--channel);
|
||||
}
|
||||
|
||||
nav a.active {
|
||||
background-color: var(--bg-modifier-selected);
|
||||
color: var(--heading);
|
||||
}
|
||||
|
||||
textarea {
|
||||
border: 1px solid var(--bg-secondary-alt);
|
||||
caret-color: var(--heading);
|
||||
}
|
||||
|
||||
textarea:hover {
|
||||
border: 1px solid var(--bg-tertiary);
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
border: 1px solid var(--primary);
|
||||
}
|
||||
|
||||
/* Util */
|
||||
.light {
|
||||
color: var(--light);
|
||||
}
|
||||
|
||||
.dark {
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.font {
|
||||
color: var(--font);
|
||||
}
|
||||
|
||||
.success {
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.normal {
|
||||
color: var(--normal);
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.danger {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: var(--heading);
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.secondary {
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.tertiary {
|
||||
color: var(--tertiary);
|
||||
}
|
||||
@ -73,36 +99,47 @@ nav a.active {
|
||||
.bg-light {
|
||||
background-color: var(--light);
|
||||
}
|
||||
|
||||
.bg-dark {
|
||||
background-color: var(--dark);
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
|
||||
.bg-secondary {
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
|
||||
.bg-tertiary {
|
||||
background-color: var(--tertiary);
|
||||
}
|
||||
|
||||
.bg-warning {
|
||||
background-color: var(--warning);
|
||||
}
|
||||
|
||||
.bg-danger {
|
||||
background-color: var(--danger);
|
||||
}
|
||||
|
||||
.bg-success {
|
||||
background-color: var(--success);
|
||||
}
|
||||
|
||||
.bg-bg-primary {
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
.bg-bg-secondary {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.bg-bg-secondary-alt {
|
||||
background-color: var(--bg-secondary-alt);
|
||||
}
|
||||
|
||||
.bg-bg-tertiary {
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
@ -112,12 +149,15 @@ nav a.active {
|
||||
background-color: var(--bg-modifier-accent);
|
||||
color: var(--heading);
|
||||
}
|
||||
|
||||
.bg-bg-floating {
|
||||
background-color: var(--bg-floating);
|
||||
}
|
||||
|
||||
.shadow-elevation {
|
||||
box-shadow: var(--elevation);
|
||||
}
|
||||
|
||||
.font-primary {
|
||||
font-family: var(--font-primary);
|
||||
}
|
||||
@ -126,36 +166,51 @@ nav a.active {
|
||||
.border-light {
|
||||
border-color: var(---light);
|
||||
}
|
||||
|
||||
.border-dark {
|
||||
border-color: var(--dark);
|
||||
}
|
||||
|
||||
.border-muted {
|
||||
border-color: var(--muted);
|
||||
}
|
||||
|
||||
.border-primary {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.border-secondary {
|
||||
border-color: var(--secondary);
|
||||
}
|
||||
|
||||
.border-tertiary {
|
||||
border-color: var(--tertiary);
|
||||
}
|
||||
|
||||
.border-danger {
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.border-success {
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
.border-bg-primary {
|
||||
border-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
.border-bg-secondary {
|
||||
border-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.border-bg-secondary-alt {
|
||||
border-color: var(--bg-secondary-alt);
|
||||
}
|
||||
|
||||
.border-bg-tertiary {
|
||||
border-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.border-transparent {
|
||||
border-color: transparent !important;
|
||||
}
|
@ -10,6 +10,7 @@ export declare namespace PermissionTypes {
|
||||
ADMINISTRATOR = 1
|
||||
}
|
||||
enum Text {
|
||||
SEND_FILES = 16384,
|
||||
READ_MESSAGES = 8192,
|
||||
MANAGE_MESSAGES = 4096,
|
||||
SEND_MESSAGES = 2048
|
||||
@ -26,6 +27,7 @@ export declare namespace PermissionTypes {
|
||||
MUTE_MEMBERS: Voice;
|
||||
SPEAK: Voice;
|
||||
CONNECT: Voice;
|
||||
SEND_FILES: Text;
|
||||
READ_MESSAGES: Text;
|
||||
MANAGE_MESSAGES: Text;
|
||||
SEND_MESSAGES: Text;
|
||||
|
@ -1,5 +1,4 @@
|
||||
"use strict";
|
||||
// REMINDER: 8 is admin in Discord, but 1 in Accord
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getPermString = exports.PermissionTypes = void 0;
|
||||
var PermissionTypes;
|
||||
@ -22,7 +21,7 @@ var PermissionTypes;
|
||||
let Text;
|
||||
(function (Text) {
|
||||
// ADD_REACTIONS = 2048 * 16,
|
||||
// MENTION_EVERYONE = 2048 * 8,
|
||||
Text[Text["SEND_FILES"] = 16384] = "SEND_FILES";
|
||||
Text[Text["READ_MESSAGES"] = 8192] = "READ_MESSAGES";
|
||||
Text[Text["MANAGE_MESSAGES"] = 4096] = "MANAGE_MESSAGES";
|
||||
Text[Text["SEND_MESSAGES"] = 2048] = "SEND_MESSAGES";
|
||||
@ -39,6 +38,7 @@ var PermissionTypes;
|
||||
| PermissionTypes.General.CREATE_INVITE
|
||||
| PermissionTypes.Text.SEND_MESSAGES
|
||||
| PermissionTypes.Text.READ_MESSAGES
|
||||
| PermissionTypes.Text.SEND_FILES
|
||||
// | PermissionTypes.Text.ADD_REACTIONS
|
||||
| PermissionTypes.Voice.CONNECT
|
||||
| PermissionTypes.Voice.SPEAK;
|
||||
@ -53,4 +53,4 @@ function getPermString(integer) {
|
||||
: integer.toString();
|
||||
}
|
||||
exports.getPermString = getPermString;
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVybWlzc2lvbnMudHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvcGVybWlzc2lvbnMudHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLG9EQUFvRDs7O0FBRXBELElBQWlCLGVBQWUsQ0E0Qy9CO0FBNUNELFdBQWlCLGVBQWU7SUFDOUIsSUFBWSxPQWFYO0lBYkQsV0FBWSxPQUFPO1FBQ2pCLDBEQUFvQixDQUFBO1FBQ3BCLDJDQUEyQztRQUMzQywwQ0FBMEM7UUFDMUMsMkRBQW9CLENBQUE7UUFDcEIseURBQW1CLENBQUE7UUFDbkIsc0RBQWlCLENBQUE7UUFDakIscUNBQXFDO1FBQ3JDLDREQUFvQixDQUFBO1FBQ3BCLHFEQUFnQixDQUFBO1FBQ2hCLHFEQUFnQixDQUFBO1FBQ2hCLHVDQUF1QztRQUN2Qyx1REFBaUIsQ0FBQTtJQUNuQixDQUFDLEVBYlcsT0FBTyxHQUFQLHVCQUFPLEtBQVAsdUJBQU8sUUFhbEI7SUFDRCxJQUFZLElBTVg7SUFORCxXQUFZLElBQUk7UUFDZCw2QkFBNkI7UUFDN0IsK0JBQStCO1FBQy9CLG9EQUF3QixDQUFBO1FBQ3hCLHdEQUEwQixDQUFBO1FBQzFCLG9EQUFvQixDQUFBO0lBQ3RCLENBQUMsRUFOVyxJQUFJLEdBQUosb0JBQUksS0FBSixvQkFBSSxRQU1mO0lBQ0QsSUFBWSxLQUtYO0lBTEQsV0FBWSxLQUFLO1FBQ2Ysc0RBQXdCLENBQUE7UUFDeEIsc0RBQXdCLENBQUE7UUFDeEIsdUNBQWlCLENBQUE7UUFDakIsMkNBQWUsQ0FBQTtJQUNqQixDQUFDLEVBTFcsS0FBSyxHQUFMLHFCQUFLLEtBQUwscUJBQUssUUFLaEI7SUFDWSxtQkFBRyxpREFDWCxPQUFPLEdBQ1AsSUFBSSxHQUNKLEtBQUssQ0FDVCxDQUFBO0lBSVksa0NBQWtCLEdBQzdCLGVBQWUsQ0FBQyxPQUFPLENBQUMsYUFBYTtVQUNuQyxlQUFlLENBQUMsT0FBTyxDQUFDLGFBQWE7VUFDckMsZUFBZSxDQUFDLElBQUksQ0FBQyxhQUFhO1VBQ2xDLGVBQWUsQ0FBQyxJQUFJLENBQUMsYUFBYTtRQUNwQyx1Q0FBdUM7VUFDckMsZUFBZSxDQUFDLEtBQUssQ0FBQyxPQUFPO1VBQzdCLGVBQWUsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDO0FBQ2xDLENBQUMsRUE1Q2dCLGVBQWUsR0FBZix1QkFBZSxLQUFmLHVCQUFlLFFBNEMvQjtBQUVELFNBQWdCLGFBQWEsQ0FBQyxPQUF3Qjs7SUFDcEQsT0FBTyxDQUFDLE9BQU8sT0FBTyxLQUFLLFFBQVEsQ0FBQztRQUNsQyxDQUFDLENBQUMsTUFBQSxNQUFBLE1BQU07YUFDTCxPQUFPLENBQUMsZUFBZSxDQUFDLEdBQUcsQ0FBQzthQUM1QixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO2FBQ3hDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEtBQUssT0FBTyxJQUFJLENBQUMsS0FBSyxPQUFPLENBQUMsMENBQUcsQ0FBQyxDQUFDLG1DQUFJLEVBQUU7UUFDOUQsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxRQUFRLEVBQUUsQ0FBQztBQUN6QixDQUFDO0FBUEQsc0NBT0MifQ==
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVybWlzc2lvbnMudHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvcGVybWlzc2lvbnMudHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsSUFBaUIsZUFBZSxDQTZDL0I7QUE3Q0QsV0FBaUIsZUFBZTtJQUM5QixJQUFZLE9BYVg7SUFiRCxXQUFZLE9BQU87UUFDakIsMERBQW9CLENBQUE7UUFDcEIsMkNBQTJDO1FBQzNDLDBDQUEwQztRQUMxQywyREFBb0IsQ0FBQTtRQUNwQix5REFBbUIsQ0FBQTtRQUNuQixzREFBaUIsQ0FBQTtRQUNqQixxQ0FBcUM7UUFDckMsNERBQW9CLENBQUE7UUFDcEIscURBQWdCLENBQUE7UUFDaEIscURBQWdCLENBQUE7UUFDaEIsdUNBQXVDO1FBQ3ZDLHVEQUFpQixDQUFBO0lBQ25CLENBQUMsRUFiVyxPQUFPLEdBQVAsdUJBQU8sS0FBUCx1QkFBTyxRQWFsQjtJQUNELElBQVksSUFNWDtJQU5ELFdBQVksSUFBSTtRQUNkLDZCQUE2QjtRQUM3QiwrQ0FBcUIsQ0FBQTtRQUNyQixvREFBd0IsQ0FBQTtRQUN4Qix3REFBMEIsQ0FBQTtRQUMxQixvREFBb0IsQ0FBQTtJQUN0QixDQUFDLEVBTlcsSUFBSSxHQUFKLG9CQUFJLEtBQUosb0JBQUksUUFNZjtJQUNELElBQVksS0FLWDtJQUxELFdBQVksS0FBSztRQUNmLHNEQUF3QixDQUFBO1FBQ3hCLHNEQUF3QixDQUFBO1FBQ3hCLHVDQUFpQixDQUFBO1FBQ2pCLDJDQUFlLENBQUE7SUFDakIsQ0FBQyxFQUxXLEtBQUssR0FBTCxxQkFBSyxLQUFMLHFCQUFLLFFBS2hCO0lBQ1ksbUJBQUcsaURBQ1gsT0FBTyxHQUNQLElBQUksR0FDSixLQUFLLENBQ1QsQ0FBQTtJQUlZLGtDQUFrQixHQUM3QixlQUFlLENBQUMsT0FBTyxDQUFDLGFBQWE7VUFDbkMsZUFBZSxDQUFDLE9BQU8sQ0FBQyxhQUFhO1VBQ3JDLGVBQWUsQ0FBQyxJQUFJLENBQUMsYUFBYTtVQUNsQyxlQUFlLENBQUMsSUFBSSxDQUFDLGFBQWE7VUFDbEMsZUFBZSxDQUFDLElBQUksQ0FBQyxVQUFVO1FBQ2pDLHVDQUF1QztVQUNyQyxlQUFlLENBQUMsS0FBSyxDQUFDLE9BQU87VUFDN0IsZUFBZSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUM7QUFDbEMsQ0FBQyxFQTdDZ0IsZUFBZSxHQUFmLHVCQUFlLEtBQWYsdUJBQWUsUUE2Qy9CO0FBRUQsU0FBZ0IsYUFBYSxDQUFDLE9BQXdCOztJQUNwRCxPQUFPLENBQUMsT0FBTyxPQUFPLEtBQUssUUFBUSxDQUFDO1FBQ2xDLENBQUMsQ0FBQyxNQUFBLE1BQUEsTUFBTTthQUNMLE9BQU8sQ0FBQyxlQUFlLENBQUMsR0FBRyxDQUFDO2FBQzVCLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7YUFDeEMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxPQUFPLElBQUksQ0FBQyxLQUFLLE9BQU8sQ0FBQywwQ0FBRyxDQUFDLENBQUMsbUNBQUksRUFBRTtRQUM5RCxDQUFDLENBQUMsT0FBTyxDQUFDLFFBQVEsRUFBRSxDQUFDO0FBQ3pCLENBQUM7QUFQRCxzQ0FPQyJ9
|
@ -109,7 +109,7 @@ export namespace ChannelTypes {
|
||||
}
|
||||
export interface VoiceConnection {
|
||||
userId: string;
|
||||
blob?: Blob;
|
||||
blob?: any;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
// REMINDER: 8 is admin in Discord, but 1 in Accord
|
||||
|
||||
export namespace PermissionTypes {
|
||||
export enum General {
|
||||
VIEW_CHANNELS = 1024,
|
||||
@ -16,8 +14,7 @@ export namespace PermissionTypes {
|
||||
ADMINISTRATOR = 1,
|
||||
}
|
||||
export enum Text {
|
||||
// ADD_REACTIONS = 2048 * 16,
|
||||
// MENTION_EVERYONE = 2048 * 8,
|
||||
SEND_FILES = 2048 * 8,
|
||||
READ_MESSAGES = 2048 * 4,
|
||||
MANAGE_MESSAGES = 2048 * 2,
|
||||
SEND_MESSAGES = 2048,
|
||||
@ -35,18 +32,18 @@ export namespace PermissionTypes {
|
||||
}
|
||||
export type Permission = General | Text | Voice;
|
||||
export type PermissionString = keyof typeof All;
|
||||
|
||||
|
||||
export const defaultPermissions =
|
||||
PermissionTypes.General.VIEW_CHANNELS
|
||||
| PermissionTypes.General.CREATE_INVITE
|
||||
| PermissionTypes.Text.SEND_MESSAGES
|
||||
| PermissionTypes.Text.READ_MESSAGES
|
||||
// | PermissionTypes.Text.ADD_REACTIONS
|
||||
| PermissionTypes.Text.SEND_FILES
|
||||
| PermissionTypes.Voice.CONNECT
|
||||
| PermissionTypes.Voice.SPEAK;
|
||||
}
|
||||
|
||||
export function getPermString(integer: number | string): string {
|
||||
export function getPermString(integer: number | string): string {
|
||||
return (typeof integer === 'string')
|
||||
? Object
|
||||
.entries(PermissionTypes.All)
|
@ -59,7 +59,7 @@ export declare namespace WS {
|
||||
/** Manually disconnect from the websocket; logout. */
|
||||
'disconnect': any;
|
||||
}
|
||||
|
||||
|
||||
/** WS Args are what is received from the websocket. */
|
||||
export interface From {
|
||||
/** Called when a guild channel is created. */
|
||||
@ -114,7 +114,7 @@ export declare namespace WS {
|
||||
'VOICE_STATE_UPDATE': Args.VoiceStateUpdate;
|
||||
'error': object;
|
||||
}
|
||||
|
||||
|
||||
export namespace Params {
|
||||
export interface AddFriend {
|
||||
/** Username of user (case insensitive). */
|
||||
@ -146,7 +146,7 @@ export declare namespace WS {
|
||||
/** ID of the channel to join. */
|
||||
channelId: string;
|
||||
}
|
||||
export interface ChannelLeave {}
|
||||
export interface ChannelLeave { }
|
||||
export interface GuildCreate {
|
||||
/** Name of the guild. */
|
||||
name: string;
|
||||
@ -237,10 +237,10 @@ export declare namespace WS {
|
||||
}
|
||||
export interface VoiceData {
|
||||
channelId: string;
|
||||
blob?: Blob;
|
||||
blob?: any;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export namespace Args {
|
||||
export interface ChannelCreate {
|
||||
/** ID of guild that the channel is in. */
|
24
frontend/src/types/tsconfig.json
Normal file
24
frontend/src/types/tsconfig.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
"lib": [
|
||||
"ES2017"
|
||||
], /* Specify library files to be included in the compilation. */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
||||
// build
|
||||
"outDir": "lib",
|
||||
"inlineSourceMap": true,
|
||||
"declaration": true,
|
||||
},
|
||||
"include": [
|
||||
"src/**.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"test"
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user