@milkdown/plugin-upload 6.5.4 → 7.0.0-next.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.
@@ -1,3 +1,7 @@
1
1
  import type { Uploader } from './upload';
2
+ export declare const readImageAsBase64: (file: File) => Promise<{
3
+ alt: string;
4
+ src: string;
5
+ }>;
2
6
  export declare const defaultUploader: Uploader;
3
7
  //# sourceMappingURL=default-uploader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"default-uploader.d.ts","sourceRoot":"","sources":["../src/default-uploader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAmBxC,eAAO,MAAM,eAAe,EAAE,QAqB7B,CAAA"}
1
+ {"version":3,"file":"default-uploader.d.ts","sourceRoot":"","sources":["../src/default-uploader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAGxC,eAAO,MAAM,iBAAiB,SAAU,IAAI,KAAG,QAAQ;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAelF,CAAA;AAID,eAAO,MAAM,eAAe,EAAE,QAqB7B,CAAA"}
package/lib/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { AtomList } from '@milkdown/utils';
2
- export type { Uploader } from './upload';
3
- export { key, uploadPlugin } from './upload';
4
- export declare const upload: AtomList<import("@milkdown/utils").Metadata<import("@milkdown/utils").GetPlugin<string, import("./upload").Options>> & import("@milkdown/core").MilkdownPlugin>;
1
+ import type { MilkdownPlugin } from '@milkdown/ctx';
2
+ export * from './upload';
3
+ export * from './default-uploader';
4
+ export declare const upload: MilkdownPlugin[];
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAI1C,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,eAAO,MAAM,MAAM,iKAAoC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAGnD,cAAc,UAAU,CAAA;AACxB,cAAc,oBAAoB,CAAA;AAGlC,eAAO,MAAM,MAAM,EAAE,cAAc,EAAiC,CAAA"}
package/lib/index.es.js CHANGED
@@ -1,102 +1,103 @@
1
- import { createPlugin as I, AtomList as w } from "@milkdown/utils";
2
- import { schemaCtx as C, themeManagerCtx as E, ThemeIcon as L } from "@milkdown/core";
3
- import { missingNodeInSchema as M, missingIcon as S } from "@milkdown/exception";
4
- import { PluginKey as U, Plugin as D } from "@milkdown/prose/state";
5
- import { DecorationSet as _, Decoration as x } from "@milkdown/prose/view";
6
- const F = (l) => new Promise((o) => {
7
- const i = new FileReader();
8
- i.addEventListener(
1
+ import { schemaCtx as h } from "@milkdown/core";
2
+ import { PluginKey as y, Plugin as D } from "@milkdown/prose/state";
3
+ import { Decoration as P, DecorationSet as b } from "@milkdown/prose/view";
4
+ import { $ctx as U, $prose as C } from "@milkdown/utils";
5
+ import { missingNodeInSchema as F } from "@milkdown/exception";
6
+ const A = (a) => new Promise((i) => {
7
+ const r = new FileReader();
8
+ r.addEventListener(
9
9
  "load",
10
10
  () => {
11
- o({
12
- alt: l.name,
13
- src: i.result
11
+ i({
12
+ alt: a.name,
13
+ src: r.result
14
14
  });
15
15
  },
16
16
  !1
17
- ), i.readAsDataURL(l);
18
- }), K = async (l, o) => {
19
- const i = [];
20
- for (let s = 0; s < l.length; s++) {
21
- const d = l.item(s);
22
- !d || !d.type.includes("image") || i.push(d);
17
+ ), r.readAsDataURL(a);
18
+ }), E = async (a, i) => {
19
+ const r = [];
20
+ for (let t = 0; t < a.length; t++) {
21
+ const e = a.item(t);
22
+ !e || !e.type.includes("image") || r.push(e);
23
23
  }
24
- const { image: u } = o.nodes;
25
- if (!u)
26
- throw M("image");
27
- return (await Promise.all(i.map((s) => F(s)))).map(({ alt: s, src: d }) => u.createAndFill({ src: d, alt: s }));
28
- }, N = new U("MILKDOWN_UPLOAD"), O = I((l, o) => {
29
- var u;
30
- const i = (u = o == null ? void 0 : o.uploader) != null ? u : K;
31
- return {
32
- prosePlugins: (y, s) => {
33
- const d = s.get(C), p = new D({
34
- key: N,
35
- state: {
36
- init() {
37
- return _.empty;
38
- },
39
- apply(e, r) {
40
- const t = r.map(e.mapping, e.doc), a = e.getMeta(this);
41
- if (!a)
42
- return t;
43
- if (a.add) {
44
- const n = document.createElement("span"), c = s.get(E).get(L, "loading");
45
- if (!c)
46
- throw S("loading");
47
- n.appendChild(c.dom);
48
- const m = x.widget(a.add.pos, n, { id: a.add.id });
49
- return t.add(e.doc, [m]);
50
- }
51
- return a.remove ? t.remove(
52
- t.find(void 0, void 0, (n) => n.id === a.remove.id)
53
- ) : t;
54
- }
55
- },
56
- props: {
57
- decorations(e) {
58
- return this.getState(e);
59
- }
24
+ const { image: c } = i.nodes;
25
+ if (!c)
26
+ throw F("image");
27
+ return (await Promise.all(r.map((t) => A(t)))).map(({ alt: t, src: e }) => c.createAndFill({ src: e, alt: t }));
28
+ }, l = U({
29
+ uploader: E,
30
+ enableHtmlFileUploader: !1,
31
+ uploadWidgetFactory: (a, i) => {
32
+ const r = document.createElement("span");
33
+ return r.textContent = "Upload in progress...", P.widget(a, r, i);
34
+ }
35
+ }, "uploadConfig"), M = C((a) => {
36
+ const i = new y("MILKDOWN_UPLOAD"), r = (o, t) => {
37
+ var s;
38
+ const e = i.getState(o);
39
+ if (!e)
40
+ return -1;
41
+ const n = e.find(void 0, void 0, (d) => d.id === t);
42
+ return n.length ? ((s = n[0]) == null ? void 0 : s.from) ?? -1 : -1;
43
+ }, c = (o, t, e) => {
44
+ var m;
45
+ if (!e || e.length <= 0)
46
+ return !1;
47
+ const n = Symbol("upload symbol"), s = a.get(h), { tr: d } = o.state, f = t instanceof DragEvent ? ((m = o.posAtCoords({ left: t.clientX, top: t.clientY })) == null ? void 0 : m.pos) ?? d.selection.from : d.selection.from;
48
+ o.dispatch(d.setMeta(i, { add: { id: n, pos: f } }));
49
+ const { uploader: g } = a.get(l.key);
50
+ return g(e, s).then((p) => {
51
+ const u = r(o.state, n);
52
+ u < 0 || o.dispatch(
53
+ o.state.tr.replaceWith(u, u, p).setMeta(i, { remove: { id: n } })
54
+ );
55
+ }).catch((p) => {
56
+ console.error(p);
57
+ }), !0;
58
+ };
59
+ return new D({
60
+ key: i,
61
+ state: {
62
+ init() {
63
+ return b.empty;
64
+ },
65
+ apply(o, t) {
66
+ const e = t.map(o.mapping, o.doc), n = o.getMeta(this);
67
+ if (!n)
68
+ return e;
69
+ if (n.add) {
70
+ const { uploadWidgetFactory: s } = a.get(l.key), d = s(n.add.pos, { id: n.add.id });
71
+ return e.add(o.doc, [d]);
60
72
  }
61
- }), b = (e, r) => {
62
- var n, c;
63
- const t = p.getState(e);
64
- if (!t)
65
- return -1;
66
- const a = t.find(void 0, void 0, (m) => m.id === r);
67
- return a.length && (c = (n = a[0]) == null ? void 0 : n.from) != null ? c : -1;
68
- }, h = (e, r, t) => {
69
- var m, P;
70
- if (!t || t.length <= 0)
71
- return !1;
72
- const a = Symbol("upload symbol"), { tr: n } = e.state, c = r instanceof DragEvent && (P = (m = e.posAtCoords({ left: r.clientX, top: r.clientY })) == null ? void 0 : m.pos) != null ? P : n.selection.from;
73
- return e.dispatch(n.setMeta(p, { add: { id: a, pos: c } })), i(t, d).then((f) => {
74
- const g = b(e.state, a);
75
- g < 0 || e.dispatch(
76
- e.state.tr.replaceWith(g, g, f).setMeta(p, { remove: { id: a } })
77
- );
78
- }).catch((f) => {
79
- console.error(f);
80
- }), !0;
81
- }, A = new D({
82
- props: {
83
- handlePaste: (e, r) => {
84
- var t, a;
85
- return !(r instanceof ClipboardEvent) || !(o != null && o.enableHtmlFileUploader) && ((t = r.clipboardData) == null ? void 0 : t.getData("text/html")) ? !1 : h(e, r, (a = r.clipboardData) == null ? void 0 : a.files);
86
- },
87
- handleDrop: (e, r) => {
88
- var t;
89
- return r instanceof DragEvent ? h(e, r, (t = r.dataTransfer) == null ? void 0 : t.files) : !1;
90
- }
73
+ if (n.remove) {
74
+ const s = e.find(void 0, void 0, (d) => d.id === n.remove.id);
75
+ return e.remove(s);
91
76
  }
92
- });
93
- return [p, A];
77
+ return e;
78
+ }
79
+ },
80
+ props: {
81
+ decorations(o) {
82
+ return this.getState(o);
83
+ },
84
+ handlePaste: (o, t) => {
85
+ var n, s;
86
+ const { enableHtmlFileUploader: e } = a.get(l.key);
87
+ return !(t instanceof ClipboardEvent) || !e && ((n = t.clipboardData) == null ? void 0 : n.getData("text/html")) ? !1 : c(o, t, (s = t.clipboardData) == null ? void 0 : s.files);
88
+ },
89
+ handleDrop: (o, t) => {
90
+ var e;
91
+ return t instanceof DragEvent ? c(o, t, (e = t.dataTransfer) == null ? void 0 : e.files) : !1;
92
+ }
94
93
  }
95
- };
96
- }), H = w.create([O()]);
94
+ });
95
+ }), I = [l, M];
97
96
  export {
98
- N as key,
99
- H as upload,
100
- O as uploadPlugin
97
+ E as defaultUploader,
98
+ A as readImageAsBase64,
99
+ I as upload,
100
+ l as uploadConfig,
101
+ M as uploadPlugin
101
102
  };
102
103
  //# sourceMappingURL=index.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","sources":["../src/default-uploader.ts","../src/upload.ts","../src/index.ts"],"sourcesContent":["/* Copyright 2021, Milkdown by Mirone. */\nimport { missingNodeInSchema } from '@milkdown/exception'\nimport type { Node } from '@milkdown/prose/model'\n\nimport type { Uploader } from './upload'\n\nconst readImageAsBase64 = (file: File): Promise<{ alt: string; src: string }> => {\n return new Promise((resolve) => {\n const reader = new FileReader()\n reader.addEventListener(\n 'load',\n () => {\n resolve({\n alt: file.name,\n src: reader.result as string,\n })\n },\n false,\n )\n reader.readAsDataURL(file)\n })\n}\n\nexport const defaultUploader: Uploader = async (files, schema) => {\n const imgs: File[] = []\n\n for (let i = 0; i < files.length; i++) {\n const file = files.item(i)\n if (!file)\n continue\n\n if (!file.type.includes('image'))\n continue\n\n imgs.push(file)\n }\n\n const { image } = schema.nodes\n if (!image)\n throw missingNodeInSchema('image')\n\n const data = await Promise.all(imgs.map(img => readImageAsBase64(img)))\n\n return data.map(({ alt, src }) => image.createAndFill({ src, alt }) as Node)\n}\n","/* Copyright 2021, Milkdown by Mirone. */\nimport { ThemeIcon, schemaCtx, themeManagerCtx } from '@milkdown/core'\nimport { missingIcon } from '@milkdown/exception'\nimport type { Fragment, Node, Schema } from '@milkdown/prose/model'\nimport type { EditorState } from '@milkdown/prose/state'\nimport { Plugin, PluginKey } from '@milkdown/prose/state'\nimport type { EditorView } from '@milkdown/prose/view'\nimport { Decoration, DecorationSet } from '@milkdown/prose/view'\nimport { createPlugin } from '@milkdown/utils'\n\nimport { defaultUploader } from './default-uploader'\n\nexport type Uploader = (files: FileList, schema: Schema) => Promise<Fragment | Node | Node[]>\ninterface Spec { id: symbol; pos: number }\nexport interface Options {\n uploader: Uploader\n enableHtmlFileUploader: boolean\n}\nexport const key = new PluginKey('MILKDOWN_UPLOAD')\n\nexport const uploadPlugin = createPlugin<string, Options>((_, options) => {\n const uploader = options?.uploader ?? defaultUploader\n\n return {\n prosePlugins: (_, ctx) => {\n const schema = ctx.get(schemaCtx)\n\n const placeholderPlugin = new Plugin({\n key,\n state: {\n init() {\n return DecorationSet.empty\n },\n apply(this: Plugin, tr, set) {\n const _set = set.map(tr.mapping, tr.doc)\n const action = tr.getMeta(this)\n if (!action)\n return _set\n\n if (action.add) {\n const widget = document.createElement('span')\n const loadingIcon = ctx.get(themeManagerCtx).get(ThemeIcon, 'loading')\n if (!loadingIcon)\n throw missingIcon('loading')\n\n widget.appendChild(loadingIcon.dom)\n\n const decoration = Decoration.widget(action.add.pos, widget, { id: action.add.id } as any)\n return _set.add(tr.doc, [decoration])\n }\n if (action.remove) {\n return _set.remove(\n _set.find(undefined, undefined, (spec: Spec) => spec.id === action.remove.id),\n )\n }\n\n return _set\n },\n },\n props: {\n decorations(this: Plugin, state) {\n return this.getState(state)\n },\n },\n })\n\n const findPlaceholder = (state: EditorState, id: symbol): number => {\n const decorations = placeholderPlugin.getState(state)\n if (!decorations)\n return -1\n const found = decorations.find(undefined, undefined, (spec: Spec) => spec.id === id)\n if (!found.length)\n return -1\n return found[0]?.from ?? -1\n }\n\n const handleUpload = (view: EditorView, event: DragEvent | ClipboardEvent, files: FileList | undefined) => {\n if (!files || files.length <= 0)\n return false\n\n const id = Symbol('upload symbol')\n const { tr } = view.state\n const insertPos\n = event instanceof DragEvent\n ? view.posAtCoords({ left: event.clientX, top: event.clientY })?.pos ?? tr.selection.from\n : tr.selection.from\n view.dispatch(tr.setMeta(placeholderPlugin, { add: { id, pos: insertPos } }))\n\n uploader(files, schema)\n .then((fragment) => {\n const pos = findPlaceholder(view.state, id)\n if (pos < 0)\n return\n\n view.dispatch(\n view.state.tr\n .replaceWith(pos, pos, fragment)\n .setMeta(placeholderPlugin, { remove: { id } }),\n )\n })\n .catch((e) => {\n console.error(e)\n })\n return true\n }\n\n const uploadPlugin = new Plugin({\n props: {\n handlePaste: (view, event) => {\n if (!(event instanceof ClipboardEvent))\n return false\n\n if (!options?.enableHtmlFileUploader && event.clipboardData?.getData('text/html'))\n return false\n\n return handleUpload(view, event, event.clipboardData?.files)\n },\n handleDrop: (view, event) => {\n if (!(event instanceof DragEvent))\n return false\n\n return handleUpload(view, event, event.dataTransfer?.files)\n },\n },\n })\n return [placeholderPlugin, uploadPlugin]\n },\n }\n})\n","/* Copyright 2021, Milkdown by Mirone. */\nimport { AtomList } from '@milkdown/utils'\n\nimport { uploadPlugin } from './upload'\n\nexport type { Uploader } from './upload'\nexport { key, uploadPlugin } from './upload'\n\nexport const upload = AtomList.create([uploadPlugin()])\n"],"names":["readImageAsBase64","file","resolve","reader","defaultUploader","files","schema","imgs","i","image","missingNodeInSchema","img","alt","src","key","PluginKey","uploadPlugin","createPlugin","_","options","uploader","_a","ctx","schemaCtx","placeholderPlugin","Plugin","DecorationSet","tr","set","_set","action","widget","loadingIcon","themeManagerCtx","ThemeIcon","missingIcon","decoration","Decoration","spec","state","findPlaceholder","id","decorations","found","_b","handleUpload","view","event","insertPos","fragment","pos","e","upload","AtomList"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","sources":["../src/default-uploader.ts","../src/upload.ts","../src/index.ts"],"sourcesContent":["/* Copyright 2021, Milkdown by Mirone. */\nimport { missingNodeInSchema } from '@milkdown/exception'\nimport type { Node } from '@milkdown/prose/model'\n\nimport type { Uploader } from './upload'\n\n/// Read the image file as base64.\nexport const readImageAsBase64 = (file: File): Promise<{ alt: string; src: string }> => {\n return new Promise((resolve) => {\n const reader = new FileReader()\n reader.addEventListener(\n 'load',\n () => {\n resolve({\n alt: file.name,\n src: reader.result as string,\n })\n },\n false,\n )\n reader.readAsDataURL(file)\n })\n}\n\n/// The default uploader.\n/// It will upload transform images to base64.\nexport const defaultUploader: Uploader = async (files, schema) => {\n const imgs: File[] = []\n\n for (let i = 0; i < files.length; i++) {\n const file = files.item(i)\n if (!file)\n continue\n\n if (!file.type.includes('image'))\n continue\n\n imgs.push(file)\n }\n\n const { image } = schema.nodes\n if (!image)\n throw missingNodeInSchema('image')\n\n const data = await Promise.all(imgs.map(img => readImageAsBase64(img)))\n\n return data.map(({ alt, src }) => image.createAndFill({ src, alt }) as Node)\n}\n","/* Copyright 2021, Milkdown by Mirone. */\nimport { schemaCtx } from '@milkdown/core'\nimport type { Fragment, Node, Schema } from '@milkdown/prose/model'\nimport type { EditorState } from '@milkdown/prose/state'\nimport { Plugin, PluginKey } from '@milkdown/prose/state'\nimport type { EditorView } from '@milkdown/prose/view'\nimport { Decoration, DecorationSet } from '@milkdown/prose/view'\nimport { $ctx, $prose } from '@milkdown/utils'\n\nimport { defaultUploader } from './default-uploader'\n\n/// @internal\nexport type Uploader = UploadOptions['uploader']\ninterface Spec { id: symbol; pos: number }\n\n/// The configuration for upload.\nexport interface UploadOptions {\n /// The uploader for upload plugin.\n /// It takes the files and schema as parameters.\n /// It should return a `Promise` of Prosemirror `Fragment` or `Node` or `Node[]`.\n uploader: (files: FileList, schema: Schema) => Promise<Fragment | Node | Node[]>\n /// Whether to enable the html file uploader.\n /// When paste files from html (for example copy images by right click context menu),\n /// this option will make the plugin to upload the image copied instead of using the original link.\n enableHtmlFileUploader: boolean\n /// The factory for upload widget.\n /// The widget will be displayed when the file is uploading.\n /// It takes the position and spec as parameters.\n /// It should return a `Decoration` of Prosemirror.\n /// By default, it will return `<span>Upload in progress...</span>`.\n uploadWidgetFactory: (pos: number, spec: Parameters<typeof Decoration.widget>[2]) => Decoration\n}\n\n/// A slice that contains the configuration for upload.\n/// It should be type of `UploadConfig`.\nexport const uploadConfig = $ctx<UploadOptions, 'uploadConfig'>({\n uploader: defaultUploader,\n enableHtmlFileUploader: false,\n uploadWidgetFactory: (pos, spec) => {\n const widgetDOM = document.createElement('span')\n widgetDOM.textContent = 'Upload in progress...'\n return Decoration.widget(pos, widgetDOM, spec)\n },\n}, 'uploadConfig')\n\n/// The prosemirror plugin for upload.\nexport const uploadPlugin = $prose((ctx) => {\n const pluginKey = new PluginKey('MILKDOWN_UPLOAD')\n\n const findPlaceholder = (state: EditorState, id: symbol): number => {\n const decorations = pluginKey.getState(state)\n if (!decorations)\n return -1\n const found = decorations.find(undefined, undefined, (spec: Spec) => spec.id === id)\n if (!found.length)\n return -1\n return found[0]?.from ?? -1\n }\n\n const handleUpload = (view: EditorView, event: DragEvent | ClipboardEvent, files: FileList | undefined) => {\n if (!files || files.length <= 0)\n return false\n\n const id = Symbol('upload symbol')\n const schema = ctx.get(schemaCtx)\n const { tr } = view.state\n const insertPos = event instanceof DragEvent\n ? view.posAtCoords({ left: event.clientX, top: event.clientY })?.pos ?? tr.selection.from\n : tr.selection.from\n view.dispatch(tr.setMeta(pluginKey, { add: { id, pos: insertPos } }))\n\n const { uploader } = ctx.get(uploadConfig.key)\n uploader(files, schema)\n .then((fragment) => {\n const pos = findPlaceholder(view.state, id)\n if (pos < 0)\n return\n\n view.dispatch(\n view.state.tr\n .replaceWith(pos, pos, fragment)\n .setMeta(pluginKey, { remove: { id } }),\n )\n })\n .catch((e) => {\n console.error(e)\n })\n return true\n }\n\n return new Plugin({\n key: pluginKey,\n state: {\n init() {\n return DecorationSet.empty\n },\n apply(this: Plugin, tr, set) {\n const _set = set.map(tr.mapping, tr.doc)\n const action = tr.getMeta(this)\n if (!action)\n return _set\n\n if (action.add) {\n const { uploadWidgetFactory } = ctx.get(uploadConfig.key)\n\n const decoration = uploadWidgetFactory(action.add.pos, { id: action.add.id })\n return _set.add(tr.doc, [decoration])\n }\n if (action.remove) {\n const target = _set.find(undefined, undefined, (spec: Spec) => spec.id === action.remove.id)\n return _set.remove(target)\n }\n\n return _set\n },\n },\n props: {\n decorations(this: Plugin, state) {\n return this.getState(state)\n },\n handlePaste: (view, event) => {\n const { enableHtmlFileUploader } = ctx.get(uploadConfig.key)\n if (!(event instanceof ClipboardEvent))\n return false\n\n if (!enableHtmlFileUploader && event.clipboardData?.getData('text/html'))\n return false\n\n return handleUpload(view, event, event.clipboardData?.files)\n },\n handleDrop: (view, event) => {\n if (!(event instanceof DragEvent))\n return false\n\n return handleUpload(view, event, event.dataTransfer?.files)\n },\n },\n })\n})\n","/* Copyright 2021, Milkdown by Mirone. */\nimport type { MilkdownPlugin } from '@milkdown/ctx'\nimport { uploadConfig, uploadPlugin } from './upload'\n\nexport * from './upload'\nexport * from './default-uploader'\n\n/// All plugins exported by this package.\nexport const upload: MilkdownPlugin[] = [uploadConfig, uploadPlugin]\n"],"names":["readImageAsBase64","file","resolve","reader","defaultUploader","files","schema","imgs","i","image","missingNodeInSchema","img","alt","src","uploadConfig","$ctx","pos","spec","widgetDOM","Decoration","uploadPlugin","$prose","ctx","pluginKey","PluginKey","findPlaceholder","state","id","decorations","found","_a","handleUpload","view","event","schemaCtx","tr","insertPos","uploader","fragment","e","Plugin","DecorationSet","set","_set","action","uploadWidgetFactory","decoration","target","enableHtmlFileUploader","_b","upload"],"mappings":"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"}
package/lib/upload.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import type { Fragment, Node, Schema } from '@milkdown/prose/model';
2
- import { PluginKey } from '@milkdown/prose/state';
3
- export type Uploader = (files: FileList, schema: Schema) => Promise<Fragment | Node | Node[]>;
4
- export interface Options {
5
- uploader: Uploader;
2
+ import { Decoration } from '@milkdown/prose/view';
3
+ export type Uploader = UploadOptions['uploader'];
4
+ export interface UploadOptions {
5
+ uploader: (files: FileList, schema: Schema) => Promise<Fragment | Node | Node[]>;
6
6
  enableHtmlFileUploader: boolean;
7
+ uploadWidgetFactory: (pos: number, spec: Parameters<typeof Decoration.widget>[2]) => Decoration;
7
8
  }
8
- export declare const key: PluginKey<any>;
9
- export declare const uploadPlugin: import("@milkdown/utils").WithExtend<string, Options, import("@milkdown/utils").TypeMapping<string, string>, import("@milkdown/utils").PluginRest<string, string>>;
9
+ export declare const uploadConfig: import("@milkdown/utils").$Ctx<UploadOptions, "uploadConfig">;
10
+ export declare const uploadPlugin: import("@milkdown/utils").$Prose;
10
11
  //# sourceMappingURL=upload.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAEnE,OAAO,EAAU,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAOzD,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAA;AAE7F,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,QAAQ,CAAA;IAClB,sBAAsB,EAAE,OAAO,CAAA;CAChC;AACD,eAAO,MAAM,GAAG,gBAAmC,CAAA;AAEnD,eAAO,MAAM,YAAY,oKA4GvB,CAAA"}
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAInE,OAAO,EAAE,UAAU,EAAiB,MAAM,sBAAsB,CAAA;AAMhE,MAAM,MAAM,QAAQ,GAAG,aAAa,CAAC,UAAU,CAAC,CAAA;AAIhD,MAAM,WAAW,aAAa;IAI5B,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAA;IAIhF,sBAAsB,EAAE,OAAO,CAAA;IAM/B,mBAAmB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,UAAU,CAAA;CAChG;AAID,eAAO,MAAM,YAAY,+DAQP,CAAA;AAGlB,eAAO,MAAM,YAAY,kCA4FvB,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@milkdown/plugin-upload",
3
3
  "type": "module",
4
- "version": "6.5.4",
4
+ "version": "7.0.0-next.1",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -20,17 +20,19 @@
20
20
  "src"
21
21
  ],
22
22
  "peerDependencies": {
23
- "@milkdown/core": "^6.0.1",
24
- "@milkdown/prose": "^6.0.1"
23
+ "@milkdown/core": "^7.0.0-next.0",
24
+ "@milkdown/ctx": "^7.0.0-next.0",
25
+ "@milkdown/prose": "^7.0.0-next.0"
25
26
  },
26
27
  "dependencies": {
27
28
  "tslib": "^2.4.0",
28
- "@milkdown/exception": "6.5.4",
29
- "@milkdown/utils": "6.5.4"
29
+ "@milkdown/exception": "7.0.0-next.1",
30
+ "@milkdown/utils": "7.0.0-next.1"
30
31
  },
31
32
  "devDependencies": {
32
- "@milkdown/core": "6.5.4",
33
- "@milkdown/prose": "6.5.4"
33
+ "@milkdown/core": "7.0.0-next.1",
34
+ "@milkdown/ctx": "7.0.0-next.1",
35
+ "@milkdown/prose": "7.0.0-next.1"
34
36
  },
35
37
  "nx": {
36
38
  "targets": {
@@ -4,7 +4,8 @@ import type { Node } from '@milkdown/prose/model'
4
4
 
5
5
  import type { Uploader } from './upload'
6
6
 
7
- const readImageAsBase64 = (file: File): Promise<{ alt: string; src: string }> => {
7
+ /// Read the image file as base64.
8
+ export const readImageAsBase64 = (file: File): Promise<{ alt: string; src: string }> => {
8
9
  return new Promise((resolve) => {
9
10
  const reader = new FileReader()
10
11
  reader.addEventListener(
@@ -21,6 +22,8 @@ const readImageAsBase64 = (file: File): Promise<{ alt: string; src: string }> =>
21
22
  })
22
23
  }
23
24
 
25
+ /// The default uploader.
26
+ /// It will upload transform images to base64.
24
27
  export const defaultUploader: Uploader = async (files, schema) => {
25
28
  const imgs: File[] = []
26
29
 
package/src/index.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /* Copyright 2021, Milkdown by Mirone. */
2
- import { AtomList } from '@milkdown/utils'
2
+ import type { MilkdownPlugin } from '@milkdown/ctx'
3
+ import { uploadConfig, uploadPlugin } from './upload'
3
4
 
4
- import { uploadPlugin } from './upload'
5
+ export * from './upload'
6
+ export * from './default-uploader'
5
7
 
6
- export type { Uploader } from './upload'
7
- export { key, uploadPlugin } from './upload'
8
-
9
- export const upload = AtomList.create([uploadPlugin()])
8
+ /// All plugins exported by this package.
9
+ export const upload: MilkdownPlugin[] = [uploadConfig, uploadPlugin]
package/src/upload.ts CHANGED
@@ -1,129 +1,139 @@
1
1
  /* Copyright 2021, Milkdown by Mirone. */
2
- import { ThemeIcon, schemaCtx, themeManagerCtx } from '@milkdown/core'
3
- import { missingIcon } from '@milkdown/exception'
2
+ import { schemaCtx } from '@milkdown/core'
4
3
  import type { Fragment, Node, Schema } from '@milkdown/prose/model'
5
4
  import type { EditorState } from '@milkdown/prose/state'
6
5
  import { Plugin, PluginKey } from '@milkdown/prose/state'
7
6
  import type { EditorView } from '@milkdown/prose/view'
8
7
  import { Decoration, DecorationSet } from '@milkdown/prose/view'
9
- import { createPlugin } from '@milkdown/utils'
8
+ import { $ctx, $prose } from '@milkdown/utils'
10
9
 
11
10
  import { defaultUploader } from './default-uploader'
12
11
 
13
- export type Uploader = (files: FileList, schema: Schema) => Promise<Fragment | Node | Node[]>
12
+ /// @internal
13
+ export type Uploader = UploadOptions['uploader']
14
14
  interface Spec { id: symbol; pos: number }
15
- export interface Options {
16
- uploader: Uploader
15
+
16
+ /// The configuration for upload.
17
+ export interface UploadOptions {
18
+ /// The uploader for upload plugin.
19
+ /// It takes the files and schema as parameters.
20
+ /// It should return a `Promise` of Prosemirror `Fragment` or `Node` or `Node[]`.
21
+ uploader: (files: FileList, schema: Schema) => Promise<Fragment | Node | Node[]>
22
+ /// Whether to enable the html file uploader.
23
+ /// When paste files from html (for example copy images by right click context menu),
24
+ /// this option will make the plugin to upload the image copied instead of using the original link.
17
25
  enableHtmlFileUploader: boolean
26
+ /// The factory for upload widget.
27
+ /// The widget will be displayed when the file is uploading.
28
+ /// It takes the position and spec as parameters.
29
+ /// It should return a `Decoration` of Prosemirror.
30
+ /// By default, it will return `<span>Upload in progress...</span>`.
31
+ uploadWidgetFactory: (pos: number, spec: Parameters<typeof Decoration.widget>[2]) => Decoration
18
32
  }
19
- export const key = new PluginKey('MILKDOWN_UPLOAD')
20
-
21
- export const uploadPlugin = createPlugin<string, Options>((_, options) => {
22
- const uploader = options?.uploader ?? defaultUploader
23
-
24
- return {
25
- prosePlugins: (_, ctx) => {
26
- const schema = ctx.get(schemaCtx)
27
-
28
- const placeholderPlugin = new Plugin({
29
- key,
30
- state: {
31
- init() {
32
- return DecorationSet.empty
33
- },
34
- apply(this: Plugin, tr, set) {
35
- const _set = set.map(tr.mapping, tr.doc)
36
- const action = tr.getMeta(this)
37
- if (!action)
38
- return _set
39
-
40
- if (action.add) {
41
- const widget = document.createElement('span')
42
- const loadingIcon = ctx.get(themeManagerCtx).get(ThemeIcon, 'loading')
43
- if (!loadingIcon)
44
- throw missingIcon('loading')
45
-
46
- widget.appendChild(loadingIcon.dom)
47
-
48
- const decoration = Decoration.widget(action.add.pos, widget, { id: action.add.id } as any)
49
- return _set.add(tr.doc, [decoration])
50
- }
51
- if (action.remove) {
52
- return _set.remove(
53
- _set.find(undefined, undefined, (spec: Spec) => spec.id === action.remove.id),
54
- )
55
- }
56
-
57
- return _set
58
- },
59
- },
60
- props: {
61
- decorations(this: Plugin, state) {
62
- return this.getState(state)
63
- },
64
- },
33
+
34
+ /// A slice that contains the configuration for upload.
35
+ /// It should be type of `UploadConfig`.
36
+ export const uploadConfig = $ctx<UploadOptions, 'uploadConfig'>({
37
+ uploader: defaultUploader,
38
+ enableHtmlFileUploader: false,
39
+ uploadWidgetFactory: (pos, spec) => {
40
+ const widgetDOM = document.createElement('span')
41
+ widgetDOM.textContent = 'Upload in progress...'
42
+ return Decoration.widget(pos, widgetDOM, spec)
43
+ },
44
+ }, 'uploadConfig')
45
+
46
+ /// The prosemirror plugin for upload.
47
+ export const uploadPlugin = $prose((ctx) => {
48
+ const pluginKey = new PluginKey('MILKDOWN_UPLOAD')
49
+
50
+ const findPlaceholder = (state: EditorState, id: symbol): number => {
51
+ const decorations = pluginKey.getState(state)
52
+ if (!decorations)
53
+ return -1
54
+ const found = decorations.find(undefined, undefined, (spec: Spec) => spec.id === id)
55
+ if (!found.length)
56
+ return -1
57
+ return found[0]?.from ?? -1
58
+ }
59
+
60
+ const handleUpload = (view: EditorView, event: DragEvent | ClipboardEvent, files: FileList | undefined) => {
61
+ if (!files || files.length <= 0)
62
+ return false
63
+
64
+ const id = Symbol('upload symbol')
65
+ const schema = ctx.get(schemaCtx)
66
+ const { tr } = view.state
67
+ const insertPos = event instanceof DragEvent
68
+ ? view.posAtCoords({ left: event.clientX, top: event.clientY })?.pos ?? tr.selection.from
69
+ : tr.selection.from
70
+ view.dispatch(tr.setMeta(pluginKey, { add: { id, pos: insertPos } }))
71
+
72
+ const { uploader } = ctx.get(uploadConfig.key)
73
+ uploader(files, schema)
74
+ .then((fragment) => {
75
+ const pos = findPlaceholder(view.state, id)
76
+ if (pos < 0)
77
+ return
78
+
79
+ view.dispatch(
80
+ view.state.tr
81
+ .replaceWith(pos, pos, fragment)
82
+ .setMeta(pluginKey, { remove: { id } }),
83
+ )
65
84
  })
85
+ .catch((e) => {
86
+ console.error(e)
87
+ })
88
+ return true
89
+ }
66
90
 
67
- const findPlaceholder = (state: EditorState, id: symbol): number => {
68
- const decorations = placeholderPlugin.getState(state)
69
- if (!decorations)
70
- return -1
71
- const found = decorations.find(undefined, undefined, (spec: Spec) => spec.id === id)
72
- if (!found.length)
73
- return -1
74
- return found[0]?.from ?? -1
75
- }
76
-
77
- const handleUpload = (view: EditorView, event: DragEvent | ClipboardEvent, files: FileList | undefined) => {
78
- if (!files || files.length <= 0)
91
+ return new Plugin({
92
+ key: pluginKey,
93
+ state: {
94
+ init() {
95
+ return DecorationSet.empty
96
+ },
97
+ apply(this: Plugin, tr, set) {
98
+ const _set = set.map(tr.mapping, tr.doc)
99
+ const action = tr.getMeta(this)
100
+ if (!action)
101
+ return _set
102
+
103
+ if (action.add) {
104
+ const { uploadWidgetFactory } = ctx.get(uploadConfig.key)
105
+
106
+ const decoration = uploadWidgetFactory(action.add.pos, { id: action.add.id })
107
+ return _set.add(tr.doc, [decoration])
108
+ }
109
+ if (action.remove) {
110
+ const target = _set.find(undefined, undefined, (spec: Spec) => spec.id === action.remove.id)
111
+ return _set.remove(target)
112
+ }
113
+
114
+ return _set
115
+ },
116
+ },
117
+ props: {
118
+ decorations(this: Plugin, state) {
119
+ return this.getState(state)
120
+ },
121
+ handlePaste: (view, event) => {
122
+ const { enableHtmlFileUploader } = ctx.get(uploadConfig.key)
123
+ if (!(event instanceof ClipboardEvent))
79
124
  return false
80
125
 
81
- const id = Symbol('upload symbol')
82
- const { tr } = view.state
83
- const insertPos
84
- = event instanceof DragEvent
85
- ? view.posAtCoords({ left: event.clientX, top: event.clientY })?.pos ?? tr.selection.from
86
- : tr.selection.from
87
- view.dispatch(tr.setMeta(placeholderPlugin, { add: { id, pos: insertPos } }))
88
-
89
- uploader(files, schema)
90
- .then((fragment) => {
91
- const pos = findPlaceholder(view.state, id)
92
- if (pos < 0)
93
- return
94
-
95
- view.dispatch(
96
- view.state.tr
97
- .replaceWith(pos, pos, fragment)
98
- .setMeta(placeholderPlugin, { remove: { id } }),
99
- )
100
- })
101
- .catch((e) => {
102
- console.error(e)
103
- })
104
- return true
105
- }
106
-
107
- const uploadPlugin = new Plugin({
108
- props: {
109
- handlePaste: (view, event) => {
110
- if (!(event instanceof ClipboardEvent))
111
- return false
112
-
113
- if (!options?.enableHtmlFileUploader && event.clipboardData?.getData('text/html'))
114
- return false
115
-
116
- return handleUpload(view, event, event.clipboardData?.files)
117
- },
118
- handleDrop: (view, event) => {
119
- if (!(event instanceof DragEvent))
120
- return false
121
-
122
- return handleUpload(view, event, event.dataTransfer?.files)
123
- },
124
- },
125
- })
126
- return [placeholderPlugin, uploadPlugin]
126
+ if (!enableHtmlFileUploader && event.clipboardData?.getData('text/html'))
127
+ return false
128
+
129
+ return handleUpload(view, event, event.clipboardData?.files)
130
+ },
131
+ handleDrop: (view, event) => {
132
+ if (!(event instanceof DragEvent))
133
+ return false
134
+
135
+ return handleUpload(view, event, event.dataTransfer?.files)
136
+ },
127
137
  },
128
- }
138
+ })
129
139
  })