refac
This commit is contained in:
		
							parent
							
								
									d1eaa9d4ee
								
							
						
					
					
						commit
						175bbe27e9
					
				|  | @ -371,8 +371,8 @@ | ||||||
| 					placeholder={$i18n.t('Write something...')} | 					placeholder={$i18n.t('Write something...')} | ||||||
| 					json={true} | 					json={true} | ||||||
| 					onChange={(content) => { | 					onChange={(content) => { | ||||||
| 						note.data.html = content.html; | 						note.data.content.html = content.html; | ||||||
| 						note.data.md = content.md; | 						note.data.content.md = content.md; | ||||||
| 					}} | 					}} | ||||||
| 				/> | 				/> | ||||||
| 			</div> | 			</div> | ||||||
|  |  | ||||||
|  | @ -3,6 +3,9 @@ | ||||||
| 	import fileSaver from 'file-saver'; | 	import fileSaver from 'file-saver'; | ||||||
| 	const { saveAs } = fileSaver; | 	const { saveAs } = fileSaver; | ||||||
| 
 | 
 | ||||||
|  | 	import jsPDF from 'jspdf'; | ||||||
|  | 	import html2canvas from 'html2canvas-pro'; | ||||||
|  | 
 | ||||||
| 	import dayjs from '$lib/dayjs'; | 	import dayjs from '$lib/dayjs'; | ||||||
| 	import duration from 'dayjs/plugin/duration'; | 	import duration from 'dayjs/plugin/duration'; | ||||||
| 	import relativeTime from 'dayjs/plugin/relativeTime'; | 	import relativeTime from 'dayjs/plugin/relativeTime'; | ||||||
|  | @ -29,6 +32,7 @@ | ||||||
| 	import { WEBUI_NAME, config, prompts as _prompts, user } from '$lib/stores'; | 	import { WEBUI_NAME, config, prompts as _prompts, user } from '$lib/stores'; | ||||||
| 
 | 
 | ||||||
| 	import { createNewNote, getNotes } from '$lib/apis/notes'; | 	import { createNewNote, getNotes } from '$lib/apis/notes'; | ||||||
|  | 	import { capitalizeFirstLetter } from '$lib/utils'; | ||||||
| 
 | 
 | ||||||
| 	import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte'; | 	import EllipsisHorizontal from '../icons/EllipsisHorizontal.svelte'; | ||||||
| 	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; | 	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte'; | ||||||
|  | @ -37,7 +41,7 @@ | ||||||
| 	import ChevronRight from '../icons/ChevronRight.svelte'; | 	import ChevronRight from '../icons/ChevronRight.svelte'; | ||||||
| 	import Spinner from '../common/Spinner.svelte'; | 	import Spinner from '../common/Spinner.svelte'; | ||||||
| 	import Tooltip from '../common/Tooltip.svelte'; | 	import Tooltip from '../common/Tooltip.svelte'; | ||||||
| 	import { capitalizeFirstLetter } from '$lib/utils'; | 	import NoteMenu from './Notes/NoteMenu.svelte'; | ||||||
| 
 | 
 | ||||||
| 	const i18n = getContext('i18n'); | 	const i18n = getContext('i18n'); | ||||||
| 	let loaded = false; | 	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 () => { | 	onMount(async () => { | ||||||
| 		await init(); | 		await init(); | ||||||
| 		loaded = true; | 		loaded = true; | ||||||
|  | @ -117,15 +177,36 @@ | ||||||
| 									class="w-full -translate-y-0.5 flex flex-col justify-between" | 									class="w-full -translate-y-0.5 flex flex-col justify-between" | ||||||
| 								> | 								> | ||||||
| 									<div class="flex-1"> | 									<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 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> | ||||||
| 
 | 
 | ||||||
| 										<div | 										<div | ||||||
| 											class=" text-xs text-gray-500 dark:text-gray-500 mb-3 line-clamp-5 min-h-18" | 											class=" text-xs text-gray-500 dark:text-gray-500 mb-3 line-clamp-5 min-h-18" | ||||||
| 										> | 										> | ||||||
| 											{#if note.data?.md} | 											{#if note.data?.content?.md} | ||||||
| 												{note.data?.md} | 												{note.data?.content?.md} | ||||||
| 											{:else} | 											{:else} | ||||||
| 												{$i18n.t('No content')} | 												{$i18n.t('No content')} | ||||||
| 											{/if} | 											{/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