dn-react-text-editor 0.1.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/LICENSE +7 -0
- package/README.md +222 -0
- package/dist/attach_file.d.mts +28 -0
- package/dist/attach_file.d.ts +28 -0
- package/dist/attach_file.js +169 -0
- package/dist/attach_file.mjs +144 -0
- package/dist/cn.d.mts +3 -0
- package/dist/cn.d.ts +3 -0
- package/dist/cn.js +32 -0
- package/dist/cn.mjs +7 -0
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +735 -0
- package/dist/index.mjs +698 -0
- package/dist/plugins/drag_and_drop.d.mts +12 -0
- package/dist/plugins/drag_and_drop.d.ts +12 -0
- package/dist/plugins/drag_and_drop.js +57 -0
- package/dist/plugins/drag_and_drop.mjs +32 -0
- package/dist/plugins/keymap.d.mts +6 -0
- package/dist/plugins/keymap.d.ts +6 -0
- package/dist/plugins/keymap.js +59 -0
- package/dist/plugins/keymap.mjs +34 -0
- package/dist/plugins/placehoder.d.mts +5 -0
- package/dist/plugins/placehoder.d.ts +5 -0
- package/dist/plugins/placehoder.js +114 -0
- package/dist/plugins/placehoder.mjs +89 -0
- package/dist/plugins/trailing_paragraph.d.mts +5 -0
- package/dist/plugins/trailing_paragraph.d.ts +5 -0
- package/dist/plugins/trailing_paragraph.js +46 -0
- package/dist/plugins/trailing_paragraph.mjs +21 -0
- package/dist/plugins/upload_placeholder.d.mts +7 -0
- package/dist/plugins/upload_placeholder.d.ts +7 -0
- package/dist/plugins/upload_placeholder.js +94 -0
- package/dist/plugins/upload_placeholder.mjs +68 -0
- package/dist/prosemirror/index.d.mts +7 -0
- package/dist/prosemirror/index.d.ts +7 -0
- package/dist/prosemirror/index.js +36 -0
- package/dist/prosemirror/index.mjs +8 -0
- package/dist/schema.d.mts +7 -0
- package/dist/schema.d.ts +7 -0
- package/dist/schema.js +286 -0
- package/dist/schema.mjs +261 -0
- package/dist/text_editor.d.mts +36 -0
- package/dist/text_editor.d.ts +36 -0
- package/dist/text_editor.js +729 -0
- package/dist/text_editor.mjs +696 -0
- package/package.json +56 -0
- package/tsup.config.ts +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2025 Dndnsoft.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# React Store Input
|
|
2
|
+
|
|
3
|
+
The goal of this package is to make state management easier when using input elements in React.
|
|
4
|
+
|
|
5
|
+
It eliminates repetitive code required to implement state changes and subscriptions for input elements, and provides a simple interface.
|
|
6
|
+
|
|
7
|
+
At the same time, it allows you to use all the attributes originally provided by the input tag as-is, without needing to learn this package.
|
|
8
|
+
|
|
9
|
+
## Get Started
|
|
10
|
+
|
|
11
|
+
This is a simple example of how to use this package.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { useFormStore } from "dn-react-input";
|
|
15
|
+
|
|
16
|
+
export default function App() {
|
|
17
|
+
const store = useFormStore({
|
|
18
|
+
email: "",
|
|
19
|
+
password: "",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const submit = async () => {
|
|
23
|
+
const { email, password } = store.state;
|
|
24
|
+
|
|
25
|
+
alert(`Email: ${email}\nPassword: ${password}`);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<form
|
|
30
|
+
onSubmit={(e) => {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
submit();
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
<store.input name="email" type="email" />
|
|
36
|
+
<store.input name="password" type="password" />
|
|
37
|
+
<button type="submit">Submit</button>
|
|
38
|
+
</form>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## How to define state?
|
|
44
|
+
|
|
45
|
+
You can define any state you want as an object when calling `useStore`.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
function Component() {
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
const store = useStore({
|
|
52
|
+
email: "",
|
|
53
|
+
password: "",
|
|
54
|
+
rememberMe: false,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
...
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
It's a single source of truth for your form state.
|
|
62
|
+
|
|
63
|
+
## How to get input values?
|
|
64
|
+
|
|
65
|
+
You can access the current values of the input elements through the `state` property of the store.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
function Component() {
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
const submit = () => {
|
|
72
|
+
const { email, password, rememberMe } = store.state;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
...
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## How to add input elements?
|
|
80
|
+
|
|
81
|
+
You can add input elements using the `Input` component provided by the store. There are 'Select' and 'Textarea' components as well.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { Input } from "dn-react-input";
|
|
85
|
+
|
|
86
|
+
function Component() {
|
|
87
|
+
...
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<form>
|
|
91
|
+
<Input store={store} name="email" type="email" />
|
|
92
|
+
<Input store={store} name="password" type="password" />
|
|
93
|
+
<Input store={store} name="rememberMe" type="checkbox" />
|
|
94
|
+
</form>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If you want to avoid passing the store to each input component, use `useStoreInput`. This hook provides input components that are already connected to the store.
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { useStoreInput } from "dn-react-input";
|
|
103
|
+
|
|
104
|
+
function Component() {
|
|
105
|
+
...
|
|
106
|
+
const Input = useStoreInput(store);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<form>
|
|
110
|
+
<Input.input name="email" type="email" />
|
|
111
|
+
<Input.input name="password" type="password" />
|
|
112
|
+
<Input.input name="rememberMe" type="checkbox" />
|
|
113
|
+
</form>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`useFormStore` is a facade that combines `useStore` and `useStoreInput` for convenience.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { useFormStore } from "dn-react-input";
|
|
122
|
+
|
|
123
|
+
function Component() {
|
|
124
|
+
...
|
|
125
|
+
const store = useFormStore({
|
|
126
|
+
email: "",
|
|
127
|
+
password: "",
|
|
128
|
+
rememberMe: false,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<form>
|
|
133
|
+
<store.input name="email" type="email" />
|
|
134
|
+
<store.input name="password" type="password" />
|
|
135
|
+
<store.input name="rememberMe" type="checkbox" />
|
|
136
|
+
</form>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## How to render components on state changes?
|
|
142
|
+
|
|
143
|
+
If you want to render a component only when specific parts of the state change, use the `useSelector` hook.
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { useSelector } from "dn-react-input";
|
|
147
|
+
|
|
148
|
+
function Component() {
|
|
149
|
+
...
|
|
150
|
+
const email = useSelector(store, (state) => state.email);
|
|
151
|
+
|
|
152
|
+
return <div>Your email is: {email}</div>;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
If you want to render components in an inline manner, use the `createRender` function. By using this, you can avoid creating separate components for each part of the state you want to track.
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import { createRender } from "dn-react-input";
|
|
160
|
+
|
|
161
|
+
function Component() {
|
|
162
|
+
...
|
|
163
|
+
return (
|
|
164
|
+
<div>
|
|
165
|
+
{createRender(store, (state) => <p>{state.email}</p>)}
|
|
166
|
+
{createRender(store, (state) => <p>{state.password}</p>)}
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## How to subscribe to state changes?
|
|
173
|
+
|
|
174
|
+
You can subscribe to state changes using the `subscribe` method of the store.
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
function Component() {
|
|
178
|
+
...
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
const unsubscribe = store.subscribe((state) => {
|
|
181
|
+
console.log(`State changed`, state);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return () => {
|
|
185
|
+
unsubscribe();
|
|
186
|
+
};
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
...
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## How to update state manually?
|
|
194
|
+
|
|
195
|
+
You can update the state manually using the `dispatch` method of the store.
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
function Component() {
|
|
199
|
+
...
|
|
200
|
+
const updateEmail = () => {
|
|
201
|
+
store.dispatch({ email: "ohjinsu98@icloud.com" });
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return <button onClick={updateEmail}>Update Email</button>;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The `dispatch` method uses immerjs internally to update the state, so you can also use a function to update the state based on the previous state.
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
function Component() {
|
|
212
|
+
...
|
|
213
|
+
|
|
214
|
+
const updateEmail = () => {
|
|
215
|
+
store.dispatch((state) => {
|
|
216
|
+
state.email = "ohjinsu98@icloud.com";
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return <button onClick={updateEmail}>Update Email</button>;
|
|
221
|
+
}
|
|
222
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { EditorView } from 'prosemirror-view';
|
|
2
|
+
import { createSchema } from './schema.mjs';
|
|
3
|
+
import 'orderedmap';
|
|
4
|
+
import 'prosemirror-model';
|
|
5
|
+
|
|
6
|
+
type AttachFile = (view: EditorView, files: File[]) => Promise<void>;
|
|
7
|
+
type AttachFileOptions = {
|
|
8
|
+
schema: ReturnType<typeof createSchema>;
|
|
9
|
+
generateMetadata?: (file: File) => Promise<{
|
|
10
|
+
width?: number;
|
|
11
|
+
height?: number;
|
|
12
|
+
poster?: string;
|
|
13
|
+
}> | {
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
poster?: string;
|
|
17
|
+
};
|
|
18
|
+
uploadFile?: (file: File) => Promise<{
|
|
19
|
+
src: string;
|
|
20
|
+
alt?: string;
|
|
21
|
+
}> | {
|
|
22
|
+
src: string;
|
|
23
|
+
alt?: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
declare function createAttachFile({ schema, generateMetadata, uploadFile, }: AttachFileOptions): AttachFile;
|
|
27
|
+
|
|
28
|
+
export { type AttachFile, createAttachFile };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { EditorView } from 'prosemirror-view';
|
|
2
|
+
import { createSchema } from './schema.js';
|
|
3
|
+
import 'orderedmap';
|
|
4
|
+
import 'prosemirror-model';
|
|
5
|
+
|
|
6
|
+
type AttachFile = (view: EditorView, files: File[]) => Promise<void>;
|
|
7
|
+
type AttachFileOptions = {
|
|
8
|
+
schema: ReturnType<typeof createSchema>;
|
|
9
|
+
generateMetadata?: (file: File) => Promise<{
|
|
10
|
+
width?: number;
|
|
11
|
+
height?: number;
|
|
12
|
+
poster?: string;
|
|
13
|
+
}> | {
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
poster?: string;
|
|
17
|
+
};
|
|
18
|
+
uploadFile?: (file: File) => Promise<{
|
|
19
|
+
src: string;
|
|
20
|
+
alt?: string;
|
|
21
|
+
}> | {
|
|
22
|
+
src: string;
|
|
23
|
+
alt?: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
declare function createAttachFile({ schema, generateMetadata, uploadFile, }: AttachFileOptions): AttachFile;
|
|
27
|
+
|
|
28
|
+
export { type AttachFile, createAttachFile };
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/attach_file.tsx
|
|
21
|
+
var attach_file_exports = {};
|
|
22
|
+
__export(attach_file_exports, {
|
|
23
|
+
createAttachFile: () => createAttachFile
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(attach_file_exports);
|
|
26
|
+
var import_prosemirror_view2 = require("prosemirror-view");
|
|
27
|
+
|
|
28
|
+
// src/plugins/upload_placeholder.tsx
|
|
29
|
+
var import_prosemirror_state = require("prosemirror-state");
|
|
30
|
+
var import_prosemirror_view = require("prosemirror-view");
|
|
31
|
+
var uploadPlaceholderPlugin = new import_prosemirror_state.Plugin({
|
|
32
|
+
state: {
|
|
33
|
+
init() {
|
|
34
|
+
return import_prosemirror_view.DecorationSet.empty;
|
|
35
|
+
},
|
|
36
|
+
apply(tr, set) {
|
|
37
|
+
set = set.map(tr.mapping, tr.doc);
|
|
38
|
+
const action = tr.getMeta(this);
|
|
39
|
+
if (action && action.add) {
|
|
40
|
+
const { type, width, height } = action.add;
|
|
41
|
+
const widget = document.createElement("div");
|
|
42
|
+
widget.className = "upload-placeholder";
|
|
43
|
+
widget.style.width = `100%`;
|
|
44
|
+
if (type.startsWith("image/") || type.startsWith("video/")) {
|
|
45
|
+
widget.style.aspectRatio = `${width} / ${height}`;
|
|
46
|
+
widget.style.maxWidth = `${width}px`;
|
|
47
|
+
} else {
|
|
48
|
+
widget.style.height = "80px";
|
|
49
|
+
}
|
|
50
|
+
const progress = document.createElement("div");
|
|
51
|
+
progress.className = "upload-progress";
|
|
52
|
+
widget.appendChild(progress);
|
|
53
|
+
const deco = import_prosemirror_view.Decoration.widget(action.add.pos, widget, {
|
|
54
|
+
id: action.add.id
|
|
55
|
+
});
|
|
56
|
+
set = set.add(tr.doc, [deco]);
|
|
57
|
+
} else if (action && action.progress) {
|
|
58
|
+
const found = set.find(
|
|
59
|
+
void 0,
|
|
60
|
+
void 0,
|
|
61
|
+
(spec) => spec.id === action.progress.id
|
|
62
|
+
);
|
|
63
|
+
if (found.length) {
|
|
64
|
+
const widget = found[0].type.toDOM;
|
|
65
|
+
const progress = widget.querySelector(".upload-progress");
|
|
66
|
+
if (progress) {
|
|
67
|
+
progress.innerHTML = `${Math.round(action.progress.progress)}%`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} else if (action && action.remove) {
|
|
71
|
+
set = set.remove(
|
|
72
|
+
set.find(void 0, void 0, (spec) => spec.id === action.remove.id)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return set;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
props: {
|
|
79
|
+
decorations(state) {
|
|
80
|
+
return this.getState(state);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
var findPlaceholder = (state, id) => {
|
|
85
|
+
const decos = uploadPlaceholderPlugin.getState(state);
|
|
86
|
+
if (!decos) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const found = decos.find(void 0, void 0, (spec) => spec.id === id);
|
|
90
|
+
return found.length ? found[0].from : null;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/attach_file.tsx
|
|
94
|
+
function createAttachFile({
|
|
95
|
+
schema,
|
|
96
|
+
generateMetadata,
|
|
97
|
+
uploadFile = (file) => {
|
|
98
|
+
return {
|
|
99
|
+
src: URL.createObjectURL(file),
|
|
100
|
+
alt: file.name
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}) {
|
|
104
|
+
const attachEachFile = async (view, file, pos) => {
|
|
105
|
+
const metadata = generateMetadata ? await generateMetadata(file) : {};
|
|
106
|
+
const id = {};
|
|
107
|
+
view.focus();
|
|
108
|
+
const tr = view.state.tr;
|
|
109
|
+
if (!tr.selection.empty) {
|
|
110
|
+
tr.deleteSelection();
|
|
111
|
+
}
|
|
112
|
+
tr.setMeta(uploadPlaceholderPlugin, {
|
|
113
|
+
add: {
|
|
114
|
+
id,
|
|
115
|
+
pos: pos ?? tr.selection.from,
|
|
116
|
+
type: file.type,
|
|
117
|
+
...metadata
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
view.dispatch(tr);
|
|
121
|
+
const $pos = findPlaceholder(view.state, id);
|
|
122
|
+
if (!$pos) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const { src, alt } = await uploadFile(file);
|
|
127
|
+
const tr2 = view.state.tr.setMeta(uploadPlaceholderPlugin, {
|
|
128
|
+
remove: { id }
|
|
129
|
+
});
|
|
130
|
+
const createNode = () => {
|
|
131
|
+
if (file.type.startsWith("image/")) {
|
|
132
|
+
return schema.nodes.image.create({
|
|
133
|
+
src,
|
|
134
|
+
alt,
|
|
135
|
+
width: metadata.width,
|
|
136
|
+
height: metadata.height
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (file.type.startsWith("video/")) {
|
|
140
|
+
return schema.nodes.video.create({
|
|
141
|
+
src,
|
|
142
|
+
width: metadata.width,
|
|
143
|
+
height: metadata.height,
|
|
144
|
+
poster: metadata.poster
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const node = createNode();
|
|
149
|
+
if (!node) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
view.dispatch(tr2.replaceWith($pos, $pos, node));
|
|
153
|
+
} catch (e) {
|
|
154
|
+
view.dispatch(
|
|
155
|
+
tr.setMeta(uploadPlaceholderPlugin, { remove: { id } })
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
return async (view, files, pos) => {
|
|
160
|
+
for (let i = 0; i < files.length; i++) {
|
|
161
|
+
const file = files[i];
|
|
162
|
+
await attachEachFile(view, file, pos);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
167
|
+
0 && (module.exports = {
|
|
168
|
+
createAttachFile
|
|
169
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// src/attach_file.tsx
|
|
2
|
+
import "prosemirror-view";
|
|
3
|
+
|
|
4
|
+
// src/plugins/upload_placeholder.tsx
|
|
5
|
+
import { Plugin } from "prosemirror-state";
|
|
6
|
+
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
7
|
+
var uploadPlaceholderPlugin = new Plugin({
|
|
8
|
+
state: {
|
|
9
|
+
init() {
|
|
10
|
+
return DecorationSet.empty;
|
|
11
|
+
},
|
|
12
|
+
apply(tr, set) {
|
|
13
|
+
set = set.map(tr.mapping, tr.doc);
|
|
14
|
+
const action = tr.getMeta(this);
|
|
15
|
+
if (action && action.add) {
|
|
16
|
+
const { type, width, height } = action.add;
|
|
17
|
+
const widget = document.createElement("div");
|
|
18
|
+
widget.className = "upload-placeholder";
|
|
19
|
+
widget.style.width = `100%`;
|
|
20
|
+
if (type.startsWith("image/") || type.startsWith("video/")) {
|
|
21
|
+
widget.style.aspectRatio = `${width} / ${height}`;
|
|
22
|
+
widget.style.maxWidth = `${width}px`;
|
|
23
|
+
} else {
|
|
24
|
+
widget.style.height = "80px";
|
|
25
|
+
}
|
|
26
|
+
const progress = document.createElement("div");
|
|
27
|
+
progress.className = "upload-progress";
|
|
28
|
+
widget.appendChild(progress);
|
|
29
|
+
const deco = Decoration.widget(action.add.pos, widget, {
|
|
30
|
+
id: action.add.id
|
|
31
|
+
});
|
|
32
|
+
set = set.add(tr.doc, [deco]);
|
|
33
|
+
} else if (action && action.progress) {
|
|
34
|
+
const found = set.find(
|
|
35
|
+
void 0,
|
|
36
|
+
void 0,
|
|
37
|
+
(spec) => spec.id === action.progress.id
|
|
38
|
+
);
|
|
39
|
+
if (found.length) {
|
|
40
|
+
const widget = found[0].type.toDOM;
|
|
41
|
+
const progress = widget.querySelector(".upload-progress");
|
|
42
|
+
if (progress) {
|
|
43
|
+
progress.innerHTML = `${Math.round(action.progress.progress)}%`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else if (action && action.remove) {
|
|
47
|
+
set = set.remove(
|
|
48
|
+
set.find(void 0, void 0, (spec) => spec.id === action.remove.id)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return set;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
props: {
|
|
55
|
+
decorations(state) {
|
|
56
|
+
return this.getState(state);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
var findPlaceholder = (state, id) => {
|
|
61
|
+
const decos = uploadPlaceholderPlugin.getState(state);
|
|
62
|
+
if (!decos) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const found = decos.find(void 0, void 0, (spec) => spec.id === id);
|
|
66
|
+
return found.length ? found[0].from : null;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/attach_file.tsx
|
|
70
|
+
function createAttachFile({
|
|
71
|
+
schema,
|
|
72
|
+
generateMetadata,
|
|
73
|
+
uploadFile = (file) => {
|
|
74
|
+
return {
|
|
75
|
+
src: URL.createObjectURL(file),
|
|
76
|
+
alt: file.name
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}) {
|
|
80
|
+
const attachEachFile = async (view, file, pos) => {
|
|
81
|
+
const metadata = generateMetadata ? await generateMetadata(file) : {};
|
|
82
|
+
const id = {};
|
|
83
|
+
view.focus();
|
|
84
|
+
const tr = view.state.tr;
|
|
85
|
+
if (!tr.selection.empty) {
|
|
86
|
+
tr.deleteSelection();
|
|
87
|
+
}
|
|
88
|
+
tr.setMeta(uploadPlaceholderPlugin, {
|
|
89
|
+
add: {
|
|
90
|
+
id,
|
|
91
|
+
pos: pos ?? tr.selection.from,
|
|
92
|
+
type: file.type,
|
|
93
|
+
...metadata
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
view.dispatch(tr);
|
|
97
|
+
const $pos = findPlaceholder(view.state, id);
|
|
98
|
+
if (!$pos) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const { src, alt } = await uploadFile(file);
|
|
103
|
+
const tr2 = view.state.tr.setMeta(uploadPlaceholderPlugin, {
|
|
104
|
+
remove: { id }
|
|
105
|
+
});
|
|
106
|
+
const createNode = () => {
|
|
107
|
+
if (file.type.startsWith("image/")) {
|
|
108
|
+
return schema.nodes.image.create({
|
|
109
|
+
src,
|
|
110
|
+
alt,
|
|
111
|
+
width: metadata.width,
|
|
112
|
+
height: metadata.height
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (file.type.startsWith("video/")) {
|
|
116
|
+
return schema.nodes.video.create({
|
|
117
|
+
src,
|
|
118
|
+
width: metadata.width,
|
|
119
|
+
height: metadata.height,
|
|
120
|
+
poster: metadata.poster
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const node = createNode();
|
|
125
|
+
if (!node) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
view.dispatch(tr2.replaceWith($pos, $pos, node));
|
|
129
|
+
} catch (e) {
|
|
130
|
+
view.dispatch(
|
|
131
|
+
tr.setMeta(uploadPlaceholderPlugin, { remove: { id } })
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
return async (view, files, pos) => {
|
|
136
|
+
for (let i = 0; i < files.length; i++) {
|
|
137
|
+
const file = files[i];
|
|
138
|
+
await attachEachFile(view, file, pos);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export {
|
|
143
|
+
createAttachFile
|
|
144
|
+
};
|
package/dist/cn.d.mts
ADDED
package/dist/cn.d.ts
ADDED
package/dist/cn.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/cn.ts
|
|
21
|
+
var cn_exports = {};
|
|
22
|
+
__export(cn_exports, {
|
|
23
|
+
cn: () => cn
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(cn_exports);
|
|
26
|
+
function cn(...classes) {
|
|
27
|
+
return classes.filter(Boolean).join(" ").trim();
|
|
28
|
+
}
|
|
29
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
30
|
+
0 && (module.exports = {
|
|
31
|
+
cn
|
|
32
|
+
});
|
package/dist/cn.mjs
ADDED
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { TextEditorController, TextEditorProps, createTextEditor } from './text_editor.mjs';
|
|
2
|
+
export { createSchema } from './schema.mjs';
|
|
3
|
+
export { AttachFile, createAttachFile } from './attach_file.mjs';
|
|
4
|
+
import 'orderedmap';
|
|
5
|
+
import 'prosemirror-model';
|
|
6
|
+
import 'react';
|
|
7
|
+
import 'prosemirror-state';
|
|
8
|
+
import 'prosemirror-view';
|
|
9
|
+
import 'rxjs';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { TextEditorController, TextEditorProps, createTextEditor } from './text_editor.js';
|
|
2
|
+
export { createSchema } from './schema.js';
|
|
3
|
+
export { AttachFile, createAttachFile } from './attach_file.js';
|
|
4
|
+
import 'orderedmap';
|
|
5
|
+
import 'prosemirror-model';
|
|
6
|
+
import 'react';
|
|
7
|
+
import 'prosemirror-state';
|
|
8
|
+
import 'prosemirror-view';
|
|
9
|
+
import 'rxjs';
|