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 item = $bindable(),
15 key,
16 class: className,
17 placeholder = '',
18 defaultContent = ''
19 }: {
20 item: Item;
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 item.cardData[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: item.cardData[key] ?? defaultContent,
57
58 editorProps: {
59 attributes: {
60 class: 'outline-none pointer-events-auto'
61 }
62 }
63 });
64 });
65
66 onDestroy(() => {
67 if (editor) {
68 editor.destroy();
69 }
70 });
71</script>
72
73<span class={className} bind:this={element}></span>
74
75<style>
76 :global(.tiptap p.is-editor-empty:first-child::before) {
77 color: var(--color-base-800);
78 content: attr(data-placeholder);
79 opacity: 50%;
80 float: left;
81 height: 0;
82 pointer-events: none;
83 }
84 :global(.dark .tiptap p.is-editor-empty:first-child::before) {
85 color: var(--color-base-200);
86 }
87</style>