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 onupdate
20 }: {
21 contentDict: Record<string, any>;
22 key: string;
23 class?: string;
24 placeholder?: string;
25 defaultContent?: string;
26 onupdate?: (content: string) => void;
27 } = $props();
28
29 const update = async () => {
30 if (!editor) return;
31
32 const text = editor.getText();
33
34 contentDict[key] = text;
35
36 onupdate?.(text);
37 };
38
39 onMount(async () => {
40 if (!element || editor) return;
41
42 let extensions: Extensions = [Document.configure(), Paragraph.configure(), Text.configure()];
43
44 if (placeholder) {
45 extensions.push(
46 Placeholder.configure({
47 placeholder: placeholder
48 })
49 );
50 }
51
52 editor = new Editor({
53 element: element,
54 extensions: extensions,
55 onTransaction: () => {
56 editor = editor;
57 },
58 onUpdate: () => {
59 update();
60 },
61
62 content: contentDict[key] ?? defaultContent,
63
64 editorProps: {
65 attributes: {
66 class: 'outline-none pointer-events-auto'
67 },
68 handleKeyDown: (_view, event) => {
69 // Prevent newlines by blocking Enter key
70 if (event.key === 'Enter') {
71 return true;
72 }
73 return false;
74 }
75 }
76 });
77 });
78
79 onDestroy(() => {
80 if (editor) {
81 editor.destroy();
82 }
83 });
84</script>
85
86<span class={className} bind:this={element}></span>
87
88<style>
89 :global(.tiptap p.is-editor-empty:first-child::before) {
90 color: var(--color-base-500);
91 content: attr(data-placeholder);
92 opacity: 100%;
93 float: left;
94 height: 0;
95 pointer-events: none;
96 }
97</style>