Co-Authored-By: Classic298 <27028174+Classic298@users.noreply.github.com>
This commit is contained in:
Timothy Jaeryang Baek
2026-08-11 01:25:20 -06:00
co-authored by Classic298
parent 865c80c160
commit c8f8fa451a
29 changed files with 103 additions and 76 deletions
+10
View File
@@ -64,6 +64,16 @@ code {
@apply bg-gray-50/40 dark:bg-gray-800/40; @apply bg-gray-50/40 dark:bg-gray-800/40;
} }
/* visibility:hidden is not focusable, so a keyboard user could never reveal these */
.hover-reveal {
@apply opacity-0 pointer-events-none;
}
.group:hover .hover-reveal,
.group:focus-within .hover-reveal {
@apply opacity-100 pointer-events-auto;
}
.marked a { .marked a {
@apply underline; @apply underline;
} }
+1 -1
View File
@@ -101,7 +101,7 @@
<!-- Close button (visible on hover) --> <!-- Close button (visible on hover) -->
<button <button
bind:this={closeButtonElement} bind:this={closeButtonElement}
class="absolute -top-0.5 -left-0.5 p-0.5 rounded-full opacity-0 group-hover:opacity-100 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-opacity z-10" class="absolute -top-0.5 -left-0.5 p-0.5 rounded-full opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-opacity z-10"
on:click|stopPropagation={closeHandler} on:click|stopPropagation={closeHandler}
aria-label="Dismiss notification" aria-label="Dismiss notification"
> >
@@ -149,7 +149,9 @@
</div> </div>
</div> </div>
<div class="grid grid-cols-7 text-center text-[0.5625rem] text-gray-400 dark:text-gray-500 mb-0.5"> <div
class="grid grid-cols-7 text-center text-[0.5625rem] text-gray-400 dark:text-gray-500 mb-0.5"
>
{#each ['S', 'M', 'T', 'W', 'T', 'F', 'S'] as d} {#each ['S', 'M', 'T', 'W', 'T', 'F', 'S'] as d}
<div class="py-0.5">{d}</div> <div class="py-0.5">{d}</div>
{/each} {/each}
@@ -224,7 +226,7 @@
{#if isDeletable(cal)} {#if isDeletable(cal)}
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<span <span
class="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 class="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 group-focus-within:opacity-100
transition-all duration-150" transition-all duration-150"
role="button" role="button"
tabindex="-1" tabindex="-1"
@@ -850,7 +850,7 @@
</div> </div>
<div class=" absolute -top-1 -right-1"> <div class=" absolute -top-1 -right-1">
<button <button
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition" class=" bg-white text-black border border-white rounded-full hover-reveal transition"
type="button" type="button"
aria-label={$i18n.t('Remove file')} aria-label={$i18n.t('Remove file')}
on:click={() => { on:click={() => {
@@ -209,9 +209,7 @@
: 'transition: transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1);'}" : 'transition: transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1);'}"
> >
{#if !edit && !disabled} {#if !edit && !disabled}
<div <div class=" absolute {showButtons ? '' : 'hover-reveal'} right-1 -top-2 z-10">
class=" absolute {showButtons ? '' : 'invisible group-hover:visible'} right-1 -top-2 z-10"
>
<div <div
class="flex gap-1 rounded-lg bg-white dark:bg-gray-850 shadow-md p-0.5 border border-gray-100/30 dark:border-gray-850/30" class="flex gap-1 rounded-lg bg-white dark:bg-gray-850 shadow-md p-0.5 border border-gray-100/30 dark:border-gray-850/30"
> >
@@ -413,7 +411,7 @@
{#if message.created_at} {#if message.created_at}
<div <div
class="mt-1.5 flex shrink-0 items-center text-xs self-center invisible group-hover:visible text-gray-500 font-normal first-letter:capitalize" class="mt-1.5 flex shrink-0 items-center text-xs self-center hover-reveal text-gray-500 font-normal first-letter:capitalize"
> >
<Tooltip content={dayjs(message.created_at / 1000000).format('LLLL')}> <Tooltip content={dayjs(message.created_at / 1000000).format('LLLL')}>
{dayjs(message.created_at / 1000000).format('HH:mm')} {dayjs(message.created_at / 1000000).format('HH:mm')}
@@ -555,7 +553,8 @@
allowEmbeds={!!message?.meta?.model_id} allowEmbeds={!!message?.meta?.model_id}
/> />
</span>{#if message.created_at !== message.updated_at && (message?.meta?.model_id ?? null) === null}<span </span>{#if message.created_at !== message.updated_at && (message?.meta?.model_id ?? null) === null}<span
class="text-gray-500 text-[0.625rem] pl-1 self-center">({$i18n.t('edited')})</span class="text-gray-500 text-[0.625rem] pl-1 self-center"
>({$i18n.t('edited')})</span
>{/if} >{/if}
{/if} {/if}
</div> </div>
@@ -40,7 +40,7 @@
> >
<span class="min-w-0 truncate">{title}</span> <span class="min-w-0 truncate">{title}</span>
<ChevronRight <ChevronRight
className="size-3.5 shrink-0 text-gray-400/70 opacity-0 transition-opacity group-hover:opacity-100 dark:text-gray-500/70" className="size-3.5 shrink-0 text-gray-400/70 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 dark:text-gray-500/70"
strokeWidth="2" strokeWidth="2"
/> />
</button> </button>
@@ -22,7 +22,7 @@
</script> </script>
<div <div
class="group/item flex h-[1.6875rem] w-full cursor-pointer select-none items-center rounded-xl px-2 text-left text-[0.8125rem] font-normal text-gray-700 outline-hidden transition-colors duration-75 hover:bg-gray-50/40 dark:text-gray-100 dark:hover:bg-gray-800/40 [&_button:hover]:bg-transparent! dark:[&_button:hover]:bg-transparent! {selected class="group flex h-[1.6875rem] w-full cursor-pointer select-none items-center rounded-xl px-2 text-left text-[0.8125rem] font-normal text-gray-700 outline-hidden transition-colors duration-75 hover:bg-gray-50/40 dark:text-gray-100 dark:hover:bg-gray-800/40 [&_button:hover]:bg-transparent! dark:[&_button:hover]:bg-transparent! {selected
? 'bg-gray-50/70 dark:bg-gray-800/60' ? 'bg-gray-50/70 dark:bg-gray-800/60'
: ''}" : ''}"
> >
@@ -41,9 +41,7 @@
</button> </button>
<div <div
class="{showMenu class="{showMenu ? 'visible' : 'hover-reveal'} ml-auto flex shrink-0 items-center gap-1.5 pl-2"
? 'visible'
: 'invisible group-hover/item:visible'} ml-auto flex shrink-0 items-center gap-1.5 pl-2"
> >
<Dropdown <Dropdown
bind:show={showMenu} bind:show={showMenu}
@@ -54,7 +52,10 @@
onMenuOpenChange(item.id, state); onMenuOpenChange(item.id, state);
}} }}
> >
<Tooltip content={$i18n.t('More')} className="group-hover/item:opacity-100 opacity-0"> <Tooltip
content={$i18n.t('More')}
className="group-hover:opacity-100 group-focus-within:opacity-100 opacity-0"
>
<button <button
type="button" type="button"
aria-label={$i18n.t('More Options')} aria-label={$i18n.t('More Options')}
@@ -125,7 +125,7 @@
<span <span
role="button" role="button"
tabindex="-1" tabindex="-1"
class="text-gray-400 dark:text-gray-500 opacity-0 group-hover:opacity-100 transition shrink-0 p-0.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700" class="text-gray-400 dark:text-gray-500 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition shrink-0 p-0.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
on:click|stopPropagation={() => openPortExternal(port.port)} on:click|stopPropagation={() => openPortExternal(port.port)}
> >
<svg <svg
+1 -1
View File
@@ -1765,7 +1765,7 @@
class=" bg-white text-black border border-white rounded-full {($settings?.highContrastMode ?? class=" bg-white text-black border border-white rounded-full {($settings?.highContrastMode ??
false) false)
? '' ? ''
: 'outline-hidden focus:outline-hidden group-hover:visible invisible transition'}" : 'hover-reveal transition'}"
type="button" type="button"
aria-label={$i18n.t('Remove file')} aria-label={$i18n.t('Remove file')}
on:click={() => { on:click={() => {
@@ -46,7 +46,7 @@
{header} {header}
</span> </span>
<div class="invisible group-hover:visible flex gap-0.5 shrink-0"> <div class="hover-reveal flex gap-0.5 shrink-0">
<Tooltip content={copied ? $i18n.t('Copied') : $i18n.t('Copy')}> <Tooltip content={copied ? $i18n.t('Copied') : $i18n.t('Copy')}>
<button <button
class="p-1 rounded-lg bg-transparent hover:bg-black/5 dark:hover:bg-white/5 transition" class="p-1 rounded-lg bg-transparent hover:bg-black/5 dark:hover:bg-white/5 transition"
@@ -245,7 +245,7 @@
</table> </table>
</div> </div>
<div class=" absolute top-1 right-1.5 z-20 invisible group-hover:visible flex gap-0.5"> <div class=" absolute top-1 right-1.5 z-20 hover-reveal flex gap-0.5">
<Tooltip content={$i18n.t('Copy')}> <Tooltip content={$i18n.t('Copy')}>
<button <button
class="p-1 rounded-lg bg-transparent transition" class="p-1 rounded-lg bg-transparent transition"
@@ -443,7 +443,7 @@
> >
<time <time
datetime={new Date(message.timestamp * 1000).toISOString()} datetime={new Date(message.timestamp * 1000).toISOString()}
class="invisible group-hover:visible ml-1 shrink-0 whitespace-nowrap text-[0.6875rem] tabular-nums text-gray-400 dark:text-gray-600 select-none" class="hover-reveal ml-1 shrink-0 whitespace-nowrap text-[0.6875rem] tabular-nums text-gray-400 dark:text-gray-600 select-none"
> >
{formatMessageTimestamp(message.timestamp * 1000)} {formatMessageTimestamp(message.timestamp * 1000)}
</time> </time>
@@ -462,7 +462,7 @@
id="merge-response-button" id="merge-response-button"
class="{true class="{true
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition" : 'hover-reveal'} p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
on:click={() => { on:click={() => {
mergeResponsesHandler(); mergeResponsesHandler();
}} }}
@@ -335,8 +335,8 @@
<!-- Delete --> <!-- Delete -->
<div class="pt-1.5"> <div class="pt-1.5">
<button <button
class="hover-reveal p-1 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition rounded-lg"
aria-label={$i18n.t('Delete')} aria-label={$i18n.t('Delete')}
class="invisible group-hover:visible p-1 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition rounded-lg"
on:click={() => deleteIndices(di.indices)} on:click={() => deleteIndices(di.indices)}
> >
<svg <svg
@@ -1026,7 +1026,7 @@
aria-label={$i18n.t('Edit')} aria-label={$i18n.t('Edit')}
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
on:click={() => { on:click={() => {
editMessageHandler(); editMessageHandler();
}} }}
@@ -1056,7 +1056,7 @@
aria-label={$i18n.t('Copy')} aria-label={$i18n.t('Copy')}
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition copy-response-button" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition copy-response-button"
on:click={() => { on:click={() => {
copyToClipboard(visibleResponseContent); copyToClipboard(visibleResponseContent);
}} }}
@@ -1085,7 +1085,7 @@
aria-label={$i18n.t('Insert into note')} aria-label={$i18n.t('Insert into note')}
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} rounded-lg px-2 py-1.5 text-xs text-gray-500 transition hover:bg-black/5 hover:text-black dark:hover:bg-white/5 dark:hover:text-white" : 'hover-reveal'} rounded-lg px-2 py-1.5 text-xs text-gray-500 transition hover:bg-black/5 hover:text-black dark:hover:bg-white/5 dark:hover:text-white"
on:click={() => { on:click={() => {
onInsertToNote?.(visibleResponseContent); onInsertToNote?.(visibleResponseContent);
}} }}
@@ -1102,7 +1102,7 @@
id="speak-button-{message.id}" id="speak-button-{message.id}"
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
on:click={() => { on:click={() => {
if (!loadingSpeech) { if (!loadingSpeech) {
if (speaking) { if (speaking) {
@@ -1202,7 +1202,7 @@
aria-hidden="true" aria-hidden="true"
class=" {isLastMessage || ($settings?.highContrastMode ?? false) class=" {isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition whitespace-pre-wrap" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition whitespace-pre-wrap"
on:click={() => { on:click={() => {
console.log(message); console.log(message);
}} }}
@@ -1234,7 +1234,7 @@
aria-label={$i18n.t('Good Response')} aria-label={$i18n.t('Good Response')}
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg {( : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg {(
message?.annotation?.rating ?? '' message?.annotation?.rating ?? ''
).toString() === '1' ).toString() === '1'
? 'bg-gray-100 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
@@ -1272,7 +1272,7 @@
aria-label={$i18n.t('Bad Response')} aria-label={$i18n.t('Bad Response')}
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg {( : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg {(
message?.annotation?.rating ?? '' message?.annotation?.rating ?? ''
).toString() === '-1' ).toString() === '-1'
? 'bg-gray-100 dark:bg-gray-800' ? 'bg-gray-100 dark:bg-gray-800'
@@ -1314,7 +1314,7 @@
id="continue-response-button" id="continue-response-button"
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
on:click={() => { on:click={() => {
continueResponse(); continueResponse();
}} }}
@@ -1390,7 +1390,7 @@
aria-label={$i18n.t('Regenerate')} aria-label={$i18n.t('Regenerate')}
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -1417,7 +1417,7 @@
aria-label={$i18n.t('Regenerate')} aria-label={$i18n.t('Regenerate')}
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={() => { on:click={() => {
showRateComment = false; showRateComment = false;
regenerateResponse(message); regenerateResponse(message);
@@ -1462,7 +1462,7 @@
aria-label={action.name} aria-label={action.name}
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
on:click={() => { on:click={() => {
actionMessage(action.id, message); actionMessage(action.id, message);
}} }}
@@ -1492,7 +1492,7 @@
aria-label="Fork chat" aria-label="Fork chat"
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
on:click={() => { on:click={() => {
forkHandler?.(message.id); forkHandler?.(message.id);
}} }}
@@ -1526,7 +1526,7 @@
id="delete-response-button" id="delete-response-button"
class="{isLastMessage || ($settings?.highContrastMode ?? false) class="{isLastMessage || ($settings?.highContrastMode ?? false)
? 'visible' ? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition disabled:opacity-50 disabled:hover:bg-transparent" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition disabled:opacity-50 disabled:hover:bg-transparent"
disabled={!allowDelete} disabled={!allowDelete}
on:click={(e) => { on:click={(e) => {
if (!allowDelete) { if (!allowDelete) {
@@ -1567,7 +1567,7 @@
> >
<time <time
datetime={new Date(message.timestamp * 1000).toISOString()} datetime={new Date(message.timestamp * 1000).toISOString()}
class="invisible group-hover:visible ml-1 shrink-0 whitespace-nowrap text-[0.6875rem] tabular-nums text-gray-400 dark:text-gray-600 select-none" class="hover-reveal ml-1 shrink-0 whitespace-nowrap text-[0.6875rem] tabular-nums text-gray-400 dark:text-gray-600 select-none"
> >
{formatMessageTimestamp(message.timestamp * 1000)} {formatMessageTimestamp(message.timestamp * 1000)}
</time> </time>
@@ -225,7 +225,7 @@
class=" bg-white text-black border border-white rounded-full {($settings?.highContrastMode ?? class=" bg-white text-black border border-white rounded-full {($settings?.highContrastMode ??
false) false)
? '' ? ''
: 'group-hover:visible invisible transition'}" : 'hover-reveal transition'}"
type="button" type="button"
on:click={() => { on:click={() => {
editedFiles.splice(fileIdx, 1); editedFiles.splice(fileIdx, 1);
@@ -421,9 +421,7 @@
> >
<time <time
datetime={new Date(message.timestamp * 1000).toISOString()} datetime={new Date(message.timestamp * 1000).toISOString()}
class="{compactPreview class="{compactPreview ? '' : 'hover-reveal'} {($settings?.chatBubble ?? true)
? ''
: 'invisible group-hover:visible'} {($settings?.chatBubble ?? true)
? 'mr-1' ? 'mr-1'
: 'ml-1 shrink-0 whitespace-nowrap'} text-[0.6875rem] tabular-nums text-gray-400 dark:text-gray-600 select-none" : 'ml-1 shrink-0 whitespace-nowrap'} text-[0.6875rem] tabular-nums text-gray-400 dark:text-gray-600 select-none"
> >
@@ -533,7 +531,7 @@
<button <button
class="{($settings?.highContrastMode ?? false) class="{($settings?.highContrastMode ?? false)
? '' ? ''
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition edit-user-message-button" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition edit-user-message-button"
aria-label={$i18n.t('Edit')} aria-label={$i18n.t('Edit')}
on:click={() => { on:click={() => {
editMessageHandler(); editMessageHandler();
@@ -562,7 +560,7 @@
<button <button
class="{($settings?.highContrastMode ?? false) class="{($settings?.highContrastMode ?? false)
? '' ? ''
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition" : 'hover-reveal'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition"
aria-label={$i18n.t('Copy')} aria-label={$i18n.t('Copy')}
on:click={() => { on:click={() => {
copyToClipboard(message.content); copyToClipboard(message.content);
@@ -593,7 +591,7 @@
aria-label={$i18n.t('Delete')} aria-label={$i18n.t('Delete')}
class="{($settings?.highContrastMode ?? false) class="{($settings?.highContrastMode ?? false)
? '' ? ''
: 'invisible group-hover:visible'} p-1 rounded-sm dark:hover:text-white hover:text-black transition" : 'hover-reveal'} p-1 rounded-sm dark:hover:text-white hover:text-black transition"
on:click={(e) => { on:click={(e) => {
if (e.shiftKey) { if (e.shiftKey) {
deleteMessageHandler(); deleteMessageHandler();
+1 -1
View File
@@ -61,7 +61,7 @@
</div> </div>
<button <button
class={data?.message?.favorite ? '' : 'invisible group-hover:visible'} class={data?.message?.favorite ? '' : 'hover-reveal'}
aria-label={data?.message?.favorite aria-label={data?.message?.favorite
? $i18n.t('Remove from favorites') ? $i18n.t('Remove from favorites')
: $i18n.t('Add to favorites')} : $i18n.t('Add to favorites')}
@@ -101,7 +101,7 @@
class="absolute inset-0 flex items-center justify-center bg-black/0 transition-colors duration-200 group-hover:bg-black/40" class="absolute inset-0 flex items-center justify-center bg-black/0 transition-colors duration-200 group-hover:bg-black/40"
> >
<svg <svg
class="h-4 w-4 text-white opacity-0 drop-shadow-sm transition-opacity duration-200 group-hover:opacity-100" class="h-4 w-4 text-white opacity-0 drop-shadow-sm transition-opacity duration-200 group-hover:opacity-100 group-focus-within:opacity-100"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@@ -187,7 +187,9 @@
class=" rounded-full {imageClassName} object-cover" class=" rounded-full {imageClassName} object-cover"
/> />
<div class="absolute bottom-0 right-0 opacity-0 group-hover:opacity-100 transition"> <div
class="absolute bottom-0 right-0 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition"
>
<div class="p-1 rounded-full bg-white text-black border-gray-100 shadow"> <div class="p-1 rounded-full bg-white text-black border-gray-100 shadow">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -205,7 +207,7 @@
</div> </div>
<div class="flex flex-col w-full justify-center mt-2"> <div class="flex flex-col w-full justify-center mt-2">
<button <button
class=" text-xs text-center text-gray-500 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 transition-all" class=" text-xs text-center text-gray-500 rounded-lg py-0.5 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-all"
type="button" type="button"
on:click={async () => { on:click={async () => {
profileImageUrl = `${WEBUI_BASE_URL}/user.png`; profileImageUrl = `${WEBUI_BASE_URL}/user.png`;
@@ -213,7 +215,7 @@
> >
<button <button
class="rounded-lg py-0.5 text-center text-xs text-gray-600 opacity-0 transition-all group-hover:opacity-100 dark:text-gray-400" class="rounded-lg py-0.5 text-center text-xs text-gray-600 opacity-0 transition-all group-hover:opacity-100 group-focus-within:opacity-100 dark:text-gray-400"
type="button" type="button"
on:click={async () => { on:click={async () => {
if (canvasPixelTest()) { if (canvasPixelTest()) {
@@ -232,7 +234,7 @@
> >
<button <button
class="rounded-lg py-0.5 text-center text-xs text-gray-600 opacity-0 transition-all group-hover:opacity-100 dark:text-gray-400" class="rounded-lg py-0.5 text-center text-xs text-gray-600 opacity-0 transition-all group-hover:opacity-100 group-focus-within:opacity-100 dark:text-gray-400"
type="button" type="button"
on:click={async () => { on:click={async () => {
const url = await getGravatarUrl(localStorage.token, user?.email); const url = await getGravatarUrl(localStorage.token, user?.email);
+1 -1
View File
@@ -183,7 +183,7 @@
class=" bg-white text-black border border-gray-50 rounded-full {($settings?.highContrastMode ?? class=" bg-white text-black border border-gray-50 rounded-full {($settings?.highContrastMode ??
false) false)
? '' ? ''
: 'outline-hidden focus:outline-hidden group-hover:visible invisible transition'}" : 'hover-reveal transition'}"
type="button" type="button"
on:click|stopPropagation={() => { on:click|stopPropagation={() => {
dispatch('dismiss'); dispatch('dismiss');
+4 -1
View File
@@ -174,7 +174,10 @@
{#if onAdd} {#if onAdd}
<button <button
class="absolute z-10 right-2 invisible group-hover:visible self-center flex items-center dark:text-gray-300" class="absolute z-10 right-2 hover-reveal self-center flex items-center dark:text-gray-300"
on:pointerup={(e) => {
e.stopPropagation();
}}
on:click={(e) => { on:click={(e) => {
e.stopPropagation(); e.stopPropagation();
onAdd(); onAdd();
+1 -1
View File
@@ -80,7 +80,7 @@
<div class=" absolute -top-1 -right-1"> <div class=" absolute -top-1 -right-1">
<button <button
aria-label={$i18n.t('Remove image')} aria-label={$i18n.t('Remove image')}
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition" class=" bg-white text-black border border-white rounded-full hover-reveal transition"
type="button" type="button"
on:click={() => { on:click={() => {
onDismiss(); onDismiss();
+2 -2
View File
@@ -1204,7 +1204,7 @@
<div class=" self-center text-[0.8125rem] leading-5">{$i18n.t('New Chat')}</div> <div class=" self-center text-[0.8125rem] leading-5">{$i18n.t('New Chat')}</div>
</div> </div>
<HotkeyHint name="newChat" className=" group-hover:visible invisible" /> <HotkeyHint name="newChat" className=" hover-reveal " />
</a> </a>
</div> </div>
@@ -1225,7 +1225,7 @@
<div class="flex flex-1 self-center translate-y-[0.5px]"> <div class="flex flex-1 self-center translate-y-[0.5px]">
<div class=" self-center text-[0.8125rem] leading-5">{$i18n.t('Search')}</div> <div class=" self-center text-[0.8125rem] leading-5">{$i18n.t('Search')}</div>
</div> </div>
<HotkeyHint name="search" className=" group-hover:visible invisible" /> <HotkeyHint name="search" className=" hover-reveal " />
</button> </button>
</div> </div>
@@ -203,9 +203,7 @@
</a> </a>
{#if ['dm'].includes(channel?.type)} {#if ['dm'].includes(channel?.type)}
<div <div class="ml-0.5 mr-1 hover-reveal self-center flex items-center dark:text-gray-300">
class="ml-0.5 mr-1 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
>
<button <button
type="button" type="button"
class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto" class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto"
@@ -230,9 +228,7 @@
</button> </button>
</div> </div>
{:else if $user?.role === 'admin' || channel.user_id === $user?.id} {:else if $user?.role === 'admin' || channel.user_id === $user?.id}
<div <div class="ml-0.5 mr-1 hover-reveal self-center flex items-center dark:text-gray-300">
class="ml-0.5 mr-1 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
>
<button <button
type="button" type="button"
class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto" class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto"
@@ -103,6 +103,8 @@
let chat = null; let chat = null;
let mouseOver = false; let mouseOver = false;
let focusWithin = false;
let menuOpen = false;
let openPreview = false; let openPreview = false;
const closeHoverPreview = () => { const closeHoverPreview = () => {
@@ -131,7 +133,8 @@
id !== $chatId && id !== $chatId &&
!active && !active &&
(effectiveReadAt === null || (updatedAt !== null && updatedAt > effectiveReadAt)); (effectiveReadAt === null || (updatedAt !== null && updatedAt > effectiveReadAt));
$: showInlineActions = id === $chatId || confirmEdit || mouseOver || selected; $: showInlineActions =
id === $chatId || confirmEdit || mouseOver || focusWithin || menuOpen || selected;
$: chatItemClass = ` w-full flex justify-between rounded-xl px-2 py-1.5 ${ $: chatItemClass = ` w-full flex justify-between rounded-xl px-2 py-1.5 ${
id === $chatId || confirmEdit id === $chatId || confirmEdit
? ($settings?.highContrastMode ?? false) ? ($settings?.highContrastMode ?? false)
@@ -582,6 +585,12 @@
on:mouseleave={() => { on:mouseleave={() => {
mouseOver = false; mouseOver = false;
}} }}
on:focusin={() => {
focusWithin = true;
}}
on:focusout={() => {
focusWithin = false;
}}
> >
{#if confirmEdit} {#if confirmEdit}
<div <div
@@ -669,7 +678,7 @@
? 'selected' ? 'selected'
: showInlineActions : showInlineActions
? 'selected' ? 'selected'
: 'invisible group-hover:visible'} absolute {className === 'pr-2' : 'hover-reveal'} absolute {className === 'pr-2'
? 'right-[0.5rem]' ? 'right-[0.5rem]'
: 'right-1'} inset-y-0 mr-1.5 flex items-center" : 'right-1'} inset-y-0 mr-1.5 flex items-center"
> >
@@ -739,7 +748,12 @@
showDeleteConfirm = true; showDeleteConfirm = true;
}} }}
{markUnreadHandler} {markUnreadHandler}
onOpen={() => {
menuOpen = true;
dispatch('select');
}}
onClose={() => { onClose={() => {
menuOpen = false;
dispatch('unselect'); dispatch('unselect');
}} }}
onPinChange={async () => { onPinChange={async () => {
@@ -750,9 +764,6 @@
type="button" type="button"
aria-label="Chat Menu" aria-label="Chat Menu"
class="flex size-5 items-center justify-center self-center dark:hover:text-white transition m-0" class="flex size-5 items-center justify-center self-center dark:hover:text-white transition m-0"
on:click={() => {
dispatch('select');
}}
> >
<MoreHorizontalIcon className="size-3.5" strokeWidth="2" /> <MoreHorizontalIcon className="size-3.5" strokeWidth="2" />
</button> </button>
@@ -39,11 +39,13 @@
export let archiveChatHandler: Function; export let archiveChatHandler: Function;
export let renameHandler: Function; export let renameHandler: Function;
export let deleteHandler: Function; export let deleteHandler: Function;
export let onOpen: () => void = () => {};
export let onClose: Function; export let onClose: Function;
export let markUnreadHandler: Function = () => {}; export let markUnreadHandler: Function = () => {};
export let chatId = ''; export let chatId = '';
let dropdown: Dropdown;
let show = false; let show = false;
let pinned = false; let pinned = false;
@@ -290,9 +292,12 @@
{/if} {/if}
<Dropdown <Dropdown
bind:this={dropdown}
bind:show bind:show
onOpenChange={(state) => { onOpenChange={(state) => {
if (state === false) { if (state) {
onOpen();
} else {
onClose(); onClose();
} }
}} }}
@@ -363,7 +368,7 @@
draggable="false" draggable="false"
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" 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"
on:click={() => { on:click={() => {
show = false; dropdown.close();
renameHandler(); renameHandler();
}} }}
> >
@@ -375,7 +380,7 @@
draggable="false" draggable="false"
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" 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"
on:click={() => { on:click={() => {
show = false; dropdown.close();
markUnreadHandler(); markUnreadHandler();
}} }}
> >
@@ -389,7 +394,7 @@
draggable="false" draggable="false"
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" 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"
on:click={() => { on:click={() => {
show = false; dropdown.close();
pinHandler(); pinHandler();
}} }}
> >
@@ -407,7 +412,7 @@
draggable="false" draggable="false"
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" 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"
on:click={() => { on:click={() => {
show = false; dropdown.close();
cloneChatHandler(); cloneChatHandler();
}} }}
> >
@@ -86,7 +86,7 @@
</div> </div>
</a> </a>
<button <button
class="invisible group-hover:visible self-center p-0.5 hover:bg-gray-200 dark:hover:bg-gray-800 rounded-lg transition" class="hover-reveal self-center p-0.5 hover:bg-gray-200 dark:hover:bg-gray-800 rounded-lg transition"
on:click|preventDefault|stopPropagation={async () => { on:click|preventDefault|stopPropagation={async () => {
await toggleNotePinnedStatusById(localStorage.token, note.id); await toggleNotePinnedStatusById(localStorage.token, note.id);
const _pinnedNotes = await getPinnedNoteList(localStorage.token).catch(() => []); const _pinnedNotes = await getPinnedNoteList(localStorage.token).catch(() => []);
@@ -855,7 +855,7 @@
{#if isWritable} {#if isWritable}
<button <button
class="absolute z-10 right-2 invisible group-hover:visible self-center flex items-center dark:text-gray-300" class="absolute z-10 right-2 hover-reveal self-center flex items-center dark:text-gray-300"
> >
<FolderMenu <FolderMenu
onEdit={() => { onEdit={() => {
@@ -134,7 +134,7 @@
> >
<span>{name}</span> <span>{name}</span>
<span <span
class="flex opacity-0 group-hover:opacity-100 transition-all duration-100" class="flex opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-all duration-100"
style="transform: rotate({open ? '90deg' : '0deg'})" style="transform: rotate({open ? '90deg' : '0deg'})"
> >
<ChevronRight className="size-[0.6875rem]" /> <ChevronRight className="size-[0.6875rem]" />
+2 -2
View File
@@ -144,7 +144,7 @@
class="w-full aspect-square object-cover rounded-lg border border-gray-100/30 dark:border-gray-850/30" class="w-full aspect-square object-cover rounded-lg border border-gray-100/30 dark:border-gray-850/30"
/> />
<div <div
class="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition rounded-lg flex items-center justify-center" class="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition rounded-lg flex items-center justify-center"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -188,7 +188,7 @@
</div> </div>
<div class=" absolute -top-1 -right-1"> <div class=" absolute -top-1 -right-1">
<button <button
class=" bg-white text-black border border-white rounded-full group-hover:visible invisible transition" class=" bg-white text-black border border-white rounded-full hover-reveal transition"
type="button" type="button"
aria-label={$i18n.t('Remove image')} aria-label={$i18n.t('Remove image')}
on:click={() => removeImage(index)} on:click={() => removeImage(index)}
@@ -633,7 +633,7 @@
{/if} {/if}
<div <div
class="absolute bottom-0 right-0 z-10 opacity-0 transition group-hover:opacity-100" class="absolute bottom-0 right-0 z-10 opacity-0 transition group-hover:opacity-100 group-focus-within:opacity-100"
> >
<div class="m-1"> <div class="m-1">
<div <div