inline-attacher 0.0.8 → 0.1.1

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/README.md CHANGED
@@ -28,7 +28,7 @@ npm i inline-attacher
28
28
  - CodeMirror v6
29
29
 
30
30
  ```ts
31
- import { EditorView } from "codemirror";
31
+ import { EditorView } from "@codemirror/view";
32
32
  import { inlineAttachmentExtension } from "inline-attacher";
33
33
 
34
34
  const editor = new EditorView({
@@ -38,3 +38,27 @@ npm i inline-attacher
38
38
  parent: document.body,
39
39
  });
40
40
  ```
41
+
42
+ - Custom upload handler
43
+
44
+ ```ts
45
+ import { attach } from "inline-attacher";
46
+
47
+ const textarea = document.querySelector("textarea");
48
+
49
+ attach(textarea, {
50
+ async uploadHandler({ file, filename, formData }) {
51
+ const response = await myUploader.upload({
52
+ file,
53
+ filename,
54
+ formData,
55
+ });
56
+
57
+ return { url: response.publicUrl };
58
+ },
59
+ });
60
+ ```
61
+
62
+ The returned object is handled the same way as the built-in upload response:
63
+ `responseUrlKey` is used to read the uploaded file URL before replacing the
64
+ placeholder text.
package/dist/core.d.ts CHANGED
@@ -4,9 +4,10 @@ export declare class InlineAttachment<TInstance> {
4
4
  editor: Editor<TInstance>;
5
5
  filename: string;
6
6
  lastValue: string;
7
+ private pendingPlaceholders;
7
8
  constructor(editor: Editor<TInstance>, options: Partial<InlineAttachmentOptions>);
8
9
  /** Uploads file */
9
- uploadFile(file: File): Promise<void>;
10
+ uploadFile(file: File, placeholder?: string): Promise<void>;
10
11
  /**
11
12
  * Returns if the given file is allowed to handle
12
13
  */
@@ -14,16 +15,18 @@ export declare class InlineAttachment<TInstance> {
14
15
  /**
15
16
  * Handles upload response
16
17
  */
17
- onFileUploadSucceed(response: Record<string, unknown>): void;
18
+ onFileUploadSucceed(response: Record<string, unknown>, placeholder?: string, filename?: string): void;
18
19
  /**
19
20
  * Called when a file has failed to upload
20
21
  */
21
- onFileUploadError(error: Error): void;
22
+ onFileUploadError(error: Error, placeholder?: string): void;
22
23
  /**
23
24
  * Called when a file has been inserted, either by drop or paste
24
25
  */
25
- onFileInserted(file: File): void;
26
+ onFileInserted(file: File): string | false;
26
27
  handleFiles(files: FileList): void;
28
+ private insertFile;
29
+ private replacePlaceholder;
27
30
  /**
28
31
  * Called when a paste event occurred
29
32
  */
@@ -1,7 +1,4 @@
1
- var w = Object.defineProperty;
2
- var x = (n, t, e) => t in n ? w(n, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[t] = e;
3
- var o = (n, t, e) => (x(n, typeof t != "symbol" ? t + "" : t, e), e);
4
- import { EditorView as V } from "@codemirror/view";
1
+ import { EditorView as w } from "@codemirror/view";
5
2
  const E = {
6
3
  /**
7
4
  * URL where the file will be send
@@ -90,7 +87,7 @@ const E = {
90
87
  onFileUploaded() {
91
88
  }
92
89
  };
93
- async function D({
90
+ async function P({
94
91
  url: n,
95
92
  ...t
96
93
  }) {
@@ -109,10 +106,10 @@ async function D({
109
106
  };
110
107
  }
111
108
  }
112
- function T(n) {
109
+ function U(n) {
113
110
  return typeof n == "function";
114
111
  }
115
- function A(n, t = "", e = "") {
112
+ function y(n, t = "", e = "") {
116
113
  const s = t.split(".");
117
114
  let i = n;
118
115
  for (const r of s)
@@ -122,47 +119,69 @@ function A(n, t = "", e = "") {
122
119
  return e;
123
120
  return i !== void 0 ? i : e;
124
121
  }
125
- class v {
122
+ function T(n, t, e, s = 0) {
123
+ let i = 0;
124
+ for (let r = 0; r <= s; r += 1) {
125
+ const o = n.indexOf(t, i);
126
+ if (o === -1) return n;
127
+ if (r === s)
128
+ return n.slice(0, o) + e + n.slice(o + t.length);
129
+ i = o + t.length;
130
+ }
131
+ return n;
132
+ }
133
+ class F {
134
+ options = E;
135
+ editor;
136
+ filename = "";
137
+ lastValue = "";
138
+ pendingPlaceholders = [];
126
139
  constructor(t, e) {
127
- o(this, "options", E);
128
- o(this, "editor");
129
- o(this, "filename", "");
130
- o(this, "lastValue", "");
131
140
  this.editor = t, this.options = { ...E, ...e };
132
141
  }
133
- /** Uploads file */
134
- async uploadFile(t) {
142
+ async uploadFile(t, e) {
135
143
  const {
136
- defaultExtension: e,
137
- remoteFilename: s,
138
- uploadFieldName: i,
139
- extraParams: r,
140
- extraHeaders: l,
144
+ defaultExtension: s,
145
+ remoteFilename: i,
146
+ uploadFieldName: r,
147
+ extraParams: o,
148
+ extraHeaders: a,
141
149
  uploadUrl: c,
142
- uploadMethod: p,
143
- beforeFileUpload: d
144
- } = this.options, a = new FormData();
145
- let f = e;
146
- if (t.name) {
147
- const [u] = t.name.match(/\.(.+)$/) || [];
148
- u && (f = u);
149
- }
150
- const g = (s == null ? void 0 : s(t)) || `image-${Date.now()}.${f}`;
151
- if (this.filename = g, a.append(i, t, g), Object.keys(r).forEach((u) => {
152
- a.append(u, r[u]);
153
- }), !(d != null && d(a)))
150
+ uploadMethod: d,
151
+ beforeFileUpload: u,
152
+ uploadHandler: f
153
+ } = this.options, p = new FormData(), x = s.replace(/^\./, ""), h = i?.(t) || t.name || `image-${Date.now()}.${x}`;
154
+ if (this.filename = h, p.append(r, t, h), Object.keys(o).forEach((l) => {
155
+ p.append(l, o[l]);
156
+ }), !u?.(p)) return;
157
+ if (f) {
158
+ try {
159
+ const l = await f({
160
+ file: t,
161
+ filename: h,
162
+ formData: p,
163
+ options: this.options
164
+ });
165
+ this.onFileUploadSucceed(l, e, h);
166
+ } catch (l) {
167
+ this.onFileUploadError(
168
+ l instanceof Error ? l : new Error(String(l)),
169
+ e
170
+ );
171
+ }
154
172
  return;
155
- const { ok: F, value: m } = await D({
173
+ }
174
+ const { ok: v, value: m } = await P({
156
175
  url: c,
157
- method: p,
158
- body: a,
159
- headers: l
176
+ method: d,
177
+ body: p,
178
+ headers: a
160
179
  });
161
- if (!F) {
162
- this.onFileUploadError(m);
180
+ if (!v) {
181
+ this.onFileUploadError(m, e);
163
182
  return;
164
183
  }
165
- this.onFileUploadSucceed(m);
184
+ this.onFileUploadSucceed(m, e, h);
166
185
  }
167
186
  /**
168
187
  * Returns if the given file is allowed to handle
@@ -171,73 +190,84 @@ class v {
171
190
  const { allowedTypes: e } = this.options;
172
191
  return e.includes("*") || e.includes(t.type);
173
192
  }
174
- /**
175
- * Handles upload response
176
- */
177
- onFileUploadSucceed(t) {
178
- var d, a;
179
- const { onFileUploadSucceed: e, urlText: s, responseUrlKey: i } = this.options;
180
- if (!(e != null && e(t)) || !this.lastValue)
181
- return;
182
- const r = A(t, i) || "unknown URL";
183
- if (!r)
184
- return;
185
- const l = /!\[({[^}]+})]\(([^)]+)\)/, c = T(s) ? s(r, t) : s.replace(s.match(l)[1], this.filename).replace(s.match(l)[2], r), p = this.editor.getValue().replace(this.lastValue, c);
186
- this.editor.setValue(p), (a = (d = this.options).onFileUploaded) == null || a.call(d, r);
193
+ onFileUploadSucceed(t, e, s = this.filename) {
194
+ const { onFileUploadSucceed: i, urlText: r, responseUrlKey: o } = this.options;
195
+ if (!i?.(t)) return;
196
+ const a = typeof e == "string" ? e : e?.text || this.lastValue;
197
+ if (!a) return;
198
+ const c = y(t, o) || "unknown URL", d = /!\[({[^}]+})]\(([^)]+)\)/, u = U(r) ? r(c, t) : r.replace(r.match(d)[1], s).replace(r.match(d)[2], c), f = this.replacePlaceholder(e, a, u);
199
+ this.editor.setValue(f), this.options.onFileUploaded?.(c);
187
200
  }
188
- /**
189
- * Called when a file has failed to upload
190
- */
191
- onFileUploadError(t) {
192
- var s, i;
193
- if (!((i = (s = this.options).onFileUploadError) != null && i.call(s, t)) || !this.lastValue)
194
- return;
195
- const e = this.editor.getValue().replace(this.lastValue, this.options.errorText);
196
- this.editor.setValue(e);
201
+ onFileUploadError(t, e) {
202
+ if (!this.options.onFileUploadError?.(t)) return;
203
+ const s = typeof e == "string" ? e : e?.text || this.lastValue;
204
+ if (!s) return;
205
+ const i = this.replacePlaceholder(e, s, this.options.errorText);
206
+ this.editor.setValue(i);
197
207
  }
198
208
  /**
199
209
  * Called when a file has been inserted, either by drop or paste
200
210
  */
201
211
  onFileInserted(t) {
202
- var e, s;
203
- (s = (e = this.options).onFileReceived) != null && s.call(e, t) && (this.lastValue = this.options.progressText, this.editor.insertValue(this.lastValue));
212
+ const e = this.insertFile(t);
213
+ return e ? e.text : !1;
204
214
  }
205
215
  handleFiles(t) {
206
216
  Array.from(t).forEach((e) => {
207
- this.isFileAllowed(e) && (this.onFileInserted(e), this.uploadFile(e));
217
+ if (!this.isFileAllowed(e)) return;
218
+ const s = this.pendingPlaceholders.length, i = this.onFileInserted(e);
219
+ if (!i) return;
220
+ const r = this.pendingPlaceholders[s];
221
+ this.uploadFile(
222
+ e,
223
+ r?.text === i ? r : i
224
+ );
208
225
  });
209
226
  }
227
+ insertFile(t) {
228
+ if (!this.options.onFileReceived?.(t)) return !1;
229
+ const e = {
230
+ id: /* @__PURE__ */ Symbol("inline-attacher-placeholder"),
231
+ text: this.options.progressText
232
+ };
233
+ return this.pendingPlaceholders.push(e), this.lastValue = e.text, this.editor.insertValue(e.text), e;
234
+ }
235
+ replacePlaceholder(t, e, s) {
236
+ const i = this.editor.getValue();
237
+ if (!t || typeof t == "string")
238
+ return i.replace(e, s);
239
+ const r = this.pendingPlaceholders.findIndex(({ id: o }) => o === t.id);
240
+ return r === -1 ? i.replace(e, s) : (this.pendingPlaceholders.splice(r, 1), T(i, e, s, r));
241
+ }
210
242
  /**
211
243
  * Called when a paste event occurred
212
244
  */
213
245
  onPaste(t) {
214
- var e;
215
- (e = t.clipboardData) != null && e.files.length && this.handleFiles(t.clipboardData.files);
246
+ t.clipboardData?.files.length && this.handleFiles(t.clipboardData.files);
216
247
  }
217
248
  /**
218
249
  * Called when a drop event occurred
219
250
  */
220
251
  onDrop(t) {
221
- var e;
222
- (e = t.dataTransfer) != null && e.files.length && this.handleFiles(t.dataTransfer.files);
252
+ t.dataTransfer?.files.length && this.handleFiles(t.dataTransfer.files);
223
253
  }
224
254
  }
225
- function U(n, t) {
226
- const e = n.scrollTop, s = n.selectionStart || 0, { value: i } = n, r = i.slice(0, s), l = i.slice(s, i.length);
227
- n.value = r + t + l;
228
- const c = s + t.length;
229
- n.selectionStart = c, n.selectionEnd = c, n.scrollTop = e, n.focus();
255
+ function b(n, t) {
256
+ const e = n.scrollTop, s = n.selectionStart ?? 0, i = n.selectionEnd ?? s, r = Math.min(s, i), o = Math.max(s, i), { value: a } = n, c = a.slice(0, r), d = a.slice(o, a.length);
257
+ n.value = c + t + d;
258
+ const u = r + t.length;
259
+ n.selectionStart = u, n.selectionEnd = u, n.scrollTop = e, n.focus();
230
260
  }
231
- class b {
261
+ class D {
262
+ instance;
232
263
  constructor(t) {
233
- o(this, "instance");
234
264
  this.instance = t;
235
265
  }
236
266
  getValue() {
237
267
  return this.instance.value;
238
268
  }
239
269
  insertValue(t) {
240
- U(this.instance, t), this.dispatchInputEvent();
270
+ b(this.instance, t), this.dispatchInputEvent();
241
271
  }
242
272
  setValue(t) {
243
273
  this.instance.value = t, this.dispatchInputEvent();
@@ -249,9 +279,9 @@ class b {
249
279
  }));
250
280
  }
251
281
  }
252
- class y extends v {
282
+ class I extends F {
253
283
  constructor(t, e = {}) {
254
- super(new b(t), e);
284
+ super(new D(t), e);
255
285
  }
256
286
  bind() {
257
287
  this.editor.instance.addEventListener(
@@ -272,12 +302,12 @@ class y extends v {
272
302
  );
273
303
  }
274
304
  }
275
- function L(...n) {
276
- return new y(...n).bind();
305
+ function V(...n) {
306
+ return new I(...n).bind();
277
307
  }
278
- class P {
308
+ class A {
309
+ instance;
279
310
  constructor(t) {
280
- o(this, "instance");
281
311
  this.instance = t;
282
312
  }
283
313
  getValue() {
@@ -297,9 +327,9 @@ class P {
297
327
  }), this.instance.dispatch({ selection: { anchor: e } });
298
328
  }
299
329
  }
300
- class h extends v {
330
+ class g extends F {
301
331
  constructor(t, e = {}) {
302
- super(new P(t), e);
332
+ super(new A(t), e);
303
333
  }
304
334
  bind() {
305
335
  this.editor.instance.dom.addEventListener(
@@ -320,16 +350,16 @@ class h extends v {
320
350
  );
321
351
  }
322
352
  }
323
- function S(...n) {
324
- return new h(...n).bind();
353
+ function k(...n) {
354
+ return new g(...n).bind();
325
355
  }
326
- function M(n = {}) {
327
- return V.domEventHandlers({
356
+ function L(n = {}) {
357
+ return w.domEventHandlers({
328
358
  paste: (t, e) => {
329
- new h(e, n).onPaste(t);
359
+ new g(e, n).onPaste(t);
330
360
  },
331
361
  drop: (t, e) => {
332
- t.stopPropagation(), t.preventDefault(), new h(e, n).onDrop(t);
362
+ t.stopPropagation(), t.preventDefault(), new g(e, n).onDrop(t);
333
363
  },
334
364
  dragenter: (t) => {
335
365
  t.stopPropagation(), t.preventDefault();
@@ -340,11 +370,11 @@ function M(n = {}) {
340
370
  });
341
371
  }
342
372
  export {
343
- h as CodeMirrorInlineAttachmentAdapter,
373
+ g as CodeMirrorInlineAttachmentAdapter,
344
374
  E as DEFAULT_OPTIONS,
345
- v as InlineAttachment,
346
- y as InputInlineAttachmentAdapter,
347
- L as attach,
348
- S as attachCodeMirror,
349
- M as inlineAttachmentExtension
375
+ F as InlineAttachment,
376
+ I as InputInlineAttachmentAdapter,
377
+ V as attach,
378
+ k as attachCodeMirror,
379
+ L as inlineAttachmentExtension
350
380
  };
@@ -1 +1 @@
1
- (function(r,o){typeof exports=="object"&&typeof module<"u"?o(exports,require("@codemirror/view")):typeof define=="function"&&define.amd?define(["exports","@codemirror/view"],o):(r=typeof globalThis<"u"?globalThis:r||self,o(r.inlineAttacher={},r.CodemirrorView))})(this,function(r,o){"use strict";var L=Object.defineProperty;var M=(r,o,l)=>o in r?L(r,o,{enumerable:!0,configurable:!0,writable:!0,value:l}):r[o]=l;var d=(r,o,l)=>(M(r,typeof o!="symbol"?o+"":o,l),l);const l={uploadUrl:"/upload",uploadMethod:"POST",uploadFieldName:"file",defaultExtension:"png",responseUrlKey:"url",allowedTypes:["image/jpeg","image/png","image/jpg","image/gif"],progressText:"![Uploading file...]()",urlText:"![{alt}]({url})",errorText:"Error uploading file",extraParams:{},extraHeaders:{},beforeFileUpload(){return!0},onFileReceived(){return!0},onFileUploadSucceed(){return!0},onFileUploadError(){return!0},onFileUploaded(){}};async function T({url:n,...t}){try{const e=await fetch(n,t);if(!e.ok)throw new Error(e.statusText);return{ok:!0,value:await e.json()}}catch(e){return{ok:!1,value:e}}}function V(n){return typeof n=="function"}function y(n,t="",e=""){const i=t.split(".");let s=n;for(const a of i)if(s&&typeof s=="object"&&a in s)s=s[a];else return e;return s!==void 0?s:e}class g{constructor(t,e){d(this,"options",l);d(this,"editor");d(this,"filename","");d(this,"lastValue","");this.editor=t,this.options={...l,...e}}async uploadFile(t){const{defaultExtension:e,remoteFilename:i,uploadFieldName:s,extraParams:a,extraHeaders:u,uploadUrl:h,uploadMethod:E,beforeFileUpload:p}=this.options,c=new FormData;let w=e;if(t.name){const[f]=t.name.match(/\.(.+)$/)||[];f&&(w=f)}const F=(i==null?void 0:i(t))||`image-${Date.now()}.${w}`;if(this.filename=F,c.append(s,t,F),Object.keys(a).forEach(f=>{c.append(f,a[f])}),!(p!=null&&p(c)))return;const{ok:S,value:A}=await T({url:h,method:E,body:c,headers:u});if(!S){this.onFileUploadError(A);return}this.onFileUploadSucceed(A)}isFileAllowed(t){const{allowedTypes:e}=this.options;return e.includes("*")||e.includes(t.type)}onFileUploadSucceed(t){var p,c;const{onFileUploadSucceed:e,urlText:i,responseUrlKey:s}=this.options;if(!(e!=null&&e(t))||!this.lastValue)return;const a=y(t,s)||"unknown URL";if(!a)return;const u=/!\[({[^}]+})]\(([^)]+)\)/,h=V(i)?i(a,t):i.replace(i.match(u)[1],this.filename).replace(i.match(u)[2],a),E=this.editor.getValue().replace(this.lastValue,h);this.editor.setValue(E),(c=(p=this.options).onFileUploaded)==null||c.call(p,a)}onFileUploadError(t){var i,s;if(!((s=(i=this.options).onFileUploadError)!=null&&s.call(i,t))||!this.lastValue)return;const e=this.editor.getValue().replace(this.lastValue,this.options.errorText);this.editor.setValue(e)}onFileInserted(t){var e,i;(i=(e=this.options).onFileReceived)!=null&&i.call(e,t)&&(this.lastValue=this.options.progressText,this.editor.insertValue(this.lastValue))}handleFiles(t){Array.from(t).forEach(e=>{this.isFileAllowed(e)&&(this.onFileInserted(e),this.uploadFile(e))})}onPaste(t){var e;(e=t.clipboardData)!=null&&e.files.length&&this.handleFiles(t.clipboardData.files)}onDrop(t){var e;(e=t.dataTransfer)!=null&&e.files.length&&this.handleFiles(t.dataTransfer.files)}}function D(n,t){const e=n.scrollTop,i=n.selectionStart||0,{value:s}=n,a=s.slice(0,i),u=s.slice(i,s.length);n.value=a+t+u;const h=i+t.length;n.selectionStart=h,n.selectionEnd=h,n.scrollTop=e,n.focus()}class I{constructor(t){d(this,"instance");this.instance=t}getValue(){return this.instance.value}insertValue(t){D(this.instance,t),this.dispatchInputEvent()}setValue(t){this.instance.value=t,this.dispatchInputEvent()}dispatchInputEvent(){this.instance.dispatchEvent(new InputEvent("input",{bubbles:!0,cancelable:!0}))}}class v extends g{constructor(t,e={}){super(new I(t),e)}bind(){this.editor.instance.addEventListener("paste",t=>{this.onPaste(t)}),this.editor.instance.addEventListener("drop",t=>{t.preventDefault(),this.onDrop(t)}),this.editor.instance.addEventListener("dragover",t=>{t.preventDefault()})}}function b(...n){return new v(...n).bind()}class x{constructor(t){d(this,"instance");this.instance=t}getValue(){return this.instance.state.doc.toString()}insertValue(t){this.instance.dispatch(this.instance.state.replaceSelection(t))}setValue(t){const e=this.instance.state.selection.main.head;this.instance.dispatch({changes:{from:0,to:this.instance.state.doc.length,insert:t}}),this.instance.dispatch({selection:{anchor:e}})}}class m extends g{constructor(t,e={}){super(new x(t),e)}bind(){this.editor.instance.dom.addEventListener("paste",t=>{this.onPaste(t)}),this.editor.instance.dom.addEventListener("drop",t=>{t.preventDefault(),this.onDrop(t)}),this.editor.instance.dom.addEventListener("dragover",t=>{t.preventDefault()})}}function P(...n){return new m(...n).bind()}function U(n={}){return o.EditorView.domEventHandlers({paste:(t,e)=>{new m(e,n).onPaste(t)},drop:(t,e)=>{t.stopPropagation(),t.preventDefault(),new m(e,n).onDrop(t)},dragenter:t=>{t.stopPropagation(),t.preventDefault()},dragover:t=>{t.stopPropagation(),t.preventDefault()}})}r.CodeMirrorInlineAttachmentAdapter=m,r.DEFAULT_OPTIONS=l,r.InlineAttachment=g,r.InputInlineAttachmentAdapter=v,r.attach=b,r.attachCodeMirror=P,r.inlineAttachmentExtension=U,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})});
1
+ (function(s,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("@codemirror/view")):typeof define=="function"&&define.amd?define(["exports","@codemirror/view"],h):(s=typeof globalThis<"u"?globalThis:s||self,h(s.inlineAttacher={},s.CodemirrorView))})(this,(function(s,h){"use strict";const F={uploadUrl:"/upload",uploadMethod:"POST",uploadFieldName:"file",defaultExtension:"png",responseUrlKey:"url",allowedTypes:["image/jpeg","image/png","image/jpg","image/gif"],progressText:"![Uploading file...]()",urlText:"![{alt}]({url})",errorText:"Error uploading file",extraParams:{},extraHeaders:{},beforeFileUpload(){return!0},onFileReceived(){return!0},onFileUploadSucceed(){return!0},onFileUploadError(){return!0},onFileUploaded(){}};async function P({url:n,...t}){try{const e=await fetch(n,t);if(!e.ok)throw new Error(e.statusText);return{ok:!0,value:await e.json()}}catch(e){return{ok:!1,value:e}}}function T(n){return typeof n=="function"}function y(n,t="",e=""){const o=t.split(".");let i=n;for(const r of o)if(i&&typeof i=="object"&&r in i)i=i[r];else return e;return i!==void 0?i:e}function A(n,t,e,o=0){let i=0;for(let r=0;r<=o;r+=1){const a=n.indexOf(t,i);if(a===-1)return n;if(r===o)return n.slice(0,a)+e+n.slice(a+t.length);i=a+t.length}return n}class v{options=F;editor;filename="";lastValue="";pendingPlaceholders=[];constructor(t,e){this.editor=t,this.options={...F,...e}}async uploadFile(t,e){const{defaultExtension:o,remoteFilename:i,uploadFieldName:r,extraParams:a,extraHeaders:l,uploadUrl:d,uploadMethod:u,beforeFileUpload:p,uploadHandler:E}=this.options,f=new FormData,M=o.replace(/^\./,""),g=i?.(t)||t.name||`image-${Date.now()}.${M}`;if(this.filename=g,f.append(r,t,g),Object.keys(a).forEach(c=>{f.append(c,a[c])}),!p?.(f))return;if(E){try{const c=await E({file:t,filename:g,formData:f,options:this.options});this.onFileUploadSucceed(c,e,g)}catch(c){this.onFileUploadError(c instanceof Error?c:new Error(String(c)),e)}return}const{ok:L,value:w}=await P({url:d,method:u,body:f,headers:l});if(!L){this.onFileUploadError(w,e);return}this.onFileUploadSucceed(w,e,g)}isFileAllowed(t){const{allowedTypes:e}=this.options;return e.includes("*")||e.includes(t.type)}onFileUploadSucceed(t,e,o=this.filename){const{onFileUploadSucceed:i,urlText:r,responseUrlKey:a}=this.options;if(!i?.(t))return;const l=typeof e=="string"?e:e?.text||this.lastValue;if(!l)return;const d=y(t,a)||"unknown URL",u=/!\[({[^}]+})]\(([^)]+)\)/,p=T(r)?r(d,t):r.replace(r.match(u)[1],o).replace(r.match(u)[2],d),E=this.replacePlaceholder(e,l,p);this.editor.setValue(E),this.options.onFileUploaded?.(d)}onFileUploadError(t,e){if(!this.options.onFileUploadError?.(t))return;const o=typeof e=="string"?e:e?.text||this.lastValue;if(!o)return;const i=this.replacePlaceholder(e,o,this.options.errorText);this.editor.setValue(i)}onFileInserted(t){const e=this.insertFile(t);return e?e.text:!1}handleFiles(t){Array.from(t).forEach(e=>{if(!this.isFileAllowed(e))return;const o=this.pendingPlaceholders.length,i=this.onFileInserted(e);if(!i)return;const r=this.pendingPlaceholders[o];this.uploadFile(e,r?.text===i?r:i)})}insertFile(t){if(!this.options.onFileReceived?.(t))return!1;const e={id:Symbol("inline-attacher-placeholder"),text:this.options.progressText};return this.pendingPlaceholders.push(e),this.lastValue=e.text,this.editor.insertValue(e.text),e}replacePlaceholder(t,e,o){const i=this.editor.getValue();if(!t||typeof t=="string")return i.replace(e,o);const r=this.pendingPlaceholders.findIndex(({id:a})=>a===t.id);return r===-1?i.replace(e,o):(this.pendingPlaceholders.splice(r,1),A(i,e,o,r))}onPaste(t){t.clipboardData?.files.length&&this.handleFiles(t.clipboardData.files)}onDrop(t){t.dataTransfer?.files.length&&this.handleFiles(t.dataTransfer.files)}}function U(n,t){const e=n.scrollTop,o=n.selectionStart??0,i=n.selectionEnd??o,r=Math.min(o,i),a=Math.max(o,i),{value:l}=n,d=l.slice(0,r),u=l.slice(a,l.length);n.value=d+t+u;const p=r+t.length;n.selectionStart=p,n.selectionEnd=p,n.scrollTop=e,n.focus()}class I{instance;constructor(t){this.instance=t}getValue(){return this.instance.value}insertValue(t){U(this.instance,t),this.dispatchInputEvent()}setValue(t){this.instance.value=t,this.dispatchInputEvent()}dispatchInputEvent(){this.instance.dispatchEvent(new InputEvent("input",{bubbles:!0,cancelable:!0}))}}class x extends v{constructor(t,e={}){super(new I(t),e)}bind(){this.editor.instance.addEventListener("paste",t=>{this.onPaste(t)}),this.editor.instance.addEventListener("drop",t=>{t.preventDefault(),this.onDrop(t)}),this.editor.instance.addEventListener("dragover",t=>{t.preventDefault()})}}function b(...n){return new x(...n).bind()}class D{instance;constructor(t){this.instance=t}getValue(){return this.instance.state.doc.toString()}insertValue(t){this.instance.dispatch(this.instance.state.replaceSelection(t))}setValue(t){const e=this.instance.state.selection.main.head;this.instance.dispatch({changes:{from:0,to:this.instance.state.doc.length,insert:t}}),this.instance.dispatch({selection:{anchor:e}})}}class m extends v{constructor(t,e={}){super(new D(t),e)}bind(){this.editor.instance.dom.addEventListener("paste",t=>{this.onPaste(t)}),this.editor.instance.dom.addEventListener("drop",t=>{t.preventDefault(),this.onDrop(t)}),this.editor.instance.dom.addEventListener("dragover",t=>{t.preventDefault()})}}function S(...n){return new m(...n).bind()}function V(n={}){return h.EditorView.domEventHandlers({paste:(t,e)=>{new m(e,n).onPaste(t)},drop:(t,e)=>{t.stopPropagation(),t.preventDefault(),new m(e,n).onDrop(t)},dragenter:t=>{t.stopPropagation(),t.preventDefault()},dragover:t=>{t.stopPropagation(),t.preventDefault()}})}s.CodeMirrorInlineAttachmentAdapter=m,s.DEFAULT_OPTIONS=F,s.InlineAttachment=v,s.InputInlineAttachmentAdapter=x,s.attach=b,s.attachCodeMirror=S,s.inlineAttachmentExtension=V,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})}));
package/dist/types.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ export interface UploadHandlerContext {
2
+ file: File;
3
+ filename: string;
4
+ formData: FormData;
5
+ options: InlineAttachmentOptions;
6
+ }
1
7
  export interface InlineAttachmentOptions {
2
8
  uploadUrl: string;
3
9
  uploadMethod: string;
@@ -15,6 +21,7 @@ export interface InlineAttachmentOptions {
15
21
  [name: string]: any;
16
22
  };
17
23
  beforeFileUpload?: (formData: FormData) => boolean;
24
+ uploadHandler?: (context: UploadHandlerContext) => Promise<Record<string, unknown>> | Record<string, unknown>;
18
25
  remoteFilename?: (file: File) => string;
19
26
  onFileReceived?: (file: File) => boolean;
20
27
  onFileUploadSucceed?: (response: Record<string, unknown>) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inline-attacher",
3
- "version": "0.0.8",
3
+ "version": "0.1.1",
4
4
  "description": "📎 A modern port from Inline Attachment",
5
5
  "type": "module",
6
6
  "files": [
@@ -11,10 +11,23 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "exports": {
13
13
  ".": {
14
+ "types": "./dist/index.d.ts",
14
15
  "import": "./dist/inline-attacher.js",
15
16
  "require": "./dist/inline-attacher.umd.cjs"
16
17
  }
17
18
  },
19
+ "scripts": {
20
+ "dev": "vite",
21
+ "build": "rimraf dist && tsc && vite build",
22
+ "preview": "vite preview",
23
+ "test": "vitest run",
24
+ "playground:dev": "pnpm -C playground dev",
25
+ "playground:deploy": "pnpm -C playground run deploy",
26
+ "lint": "eslint --fix --ext .ts src",
27
+ "prepublishOnly": "pnpm build",
28
+ "release": "npx standard-version && git push --follow-tags && npm publish",
29
+ "size-check": "npx vite-bundle-visualizer"
30
+ },
18
31
  "keywords": [
19
32
  "inline-attachment",
20
33
  "attachment",
@@ -24,7 +37,7 @@
24
37
  "image",
25
38
  "file"
26
39
  ],
27
- "author": "Michael Wang 汪東陽 <michael19920327@gmail.com> (https://github.com/EastSun5566)",
40
+ "author": "Michael Wang <hi@eastsun.me> (https://github.com/EastSun5566)",
28
41
  "license": "MIT",
29
42
  "repository": {
30
43
  "type": "git",
@@ -34,36 +47,27 @@
34
47
  "url": "https://github.com/EastSun5566/inline-attachment/issues/new"
35
48
  },
36
49
  "homepage": "https://github.com/EastSun5566/inline-attachment",
50
+ "packageManager": "pnpm@10.34.4",
37
51
  "engines": {
38
- "node": ">=16.0.0"
52
+ "node": ">=22"
39
53
  },
40
54
  "peerDependencies": {
41
55
  "@codemirror/view": ">=6.0.0"
42
56
  },
43
57
  "devDependencies": {
44
- "@codemirror/state": "^6.4.1",
45
- "@types/node": "^20.16.11",
58
+ "@codemirror/state": "^6.7.0",
59
+ "@codemirror/view": "^6.43.4",
60
+ "@types/node": "^22.20.0",
46
61
  "@typescript-eslint/eslint-plugin": "^6.21.0",
47
62
  "@typescript-eslint/parser": "^6.21.0",
48
63
  "eslint": "^8.57.1",
49
64
  "eslint-config-airbnb-base": "^15.0.0",
50
65
  "eslint-config-airbnb-typescript": "^17.1.0",
51
66
  "eslint-plugin-import": "^2.31.0",
67
+ "happy-dom": "^20.0.2",
52
68
  "rimraf": "^5.0.10",
53
- "typescript": "^5.6.3",
54
- "vite": "^4.5.5"
55
- },
56
- "dependencies": {
57
- "@codemirror/view": "^6.34.1"
58
- },
59
- "scripts": {
60
- "dev": "vite",
61
- "build": "rimraf dist && tsc && vite build",
62
- "preview": "vite preview",
63
- "playground:dev": "pnpm -C playground dev",
64
- "playground:deploy": "pnpm -C playground deploy",
65
- "lint": "eslint --fix --ext .ts src",
66
- "release": "pnpx standard-version && git push --follow-tags && pnpm publish && pnpm dlx jsr publish",
67
- "size-check": "pnpx vite-bundle-visualizer"
69
+ "typescript": "^6.0.2",
70
+ "vite": "^7.3.0",
71
+ "vitest": "^4.0.16"
68
72
  }
69
- }
73
+ }