mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Replace legacy radius vars (--modal-radius, --button-radius, --input-radius) with standardized tokens (--radius-l, --radius-m). Add margins to modals and top bar divider for better spacing. Fix mobile diff view bottom margin to avoid Obsidian controls overlap.
263 lines
No EOL
6.8 KiB
Svelte
263 lines
No EOL
6.8 KiB
Svelte
<script lang="ts">
|
|
import { Copy } from "Enums/Copy";
|
|
import { setIcon } from "obsidian";
|
|
import { fade } from "svelte/transition";
|
|
|
|
export let items: Array<{id: string, date: string, updated: Date, title: string, selected: boolean}>;
|
|
export let onClose: () => void;
|
|
export let onDelete: (itemIds: string[]) => void;
|
|
export let onSelect: (itemId: string) => void;
|
|
|
|
let deleteButton: HTMLButtonElement;
|
|
let closeButton: HTMLButtonElement;
|
|
|
|
$: if (deleteButton) {
|
|
setIcon(deleteButton, 'trash-2');
|
|
}
|
|
$: if (closeButton) {
|
|
setIcon(closeButton, 'circle-x');
|
|
}
|
|
|
|
let selectedItems = new Set<string>();
|
|
let searchQuery = "";
|
|
|
|
$: filteredItems = items.filter(item =>
|
|
item.title.toLowerCase().includes(searchQuery.toLowerCase())
|
|
).sort((a, b) => b.updated.getTime() - a.updated.getTime());
|
|
|
|
function toggleSelection(itemId: string) {
|
|
if (selectedItems.has(itemId)) {
|
|
selectedItems.delete(itemId);
|
|
} else {
|
|
selectedItems.add(itemId);
|
|
}
|
|
selectedItems = selectedItems;
|
|
}
|
|
|
|
function handleDelete() {
|
|
if (selectedItems.size === 0) {
|
|
return;
|
|
}
|
|
onDelete(Array.from(selectedItems));
|
|
items = items.filter((item) => !selectedItems.has(item.id))
|
|
selectedItems.clear();
|
|
selectedItems = selectedItems;
|
|
}
|
|
|
|
function handleConversationClick(itemId: string, event: UIEvent) {
|
|
onSelect(itemId);
|
|
}
|
|
</script>
|
|
|
|
<div class="conversation-history-modal-container">
|
|
<div class="conversation-history-modal-top-bar">
|
|
<div class="conversation-history-modal-top-bar-content">
|
|
<button
|
|
bind:this={deleteButton}
|
|
id="delete-button"
|
|
class="top-bar-button clickable-icon"
|
|
class:hidden={selectedItems.size === 0}
|
|
on:click={handleDelete}
|
|
aria-label="Delete Selected Conversations"
|
|
></button>
|
|
<input
|
|
type="text"
|
|
id="search-input"
|
|
class="top-bar-button conversation-search-input"
|
|
placeholder="Search conversations..."
|
|
disabled={items.length === 0}
|
|
bind:value={searchQuery}
|
|
aria-label="Search Conversations"
|
|
/>
|
|
<button
|
|
bind:this={closeButton}
|
|
id="close-button"
|
|
class="top-bar-button clickable-icon"
|
|
on:click={onClose}
|
|
aria-label="Close Conversation History"
|
|
></button>
|
|
</div>
|
|
</div>
|
|
<div class="conversation-history-modal-content">
|
|
{#if filteredItems.length === 0}
|
|
<p class="history-empty-state" in:fade={{ duration: 200 }}>
|
|
{Copy.NoConversationsFound}
|
|
</p>
|
|
{:else}
|
|
{#each filteredItems as item (item.id)}
|
|
<div class="history-list-modal-content" in:fade={{ duration: 200 }} out:fade={{ duration: 200 }}>
|
|
<div
|
|
class="history-list-modal-clickable"
|
|
on:click={(e) => handleConversationClick(item.id, e)}
|
|
on:keydown={(e) => e.key === 'Enter' && handleConversationClick(item.id, e)}
|
|
role="button"
|
|
tabindex="0">
|
|
<span class="history-list-modal-date">{item.date}</span>
|
|
<span class="history-list-modal-separator">|</span>
|
|
<span class="history-list-modal-title">{item.title}</span>
|
|
</div>
|
|
<input
|
|
type="checkbox"
|
|
class="history-list-modal-checkbox"
|
|
checked={selectedItems.has(item.id)}
|
|
on:change={() => toggleSelection(item.id)}
|
|
/>
|
|
</div>
|
|
{/each}
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.conversation-history-modal-container {
|
|
display: grid;
|
|
grid-template-rows: auto var(--size-4-1) 1fr var(--size-4-2);
|
|
grid-template-columns: var(--size-4-2) 1fr var(--size-4-2);
|
|
max-height: 60vh;
|
|
margin: 10px;
|
|
}
|
|
|
|
.conversation-history-modal-top-bar {
|
|
grid-row: 1;
|
|
grid-column: 2;
|
|
height: var(--size-4-16);
|
|
display: grid;
|
|
grid-template-rows: var(--size-4-2) 1fr var(--size-4-2);
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.conversation-history-modal-top-bar-content {
|
|
grid-row: 2;
|
|
grid-column: 1;
|
|
display: grid;
|
|
grid-template-rows: auto;
|
|
grid-template-columns: var(--size-4-2) auto 1fr var(--size-4-2) auto var(--size-4-2);
|
|
background-color: var(--background-secondary-alt);
|
|
border-radius: var(--radius-m);
|
|
}
|
|
|
|
.conversation-history-modal-content {
|
|
grid-row: 3;
|
|
grid-column: 2;
|
|
overflow: scroll;
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
.conversation-history-modal-content::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.history-empty-state {
|
|
margin: 3vh 0px;
|
|
text-align: center;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.history-list-modal-content {
|
|
display: grid;
|
|
grid-template-rows: 1fr;
|
|
grid-template-columns: 1fr auto;
|
|
margin-bottom: var(--size-4-2);
|
|
}
|
|
|
|
.history-list-modal-clickable {
|
|
grid-row: 1;
|
|
grid-column: 1;
|
|
display: grid;
|
|
grid-template-rows: 1fr;
|
|
grid-template-columns: auto auto 1fr;
|
|
cursor: pointer;
|
|
padding: var(--size-2-2) 0;
|
|
border-radius: var(--radius-s);
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
.history-list-modal-clickable:hover {
|
|
background-color: var(--background-secondary);
|
|
}
|
|
|
|
.history-list-modal-clickable:active {
|
|
background-color: var(--background-secondary-alt);
|
|
}
|
|
|
|
.history-list-modal-date {
|
|
grid-row: 1;
|
|
grid-column: 1;
|
|
margin-left: var(--size-4-3)
|
|
}
|
|
|
|
.history-list-modal-separator {
|
|
grid-row: 1;
|
|
grid-column: 2;
|
|
color: var(--text-faint);
|
|
margin: 0px var(--size-4-3);
|
|
}
|
|
|
|
.history-list-modal-title {
|
|
grid-row: 1;
|
|
grid-column: 3;
|
|
display: inline-block;
|
|
max-width: 80%;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.history-list-modal-checkbox {
|
|
grid-row: 1;
|
|
grid-column: 2;
|
|
margin: 0px var(--size-4-3) 0px var(--size-2-3);
|
|
align-self: center;
|
|
}
|
|
|
|
#delete-button {
|
|
grid-row: 1;
|
|
grid-column: 2;
|
|
margin-right: var(--size-4-2);
|
|
transition: width 300ms ease-out, opacity 300ms ease-out, padding 300ms ease-out, margin 300ms ease-out;
|
|
}
|
|
|
|
#delete-button.hidden {
|
|
width: 0;
|
|
min-width: 0;
|
|
padding: 0;
|
|
opacity: 0;
|
|
margin: 0;
|
|
overflow: hidden;
|
|
pointer-events: none;
|
|
}
|
|
|
|
#close-button {
|
|
grid-row: 1;
|
|
grid-column: 5;
|
|
}
|
|
|
|
#search-input {
|
|
grid-row: 1;
|
|
grid-column: 3;
|
|
}
|
|
|
|
.conversation-search-input {
|
|
max-width: 200px;
|
|
background-color: var(--background-secondary);
|
|
border: 1px solid var(--background-modifier-border);
|
|
border-radius: var(--radius-s);
|
|
padding: var(--size-2-1) var(--size-2-3);
|
|
color: var(--text-normal);
|
|
font-size: var(--font-ui-small);
|
|
outline: none;
|
|
transition: border-color 0.3s ease-out;
|
|
transition: max-width 0.3s ease-out;
|
|
}
|
|
|
|
.conversation-search-input:focus {
|
|
border-color: var(--color-accent);
|
|
max-width: 100%;
|
|
transition: border-color 0.3s ease-out;
|
|
transition: max-width 0.3s ease-out;
|
|
}
|
|
|
|
.conversation-search-input::placeholder {
|
|
color: var(--text-muted);
|
|
}
|
|
</style> |