refac
This commit is contained in:
		
							parent
							
								
									d1eaa9d4ee
								
							
						
					
					
						commit
						175bbe27e9
					
				| 
						 | 
				
			
			@ -371,8 +371,8 @@
 | 
			
		|||
					placeholder={$i18n.t('Write something...')}
 | 
			
		||||
					json={true}
 | 
			
		||||
					onChange={(content) => {
 | 
			
		||||
						note.data.html = content.html;
 | 
			
		||||
						note.data.md = content.md;
 | 
			
		||||
						note.data.content.html = content.html;
 | 
			
		||||
						note.data.content.md = content.md;
 | 
			
		||||
					}}
 | 
			
		||||
				/>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,9 @@
 | 
			
		|||
	import fileSaver from 'file-saver';
 | 
			
		||||
	const { saveAs } = fileSaver;
 | 
			
		||||
 | 
			
		||||
	import jsPDF from 'jspdf';
 | 
			
		||||
	import html2canvas from 'html2canvas-pro';
 | 
			
		||||
 | 
			
		||||
	import dayjs from '$lib/dayjs';
 | 
			
		||||
	import duration from 'dayjs/plugin/duration';
 | 
			
		||||
	import relativeTime from 'dayjs/plugin/relativeTime';
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +32,7 @@
 | 
			
		|||
	import { WEBUI_NAME, config, prompts as _prompts, user } from '$lib/stores';
 | 
			
		||||
 | 
			
		||||
	import { createNewNote, getNotes } from '$lib/apis/notes';
 | 
			
		||||
	import { capitalizeFirstLetter } from '$lib/utils';
 | 
			
		||||
 | 
			
		||||
	import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte';
 | 
			
		||||
	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +41,7 @@
 | 
			
		|||
	import ChevronRight from '../icons/ChevronRight.svelte';
 | 
			
		||||
	import Spinner from '../common/Spinner.svelte';
 | 
			
		||||
	import Tooltip from '../common/Tooltip.svelte';
 | 
			
		||||
	import { capitalizeFirstLetter } from '$lib/utils';
 | 
			
		||||
	import NoteMenu from './Notes/NoteMenu.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
	let loaded = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -76,6 +80,62 @@
 | 
			
		|||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const downloadHandler = async (type) => {
 | 
			
		||||
		console.log('downloadHandler', type);
 | 
			
		||||
		console.log('selectedNote', selectedNote);
 | 
			
		||||
		if (type === 'md') {
 | 
			
		||||
			const blob = new Blob([selectedNote.data.content.md], { type: 'text/markdown' });
 | 
			
		||||
			saveAs(blob, `${selectedNote.title}.md`);
 | 
			
		||||
		} else if (type === 'pdf') {
 | 
			
		||||
			await downloadPdf(selectedNote);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const downloadPdf = async (note) => {
 | 
			
		||||
		try {
 | 
			
		||||
			// Define a fixed virtual screen size
 | 
			
		||||
			const virtualWidth = 1024; // Fixed width (adjust as needed)
 | 
			
		||||
			const virtualHeight = 1400; // Fixed height (adjust as needed)
 | 
			
		||||
 | 
			
		||||
			// Render to canvas with predefined width
 | 
			
		||||
			const canvas = await html2canvas(note.data.content.html, {
 | 
			
		||||
				useCORS: true,
 | 
			
		||||
				scale: 2, // Keep at 1x to avoid unexpected enlargements
 | 
			
		||||
				width: virtualWidth, // Set fixed virtual screen width
 | 
			
		||||
				windowWidth: virtualWidth, // Ensure consistent rendering
 | 
			
		||||
				windowHeight: virtualHeight
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			const imgData = canvas.toDataURL('image/png');
 | 
			
		||||
 | 
			
		||||
			// A4 page settings
 | 
			
		||||
			const pdf = new jsPDF('p', 'mm', 'a4');
 | 
			
		||||
			const imgWidth = 210; // A4 width in mm
 | 
			
		||||
			const pageHeight = 297; // A4 height in mm
 | 
			
		||||
 | 
			
		||||
			// Maintain aspect ratio
 | 
			
		||||
			const imgHeight = (canvas.height * imgWidth) / canvas.width;
 | 
			
		||||
			let heightLeft = imgHeight;
 | 
			
		||||
			let position = 0;
 | 
			
		||||
 | 
			
		||||
			pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
 | 
			
		||||
			heightLeft -= pageHeight;
 | 
			
		||||
 | 
			
		||||
			// Handle additional pages
 | 
			
		||||
			while (heightLeft > 0) {
 | 
			
		||||
				position -= pageHeight;
 | 
			
		||||
				pdf.addPage();
 | 
			
		||||
 | 
			
		||||
				pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
 | 
			
		||||
				heightLeft -= pageHeight;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			pdf.save(`${note.title}.pdf`);
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			console.error('Error generating PDF', error);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	onMount(async () => {
 | 
			
		||||
		await init();
 | 
			
		||||
		loaded = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -117,15 +177,36 @@
 | 
			
		|||
									class="w-full -translate-y-0.5 flex flex-col justify-between"
 | 
			
		||||
								>
 | 
			
		||||
									<div class="flex-1">
 | 
			
		||||
										<div class="  flex items-center gap-2 self-center mb-1">
 | 
			
		||||
										<div class="  flex items-center gap-2 self-center mb-1 justify-between">
 | 
			
		||||
											<div class=" font-semibold line-clamp-1 capitalize">{note.title}</div>
 | 
			
		||||
 | 
			
		||||
											<div>
 | 
			
		||||
												<NoteMenu
 | 
			
		||||
													onDownload={(type) => {
 | 
			
		||||
														selectedNote = note;
 | 
			
		||||
 | 
			
		||||
														downloadHandler(type);
 | 
			
		||||
													}}
 | 
			
		||||
													onDelete={() => {
 | 
			
		||||
														selectedNote = note;
 | 
			
		||||
														showDeleteConfirm = true;
 | 
			
		||||
													}}
 | 
			
		||||
												>
 | 
			
		||||
													<button
 | 
			
		||||
														class="self-center w-fit text-sm p-1 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 | 
			
		||||
														type="button"
 | 
			
		||||
													>
 | 
			
		||||
														<EllipsisHorizontal className="size-5" />
 | 
			
		||||
													</button>
 | 
			
		||||
												</NoteMenu>
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
 | 
			
		||||
										<div
 | 
			
		||||
											class=" text-xs text-gray-500 dark:text-gray-500 mb-3 line-clamp-5 min-h-18"
 | 
			
		||||
										>
 | 
			
		||||
											{#if note.data?.md}
 | 
			
		||||
												{note.data?.md}
 | 
			
		||||
											{#if note.data?.content?.md}
 | 
			
		||||
												{note.data?.content?.md}
 | 
			
		||||
											{:else}
 | 
			
		||||
												{$i18n.t('No content')}
 | 
			
		||||
											{/if}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,87 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { DropdownMenu } from 'bits-ui';
 | 
			
		||||
	import { getContext, onMount } from 'svelte';
 | 
			
		||||
 | 
			
		||||
	import { flyAndScale } from '$lib/utils/transitions';
 | 
			
		||||
	import { fade, slide } from 'svelte/transition';
 | 
			
		||||
 | 
			
		||||
	import { showSettings, activeUserIds, USAGE_POOL, mobile, showSidebar, user } from '$lib/stores';
 | 
			
		||||
 | 
			
		||||
	import Tooltip from '$lib/components/common/Tooltip.svelte';
 | 
			
		||||
	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
 | 
			
		||||
	import Download from '$lib/components/icons/Download.svelte';
 | 
			
		||||
	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
 | 
			
		||||
 | 
			
		||||
	const i18n = getContext('i18n');
 | 
			
		||||
 | 
			
		||||
	export let show = false;
 | 
			
		||||
	export let className = 'max-w-[160px]';
 | 
			
		||||
 | 
			
		||||
	export let onDownload = (type) => {};
 | 
			
		||||
	export let onDelete = () => {};
 | 
			
		||||
 | 
			
		||||
	export let onChange = () => {};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<DropdownMenu.Root
 | 
			
		||||
	bind:open={show}
 | 
			
		||||
	onOpenChange={(state) => {
 | 
			
		||||
		onChange(state);
 | 
			
		||||
	}}
 | 
			
		||||
>
 | 
			
		||||
	<DropdownMenu.Trigger>
 | 
			
		||||
		<slot />
 | 
			
		||||
	</DropdownMenu.Trigger>
 | 
			
		||||
 | 
			
		||||
	<slot name="content">
 | 
			
		||||
		<DropdownMenu.Content
 | 
			
		||||
			class="w-full {className} text-sm rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg font-primary"
 | 
			
		||||
			sideOffset={6}
 | 
			
		||||
			side="bottom"
 | 
			
		||||
			align="start"
 | 
			
		||||
			transition={(e) => fade(e, { duration: 100 })}
 | 
			
		||||
		>
 | 
			
		||||
			<DropdownMenu.Sub>
 | 
			
		||||
				<DropdownMenu.SubTrigger
 | 
			
		||||
					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 | 
			
		||||
				>
 | 
			
		||||
					<Download strokeWidth="2" />
 | 
			
		||||
 | 
			
		||||
					<div class="flex items-center">{$i18n.t('Download')}</div>
 | 
			
		||||
				</DropdownMenu.SubTrigger>
 | 
			
		||||
				<DropdownMenu.SubContent
 | 
			
		||||
					class="w-full rounded-xl px-1 py-1.5 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg"
 | 
			
		||||
					transition={flyAndScale}
 | 
			
		||||
					sideOffset={8}
 | 
			
		||||
				>
 | 
			
		||||
					<DropdownMenu.Item
 | 
			
		||||
						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							onDownload('md');
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<div class="flex items-center line-clamp-1">{$i18n.t('Plain text (.md)')}</div>
 | 
			
		||||
					</DropdownMenu.Item>
 | 
			
		||||
 | 
			
		||||
					<DropdownMenu.Item
 | 
			
		||||
						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 | 
			
		||||
						on:click={() => {
 | 
			
		||||
							onDownload('pdf');
 | 
			
		||||
						}}
 | 
			
		||||
					>
 | 
			
		||||
						<div class="flex items-center line-clamp-1">{$i18n.t('PDF document (.pdf)')}</div>
 | 
			
		||||
					</DropdownMenu.Item>
 | 
			
		||||
				</DropdownMenu.SubContent>
 | 
			
		||||
			</DropdownMenu.Sub>
 | 
			
		||||
			<DropdownMenu.Item
 | 
			
		||||
				class="flex  gap-2  items-center px-3 py-1.5 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 | 
			
		||||
				on:click={() => {
 | 
			
		||||
					onDelete();
 | 
			
		||||
				}}
 | 
			
		||||
			>
 | 
			
		||||
				<GarbageBin strokeWidth="2" />
 | 
			
		||||
				<div class="flex items-center">{$i18n.t('Delete')}</div>
 | 
			
		||||
			</DropdownMenu.Item>
 | 
			
		||||
		</DropdownMenu.Content>
 | 
			
		||||
	</slot>
 | 
			
		||||
</DropdownMenu.Root>
 | 
			
		||||
		Loading…
	
		Reference in New Issue