mirror of
https://github.com/open-webui/open-webui
synced 2025-03-16 02:17:33 +00:00
refac: collection styling
This commit is contained in:
parent
e8c629a2e2
commit
03282da45c
@ -6,23 +6,11 @@
|
|||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
export let size = 'md';
|
export let className = '';
|
||||||
|
|
||||||
let modalElement = null;
|
let modalElement = null;
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
|
|
||||||
const sizeToWidth = (size) => {
|
|
||||||
if (size === 'xs') {
|
|
||||||
return 'w-[16rem]';
|
|
||||||
} else if (size === 'sm') {
|
|
||||||
return 'w-[30rem]';
|
|
||||||
} else if (size === 'md') {
|
|
||||||
return 'w-[48rem]';
|
|
||||||
} else {
|
|
||||||
return 'w-[56rem]';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape' && isTopModal()) {
|
if (event.key === 'Escape' && isTopModal()) {
|
||||||
console.log('Escape');
|
console.log('Escape');
|
||||||
@ -76,7 +64,7 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" mt-auto max-w-full w-full bg-gray-50 dark:bg-gray-900 max-h-[100dvh] overflow-y-auto scrollbar-hidden"
|
class=" mt-auto max-w-full w-full bg-gray-50 dark:bg-gray-900 dark:text-gray-100 {className} max-h-[100dvh] overflow-y-auto scrollbar-hidden"
|
||||||
on:mousedown={(e) => {
|
on:mousedown={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
|
@ -6,11 +6,15 @@
|
|||||||
|
|
||||||
export let show = true;
|
export let show = true;
|
||||||
export let size = 'md';
|
export let size = 'md';
|
||||||
|
export let className = 'bg-gray-50 dark:bg-gray-900 rounded-2xl';
|
||||||
|
|
||||||
let modalElement = null;
|
let modalElement = null;
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
|
|
||||||
const sizeToWidth = (size) => {
|
const sizeToWidth = (size) => {
|
||||||
|
if (size === 'full') {
|
||||||
|
return 'w-full';
|
||||||
|
}
|
||||||
if (size === 'xs') {
|
if (size === 'xs') {
|
||||||
return 'w-[16rem]';
|
return 'w-[16rem]';
|
||||||
} else if (size === 'sm') {
|
} else if (size === 'sm') {
|
||||||
@ -68,9 +72,9 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class=" m-auto rounded-2xl max-w-full {sizeToWidth(
|
class=" m-auto max-w-full {sizeToWidth(size)} {size !== 'full'
|
||||||
size
|
? 'mx-2'
|
||||||
)} mx-2 bg-gray-50 dark:bg-gray-900 shadow-3xl max-h-[100dvh] overflow-y-auto scrollbar-hidden"
|
: ''} shadow-3xl max-h-[100dvh] overflow-y-auto scrollbar-hidden {className}"
|
||||||
in:flyAndScale
|
in:flyAndScale
|
||||||
on:mousedown={(e) => {
|
on:mousedown={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { PaneGroup, Pane, PaneResizer } from 'paneforge';
|
||||||
|
|
||||||
import { onMount, getContext, onDestroy, tick } from 'svelte';
|
import { onMount, getContext, onDestroy, tick } from 'svelte';
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
@ -34,8 +35,17 @@
|
|||||||
|
|
||||||
import SyncConfirmDialog from '../../common/ConfirmDialog.svelte';
|
import SyncConfirmDialog from '../../common/ConfirmDialog.svelte';
|
||||||
import RichTextInput from '$lib/components/common/RichTextInput.svelte';
|
import RichTextInput from '$lib/components/common/RichTextInput.svelte';
|
||||||
|
import EllipsisVertical from '$lib/components/icons/EllipsisVertical.svelte';
|
||||||
|
import Drawer from '$lib/components/common/Drawer.svelte';
|
||||||
|
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
|
||||||
|
import MenuLines from '$lib/components/icons/MenuLines.svelte';
|
||||||
|
|
||||||
let largeScreen = true;
|
let largeScreen = true;
|
||||||
|
|
||||||
|
let pane;
|
||||||
|
let showSidepanel = true;
|
||||||
|
let minSize = 0;
|
||||||
|
|
||||||
type Knowledge = {
|
type Knowledge = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -458,6 +468,30 @@
|
|||||||
mediaQuery.addEventListener('change', handleMediaQuery);
|
mediaQuery.addEventListener('change', handleMediaQuery);
|
||||||
handleMediaQuery(mediaQuery);
|
handleMediaQuery(mediaQuery);
|
||||||
|
|
||||||
|
// Select the container element you want to observe
|
||||||
|
const container = document.getElementById('collection-container');
|
||||||
|
|
||||||
|
// initialize the minSize based on the container width
|
||||||
|
minSize = !largeScreen ? 100 : Math.floor((300 / container.clientWidth) * 100);
|
||||||
|
|
||||||
|
// Create a new ResizeObserver instance
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
for (let entry of entries) {
|
||||||
|
const width = entry.contentRect.width;
|
||||||
|
// calculate the percentage of 300
|
||||||
|
const percentage = (300 / width) * 100;
|
||||||
|
// set the minSize to the percentage, must be an integer
|
||||||
|
minSize = !largeScreen ? 100 : Math.floor(percentage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start observing the container's size changes
|
||||||
|
resizeObserver.observe(container);
|
||||||
|
|
||||||
|
if (pane) {
|
||||||
|
pane.expand();
|
||||||
|
}
|
||||||
|
|
||||||
id = $page.params.id;
|
id = $page.params.id;
|
||||||
|
|
||||||
const res = await getKnowledgeById(localStorage.token, id).catch((e) => {
|
const res = await getKnowledgeById(localStorage.token, id).catch((e) => {
|
||||||
@ -551,157 +585,248 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex flex-col w-full h-full max-h-[100dvh]">
|
<div class="flex flex-col w-full h-full max-h-[100dvh]" id="collection-container">
|
||||||
{#if id && knowledge}
|
{#if id && knowledge}
|
||||||
<div class="flex flex-row flex-1 h-full max-h-full pb-2.5">
|
<div class="flex flex-row flex-1 h-full max-h-full pb-2.5">
|
||||||
<div
|
<PaneGroup direction="horizontal">
|
||||||
class=" {largeScreen
|
<Pane
|
||||||
? 'flex-shrink-0'
|
bind:pane
|
||||||
: 'flex-1'} flex py-2.5 w-80 rounded-2xl border border-gray-50 dark:border-gray-850"
|
defaultSize={minSize}
|
||||||
>
|
collapsible={true}
|
||||||
<div class=" flex flex-col w-full space-x-2 rounded-lg h-full">
|
maxSize={50}
|
||||||
<div class="w-full h-full flex flex-col">
|
{minSize}
|
||||||
<div class=" px-3">
|
class="h-full"
|
||||||
<div class="flex">
|
onExpand={() => {
|
||||||
<div class=" self-center ml-1 mr-3">
|
showSidepanel = true;
|
||||||
<svg
|
}}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
onCollapse={() => {
|
||||||
viewBox="0 0 20 20"
|
showSidepanel = false;
|
||||||
fill="currentColor"
|
}}
|
||||||
class="w-4 h-4"
|
>
|
||||||
>
|
<div
|
||||||
<path
|
class="{largeScreen ? 'flex-shrink-0' : 'flex-1'}
|
||||||
fill-rule="evenodd"
|
flex
|
||||||
d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
|
py-2
|
||||||
clip-rule="evenodd"
|
rounded-2xl
|
||||||
/>
|
border
|
||||||
</svg>
|
border-gray-50
|
||||||
</div>
|
h-full
|
||||||
<input
|
dark:border-gray-850"
|
||||||
class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
|
>
|
||||||
bind:value={query}
|
<div class=" flex flex-col w-full space-x-2 rounded-lg h-full">
|
||||||
placeholder={$i18n.t('Search Collection')}
|
<div class="w-full h-full flex flex-col">
|
||||||
on:focus={() => {
|
<div class=" px-3">
|
||||||
selectedFileId = null;
|
<div class="flex py-1">
|
||||||
}}
|
<div class=" self-center ml-1 mr-3">
|
||||||
/>
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<div>
|
viewBox="0 0 20 20"
|
||||||
<AddContentMenu
|
fill="currentColor"
|
||||||
on:upload={(e) => {
|
class="w-4 h-4"
|
||||||
if (e.detail.type === 'directory') {
|
>
|
||||||
uploadDirectoryHandler();
|
<path
|
||||||
} else if (e.detail.type === 'text') {
|
fill-rule="evenodd"
|
||||||
showAddTextContentModal = true;
|
d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
|
||||||
} else {
|
clip-rule="evenodd"
|
||||||
document.getElementById('files-input').click();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
on:sync={(e) => {
|
|
||||||
showSyncConfirmModal = true;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr class=" mt-2 mb-1 border-gray-50 dark:border-gray-850" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if filteredItems.length > 0}
|
|
||||||
<div class=" flex overflow-y-auto h-full w-full scrollbar-hidden text-xs">
|
|
||||||
<Files
|
|
||||||
files={filteredItems}
|
|
||||||
{selectedFileId}
|
|
||||||
on:click={(e) => {
|
|
||||||
selectedFileId = selectedFileId === e.detail ? null : e.detail;
|
|
||||||
}}
|
|
||||||
on:delete={(e) => {
|
|
||||||
console.log(e.detail);
|
|
||||||
|
|
||||||
selectedFileId = null;
|
|
||||||
deleteFileHandler(e.detail);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="m-auto text-gray-500 text-xs">{$i18n.t('No content found')}</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if largeScreen}
|
|
||||||
<div class="flex-1 flex justify-start h-full max-h-full pl-3">
|
|
||||||
{#if selectedFile}
|
|
||||||
<div class=" flex flex-col w-full h-full max-h-full">
|
|
||||||
<div class="flex-shrink-0 mb-2 flex items-center">
|
|
||||||
<div class=" flex-1 text-xl line-clamp-1">
|
|
||||||
{selectedFile?.meta?.name}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
class="self-center w-fit text-sm py-1 px-2.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-lg"
|
|
||||||
on:click={() => {
|
|
||||||
updateFileContentHandler();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{$i18n.t('Save')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class=" flex-1 w-full h-full max-h-full py-2.5 px-3.5 rounded-xl text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none overflow-y-auto scrollbar-hidden"
|
|
||||||
>
|
|
||||||
{#key selectedFile.id}
|
|
||||||
<RichTextInput
|
|
||||||
className="input-prose-sm"
|
|
||||||
bind:value={selectedFile.data.content}
|
|
||||||
placeholder={$i18n.t('Add content here')}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="m-auto pb-32">
|
|
||||||
<div>
|
|
||||||
<div class=" flex w-full mt-1 mb-3.5">
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="flex items-center justify-between w-full px-0.5 mb-1">
|
|
||||||
<div class="w-full">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="text-center w-full font-medium text-3xl font-primary bg-transparent outline-none"
|
|
||||||
bind:value={knowledge.name}
|
|
||||||
on:input={() => {
|
|
||||||
changeDebounceHandler();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
<input
|
||||||
|
class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
|
||||||
|
bind:value={query}
|
||||||
|
placeholder={$i18n.t('Search Collection')}
|
||||||
|
on:focus={() => {
|
||||||
|
selectedFileId = null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="flex w-full px-1">
|
<div>
|
||||||
<input
|
<AddContentMenu
|
||||||
type="text"
|
on:upload={(e) => {
|
||||||
class="text-center w-full text-gray-500 bg-transparent outline-none"
|
if (e.detail.type === 'directory') {
|
||||||
bind:value={knowledge.description}
|
uploadDirectoryHandler();
|
||||||
on:input={() => {
|
} else if (e.detail.type === 'text') {
|
||||||
changeDebounceHandler();
|
showAddTextContentModal = true;
|
||||||
|
} else {
|
||||||
|
document.getElementById('files-input').click();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:sync={(e) => {
|
||||||
|
showSyncConfirmModal = true;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" mt-2 text-center text-sm text-gray-200 dark:text-gray-700 w-full">
|
{#if filteredItems.length > 0}
|
||||||
{$i18n.t('Select a file to view or drag and drop a file to upload')}
|
<div class=" flex overflow-y-auto h-full w-full scrollbar-hidden text-xs">
|
||||||
|
<Files
|
||||||
|
files={filteredItems}
|
||||||
|
{selectedFileId}
|
||||||
|
on:click={(e) => {
|
||||||
|
selectedFileId = selectedFileId === e.detail ? null : e.detail;
|
||||||
|
}}
|
||||||
|
on:delete={(e) => {
|
||||||
|
console.log(e.detail);
|
||||||
|
|
||||||
|
selectedFileId = null;
|
||||||
|
deleteFileHandler(e.detail);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="m-auto text-gray-500 text-xs">{$i18n.t('No content found')}</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
</Pane>
|
||||||
{/if}
|
|
||||||
|
{#if largeScreen}
|
||||||
|
<PaneResizer class="relative flex w-2 items-center justify-center bg-background group">
|
||||||
|
<div class="z-10 flex h-7 w-5 items-center justify-center rounded-sm">
|
||||||
|
<EllipsisVertical className="size-4 invisible group-hover:visible" />
|
||||||
|
</div>
|
||||||
|
</PaneResizer>
|
||||||
|
<Pane>
|
||||||
|
<div class="flex-1 flex justify-start h-full max-h-full">
|
||||||
|
{#if selectedFile}
|
||||||
|
<div class=" flex flex-col w-full h-full max-h-full ml-2">
|
||||||
|
<div class="flex-shrink-0 mb-2 flex items-center">
|
||||||
|
{#if !showSidepanel}
|
||||||
|
<div class="-translate-x-2">
|
||||||
|
<button
|
||||||
|
class="w-full text-left text-sm p-1.5 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
|
||||||
|
on:click={() => {
|
||||||
|
pane.expand();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChevronLeft strokeWidth="2.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class=" flex-1 text-2xl font-medium line-clamp-1">
|
||||||
|
{selectedFile?.meta?.name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="self-center w-fit text-sm py-1 px-2.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-lg"
|
||||||
|
on:click={() => {
|
||||||
|
updateFileContentHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Save')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" flex-1 w-full h-full max-h-full text-sm bg-transparent outline-none overflow-y-auto scrollbar-hidden"
|
||||||
|
>
|
||||||
|
{#key selectedFile.id}
|
||||||
|
<RichTextInput
|
||||||
|
className="input-prose-sm"
|
||||||
|
bind:value={selectedFile.data.content}
|
||||||
|
placeholder={$i18n.t('Add content here')}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="m-auto pb-32">
|
||||||
|
<div>
|
||||||
|
<div class=" flex w-full mt-1 mb-3.5">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex items-center justify-between w-full px-0.5 mb-1">
|
||||||
|
<div class="w-full">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="text-center w-full font-medium text-3xl font-primary bg-transparent outline-none"
|
||||||
|
bind:value={knowledge.name}
|
||||||
|
on:input={() => {
|
||||||
|
changeDebounceHandler();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex w-full px-1">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="text-center w-full text-gray-500 bg-transparent outline-none"
|
||||||
|
bind:value={knowledge.description}
|
||||||
|
on:input={() => {
|
||||||
|
changeDebounceHandler();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=" mt-2 text-center text-sm text-gray-200 dark:text-gray-700 w-full">
|
||||||
|
{$i18n.t('Select a file to view or drag and drop a file to upload')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</Pane>
|
||||||
|
{:else if !largeScreen && selectedFileId !== null}
|
||||||
|
<Drawer
|
||||||
|
className="h-full"
|
||||||
|
show={selectedFileId !== null}
|
||||||
|
on:close={() => {
|
||||||
|
selectedFileId = null;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="flex flex-col justify-start h-full max-h-full p-2">
|
||||||
|
<div class=" flex flex-col w-full h-full max-h-full">
|
||||||
|
<div class="flex-shrink-0 mb-2 flex items-center">
|
||||||
|
<div class="mr-2">
|
||||||
|
<button
|
||||||
|
class="w-full text-left text-sm p-1.5 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
|
||||||
|
on:click={() => {
|
||||||
|
selectedFileId = null;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChevronLeft strokeWidth="2.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class=" flex-1 text-xl line-clamp-1">
|
||||||
|
{selectedFile?.meta?.name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="self-center w-fit text-sm py-1 px-2.5 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-lg"
|
||||||
|
on:click={() => {
|
||||||
|
updateFileContentHandler();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{$i18n.t('Save')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class=" flex-1 w-full h-full max-h-full py-2.5 px-3.5 rounded-xl text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none overflow-y-auto scrollbar-hidden"
|
||||||
|
>
|
||||||
|
{#key selectedFile.id}
|
||||||
|
<RichTextInput
|
||||||
|
className="input-prose-sm"
|
||||||
|
bind:value={selectedFile.data.content}
|
||||||
|
placeholder={$i18n.t('Add content here')}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
{/if}
|
||||||
|
</PaneGroup>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
>
|
>
|
||||||
<Tooltip content={$i18n.t('Add Content')}>
|
<Tooltip content={$i18n.t('Add Content')}>
|
||||||
<button
|
<button
|
||||||
class=" px-2 py-2 rounded-xl border border-gray-50 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
|
class=" p-1.5 rounded-xl hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition font-medium text-sm flex items-center space-x-1"
|
||||||
on:click={(e) => {
|
on:click={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
show = true;
|
show = true;
|
||||||
|
@ -7,98 +7,75 @@
|
|||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
import Modal from '$lib/components/common/Modal.svelte';
|
import Modal from '$lib/components/common/Modal.svelte';
|
||||||
|
import RichTextInput from '$lib/components/common/RichTextInput.svelte';
|
||||||
|
import XMark from '$lib/components/icons/XMark.svelte';
|
||||||
export let show = false;
|
export let show = false;
|
||||||
|
|
||||||
let name = '';
|
let name = 'Untitled';
|
||||||
let content = '';
|
let content = '';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal size="md" bind:show>
|
<Modal size="full" className="h-full bg-white dark:bg-gray-900" bind:show>
|
||||||
<div>
|
<div class="absolute top-0 right-0 p-5">
|
||||||
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4">
|
<button
|
||||||
<div class=" text-lg font-medium self-center">{$i18n.t('Add Content')}</div>
|
class="self-center dark:text-white"
|
||||||
<button
|
type="button"
|
||||||
class="self-center"
|
on:click={() => {
|
||||||
on:click={() => {
|
show = false;
|
||||||
show = false;
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<XMark className="size-4" />
|
||||||
<svg
|
</button>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
</div>
|
||||||
viewBox="0 0 20 20"
|
<div class="flex flex-col md:flex-row w-full h-full md:space-x-4 dark:text-gray-200">
|
||||||
fill="currentColor"
|
<form
|
||||||
class="w-5 h-5"
|
class="flex flex-col w-full h-full"
|
||||||
>
|
on:submit|preventDefault={() => {
|
||||||
<path
|
if (name.trim() === '' || content.trim() === '') {
|
||||||
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
|
toast.error($i18n.t('Please fill in all fields.'));
|
||||||
/>
|
name = name.trim();
|
||||||
</svg>
|
content = content.trim();
|
||||||
</button>
|
return;
|
||||||
</div>
|
}
|
||||||
<div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200">
|
|
||||||
<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
|
|
||||||
<form
|
|
||||||
class="flex flex-col w-full"
|
|
||||||
on:submit|preventDefault={() => {
|
|
||||||
if (name.trim() === '' || content.trim() === '') {
|
|
||||||
toast.error($i18n.t('Please fill in all fields.'));
|
|
||||||
name = '';
|
|
||||||
content = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch('submit', {
|
dispatch('submit', {
|
||||||
name,
|
name,
|
||||||
content
|
content
|
||||||
});
|
});
|
||||||
show = false;
|
show = false;
|
||||||
name = '';
|
name = '';
|
||||||
content = '';
|
content = '';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="mb-3 w-full">
|
<div class=" flex-1 w-full h-full flex justify-center overflow-auto px-5 py-4">
|
||||||
<div class="w-full flex flex-col gap-2.5">
|
<div class=" max-w-3xl py-2 md:py-14 w-full flex flex-col gap-2">
|
||||||
<div class="w-full">
|
<div class="flex-shrink-0 w-full flex justify-between items-center">
|
||||||
<div class=" text-sm mb-2">{$i18n.t('Title')}</div>
|
<div class="w-full">
|
||||||
|
<input
|
||||||
<div class="w-full mt-1">
|
class="w-full text-3xl font-semibold rounded-lg bg-transparent outline-none"
|
||||||
<input
|
type="text"
|
||||||
class="w-full rounded-lg py-2 px-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-none"
|
bind:value={name}
|
||||||
type="text"
|
placeholder={$i18n.t('Title')}
|
||||||
bind:value={name}
|
required
|
||||||
placeholder={`Name your content`}
|
/>
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div class="text-sm mb-2">{$i18n.t('Content')}</div>
|
|
||||||
|
|
||||||
<div class=" w-full mt-1">
|
|
||||||
<textarea
|
|
||||||
class="w-full resize-none rounded-lg py-2 px-4 text-sm bg-whites dark:text-gray-300 dark:bg-gray-850 outline-none"
|
|
||||||
rows="10"
|
|
||||||
bind:value={content}
|
|
||||||
placeholder={`Write your content here`}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end text-sm font-medium">
|
<div class=" flex-1 w-full h-full">
|
||||||
<button
|
<RichTextInput bind:value={content} placeholder={$i18n.t('Content')} />
|
||||||
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
{$i18n.t('Add Content')}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="flex justify-end text-sm font-medium flex-shrink-0 mt-1 py-3 px-3">
|
||||||
|
<button
|
||||||
|
class=" px-3.5 py-2 bg-black text-white dark:bg-white dark:text-black transition rounded-full"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{$i18n.t('Save')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<div class=" max-h-full flex flex-col w-full">
|
<div class=" max-h-full flex flex-col w-full">
|
||||||
{#each files as file}
|
{#each files as file}
|
||||||
<div class="mt-2 px-2">
|
<div class="mt-1 px-2">
|
||||||
<FileItem
|
<FileItem
|
||||||
className="w-full"
|
className="w-full"
|
||||||
colorClassName="{selectedFileId === file.id
|
colorClassName="{selectedFileId === file.id
|
||||||
|
@ -110,7 +110,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class=" pb-1 px-5 flex-1 max-h-full overflow-y-auto">
|
<div class=" pb-1 px-[18px] flex-1 max-h-full overflow-y-auto">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user