sprintify-ui 0.10.79 → 0.10.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,259 @@
1
+ <template>
2
+ <div
3
+ class="border rounded-md overflow-hidden bg-white || tiptap"
4
+ :class="[
5
+ hasError ? 'border-red-500' : 'border-slate-300',
6
+ ]"
7
+ >
8
+ <div class="divide-x px-1 py-1 border-b flex">
9
+ <button
10
+ v-for="action in filteredActions"
11
+ :key="action.name"
12
+ :class="[buttonClass, editor.isActive(action.name) ? 'bg-slate-200' : '']"
13
+ type="button"
14
+ :disabled="disabled"
15
+ @click="action.command()"
16
+ >
17
+ <BaseIcon
18
+ class="h-5 w-5"
19
+ :icon="action.icon"
20
+ />
21
+ </button>
22
+ </div>
23
+
24
+ <div
25
+ class="bg-white p-5"
26
+ :class="[disabled ? 'cursor-not-allowed' : '']"
27
+ >
28
+ <editor-content
29
+ v-if="editor"
30
+ :editor="editor"
31
+ />
32
+ </div>
33
+ </div>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import { Editor, EditorContent } from '@tiptap/vue-3';
38
+ import Document from '@tiptap/extension-document';
39
+ import Link from '@tiptap/extension-link';
40
+ import { Placeholder } from '@tiptap/extensions'
41
+ import { Footnotes, FootnoteReference, Footnote } from "tiptap-footnotes";
42
+ import StarterKit from '@tiptap/starter-kit';
43
+
44
+ const emit = defineEmits(['update:modelValue']);
45
+
46
+ const props = defineProps<{
47
+ modelValue: string | null | undefined;
48
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
49
+ hasError?: boolean;
50
+ disabled?: boolean;
51
+ placeholder?: string;
52
+ toolbar?: string[];
53
+ }>();
54
+
55
+ const editor = new Editor({
56
+ editable: !props.disabled,
57
+ extensions: [
58
+ StarterKit.configure({
59
+ document: false,
60
+ link: false,
61
+ }),
62
+ Document.extend({
63
+ content: "block+ footnotes?",
64
+ }),
65
+ Link.configure({
66
+ openOnClick: false,
67
+ defaultProtocol: 'https',
68
+ enableClickSelection: true,
69
+ }),
70
+ Placeholder.configure({
71
+ placeholder: props.placeholder || 'Start typing here...',
72
+ }),
73
+ Footnotes,
74
+ Footnote,
75
+ FootnoteReference,
76
+ ],
77
+ editorProps: {
78
+ attributes: {
79
+ class: 'prose prose-sm mx-auto max-w-full focus:outline-none',
80
+ },
81
+ },
82
+ content: props.modelValue,
83
+ onUpdate: () => {
84
+ emit('update:modelValue', editor?.getHTML() || '');
85
+ },
86
+ });
87
+
88
+ watch(
89
+ () => props.modelValue,
90
+ (newValue) => {
91
+ const isSame = editor?.getHTML() === newValue;
92
+
93
+ if (isSame) {
94
+ return;
95
+ }
96
+
97
+ editor?.commands.setContent(newValue ?? '');
98
+ },
99
+ { immediate: true },
100
+ );
101
+
102
+ watch(
103
+ () => props.disabled,
104
+ (newValue) => {
105
+ editor?.setEditable(!newValue);
106
+ },
107
+ );
108
+
109
+ watch(
110
+ () => props.size,
111
+ (newValue) => {
112
+
113
+ const lookup = {
114
+ 'xs': 'prose-sm',
115
+ 'sm': 'prose-sm',
116
+ 'md': 'prose-sm',
117
+ 'lg': 'prose-md',
118
+ 'xl': 'prose-lg',
119
+ };
120
+
121
+ const sizeClass = lookup[newValue || 'md'] ?? '';
122
+
123
+ editor?.view.setProps({
124
+ attributes: {
125
+ class: `mx-auto max-w-full prose focus:outline-none ${sizeClass}`
126
+ }
127
+ })
128
+ },
129
+ { immediate: true },
130
+ )
131
+
132
+ onBeforeUnmount(() => {
133
+ if (editor) {
134
+ editor.destroy();
135
+ }
136
+ });
137
+
138
+ function setLink() {
139
+ const previousUrl = editor.getAttributes('link').href;
140
+ const url = window.prompt('URL', previousUrl);
141
+
142
+ // cancelled
143
+ if (url === null) {
144
+ return;
145
+ }
146
+
147
+ // empty
148
+ if (url === '') {
149
+ editor.chain().focus().extendMarkRange('link').unsetLink().run();
150
+
151
+ return;
152
+ }
153
+
154
+ // update link
155
+ editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run();
156
+ }
157
+
158
+ const buttonClass = computed(() => {
159
+ const sizeClass = {
160
+ xs: 'p-1',
161
+ sm: 'p-1.5',
162
+ md: 'p-2',
163
+ lg: 'p-2.5',
164
+ xl: 'p-3',
165
+ }
166
+
167
+ return [
168
+ 'hover:bg-slate-100',
169
+ 'disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-white',
170
+ sizeClass[props.size || 'md'] || sizeClass['md'],
171
+ ];
172
+ });
173
+
174
+ interface Action {
175
+ name: string;
176
+ icon: string;
177
+ command: () => void;
178
+ }
179
+
180
+ const actions = computed<Action[]>(() => {
181
+ return [
182
+ {
183
+ name: 'undo',
184
+ icon: 'mdi:undo-variant',
185
+ command: () => editor.chain().focus().undo().run(),
186
+ },
187
+ {
188
+ name: 'redo',
189
+ icon: 'mdi:redo-variant',
190
+ command: () => editor.chain().focus().redo().run(),
191
+ },
192
+ {
193
+ name: 'paragraph',
194
+ icon: 'material-symbols:format-paragraph',
195
+ command: () => editor.chain().focus().setParagraph().run(),
196
+ },
197
+ {
198
+ name: 'heading-2',
199
+ icon: 'material-symbols:format-h2',
200
+ command: () => editor.chain().focus().setHeading({ level: 2 }).run(),
201
+ },
202
+ {
203
+ name: 'heading-3',
204
+ icon: 'material-symbols:format-h3',
205
+ command: () => editor.chain().focus().setHeading({ level: 3 }).run(),
206
+ },
207
+ {
208
+ name: 'bold',
209
+ icon: 'material-symbols:format-bold',
210
+ command: () => editor.chain().focus().toggleBold().run(),
211
+ },
212
+ {
213
+ name: 'italic',
214
+ icon: 'material-symbols:format-italic',
215
+ command: () => editor.chain().focus().toggleItalic().run(),
216
+ },
217
+ {
218
+ name: 'underline',
219
+ icon: 'material-symbols:format-underlined',
220
+ command: () => editor.chain().focus().toggleUnderline().run(),
221
+ },
222
+ {
223
+ name: 'link',
224
+ icon: 'material-symbols:link',
225
+ command: () => setLink(),
226
+ },
227
+ {
228
+ name: 'unlink',
229
+ icon: 'material-symbols:link-off',
230
+ command: () => editor.chain().focus().unsetLink().run(),
231
+ },
232
+ {
233
+ name: 'bulletList',
234
+ icon: 'material-symbols:format-list-bulleted',
235
+ command: () => editor.chain().focus().toggleBulletList().run(),
236
+ },
237
+ {
238
+ name: 'orderedList',
239
+ icon: 'material-symbols:format-list-numbered',
240
+ command: () => editor.chain().focus().toggleOrderedList().run(),
241
+ },
242
+ {
243
+ name: 'footnote',
244
+ icon: 'material-symbols:edit-note-outline',
245
+ command: () => editor?.commands.addFootnote(),
246
+ },
247
+
248
+ ];
249
+ });
250
+
251
+ const filteredActions = computed(() => {
252
+ if (!props.toolbar || props.toolbar.length === 0) {
253
+ return actions.value;
254
+ }
255
+
256
+ return actions.value.filter(action => props.toolbar?.includes(action.name));
257
+ });
258
+
259
+ </script>