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