your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import { onDestroy, onMount } from 'svelte';
3 import { Editor, type Extensions } from '@tiptap/core';
4 import Placeholder from '@tiptap/extension-placeholder';
5 import Paragraph from '@tiptap/extension-paragraph';
6 import Document from '@tiptap/extension-document';
7 import Text from '@tiptap/extension-text';
8 import type { Item } from '$lib/types';
9
10 let element: HTMLElement | undefined = $state();
11 let editor: Editor | null = $state(null);
12
13 let {
14 contentDict = $bindable(),
15 key,
16 class: className,
17 placeholder = '',
18 defaultContent = ''
19 }: {
20 contentDict: Record<string, any>;
21 key: string;
22 class?: string;
23 placeholder?: string;
24 defaultContent?: string;
25 } = $props();
26
27 const update = async () => {
28 if (!editor) return;
29
30 contentDict[key] = editor.getText();
31 };
32
33 onMount(async () => {
34 if (!element || editor) return;
35
36 let extensions: Extensions = [Document.configure(), Paragraph.configure(), Text.configure()];
37
38 if (placeholder) {
39 extensions.push(
40 Placeholder.configure({
41 placeholder: placeholder
42 })
43 );
44 }
45
46 editor = new Editor({
47 element: element,
48 extensions: extensions,
49 onTransaction: () => {
50 editor = editor;
51 },
52 onUpdate: () => {
53 update();
54 },
55
56 content: contentDict[key] ?? defaultContent,
57
58 editorProps: {
59 attributes: {
60 class: 'outline-none pointer-events-auto'
61 },
62 handleKeyDown: (_view, event) => {
63 // Prevent newlines by blocking Enter key
64 if (event.key === 'Enter') {
65 return true;
66 }
67 return false;
68 }
69 }
70 });
71 });
72
73 onDestroy(() => {
74 if (editor) {
75 editor.destroy();
76 }
77 });
78</script>
79
80<span class={className} bind:this={element}></span>
81
82<style>
83 :global(.tiptap p.is-editor-empty:first-child::before) {
84 color: var(--color-base-500);
85 content: attr(data-placeholder);
86 opacity: 100%;
87 float: left;
88 height: 0;
89 pointer-events: none;
90 }
91</style>