feat/enh: group channel
This commit is contained in:
@@ -181,9 +181,18 @@
|
||||
};
|
||||
|
||||
const initChannels = async () => {
|
||||
// default (none), group, dm type
|
||||
await channels.set(
|
||||
(await getChannels(localStorage.token)).sort((a, b) =>
|
||||
a.type === b.type ? 0 : a.type === 'dm' ? 1 : -1
|
||||
a.type === b.type
|
||||
? 0
|
||||
: a.type === 'dm'
|
||||
? 1
|
||||
: a.type === 'group'
|
||||
? b.type === 'dm'
|
||||
? -1
|
||||
: 0
|
||||
: -1
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -486,7 +495,7 @@
|
||||
|
||||
<ChannelModal
|
||||
bind:show={showCreateChannel}
|
||||
onSubmit={async ({ type, name, access_control, user_ids }) => {
|
||||
onSubmit={async ({ type, name, is_private, access_control, group_ids, user_ids }) => {
|
||||
name = name?.trim();
|
||||
|
||||
if (type === 'dm') {
|
||||
@@ -504,7 +513,9 @@
|
||||
const res = await createNewChannel(localStorage.token, {
|
||||
type: type,
|
||||
name: name,
|
||||
is_private: is_private,
|
||||
access_control: access_control,
|
||||
group_ids: group_ids,
|
||||
user_ids: user_ids
|
||||
}).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
@@ -940,14 +951,14 @@
|
||||
</Folder>
|
||||
{/if}
|
||||
|
||||
{#if $config?.features?.enable_channels && ($user?.role === 'admin' || $channels.length > 0)}
|
||||
{#if $config?.features?.enable_channels && ($user?.role === 'admin' || ($user?.permissions?.features?.channels ?? true))}
|
||||
<Folder
|
||||
id="sidebar-channels"
|
||||
className="px-2 mt-0.5"
|
||||
name={$i18n.t('Channels')}
|
||||
chevron={false}
|
||||
dragAndDrop={false}
|
||||
onAdd={$user?.role === 'admin'
|
||||
onAdd={$user?.role === 'admin' || ($user?.permissions?.features?.channels ?? true)
|
||||
? async () => {
|
||||
await tick();
|
||||
|
||||
|
||||
@@ -31,10 +31,13 @@
|
||||
{channel}
|
||||
edit={true}
|
||||
{onUpdate}
|
||||
onSubmit={async ({ name, access_control }) => {
|
||||
onSubmit={async ({ name, is_private, access_control, group_ids, user_ids }) => {
|
||||
const res = await updateChannelById(localStorage.token, channel.id, {
|
||||
name,
|
||||
access_control
|
||||
is_private,
|
||||
access_control,
|
||||
group_ids,
|
||||
user_ids
|
||||
}).catch((error) => {
|
||||
toast.error(error.message);
|
||||
});
|
||||
@@ -119,7 +122,7 @@
|
||||
{/if}
|
||||
{:else}
|
||||
<div class=" size-4 justify-center flex items-center ml-1">
|
||||
{#if channel?.access_control === null}
|
||||
{#if channel?.type === 'group' ? !channel?.is_private : channel?.access_control === null}
|
||||
<Hashtag className="size-3.5" strokeWidth="2.5" />
|
||||
{:else}
|
||||
<Lock className="size-[15px]" strokeWidth="2" />
|
||||
@@ -154,7 +157,7 @@
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{#if channel?.type === 'dm'}
|
||||
{#if ['dm'].includes(channel?.type)}
|
||||
<div
|
||||
class="ml-0.5 mr-1 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
|
||||
>
|
||||
@@ -181,7 +184,7 @@
|
||||
<XMark className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
{:else if $user?.role === 'admin'}
|
||||
{:else if $user?.role === 'admin' || channel.user_id === $user?.id}
|
||||
<div
|
||||
class="ml-0.5 mr-1 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
|
||||
>
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { getContext, createEventDispatcher, onMount } from 'svelte';
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import { createNewChannel, deleteChannelById } from '$lib/apis/channels';
|
||||
import { user } from '$lib/stores';
|
||||
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import Modal from '$lib/components/common/Modal.svelte';
|
||||
import AccessControl from '$lib/components/workspace/common/AccessControl.svelte';
|
||||
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import UserListSelector from '$lib/components/workspace/common/UserListSelector.svelte';
|
||||
const i18n = getContext('i18n');
|
||||
import MemberSelector from '$lib/components/workspace/common/MemberSelector.svelte';
|
||||
import Visibility from '$lib/components/workspace/common/Visibility.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
export let show = false;
|
||||
export let onSubmit: Function = () => {};
|
||||
@@ -21,10 +25,14 @@
|
||||
export let channel = null;
|
||||
export let edit = false;
|
||||
|
||||
let channelTypes = ['group', 'dm'];
|
||||
let type = '';
|
||||
let name = '';
|
||||
|
||||
let isPrivate = null;
|
||||
let accessControl = {};
|
||||
|
||||
let groupIds = [];
|
||||
let userIds = [];
|
||||
|
||||
let loading = false;
|
||||
@@ -33,12 +41,26 @@
|
||||
name = name.replace(/\s/g, '-').toLocaleLowerCase();
|
||||
}
|
||||
|
||||
$: onTypeChange(type);
|
||||
|
||||
const onTypeChange = (type) => {
|
||||
if (type === 'group') {
|
||||
if (isPrivate === null) {
|
||||
isPrivate = true;
|
||||
}
|
||||
} else {
|
||||
isPrivate = null;
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = async () => {
|
||||
loading = true;
|
||||
await onSubmit({
|
||||
type: type,
|
||||
name: name.replace(/\s/g, '-'),
|
||||
access_control: accessControl,
|
||||
is_private: type === 'group' ? isPrivate : null,
|
||||
access_control: type === '' ? accessControl : {},
|
||||
group_ids: groupIds,
|
||||
user_ids: userIds
|
||||
});
|
||||
show = false;
|
||||
@@ -46,16 +68,24 @@
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
type = channel?.type ?? '';
|
||||
name = channel?.name ?? '';
|
||||
accessControl = channel.access_control;
|
||||
userIds = channel?.user_ids ?? [];
|
||||
if ($user?.role === 'admin') {
|
||||
channelTypes = ['', 'group', 'dm'];
|
||||
} else {
|
||||
channelTypes = ['group', 'dm'];
|
||||
}
|
||||
|
||||
type = channel?.type ?? channelTypes[0];
|
||||
|
||||
if (channel) {
|
||||
name = channel?.name ?? '';
|
||||
isPrivate = channel?.is_private ?? null;
|
||||
accessControl = channel.access_control;
|
||||
userIds = channel?.user_ids ?? [];
|
||||
}
|
||||
};
|
||||
|
||||
$: if (show) {
|
||||
if (channel) {
|
||||
init();
|
||||
}
|
||||
init();
|
||||
} else {
|
||||
resetHandler();
|
||||
}
|
||||
@@ -119,21 +149,51 @@
|
||||
}}
|
||||
>
|
||||
{#if !edit}
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<div class="flex flex-col w-full mt-2 mb-1">
|
||||
<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Channel Type')}</div>
|
||||
|
||||
<div class="flex-1">
|
||||
<select
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
bind:value={type}
|
||||
<Tooltip
|
||||
content={type === 'dm'
|
||||
? $i18n.t('A private conversation between you and selected users')
|
||||
: type === 'group'
|
||||
? $i18n.t('A collaboration channel where people join as members')
|
||||
: $i18n.t(
|
||||
'A discussion channel where access is controlled by groups and permissions'
|
||||
)}
|
||||
placement="top-start"
|
||||
>
|
||||
<option value="">{$i18n.t('Channel')}</option>
|
||||
<option value="dm">{$i18n.t('Direct Message')}</option>
|
||||
</select>
|
||||
<select
|
||||
class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
|
||||
bind:value={type}
|
||||
>
|
||||
{#each channelTypes as channelType, channelTypeIdx (channelType)}
|
||||
<option value={channelType} selected={channelTypeIdx === 0}>
|
||||
{#if channelType === 'group'}
|
||||
{$i18n.t('Group Channel')}
|
||||
{:else if channelType === 'dm'}
|
||||
{$i18n.t('Direct Message')}
|
||||
{:else if channelType === ''}
|
||||
{$i18n.t('Channel')}
|
||||
{/if}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class=" text-gray-300 dark:text-gray-700 text-xs">
|
||||
{#if type === ''}
|
||||
{$i18n.t('Discussion channel where access is based on groups and permissions')}
|
||||
{:else if type === 'group'}
|
||||
{$i18n.t('Collaboration channel where people join as members')}
|
||||
{:else if type === 'dm'}
|
||||
{$i18n.t('Private conversation between selected users')}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-2">
|
||||
<div class=" mb-1 text-xs text-gray-500">
|
||||
{$i18n.t('Channel Name')}
|
||||
@@ -154,17 +214,31 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" border-gray-100/40 dark:border-gray-700/10 my-2.5 w-full" />
|
||||
|
||||
<div class="-mx-2">
|
||||
{#if type === 'dm'}
|
||||
<UserListSelector bind:userIds />
|
||||
{:else}
|
||||
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-950 rounded-3xl">
|
||||
{#if type !== 'dm'}
|
||||
<div class="-mx-2 mb-1 mt-2.5 px-2">
|
||||
{#if type === ''}
|
||||
<AccessControl bind:accessControl accessRoles={['read', 'write']} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if type === 'group'}
|
||||
<Visibility
|
||||
state={isPrivate ? 'private' : 'public'}
|
||||
onChange={(value) => {
|
||||
if (value === 'private') {
|
||||
isPrivate = true;
|
||||
} else {
|
||||
isPrivate = false;
|
||||
}
|
||||
console.log(value, isPrivate);
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if type !== ''}
|
||||
<div class="">
|
||||
<MemberSelector bind:userIds includeGroups={type !== 'dm'} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
|
||||
{#if edit}
|
||||
|
||||
Reference in New Issue
Block a user