fix: hide chat delete actions when the chat delete permission is disabled (#27714)
The chat delete endpoints gate on chat.delete for non-admins, but none of the UI affordances that reach them were conditioned on it, so a user without the permission is offered controls that always fail with "Access prohibited". Gates every entry point on the same condition the backend enforces, matching the existing chat.share / chat.export / chat.import gates in the same components: - Sidebar chat menu (ChatMenu) Delete item — also covers the Search modal's menu, which reuses ChatMenu - Sidebar Shift+hover inline Delete button (ChatItem) - Sidebar hidden #delete-chat-button keyboard-shortcut target (ChatItem) - Chat navbar menu Delete item (Navbar/Menu) - Search modal Shift-held inline Delete button (SearchModal) - Settings -> Data Controls -> Delete All Chats - Settings -> Archived Chats per-row Delete
This commit is contained in:
6 files changed
+83
-71
No files matched your search
@@ -19,7 +19,7 @@
|
|||||||
getArchivedChatList,
|
getArchivedChatList,
|
||||||
unarchiveAllChats
|
unarchiveAllChats
|
||||||
} from '$lib/apis/chats';
|
} from '$lib/apis/chats';
|
||||||
import { chatId, showSettings } from '$lib/stores';
|
import { chatId, showSettings, user } from '$lib/stores';
|
||||||
import { refreshChatList } from '$lib/stores/chatList';
|
import { refreshChatList } from '$lib/stores/chatList';
|
||||||
import { formatNumber } from '$lib/utils';
|
import { formatNumber } from '$lib/utils';
|
||||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||||
@@ -348,19 +348,21 @@
|
|||||||
<UndoAction className="size-3.5" strokeWidth="1.5" />
|
<UndoAction className="size-3.5" strokeWidth="1.5" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content={$i18n.t('Delete Chat')}>
|
{#if $user?.role === 'admin' || ($user?.permissions?.chat?.delete ?? true)}
|
||||||
<button
|
<Tooltip content={$i18n.t('Delete Chat')}>
|
||||||
class="rounded-lg p-1 text-gray-400 transition-colors hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300"
|
<button
|
||||||
type="button"
|
class="rounded-lg p-1 text-gray-400 transition-colors hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300"
|
||||||
aria-label={$i18n.t('Delete Chat')}
|
type="button"
|
||||||
on:click={() => {
|
aria-label={$i18n.t('Delete Chat')}
|
||||||
selectedChatId = chat.id;
|
on:click={() => {
|
||||||
showDeleteConfirmDialog = true;
|
selectedChatId = chat.id;
|
||||||
}}
|
showDeleteConfirmDialog = true;
|
||||||
>
|
}}
|
||||||
<Trash className="size-3.5" strokeWidth="1.5" />
|
>
|
||||||
</button>
|
<Trash className="size-3.5" strokeWidth="1.5" />
|
||||||
</Tooltip>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -216,20 +216,22 @@
|
|||||||
</button>
|
</button>
|
||||||
</UserSettingRow>
|
</UserSettingRow>
|
||||||
|
|
||||||
<UserSettingRow
|
{#if $user?.role === 'admin' || ($user?.permissions?.chat?.delete ?? true)}
|
||||||
label={$i18n.t('Delete All Chats')}
|
<UserSettingRow
|
||||||
description={$i18n.t('Permanently delete every chat after confirmation.')}
|
label={$i18n.t('Delete All Chats')}
|
||||||
>
|
description={$i18n.t('Permanently delete every chat after confirmation.')}
|
||||||
<button
|
|
||||||
class={actionButtonClass}
|
|
||||||
on:click={() => {
|
|
||||||
showDeleteConfirmDialog = true;
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
{$i18n.t('Delete All')}
|
<button
|
||||||
</button>
|
class={actionButtonClass}
|
||||||
</UserSettingRow>
|
on:click={() => {
|
||||||
|
showDeleteConfirmDialog = true;
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{$i18n.t('Delete All')}
|
||||||
|
</button>
|
||||||
|
</UserSettingRow>
|
||||||
|
{/if}
|
||||||
</UserSettingSection>
|
</UserSettingSection>
|
||||||
|
|
||||||
<UserSettingSection title={$i18n.t('Files')}>
|
<UserSettingSection title={$i18n.t('Files')}>
|
||||||
|
|||||||
@@ -487,16 +487,18 @@
|
|||||||
<div class="flex items-center">{$i18n.t('Archive')}</div>
|
<div class="flex items-center">{$i18n.t('Archive')}</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
{#if $user?.role === 'admin' || ($user?.permissions?.chat?.delete ?? true)}
|
||||||
draggable="false"
|
<button
|
||||||
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[0.8125rem] cursor-pointer select-none hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
draggable="false"
|
||||||
on:click={() => {
|
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[0.8125rem] cursor-pointer select-none hover:bg-gray-50/40 dark:hover:bg-gray-800/40"
|
||||||
deleteChatHandler();
|
on:click={() => {
|
||||||
}}
|
deleteChatHandler();
|
||||||
>
|
}}
|
||||||
<GarbageBin className="size-3.5" strokeWidth="1.5" />
|
>
|
||||||
<div class="flex items-center">{$i18n.t('Delete')}</div>
|
<GarbageBin className="size-3.5" strokeWidth="1.5" />
|
||||||
</button>
|
<div class="flex items-center">{$i18n.t('Delete')}</div>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<hr class="border-gray-50/30 dark:border-gray-800/30 mx-1 my-0.5" />
|
<hr class="border-gray-50/30 dark:border-gray-800/30 mx-1 my-0.5" />
|
||||||
|
|
||||||
|
|||||||
@@ -786,17 +786,19 @@
|
|||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content={$i18n.t('Delete')}>
|
{#if $user?.role === 'admin' || ($user?.permissions?.chat?.delete ?? true)}
|
||||||
<button
|
<Tooltip content={$i18n.t('Delete')}>
|
||||||
class="self-center dark:hover:text-white transition"
|
<button
|
||||||
on:click|stopPropagation={() => {
|
class="self-center dark:hover:text-white transition"
|
||||||
deleteChatHandler(chat.id);
|
on:click|stopPropagation={() => {
|
||||||
}}
|
deleteChatHandler(chat.id);
|
||||||
type="button"
|
}}
|
||||||
>
|
type="button"
|
||||||
<GarbageBin strokeWidth="2" />
|
>
|
||||||
</button>
|
<GarbageBin strokeWidth="2" />
|
||||||
</Tooltip>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
|||||||
@@ -705,18 +705,20 @@
|
|||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content={$i18n.t('Delete')}>
|
{#if $user?.role === 'admin' || ($user?.permissions?.chat?.delete ?? true)}
|
||||||
<button
|
<Tooltip content={$i18n.t('Delete')}>
|
||||||
class=" self-center dark:hover:text-white transition disabled:cursor-not-allowed"
|
<button
|
||||||
disabled={deleting}
|
class=" self-center dark:hover:text-white transition disabled:cursor-not-allowed"
|
||||||
on:click={() => {
|
disabled={deleting}
|
||||||
deleteChatHandler(id);
|
on:click={() => {
|
||||||
}}
|
deleteChatHandler(id);
|
||||||
type="button"
|
}}
|
||||||
>
|
type="button"
|
||||||
<GarbageBinIcon className="size-3.5" strokeWidth="1.7" />
|
>
|
||||||
</button>
|
<GarbageBinIcon className="size-3.5" strokeWidth="1.7" />
|
||||||
</Tooltip>
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex self-center z-10 items-end">
|
<div class="flex self-center z-10 items-end">
|
||||||
@@ -756,7 +758,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</ChatMenu>
|
</ChatMenu>
|
||||||
|
|
||||||
{#if id === $chatId}
|
{#if id === $chatId && ($user?.role === 'admin' || ($user?.permissions?.chat?.delete ?? true))}
|
||||||
<!-- Shortcut support using "delete-chat-button" id -->
|
<!-- Shortcut support using "delete-chat-button" id -->
|
||||||
<button
|
<button
|
||||||
id="delete-chat-button"
|
id="delete-chat-button"
|
||||||
|
|||||||
@@ -456,16 +456,18 @@
|
|||||||
<div class="flex items-center">{$i18n.t('Archive')}</div>
|
<div class="flex items-center">{$i18n.t('Archive')}</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
{#if $user?.role === 'admin' || ($user?.permissions?.chat?.delete ?? true)}
|
||||||
draggable="false"
|
<button
|
||||||
class="flex h-[1.6875rem] gap-2 items-center rounded-xl px-2 text-[0.8125rem] cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 w-full"
|
draggable="false"
|
||||||
on:click={() => {
|
class="flex h-[1.6875rem] gap-2 items-center rounded-xl px-2 text-[0.8125rem] cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 w-full"
|
||||||
deleteHandler();
|
on:click={() => {
|
||||||
}}
|
deleteHandler();
|
||||||
>
|
}}
|
||||||
<TrashIcon className="size-3.5" strokeWidth="1.5" />
|
>
|
||||||
<div class="flex items-center">{$i18n.t('Delete')}</div>
|
<TrashIcon className="size-3.5" strokeWidth="1.5" />
|
||||||
</button>
|
<div class="flex items-center">{$i18n.t('Delete')}</div>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
Reference in new issue
Block a user