refac: note editor

This commit is contained in:
Timothy Jaeryang Baek 2025-07-07 18:26:52 +04:00
parent 3392e2ef15
commit a31a1f3c0d
4 changed files with 466 additions and 358 deletions

View file

@ -98,7 +98,7 @@
let recording = false;
let displayMediaRecord = false;
let showSettings = false;
let showPanel = false;
let showDeleteConfirm = false;
let dragged = false;
@ -672,6 +672,11 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
dropzoneElement?.removeEventListener('dragleave', onDragLeave);
}
});
import NotePanel from '$lib/components/notes/NotePanel.svelte';
import { PaneGroup, Pane, PaneResizer } from 'paneforge';
import XMark from '../icons/XMark.svelte';
import MenuLines from '../icons/MenuLines.svelte';
</script>
<FilesOverlay show={dragged} />
@ -689,46 +694,9 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
</div>
</DeleteConfirmDialog>
<div class="relative flex-1 w-full h-full flex justify-center" id="note-editor">
<Sidebar bind:show={showSettings} className=" bg-white dark:bg-gray-900" width="300px">
<div class="flex flex-col px-5 py-3 text-sm">
<div class="flex justify-between items-center mb-2">
<div class=" font-medium text-base">Settings</div>
<div class=" translate-x-1.5">
<button
class="p-1.5 bg-transparent hover:bg-white/5 transition rounded-lg"
on:click={() => {
showSettings = !showSettings;
}}
>
<ArrowRight className="size-3" strokeWidth="2.5" />
</button>
</div>
</div>
<div class="mt-1">
<div>
<div class=" text-xs font-medium mb-1">Model</div>
<div class="w-full">
<select
class="w-full bg-transparent text-sm outline-hidden"
bind:value={selectedModelId}
>
<option value="" class="bg-gray-50 dark:bg-gray-700" disabled>
{$i18n.t('Select a model')}
</option>
{#each $models.filter((model) => !(model?.info?.meta?.hidden ?? false)) as model}
<option value={model.id} class="bg-gray-50 dark:bg-gray-700">{model.name}</option>
{/each}
</select>
</div>
</div>
</div>
</div>
</Sidebar>
<PaneGroup direction="horizontal" class="w-full h-full">
<Pane defaultSize={70} minSize={50} class="h-full flex flex-col w-full relative">
<div class="relative flex-1 w-full h-full flex justify-center pt-[11px]" id="note-editor">
{#if loading}
<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
<div class="m-auto">
@ -737,10 +705,29 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
</div>
{:else}
<div class=" w-full flex flex-col {loading ? 'opacity-20' : ''}">
<div class="shrink-0 w-full flex justify-between items-center px-4.5 mb-1.5">
<div class="shrink-0 w-full flex justify-between items-center px-3.5 mb-1.5">
<div class="w-full flex items-center">
<div
class="{$showSidebar
? 'md:hidden'
: ''} flex flex-none items-center pr-1 -translate-x-1"
>
<button
id="sidebar-toggle-button"
class="cursor-pointer p-1.5 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
on:click={() => {
showSidebar.set(!$showSidebar);
}}
aria-label="Toggle Sidebar"
>
<div class=" m-auto self-center">
<MenuLines />
</div>
</button>
</div>
<input
class="w-full text-2xl font-medium bg-transparent outline-hidden"
class="w-full text-xl font-medium bg-transparent outline-hidden"
type="text"
bind:value={note.title}
placeholder={$i18n.t('Title')}
@ -799,7 +786,7 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
<button
class="p-1.5 bg-transparent hover:bg-white/5 transition rounded-lg"
on:click={() => {
showSettings = !showSettings;
showPanel = !showPanel;
}}
>
<Cog6 />
@ -808,8 +795,10 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
</div>
</div>
<div class=" mb-2.5 px-3.5">
<div class="flex gap-1 items-center text-xs font-medium text-gray-500 dark:text-gray-500">
<div class=" mb-2.5 px-2.5">
<div
class="flex gap-1 items-center text-xs font-medium text-gray-500 dark:text-gray-500"
>
<button class=" flex items-center gap-1 w-fit py-1 px-1.5 rounded-lg">
<Calendar className="size-3.5" strokeWidth="2" />
@ -825,7 +814,7 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
</div>
<div
class=" flex-1 w-full h-full overflow-auto px-4 pb-20 relative"
class=" flex-1 w-full h-full overflow-auto px-3.5 pb-20 relative"
id="note-content-container"
>
{#if enhancing}
@ -837,7 +826,7 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
{/if}
{#if files && files.length > 0}
<div class="mb-3.5 mt-1.5 w-full flex gap-1 flex-wrap z-40">
<div class="mb-2.5 w-full flex gap-1 flex-wrap z-40">
{#each files as file, fileIdx}
<div class="w-fit">
{#if file.type === 'image'}
@ -886,12 +875,7 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
</div>
{/if}
</div>
<div
class="absolute z-20 bottom-0 right-0 p-5 max-w-full {$showSidebar
? 'md:max-w-[calc(100%-260px)]'
: ''} w-full flex justify-end"
>
<div class="absolute z-20 bottom-0 right-0 p-5 max-w-full w-full flex justify-end">
<div class="flex gap-1 justify-between w-full max-w-full">
{#if recording}
<div class="flex-1 w-full">
@ -1012,3 +996,38 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
{/if}
</div>
</div>
</Pane>
<NotePanel bind:show={showPanel}>
<div class="flex items-center mb-2">
<div class=" -translate-x-1.5">
<button
class="p-1.5 bg-transparent transition rounded-lg"
on:click={() => {
showPanel = !showPanel;
}}
>
<XMark className="size-5" strokeWidth="2.5" />
</button>
</div>
<div class=" font-medium text-base">Settings</div>
</div>
<div class="mt-1">
<div>
<div class=" text-xs font-medium mb-1">Model</div>
<div class="w-full">
<select class="w-full bg-transparent text-sm outline-hidden" bind:value={selectedModelId}>
<option value="" class="bg-gray-50 dark:bg-gray-700" disabled>
{$i18n.t('Select a model')}
</option>
{#each $models.filter((model) => !(model?.info?.meta?.hidden ?? false)) as model}
<option value={model.id} class="bg-gray-50 dark:bg-gray-700">{model.name}</option>
{/each}
</select>
</div>
</div>
</div>
</NotePanel>
</PaneGroup>

View file

@ -0,0 +1,82 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { Pane, PaneResizer } from 'paneforge';
import Drawer from '../common/Drawer.svelte';
import EllipsisVertical from '../icons/EllipsisVertical.svelte';
export let show = false;
export let pane = null;
export let containerId = 'note-editor';
let mediaQuery;
let largeScreen = false;
let minSize = 0;
const handleMediaQuery = async (e) => {
if (e.matches) {
largeScreen = true;
} else {
largeScreen = false;
pane = null;
}
};
onMount(() => {
// listen to resize 1024px
mediaQuery = window.matchMedia('(min-width: 1024px)');
mediaQuery.addEventListener('change', handleMediaQuery);
handleMediaQuery(mediaQuery);
});
onDestroy(() => {
mediaQuery.removeEventListener('change', handleMediaQuery);
});
</script>
{#if !largeScreen}
{#if show}
<Drawer
{show}
onClose={() => {
show = false;
}}
>
<div class=" px-3.5 py-2.5 h-screen">
<slot />
</div>
</Drawer>
{/if}
{:else if show}
<PaneResizer
class="relative flex w-2 items-center justify-center bg-background group bg-white dark:shadow-lg dark:bg-gray-850 border border-gray-100 dark:border-gray-850"
>
<div class="z-10 flex h-7 w-5 items-center justify-center rounded-xs">
<EllipsisVertical className="size-4 invisible group-hover:visible" />
</div>
</PaneResizer>
<Pane
bind:pane
defaultSize={30}
minSize={30}
onCollapse={() => {
show = false;
}}
collapsible={true}
class=" z-10 "
>
{#if show}
<div class="flex max-h-full min-h-full">
<div
class="w-full pl-1.5 pr-2.5 pt-2 bg-white dark:shadow-lg dark:bg-gray-850 border border-gray-100 dark:border-gray-850 z-40 pointer-events-auto overflow-y-auto scrollbar-hidden"
>
<slot />
</div>
</div>
{/if}
</Pane>
{/if}

View file

@ -32,73 +32,5 @@
</svelte:head>
{#if loaded}
<div
class=" flex flex-col w-full h-screen max-h-[100dvh] transition-width duration-200 ease-in-out {$showSidebar
? 'md:max-w-[calc(100%-260px)]'
: ''} max-w-full"
>
<nav class=" px-2 pt-1 backdrop-blur-xl w-full drag-region">
<div class=" flex items-center">
<div class="{$showSidebar ? 'md:hidden' : ''} flex flex-none items-center">
<button
id="sidebar-toggle-button"
class="cursor-pointer p-1.5 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
on:click={() => {
showSidebar.set(!$showSidebar);
}}
aria-label="Toggle Sidebar"
>
<div class=" m-auto self-center">
<MenuLines />
</div>
</button>
</div>
<div class="ml-2 py-0.5 self-center flex items-center justify-between w-full">
<div class="">
<div
class="flex gap-1 scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium bg-transparent py-1 touch-auto pointer-events-auto"
>
<a class="min-w-fit transition" href="/notes">
{$i18n.t('Notes')}
</a>
</div>
</div>
<div class=" self-center flex items-center gap-1">
{#if $user !== undefined && $user !== null}
<UserMenu
className="max-w-[240px]"
role={$user?.role}
help={true}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChats.set(true);
}
}}
>
<button
class="select-none flex rounded-xl p-1.5 w-full hover:bg-gray-50 dark:hover:bg-gray-850 transition"
aria-label="User Menu"
>
<div class=" self-center">
<img
src={$user?.profile_image_url}
class="size-6 object-cover rounded-full"
alt="User profile"
draggable="false"
/>
</div>
</button>
</UserMenu>
{/if}
</div>
</div>
</div>
</nav>
<div class=" pb-1 flex-1 max-h-full overflow-y-auto @container">
<slot />
</div>
</div>
{/if}

View file

@ -1,5 +1,80 @@
<script>
import { showSidebar, user } from '$lib/stores';
import { getContext } from 'svelte';
const i18n = getContext('i18n');
import MenuLines from '$lib/components/icons/MenuLines.svelte';
import UserMenu from '$lib/components/layout/Sidebar/UserMenu.svelte';
import Notes from '$lib/components/notes/Notes.svelte';
</script>
<div
class=" flex flex-col w-full h-screen max-h-[100dvh] transition-width duration-200 ease-in-out {$showSidebar
? 'md:max-w-[calc(100%-260px)]'
: ''} max-w-full"
>
<nav class=" px-2 pt-1 backdrop-blur-xl w-full drag-region">
<div class=" flex items-center">
<div class="{$showSidebar ? 'md:hidden' : ''} flex flex-none items-center">
<button
id="sidebar-toggle-button"
class="cursor-pointer p-1.5 flex rounded-xl hover:bg-gray-100 dark:hover:bg-gray-850 transition"
on:click={() => {
showSidebar.set(!$showSidebar);
}}
aria-label="Toggle Sidebar"
>
<div class=" m-auto self-center">
<MenuLines />
</div>
</button>
</div>
<div class="ml-2 py-0.5 self-center flex items-center justify-between w-full">
<div class="">
<div
class="flex gap-1 scrollbar-none overflow-x-auto w-fit text-center text-sm font-medium bg-transparent py-1 touch-auto pointer-events-auto"
>
<a class="min-w-fit transition" href="/notes">
{$i18n.t('Notes')}
</a>
</div>
</div>
<div class=" self-center flex items-center gap-1">
{#if $user !== undefined && $user !== null}
<UserMenu
className="max-w-[240px]"
role={$user?.role}
help={true}
on:show={(e) => {
if (e.detail === 'archived-chat') {
showArchivedChats.set(true);
}
}}
>
<button
class="select-none flex rounded-xl p-1.5 w-full hover:bg-gray-50 dark:hover:bg-gray-850 transition"
aria-label="User Menu"
>
<div class=" self-center">
<img
src={$user?.profile_image_url}
class="size-6 object-cover rounded-full"
alt="User profile"
draggable="false"
/>
</div>
</button>
</UserMenu>
{/if}
</div>
</div>
</div>
</nav>
<div class=" pb-1 flex-1 max-h-full overflow-y-auto @container">
<Notes />
</div>
</div>