@prosekit/extensions 0.7.17 → 0.7.19

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.
@@ -17,6 +17,7 @@ import { DedentListOptions } from 'prosemirror-flat-list';
17
17
  import { config as default_alias_1 } from '@prosekit/dev/config-vitest';
18
18
  import { DocExtension } from '@prosekit/core';
19
19
  import { EditorState } from '@prosekit/pm/state';
20
+ import type { EditorView } from '@prosekit/pm/view';
20
21
  import { Extension } from '@prosekit/core';
21
22
  import { ExtensionTyping } from '@prosekit/core';
22
23
  import { ExtractMarkActions } from '@prosekit/core';
@@ -572,6 +573,14 @@ export { defineDropCursor as defineDropCursor_alias_1 }
572
573
  */
573
574
  export declare function defineEnterRule({ regex, handler, stop, }: EnterRuleOptions): PlainExtension;
574
575
 
576
+ declare function defineFileDropHandler(handler: FileDropHandler): PlainExtension;
577
+ export { defineFileDropHandler }
578
+ export { defineFileDropHandler as defineFileDropHandler_alias_1 }
579
+
580
+ declare function defineFilePasteHandler(handler: FilePasteHandler): PlainExtension;
581
+ export { defineFilePasteHandler }
582
+ export { defineFilePasteHandler as defineFilePasteHandler_alias_1 }
583
+
575
584
  /**
576
585
  * Capture clicks near and arrow-key-motion past places that don't have a
577
586
  * normally selectable position nearby, and create a gap cursor selection for
@@ -1234,6 +1243,66 @@ declare const exitTable: Command;
1234
1243
  export { exitTable }
1235
1244
  export { exitTable as exitTable_alias_1 }
1236
1245
 
1246
+ /**
1247
+ * A function that handles one of the files in a drop event.
1248
+ *
1249
+ * Returns `true` if the file was handled and thus should not be handled by
1250
+ * other handlers.
1251
+ */
1252
+ export declare type FileDropHandler = (options: FileDropHandlerOptions) => boolean | void;
1253
+
1254
+ declare interface FileDropHandlerOptions {
1255
+ /**
1256
+ * The editor view.
1257
+ */
1258
+ view: EditorView;
1259
+ /**
1260
+ * The event that triggered the drop.
1261
+ */
1262
+ event: DragEvent;
1263
+ /**
1264
+ * The file that was dropped.
1265
+ */
1266
+ file: File;
1267
+ /**
1268
+ * The position of the document where the file was dropped.
1269
+ */
1270
+ pos: number;
1271
+ }
1272
+ export { FileDropHandlerOptions }
1273
+ export { FileDropHandlerOptions as FileDropHandlerOptions_alias_1 }
1274
+
1275
+ declare type FileHandler<E extends Event> = (options: {
1276
+ view: EditorView;
1277
+ event: E;
1278
+ file: File;
1279
+ }) => boolean | void;
1280
+
1281
+ /**
1282
+ * A function that handles one of the files in a paste event.
1283
+ *
1284
+ * Returns `true` if the file was handled and thus should not be handled by
1285
+ * other handlers.
1286
+ */
1287
+ export declare type FilePasteHandler = (options: FilePasteHandlerOptions) => boolean | void;
1288
+
1289
+ declare interface FilePasteHandlerOptions {
1290
+ /**
1291
+ * The editor view.
1292
+ */
1293
+ view: EditorView;
1294
+ /**
1295
+ * The event that triggered the paste.
1296
+ */
1297
+ event: ClipboardEvent;
1298
+ /**
1299
+ * The file that was pasted.
1300
+ */
1301
+ file: File;
1302
+ }
1303
+ export { FilePasteHandlerOptions }
1304
+ export { FilePasteHandlerOptions as FilePasteHandlerOptions_alias_1 }
1305
+
1237
1306
  /**
1238
1307
  * Try to find a resolved pos of a cell by using the given pos as a hit point.
1239
1308
  *
@@ -1267,6 +1336,8 @@ export declare function getPluginState(state: EditorState): PredictionPluginStat
1267
1336
 
1268
1337
  export declare function getTrMeta(tr: Transaction): PredictionPluginState;
1269
1338
 
1339
+ export declare function handleEvent<E extends Event>(view: EditorView, event: E, handlers: FileHandler<E>[], getFiles: (event: E) => File[]): boolean;
1340
+
1270
1341
  declare interface HeadingAttrs {
1271
1342
  level: number;
1272
1343
  }
@@ -1359,6 +1430,8 @@ export { HorizontalRuleSpecExtension as HorizontalRuleSpecExtension_alias_1 }
1359
1430
  */
1360
1431
  declare interface ImageAttrs {
1361
1432
  src?: string | null;
1433
+ width?: number | null;
1434
+ height?: number | null;
1362
1435
  }
1363
1436
  export { ImageAttrs }
1364
1437
  export { ImageAttrs as ImageAttrs_alias_1 }
@@ -1706,9 +1779,10 @@ export declare type ModClickPreventionExtension = PlainExtension;
1706
1779
 
1707
1780
  export declare interface PlaceholderOptions {
1708
1781
  /**
1709
- * The placeholder text to use.
1782
+ * The placeholder to use. It can be a static string or a function that
1783
+ * receives the current editor state and returns a string.
1710
1784
  */
1711
- placeholder: string;
1785
+ placeholder: string | ((state: EditorState) => string);
1712
1786
  /**
1713
1787
  * By default, the placeholder text will be shown whenever the current text
1714
1788
  * cursor is in an empty text node. If you only want to show the placeholder
@@ -1956,6 +2030,8 @@ export declare function setupTest(): {
1956
2030
  }>;
1957
2031
  image: NodeAction< {
1958
2032
  src?: (string | null) | undefined;
2033
+ width?: (number | null) | undefined;
2034
+ height?: (number | null) | undefined;
1959
2035
  }>;
1960
2036
  list: NodeAction< {
1961
2037
  kind?: ("bullet" | "ordered" | "task" | "toggle") | undefined;
@@ -2215,6 +2291,82 @@ export declare const undo: Command;
2215
2291
  export { UnwrapListOptions }
2216
2292
  export { UnwrapListOptions as UnwrapListOptions_alias_1 }
2217
2293
 
2294
+ /**
2295
+ * The implementation of the actual upload function. You need to implement this
2296
+ * function to upload files to your desired destination.
2297
+ */
2298
+ declare type Uploader<Result> = (options: UploaderOptions) => Promise<Result>;
2299
+ export { Uploader }
2300
+ export { Uploader as Uploader_alias_1 }
2301
+
2302
+ declare interface UploaderOptions {
2303
+ /**
2304
+ * The file to be uploaded.
2305
+ */
2306
+ file: File;
2307
+ /**
2308
+ * A callback function that should be called with the upload progress updates.
2309
+ */
2310
+ onProgress: (progress: UploadProgress) => void;
2311
+ }
2312
+ export { UploaderOptions }
2313
+ export { UploaderOptions as UploaderOptions_alias_1 }
2314
+
2315
+ /**
2316
+ * An interface representing the upload progress.
2317
+ */
2318
+ declare interface UploadProgress {
2319
+ loaded: number;
2320
+ total: number;
2321
+ }
2322
+ export { UploadProgress }
2323
+ export { UploadProgress as UploadProgress_alias_1 }
2324
+
2325
+ /**
2326
+ * A class that represents a upload task.
2327
+ */
2328
+ declare class UploadTask<Result> {
2329
+ /**
2330
+ * An [object URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL)
2331
+ * representing the file to be uploaded. This URL will be revoked once the
2332
+ * upload is complete successfully.
2333
+ */
2334
+ readonly objectURL: string;
2335
+ /**
2336
+ * A boolean indicating whether the upload is complete (either successfully or with an error).
2337
+ */
2338
+ protected done: boolean;
2339
+ /**
2340
+ * A promise that fulfills once the upload is complete, or rejects if an error occurs.
2341
+ */
2342
+ readonly finished: Promise<Result>;
2343
+ private subscribers;
2344
+ /**
2345
+ * Creates a new upload task. You can find the upload task by its object URL
2346
+ * later using `UploadTask.get()`.
2347
+ *
2348
+ * @param options - The options for the upload task.
2349
+ */
2350
+ constructor({ file, uploader }: {
2351
+ file: File;
2352
+ uploader: Uploader<Result>;
2353
+ });
2354
+ /**
2355
+ * Subscribes to progress updates. Returns a function to unsubscribe.
2356
+ */
2357
+ subscribeProgress(callback: (progress: UploadProgress) => void): VoidFunction;
2358
+ /**
2359
+ * Finds an upload task by its object URL.
2360
+ */
2361
+ static get<Result = unknown>(objectURL: string): UploadTask<Result> | undefined;
2362
+ /**
2363
+ * Deletes an upload task by its object URL.
2364
+ */
2365
+ static delete(objectURL: string): void;
2366
+ }
2367
+ export { UploadTask }
2368
+ export { UploadTask as UploadTask_alias_1 }
2369
+
2218
2370
  /**
2219
2371
  * @internal
2220
2372
  */
@@ -0,0 +1,8 @@
1
+ export { defineFilePasteHandler_alias_1 as defineFilePasteHandler } from './_tsup-dts-rollup';
2
+ export { FilePasteHandlerOptions_alias_1 as FilePasteHandlerOptions } from './_tsup-dts-rollup';
3
+ export { defineFileDropHandler_alias_1 as defineFileDropHandler } from './_tsup-dts-rollup';
4
+ export { FileDropHandlerOptions_alias_1 as FileDropHandlerOptions } from './_tsup-dts-rollup';
5
+ export { UploadProgress_alias_1 as UploadProgress } from './_tsup-dts-rollup';
6
+ export { UploaderOptions_alias_1 as UploaderOptions } from './_tsup-dts-rollup';
7
+ export { Uploader_alias_1 as Uploader } from './_tsup-dts-rollup';
8
+ export { UploadTask_alias_1 as UploadTask } from './_tsup-dts-rollup';
@@ -0,0 +1,151 @@
1
+ // src/file/file-paste-handler.ts
2
+ import {
3
+ defineFacet,
4
+ defineFacetPayload,
5
+ editorEventFacet
6
+ } from "@prosekit/core";
7
+
8
+ // src/file/helpers.ts
9
+ function handleFile(view, event, file, handlers) {
10
+ for (let i = handlers.length - 1; i >= 0; i--) {
11
+ const handler = handlers[i];
12
+ if (handler({ view, event, file })) {
13
+ return true;
14
+ }
15
+ }
16
+ return false;
17
+ }
18
+ function handleEvent(view, event, handlers, getFiles3) {
19
+ const files = getFiles3(event);
20
+ let handled = false;
21
+ for (const file of files) {
22
+ if (handleFile(view, event, file, handlers)) {
23
+ handled = true;
24
+ }
25
+ }
26
+ return handled;
27
+ }
28
+
29
+ // src/file/file-paste-handler.ts
30
+ function defineFilePasteHandler(handler) {
31
+ return defineFacetPayload(facet, [handler]);
32
+ }
33
+ function getFiles(event) {
34
+ var _a, _b;
35
+ return Array.from((_b = (_a = event.clipboardData) == null ? void 0 : _a.files) != null ? _b : []);
36
+ }
37
+ var facet = defineFacet({
38
+ parent: editorEventFacet,
39
+ singleton: true,
40
+ reducer: (handlers) => {
41
+ const pasteHandler = (view, event) => {
42
+ return handleEvent(view, event, handlers, getFiles);
43
+ };
44
+ return ["paste", pasteHandler];
45
+ }
46
+ });
47
+
48
+ // src/file/file-drop-handler.ts
49
+ import {
50
+ defineFacet as defineFacet2,
51
+ defineFacetPayload as defineFacetPayload2,
52
+ editorEventFacet as editorEventFacet2
53
+ } from "@prosekit/core";
54
+ function defineFileDropHandler(handler) {
55
+ return defineFacetPayload2(facet2, [handler]);
56
+ }
57
+ function getFiles2(event) {
58
+ var _a, _b;
59
+ return Array.from((_b = (_a = event.dataTransfer) == null ? void 0 : _a.files) != null ? _b : []);
60
+ }
61
+ var facet2 = defineFacet2({
62
+ parent: editorEventFacet2,
63
+ singleton: true,
64
+ reducer: (handlers) => {
65
+ const dropHandler = (view, event) => {
66
+ const position = view.posAtCoords({ left: event.x, top: event.y });
67
+ if (!position) {
68
+ return false;
69
+ }
70
+ const pos = position.inside > 0 ? position.inside : position.pos;
71
+ return handleEvent(
72
+ view,
73
+ event,
74
+ handlers.map((handler) => (options) => handler({ ...options, pos })),
75
+ getFiles2
76
+ );
77
+ };
78
+ return ["drop", dropHandler];
79
+ }
80
+ });
81
+
82
+ // src/file/file-upload.ts
83
+ var UploadTask = class {
84
+ /**
85
+ * Creates a new upload task. You can find the upload task by its object URL
86
+ * later using `UploadTask.get()`.
87
+ *
88
+ * @param options - The options for the upload task.
89
+ */
90
+ constructor({ file, uploader }) {
91
+ /**
92
+ * A boolean indicating whether the upload is complete (either successfully or with an error).
93
+ */
94
+ this.done = false;
95
+ this.subscribers = [];
96
+ this.objectURL = URL.createObjectURL(file);
97
+ this.finished = new Promise((resolve, reject) => {
98
+ const maybePromise = uploader({
99
+ file,
100
+ onProgress: (progress) => {
101
+ for (const subscriber of this.subscribers) {
102
+ subscriber(progress);
103
+ }
104
+ }
105
+ });
106
+ Promise.resolve(maybePromise).then(
107
+ (result) => {
108
+ this.done = true;
109
+ URL.revokeObjectURL(this.objectURL);
110
+ resolve(result);
111
+ },
112
+ (error) => {
113
+ this.done = true;
114
+ reject(
115
+ new Error("[prosekit] Failed to upload file", { cause: error })
116
+ );
117
+ }
118
+ );
119
+ });
120
+ store.set(this.objectURL, this);
121
+ }
122
+ /**
123
+ * Subscribes to progress updates. Returns a function to unsubscribe.
124
+ */
125
+ subscribeProgress(callback) {
126
+ this.subscribers.push(callback);
127
+ return () => {
128
+ this.subscribers = this.subscribers.filter(
129
+ (subscriber) => subscriber !== callback
130
+ );
131
+ };
132
+ }
133
+ /**
134
+ * Finds an upload task by its object URL.
135
+ */
136
+ static get(objectURL) {
137
+ return store.get(objectURL);
138
+ }
139
+ /**
140
+ * Deletes an upload task by its object URL.
141
+ */
142
+ static delete(objectURL) {
143
+ store.delete(objectURL);
144
+ }
145
+ };
146
+ var store = /* @__PURE__ */ new Map();
147
+ export {
148
+ UploadTask,
149
+ defineFileDropHandler,
150
+ defineFilePasteHandler
151
+ };
@@ -17,7 +17,9 @@ function defineImageSpec() {
17
17
  return defineNodeSpec({
18
18
  name: "image",
19
19
  attrs: {
20
- src: { default: null }
20
+ src: { default: null },
21
+ width: { default: null },
22
+ height: { default: null }
21
23
  },
22
24
  group: "block",
23
25
  defining: true,
@@ -30,7 +32,17 @@ function defineImageSpec() {
30
32
  return { src: null };
31
33
  }
32
34
  const src = element.getAttribute("src") || null;
33
- return { src };
35
+ let width = null;
36
+ let height = null;
37
+ const rect = element.getBoundingClientRect();
38
+ if (rect.width > 0 && rect.height > 0) {
39
+ width = rect.width;
40
+ height = rect.height;
41
+ } else if (element instanceof HTMLImageElement && element.naturalWidth > 0 && element.naturalHeight > 0) {
42
+ width = element.naturalWidth;
43
+ height = element.naturalHeight;
44
+ }
45
+ return { src, width, height };
34
46
  }
35
47
  }
36
48
  ],
@@ -1,22 +1,25 @@
1
1
  // src/placeholder/index.ts
2
- import { definePlugin, isInCodeBlock } from "@prosekit/core";
2
+ import { definePlugin, isInCodeBlock, maybeRun } from "@prosekit/core";
3
3
  import { Plugin, PluginKey } from "@prosekit/pm/state";
4
4
  import { Decoration, DecorationSet } from "@prosekit/pm/view";
5
5
  function definePlaceholder(options) {
6
6
  return definePlugin(createPlaceholderPlugin(options));
7
7
  }
8
- function createPlaceholderPlugin(options) {
8
+ function createPlaceholderPlugin({
9
+ placeholder,
10
+ strategy = "block"
11
+ }) {
9
12
  return new Plugin({
10
13
  key: new PluginKey("prosekit-placeholder"),
11
14
  props: {
12
15
  decorations: (state) => {
13
- if (options.strategy === "doc" && !isDocEmpty(state.doc)) {
16
+ if (strategy === "doc" && !isDocEmpty(state.doc)) {
14
17
  return null;
15
18
  }
16
19
  if (isInCodeBlock(state.selection)) {
17
20
  return null;
18
21
  }
19
- const placeholderText = options.placeholder;
22
+ const placeholderText = maybeRun(placeholder, state);
20
23
  const deco = createPlaceholderDecoration(state, placeholderText);
21
24
  if (!deco) {
22
25
  return null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prosekit/extensions",
3
3
  "type": "module",
4
- "version": "0.7.17",
4
+ "version": "0.7.19",
5
5
  "private": false,
6
6
  "author": {
7
7
  "name": "ocavue",
@@ -73,6 +73,11 @@
73
73
  "import": "./dist/prosekit-extensions-enter-rule.js",
74
74
  "default": "./dist/prosekit-extensions-enter-rule.js"
75
75
  },
76
+ "./file": {
77
+ "types": "./dist/prosekit-extensions-file.d.ts",
78
+ "import": "./dist/prosekit-extensions-file.js",
79
+ "default": "./dist/prosekit-extensions-file.js"
80
+ },
76
81
  "./gap-cursor": {
77
82
  "types": "./dist/prosekit-extensions-gap-cursor.d.ts",
78
83
  "import": "./dist/prosekit-extensions-gap-cursor.js",
@@ -214,8 +219,8 @@
214
219
  "prosemirror-highlight": "^0.9.0",
215
220
  "prosemirror-search": "^1.0.0",
216
221
  "prosemirror-tables": "^1.5.0",
217
- "shiki": "^1.16.2",
218
- "@prosekit/core": "^0.7.11",
222
+ "shiki": "^1.22.0",
223
+ "@prosekit/core": "^0.7.12",
219
224
  "@prosekit/pm": "^0.1.8"
220
225
  },
221
226
  "peerDependencies": {
@@ -239,17 +244,17 @@
239
244
  }
240
245
  },
241
246
  "devDependencies": {
242
- "@vitest/browser": "^2.0.5",
247
+ "@vitest/browser": "^2.1.3",
243
248
  "just-pick": "^4.2.0",
244
249
  "loro-crdt": "^0.16.12",
245
250
  "loro-prosemirror": "^0.0.7",
246
- "tsup": "^8.2.4",
251
+ "tsup": "^8.3.0",
247
252
  "type-fest": "^4.26.1",
248
- "typescript": "^5.5.4",
249
- "vitest": "^2.0.5",
253
+ "typescript": "^5.6.3",
254
+ "vitest": "^2.1.3",
250
255
  "y-prosemirror": "^1.2.12",
251
256
  "y-protocols": "^1.0.6",
252
- "yjs": "^13.6.18",
257
+ "yjs": "^13.6.20",
253
258
  "@prosekit/dev": "0.0.0"
254
259
  },
255
260
  "scripts": {
@@ -286,6 +291,9 @@
286
291
  "enter-rule": [
287
292
  "./dist/prosekit-extensions-enter-rule.d.ts"
288
293
  ],
294
+ "file": [
295
+ "./dist/prosekit-extensions-file.d.ts"
296
+ ],
289
297
  "gap-cursor": [
290
298
  "./dist/prosekit-extensions-gap-cursor.d.ts"
291
299
  ],