@nikala-ui/hooks 0.11.0 → 0.12.0
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.
- package/package.json +22 -1
- package/src/create-app-updater.ts +272 -0
- package/src/create-document-tabs.ts +267 -0
- package/src/create-global-shortcut.ts +140 -0
- package/src/create-tauri-window.ts +628 -0
- package/src/create-tiptap-editor.ts +326 -0
- package/src/index.ts +6 -1
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSignal,
|
|
3
|
+
onMount,
|
|
4
|
+
onCleanup,
|
|
5
|
+
type Accessor,
|
|
6
|
+
} from "solid-js";
|
|
7
|
+
import { Editor, type EditorOptions, type Extension } from "@tiptap/core";
|
|
8
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
9
|
+
import Placeholder from "@tiptap/extension-placeholder";
|
|
10
|
+
import Link from "@tiptap/extension-link";
|
|
11
|
+
import Image from "@tiptap/extension-image";
|
|
12
|
+
import TaskList from "@tiptap/extension-task-list";
|
|
13
|
+
import TaskItem from "@tiptap/extension-task-item";
|
|
14
|
+
import { Table, TableRow, TableCell, TableHeader } from "@tiptap/extension-table";
|
|
15
|
+
import Underline from "@tiptap/extension-underline";
|
|
16
|
+
import TextAlign from "@tiptap/extension-text-align";
|
|
17
|
+
import Highlight from "@tiptap/extension-highlight";
|
|
18
|
+
import CharacterCount from "@tiptap/extension-character-count";
|
|
19
|
+
import Subscript from "@tiptap/extension-subscript";
|
|
20
|
+
import Superscript from "@tiptap/extension-superscript";
|
|
21
|
+
import Typography from "@tiptap/extension-typography";
|
|
22
|
+
import { Markdown } from "tiptap-markdown";
|
|
23
|
+
|
|
24
|
+
export interface CreateTiptapEditorOptions
|
|
25
|
+
extends Partial<Omit<EditorOptions, "element">> {
|
|
26
|
+
placeholder?: string;
|
|
27
|
+
characterLimit?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CreateTiptapEditorReturn {
|
|
31
|
+
editor: Accessor<Editor | null>;
|
|
32
|
+
html: Accessor<string>;
|
|
33
|
+
text: Accessor<string>;
|
|
34
|
+
markdown: Accessor<string>;
|
|
35
|
+
isEmpty: Accessor<boolean>;
|
|
36
|
+
characterCount: Accessor<number>;
|
|
37
|
+
wordCount: Accessor<number>;
|
|
38
|
+
canUndo: Accessor<boolean>;
|
|
39
|
+
canRedo: Accessor<boolean>;
|
|
40
|
+
mount: (element: HTMLElement) => void;
|
|
41
|
+
destroy: () => void;
|
|
42
|
+
isActive: (name: string | Record<string, any>, attributes?: Record<string, any>) => boolean;
|
|
43
|
+
toggleBold: () => void;
|
|
44
|
+
toggleItalic: () => void;
|
|
45
|
+
toggleUnderline: () => void;
|
|
46
|
+
toggleStrike: () => void;
|
|
47
|
+
toggleCode: () => void;
|
|
48
|
+
toggleHighlight: (color?: string) => void;
|
|
49
|
+
setHeading: (level: 1 | 2 | 3 | 4 | 5 | 6) => void;
|
|
50
|
+
setParagraph: () => void;
|
|
51
|
+
toggleBulletList: () => void;
|
|
52
|
+
toggleOrderedList: () => void;
|
|
53
|
+
toggleTaskList: () => void;
|
|
54
|
+
toggleBlockquote: () => void;
|
|
55
|
+
toggleCodeBlock: () => void;
|
|
56
|
+
setTextAlign: (alignment: "left" | "center" | "right" | "justify") => void;
|
|
57
|
+
setLink: (options: string | { href: string; target?: string }) => void;
|
|
58
|
+
unsetLink: () => void;
|
|
59
|
+
setImage: (options: string | { src: string; alt?: string; title?: string }) => void;
|
|
60
|
+
insertTable: (options?: { rows?: number; cols?: number; withHeaderRow?: boolean }) => void;
|
|
61
|
+
insertHorizontalRule: () => void;
|
|
62
|
+
clearContent: () => void;
|
|
63
|
+
setContent: (content: string) => void;
|
|
64
|
+
setEditable: (editable: boolean) => void;
|
|
65
|
+
undo: () => void;
|
|
66
|
+
redo: () => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createTiptapEditor(
|
|
70
|
+
options: CreateTiptapEditorOptions = {}
|
|
71
|
+
): CreateTiptapEditorReturn {
|
|
72
|
+
const [editorInstance, setEditorInstance] = createSignal<Editor | null>(null);
|
|
73
|
+
const [html, setHtml] = createSignal<string>("");
|
|
74
|
+
const [text, setText] = createSignal<string>("");
|
|
75
|
+
const [markdown, setMarkdown] = createSignal<string>("");
|
|
76
|
+
const [isEmpty, setIsEmpty] = createSignal<boolean>(true);
|
|
77
|
+
const [characterCount, setCharacterCount] = createSignal<number>(0);
|
|
78
|
+
const [wordCount, setWordCount] = createSignal<number>(0);
|
|
79
|
+
const [canUndo, setCanUndo] = createSignal<boolean>(false);
|
|
80
|
+
const [canRedo, setCanRedo] = createSignal<boolean>(false);
|
|
81
|
+
const [transactionVersion, setTransactionVersion] = createSignal<number>(0);
|
|
82
|
+
|
|
83
|
+
let targetElement: HTMLElement | null = null;
|
|
84
|
+
|
|
85
|
+
const updateStats = (ed: Editor) => {
|
|
86
|
+
const currentHtml = ed.getHTML();
|
|
87
|
+
const currentText = ed.getText();
|
|
88
|
+
const currentMd = (ed.storage as any).markdown?.getMarkdown?.() || "";
|
|
89
|
+
setHtml(currentHtml);
|
|
90
|
+
setText(currentText);
|
|
91
|
+
setMarkdown(currentMd);
|
|
92
|
+
setIsEmpty(ed.isEmpty);
|
|
93
|
+
setCharacterCount(ed.storage.characterCount?.characters?.() ?? currentText.length);
|
|
94
|
+
setWordCount(ed.storage.characterCount?.words?.() ?? currentText.split(/\s+/).filter(Boolean).length);
|
|
95
|
+
setCanUndo(ed.can().undo());
|
|
96
|
+
setCanRedo(ed.can().redo());
|
|
97
|
+
setTransactionVersion((v) => v + 1);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const defaultExtensions: Extension[] = [
|
|
101
|
+
StarterKit.configure({
|
|
102
|
+
heading: {
|
|
103
|
+
levels: [1, 2, 3, 4, 5, 6],
|
|
104
|
+
HTMLAttributes: { class: "font-heading tracking-tight" },
|
|
105
|
+
},
|
|
106
|
+
bulletList: {
|
|
107
|
+
HTMLAttributes: { class: "list-disc pl-6 my-3 space-y-1" },
|
|
108
|
+
},
|
|
109
|
+
orderedList: {
|
|
110
|
+
HTMLAttributes: { class: "list-decimal pl-6 my-3 space-y-1" },
|
|
111
|
+
},
|
|
112
|
+
codeBlock: {
|
|
113
|
+
HTMLAttributes: {
|
|
114
|
+
class: "rounded-lg bg-muted p-4 font-mono text-xs border border-border my-3 overflow-x-auto text-foreground",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
code: {
|
|
118
|
+
HTMLAttributes: {
|
|
119
|
+
class: "rounded bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground font-semibold border border-border/50",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
blockquote: {
|
|
123
|
+
HTMLAttributes: {
|
|
124
|
+
class: "border-l-4 border-primary pl-4 italic text-muted-foreground my-3",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}) as Extension,
|
|
128
|
+
Placeholder.configure({
|
|
129
|
+
placeholder: options.placeholder || "Write something rich or type '/' for commands...",
|
|
130
|
+
emptyEditorClass: "is-editor-empty",
|
|
131
|
+
}) as Extension,
|
|
132
|
+
Underline as Extension,
|
|
133
|
+
Subscript as Extension,
|
|
134
|
+
Superscript as Extension,
|
|
135
|
+
Typography as Extension,
|
|
136
|
+
Highlight.configure({
|
|
137
|
+
multicolor: true,
|
|
138
|
+
HTMLAttributes: { class: "rounded px-1 py-0.5" },
|
|
139
|
+
}) as Extension,
|
|
140
|
+
TextAlign.configure({ types: ["heading", "paragraph"] }) as Extension,
|
|
141
|
+
Link.configure({
|
|
142
|
+
openOnClick: false,
|
|
143
|
+
HTMLAttributes: {
|
|
144
|
+
class: "text-primary font-medium underline underline-offset-4 hover:opacity-80 transition-opacity cursor-pointer",
|
|
145
|
+
},
|
|
146
|
+
}) as Extension,
|
|
147
|
+
Image.configure({
|
|
148
|
+
HTMLAttributes: {
|
|
149
|
+
class: "rounded-lg max-w-full h-auto my-4 border border-border shadow-xs",
|
|
150
|
+
},
|
|
151
|
+
}) as Extension,
|
|
152
|
+
TaskList.configure({
|
|
153
|
+
HTMLAttributes: { class: "list-none pl-0 my-3 space-y-2" },
|
|
154
|
+
}) as Extension,
|
|
155
|
+
TaskItem.configure({
|
|
156
|
+
nested: true,
|
|
157
|
+
HTMLAttributes: { class: "flex items-start gap-2.5 my-1" },
|
|
158
|
+
}) as Extension,
|
|
159
|
+
Table.configure({
|
|
160
|
+
resizable: true,
|
|
161
|
+
HTMLAttributes: {
|
|
162
|
+
class: "border-collapse table-auto w-full my-4 border border-border rounded-lg overflow-hidden text-sm",
|
|
163
|
+
},
|
|
164
|
+
}) as Extension,
|
|
165
|
+
TableRow as Extension,
|
|
166
|
+
TableHeader.configure({
|
|
167
|
+
HTMLAttributes: { class: "border border-border bg-muted/70 px-3 py-2 font-semibold text-left text-xs" },
|
|
168
|
+
}) as Extension,
|
|
169
|
+
TableCell.configure({
|
|
170
|
+
HTMLAttributes: { class: "border border-border px-3 py-2 text-xs" },
|
|
171
|
+
}) as Extension,
|
|
172
|
+
CharacterCount.configure({ limit: options.characterLimit }) as Extension,
|
|
173
|
+
Markdown.configure({
|
|
174
|
+
html: true,
|
|
175
|
+
tightLists: true,
|
|
176
|
+
bulletListMarker: "-",
|
|
177
|
+
linkify: true,
|
|
178
|
+
breaks: false,
|
|
179
|
+
transformPastedText: true,
|
|
180
|
+
transformCopiedText: true,
|
|
181
|
+
}) as Extension,
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const initEditor = (el: HTMLElement) => {
|
|
185
|
+
if (typeof window === "undefined") return;
|
|
186
|
+
|
|
187
|
+
const extensions = [...defaultExtensions, ...(options.extensions || [])];
|
|
188
|
+
|
|
189
|
+
const editor = new Editor({
|
|
190
|
+
element: el,
|
|
191
|
+
content: options.content,
|
|
192
|
+
editable: options.editable ?? true,
|
|
193
|
+
autofocus: options.autofocus ?? false,
|
|
194
|
+
extensions,
|
|
195
|
+
editorProps: {
|
|
196
|
+
attributes: {
|
|
197
|
+
class:
|
|
198
|
+
"focus:outline-hidden min-h-[220px] w-full max-w-none text-foreground leading-relaxed text-sm " +
|
|
199
|
+
"[&_h1]:text-3xl [&_h1]:font-bold [&_h1]:tracking-tight [&_h1]:mt-6 [&_h1]:mb-3 " +
|
|
200
|
+
"[&_h2]:text-2xl [&_h2]:font-semibold [&_h2]:tracking-tight [&_h2]:mt-5 [&_h2]:mb-2.5 " +
|
|
201
|
+
"[&_h3]:text-xl [&_h3]:font-semibold [&_h3]:mt-4 [&_h3]:mb-2 " +
|
|
202
|
+
"[&_h4]:text-lg [&_h4]:font-semibold [&_h4]:mt-3 [&_h4]:mb-1.5 " +
|
|
203
|
+
"[&_p]:my-2 [&_p]:leading-7 " +
|
|
204
|
+
"[&_ul]:list-disc [&_ul]:pl-6 [&_ul]:my-3 [&_ul]:space-y-1 " +
|
|
205
|
+
"[&_ol]:list-decimal [&_ol]:pl-6 [&_ol]:my-3 [&_ol]:space-y-1 " +
|
|
206
|
+
"[&_li]:my-0.5 " +
|
|
207
|
+
"[&_ul[data-type=taskList]]:list-none [&_ul[data-type=taskList]]:pl-0 [&_ul[data-type=taskList]]:my-3 [&_ul[data-type=taskList]]:space-y-2 " +
|
|
208
|
+
"[&_ul[data-type=taskList]_li]:flex [&_ul[data-type=taskList]_li]:items-start [&_ul[data-type=taskList]_li]:gap-2.5 " +
|
|
209
|
+
"[&_ul[data-type=taskList]_input[type=checkbox]]:mt-1 [&_ul[data-type=taskList]_input[type=checkbox]]:h-4 [&_ul[data-type=taskList]_input[type=checkbox]]:w-4 [&_ul[data-type=taskList]_input[type=checkbox]]:rounded [&_ul[data-type=taskList]_input[type=checkbox]]:border-border [&_ul[data-type=taskList]_input[type=checkbox]]:accent-primary [&_ul[data-type=taskList]_input[type=checkbox]]:cursor-pointer " +
|
|
210
|
+
"[&_mark]:text-foreground [&_mark]:dark:text-background [&_mark]:font-medium [&_mark]:rounded-xs [&_mark]:px-1 [&_mark]:py-0.5 " +
|
|
211
|
+
"[&_.is-editor-empty:first-child::before]:content-[attr(data-placeholder)] [&_.is-editor-empty:first-child::before]:text-muted-foreground [&_.is-editor-empty:first-child::before]:float-left [&_.is-editor-empty:first-child::before]:pointer-events-none [&_.is-editor-empty:first-child::before]:h-0",
|
|
212
|
+
},
|
|
213
|
+
...options.editorProps,
|
|
214
|
+
},
|
|
215
|
+
onUpdate: (props) => {
|
|
216
|
+
updateStats(props.editor);
|
|
217
|
+
options.onUpdate?.(props);
|
|
218
|
+
},
|
|
219
|
+
onSelectionUpdate: (props) => {
|
|
220
|
+
updateStats(props.editor);
|
|
221
|
+
options.onSelectionUpdate?.(props);
|
|
222
|
+
},
|
|
223
|
+
onTransaction: (props) => {
|
|
224
|
+
updateStats(props.editor);
|
|
225
|
+
options.onTransaction?.(props);
|
|
226
|
+
},
|
|
227
|
+
onFocus: (args) => options.onFocus?.(args),
|
|
228
|
+
onBlur: (args) => options.onBlur?.(args),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
setEditorInstance(editor);
|
|
232
|
+
updateStats(editor);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const mount = (element: HTMLElement) => {
|
|
236
|
+
targetElement = element;
|
|
237
|
+
initEditor(element);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const destroy = () => {
|
|
241
|
+
editorInstance()?.destroy();
|
|
242
|
+
setEditorInstance(null);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
onCleanup(() => {
|
|
246
|
+
destroy();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const isActive = (name: string | Record<string, any>, attributes?: Record<string, any>) => {
|
|
250
|
+
transactionVersion();
|
|
251
|
+
const ed = editorInstance();
|
|
252
|
+
if (!ed) return false;
|
|
253
|
+
if (typeof name === "string") {
|
|
254
|
+
return ed.isActive(name, attributes);
|
|
255
|
+
}
|
|
256
|
+
return ed.isActive(name);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
editor: editorInstance,
|
|
261
|
+
html,
|
|
262
|
+
text,
|
|
263
|
+
markdown,
|
|
264
|
+
isEmpty,
|
|
265
|
+
characterCount,
|
|
266
|
+
wordCount,
|
|
267
|
+
canUndo,
|
|
268
|
+
canRedo,
|
|
269
|
+
mount,
|
|
270
|
+
destroy,
|
|
271
|
+
isActive,
|
|
272
|
+
toggleBold: () => editorInstance()?.chain().focus().toggleBold().run(),
|
|
273
|
+
toggleItalic: () => editorInstance()?.chain().focus().toggleItalic().run(),
|
|
274
|
+
toggleUnderline: () => editorInstance()?.chain().focus().toggleUnderline().run(),
|
|
275
|
+
toggleStrike: () => editorInstance()?.chain().focus().toggleStrike().run(),
|
|
276
|
+
toggleCode: () => editorInstance()?.chain().focus().toggleCode().run(),
|
|
277
|
+
toggleHighlight: (color) => {
|
|
278
|
+
const ed = editorInstance();
|
|
279
|
+
if (!ed) return;
|
|
280
|
+
if (color) {
|
|
281
|
+
ed.chain().focus().toggleHighlight({ color }).run();
|
|
282
|
+
} else {
|
|
283
|
+
ed.chain().focus().toggleHighlight().run();
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
setHeading: (level) => editorInstance()?.chain().focus().toggleHeading({ level }).run(),
|
|
287
|
+
setParagraph: () => editorInstance()?.chain().focus().setParagraph().run(),
|
|
288
|
+
toggleBulletList: () => editorInstance()?.chain().focus().toggleBulletList().run(),
|
|
289
|
+
toggleOrderedList: () => editorInstance()?.chain().focus().toggleOrderedList().run(),
|
|
290
|
+
toggleTaskList: () => editorInstance()?.chain().focus().toggleTaskList().run(),
|
|
291
|
+
toggleBlockquote: () => editorInstance()?.chain().focus().toggleBlockquote().run(),
|
|
292
|
+
toggleCodeBlock: () => editorInstance()?.chain().focus().toggleCodeBlock().run(),
|
|
293
|
+
setTextAlign: (alignment) => editorInstance()?.chain().focus().setTextAlign(alignment).run(),
|
|
294
|
+
setLink: (opts) => {
|
|
295
|
+
const ed = editorInstance();
|
|
296
|
+
if (!ed) return;
|
|
297
|
+
if (typeof opts === "string") {
|
|
298
|
+
ed.chain().focus().setLink({ href: opts, target: "_blank" }).run();
|
|
299
|
+
} else {
|
|
300
|
+
ed.chain().focus().setLink({ href: opts.href, target: opts.target ?? "_blank" }).run();
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
unsetLink: () => editorInstance()?.chain().focus().unsetLink().run(),
|
|
304
|
+
setImage: (opts) => {
|
|
305
|
+
const ed = editorInstance();
|
|
306
|
+
if (!ed) return;
|
|
307
|
+
if (typeof opts === "string") {
|
|
308
|
+
ed.chain().focus().setImage({ src: opts }).run();
|
|
309
|
+
} else {
|
|
310
|
+
ed.chain().focus().setImage(opts).run();
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
insertTable: (opts) =>
|
|
314
|
+
editorInstance()?.chain().focus().insertTable({
|
|
315
|
+
rows: opts?.rows ?? 3,
|
|
316
|
+
cols: opts?.cols ?? 3,
|
|
317
|
+
withHeaderRow: opts?.withHeaderRow ?? true,
|
|
318
|
+
}).run(),
|
|
319
|
+
insertHorizontalRule: () => editorInstance()?.chain().focus().setHorizontalRule().run(),
|
|
320
|
+
clearContent: () => editorInstance()?.chain().focus().clearContent().run(),
|
|
321
|
+
setContent: (c) => editorInstance()?.commands.setContent(c),
|
|
322
|
+
setEditable: (editable: boolean) => editorInstance()?.setEditable(editable),
|
|
323
|
+
undo: () => editorInstance()?.chain().focus().undo().run(),
|
|
324
|
+
redo: () => editorInstance()?.chain().focus().redo().run(),
|
|
325
|
+
};
|
|
326
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -40,4 +40,9 @@ export * from "./create-event-source";
|
|
|
40
40
|
export * from "./create-scroll-into-view";
|
|
41
41
|
export * from "./create-drop-zone";
|
|
42
42
|
export * from "./create-pagination";
|
|
43
|
-
export * from "./create-chat-scroll";
|
|
43
|
+
export * from "./create-chat-scroll";
|
|
44
|
+
export * from "./create-tauri-window";
|
|
45
|
+
export * from "./create-global-shortcut";
|
|
46
|
+
export * from "./create-app-updater";
|
|
47
|
+
export * from "./create-document-tabs";
|
|
48
|
+
export * from "./create-tiptap-editor";
|