@finema/core 3.8.1 → 3.9.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/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finema/core",
3
- "version": "3.8.1",
3
+ "version": "3.9.0",
4
4
  "configKey": "core",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
package/dist/module.mjs CHANGED
@@ -4,7 +4,7 @@ import * as lodash from 'lodash-es';
4
4
  import * as theme from '../dist/runtime/theme/index.js';
5
5
 
6
6
  const name = "@finema/core";
7
- const version = "3.8.1";
7
+ const version = "3.9.0";
8
8
 
9
9
  const nuxtAppOptions = {
10
10
  head: {
@@ -0,0 +1,16 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import type { AxiosRequestConfig } from 'axios';
3
+ export interface ImagePasteOptions {
4
+ requestOptions?: Omit<AxiosRequestConfig, 'baseURL'> & {
5
+ baseURL: string;
6
+ };
7
+ uploadPathURL?: string;
8
+ bodyKey?: string;
9
+ responseURL?: string;
10
+ responsePath?: string;
11
+ responseName?: string;
12
+ responseSize?: string;
13
+ responseID?: string;
14
+ maxSize?: number;
15
+ }
16
+ export declare const ImagePaste: Extension<ImagePasteOptions, any>;
@@ -0,0 +1,106 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
3
+ import { useUploadLoader } from "#core/composables/useUpload";
4
+ import { _get } from "#core/utils/lodash";
5
+ export const ImagePaste = Extension.create({
6
+ name: "imagePaste",
7
+ addOptions() {
8
+ return {
9
+ bodyKey: "file",
10
+ responseURL: "url",
11
+ responsePath: "path",
12
+ responseName: "name",
13
+ responseSize: "size",
14
+ responseID: "id"
15
+ };
16
+ },
17
+ addProseMirrorPlugins() {
18
+ const options = this.options;
19
+ return [
20
+ new Plugin({
21
+ key: new PluginKey("imagePaste"),
22
+ props: {
23
+ handlePaste: async (view, event) => {
24
+ const items = Array.from(event.clipboardData?.items || []);
25
+ const imageItems = items.filter((item) => item.type.includes("image"));
26
+ if (imageItems.length === 0) {
27
+ return false;
28
+ }
29
+ event.preventDefault();
30
+ for (const item of imageItems) {
31
+ const file = item.getAsFile();
32
+ if (!file) continue;
33
+ if (options.maxSize && file.size > options.maxSize * 1024) {
34
+ console.warn(`Image size (${file.size} bytes) exceeds maximum allowed size (${options.maxSize} KB)`);
35
+ continue;
36
+ }
37
+ if (!options.requestOptions) {
38
+ console.warn("ImagePaste: requestOptions is not configured");
39
+ continue;
40
+ }
41
+ const {
42
+ schema
43
+ } = view.state;
44
+ const pos = view.state.selection.from;
45
+ if (schema.nodes.imageUpload) {
46
+ const transaction = view.state.tr.insert(pos, schema.nodes.imageUpload.create());
47
+ view.dispatch(transaction);
48
+ }
49
+ const request = {
50
+ requestOptions: options.requestOptions,
51
+ pathURL: options.uploadPathURL
52
+ };
53
+ const uploadLoader = useUploadLoader(request);
54
+ const formData = new FormData();
55
+ const bodyKey = options.bodyKey || "file";
56
+ formData.append(bodyKey, file);
57
+ await uploadLoader.run({
58
+ data: formData
59
+ });
60
+ if (uploadLoader.status.value.isSuccess && uploadLoader.data.value) {
61
+ const responseURL = options.responseURL || "url";
62
+ const url = _get(uploadLoader.data.value, responseURL);
63
+ if (!url) {
64
+ console.error("ImagePaste: Could not find URL in response", uploadLoader.data.value);
65
+ continue;
66
+ }
67
+ const currentState = view.state;
68
+ let placeholderPos = -1;
69
+ currentState.doc.descendants((node, nodePos) => {
70
+ if (node.type.name === "imageUpload" && placeholderPos === -1) {
71
+ placeholderPos = nodePos;
72
+ return false;
73
+ }
74
+ });
75
+ if (placeholderPos !== -1 && schema.nodes.image) {
76
+ const imageNode = schema.nodes.image.create({
77
+ src: url
78
+ });
79
+ let tr = currentState.tr.delete(placeholderPos, placeholderPos + 1).insert(placeholderPos, imageNode);
80
+ const resolvedPos = tr.doc.resolve(placeholderPos + imageNode.nodeSize);
81
+ tr = tr.setSelection(TextSelection.near(resolvedPos));
82
+ view.dispatch(tr);
83
+ }
84
+ } else if (uploadLoader.status.value.isError) {
85
+ console.error("ImagePaste: Upload failed", uploadLoader.status.value.errorData);
86
+ const currentState = view.state;
87
+ let placeholderPos = -1;
88
+ currentState.doc.descendants((node, nodePos) => {
89
+ if (node.type.name === "imageUpload" && placeholderPos === -1) {
90
+ placeholderPos = nodePos;
91
+ return false;
92
+ }
93
+ });
94
+ if (placeholderPos !== -1) {
95
+ const tr = currentState.tr.delete(placeholderPos, placeholderPos + 1);
96
+ view.dispatch(tr);
97
+ }
98
+ }
99
+ }
100
+ return true;
101
+ }
102
+ }
103
+ })
104
+ ];
105
+ }
106
+ });
@@ -0,0 +1,96 @@
1
+ # InputWYSIWYG - TipTap WYSIWYG Editor
2
+
3
+ A rich text editor component built with TipTap that supports text formatting, images, and more.
4
+
5
+ ## Features
6
+
7
+ - **Text Formatting**: Bold, italic, underline, strikethrough, code
8
+ - **Headings**: H1, H2, H3, H4
9
+ - **Lists**: Bullet lists and ordered lists
10
+ - **Text Alignment**: Left, center, right, justify
11
+ - **Blockquotes and Code Blocks**
12
+ - **Links**: Add and edit links with a popover interface
13
+ - **Image Upload**: Upload images via button click
14
+ - **Image Paste**: ✨ **NEW** - Copy and paste images directly from your computer
15
+ - **Horizontal Rules**
16
+ - **Undo/Redo**
17
+
18
+ ## Image Paste Feature
19
+
20
+ The editor now supports pasting images directly from your clipboard! This works in two ways:
21
+
22
+ ### 1. Copy from File System
23
+ - Copy an image file from your file explorer/finder
24
+ - Click into the editor
25
+ - Press `Cmd+V` (Mac) or `Ctrl+V` (Windows/Linux)
26
+ - The image will automatically upload and insert into the editor
27
+
28
+ ### 2. Copy from Screenshot
29
+ - Take a screenshot (or copy an image from any application)
30
+ - Click into the editor
31
+ - Press `Cmd+V` (Mac) or `Ctrl+V` (Windows/Linux)
32
+ - The image will automatically upload and insert into the editor
33
+
34
+ ### How It Works
35
+
36
+ The `ImagePaste` extension intercepts paste events and:
37
+ 1. Detects if the clipboard contains image data
38
+ 2. Validates the image size (if `maxSize` is configured)
39
+ 3. Inserts a placeholder/loading node
40
+ 4. Uploads the image to your configured endpoint
41
+ 5. Replaces the placeholder with the actual image once uploaded
42
+
43
+ ## Usage
44
+
45
+ ```vue
46
+ <template>
47
+ <InputWYSIWYG
48
+ name="content"
49
+ label="Content"
50
+ :image="{
51
+ requestOptions: useRequestOptions().getFile(),
52
+ uploadPathURL: '/uploads',
53
+ maxSize: 5120, // 5MB in KB
54
+ bodyKey: 'file',
55
+ responseURL: 'url',
56
+ responsePath: 'path',
57
+ responseName: 'name',
58
+ responseSize: 'size',
59
+ responseID: 'id',
60
+ }"
61
+ />
62
+ </template>
63
+ ```
64
+
65
+ ## Configuration
66
+
67
+ ### Image Upload Options
68
+
69
+ | Option | Type | Default | Description |
70
+ |--------|------|---------|-------------|
71
+ | `requestOptions` | `AxiosRequestConfig` | - | Axios configuration for upload requests |
72
+ | `uploadPathURL` | `string` | - | API endpoint path for uploads |
73
+ | `bodyKey` | `string` | `'file'` | Form data key for the file |
74
+ | `responseURL` | `string` | `'url'` | Path to URL in upload response |
75
+ | `responsePath` | `string` | `'path'` | Path to file path in response |
76
+ | `responseName` | `string` | `'name'` | Path to file name in response |
77
+ | `responseSize` | `string` | `'size'` | Path to file size in response |
78
+ | `responseID` | `string` | `'id'` | Path to file ID in response |
79
+ | `maxSize` | `number` | - | Maximum file size in KB |
80
+
81
+ ## Extensions
82
+
83
+ The component uses the following TipTap extensions:
84
+
85
+ - **TextAlign**: For text alignment options
86
+ - **ImageUpload**: Custom extension for manual image uploads via button
87
+ - **ImagePaste**: Custom extension for pasting images from clipboard
88
+
89
+ ## Files
90
+
91
+ - `index.vue` - Main component
92
+ - `EditorImageUploadExtension.ts` - Extension for manual image upload
93
+ - `EditorImageUploadNode.vue` - Vue component for image upload UI
94
+ - `EditorImagePasteExtension.ts` - Extension for paste image functionality
95
+ - `EditorLinkPopover.vue` - Link editing popover
96
+ - `types.ts` - TypeScript type definitions
@@ -14,7 +14,7 @@
14
14
  types: ['heading', 'paragraph'],
15
15
  alignments: ['left', 'center', 'right', 'justify']
16
16
  }),
17
- ImageUpload.configure(image)
17
+ ...image.requestOptions ? [ImageUpload.configure(image), ImagePaste.configure(image)] : []
18
18
  ]"
19
19
  :ui="{
20
20
  content: '',
@@ -61,6 +61,7 @@ import { wysiwygTheme } from "#core/theme/wysiwyg";
61
61
  import { useUiConfig } from "#core/composables/useConfig";
62
62
  import { TextAlign } from "@tiptap/extension-text-align";
63
63
  import { ImageUpload } from "./EditorImageUploadExtension";
64
+ import { ImagePaste } from "./EditorImagePasteExtension";
64
65
  import EditorLinkPopover from "./EditorLinkPopover.vue";
65
66
  const props = defineProps({
66
67
  editable: { type: Boolean, required: false, default: () => true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finema/core",
3
- "version": "3.8.1",
3
+ "version": "3.9.0",
4
4
  "repository": "https://gitlab.finema.co/finema/ui-kit",
5
5
  "license": "MIT",
6
6
  "author": "Finema Dev Core Team",