@ctzhian/tiptap 1.6.0 → 1.6.2

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.
@@ -4,7 +4,7 @@ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try
4
4
  function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
5
5
  import { Editor, EditorThemeProvider, EditorToolbar, useTiptap } from "./..";
6
6
  import { Box } from '@mui/material';
7
- import React, { useEffect } from 'react';
7
+ import React from 'react';
8
8
  import { AiGenerate2Icon } from "../component/Icons";
9
9
  import "../index.css";
10
10
  var Reader = function Reader() {
@@ -15,17 +15,22 @@ var Reader = function Reader() {
15
15
  console.log(editor.getHTML());
16
16
  editor.commands.setContent(editor.getHTML());
17
17
  },
18
+ onCreate: function onCreate(_ref) {
19
+ var currentEditor = _ref.editor;
20
+ currentEditor.commands.setAiWriting(true);
21
+ },
18
22
  onAiWritingGetSuggestion: function () {
19
- var _onAiWritingGetSuggestion = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(_ref) {
20
- var text;
23
+ var _onAiWritingGetSuggestion = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(_ref2) {
24
+ var prefix, suffix;
21
25
  return _regeneratorRuntime().wrap(function _callee$(_context) {
22
26
  while (1) switch (_context.prev = _context.next) {
23
27
  case 0:
24
- text = _ref.text;
28
+ prefix = _ref2.prefix, suffix = _ref2.suffix;
29
+ console.log('onAiWritingGetSuggestion', prefix, suffix);
25
30
  return _context.abrupt("return", new Promise(function (resolve) {
26
31
  resolve(['this is a default suggestion.', 'we are good.', 'what is your name?', 'how are you?', 'what is your favorite color?', 'what is your favorite food?', 'what is your favorite animal?', 'what is your favorite book?', 'what is your favorite movie?', 'what is your favorite song?', 'what is your favorite artist?', 'what is your favorite band?', 'what is your favorite city?', 'what is your favorite country?', 'what is your favorite sport?'][Math.floor(Math.random() * 10)]);
27
32
  }));
28
- case 2:
33
+ case 3:
29
34
  case "end":
30
35
  return _context.stop();
31
36
  }
@@ -38,12 +43,12 @@ var Reader = function Reader() {
38
43
  }(),
39
44
  // onTocUpdate: handleTocUpdate,
40
45
  onMentionFilter: function () {
41
- var _onMentionFilter = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(_ref2) {
46
+ var _onMentionFilter = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(_ref3) {
42
47
  var query;
43
48
  return _regeneratorRuntime().wrap(function _callee2$(_context2) {
44
49
  while (1) switch (_context2.prev = _context2.next) {
45
50
  case 0:
46
- query = _ref2.query;
51
+ query = _ref3.query;
47
52
  return _context2.abrupt("return", new Promise(function (resolve) {
48
53
  resolve(['Winona Ryder', 'Molly Ringwald', 'Ally Sheedy', 'Debbie Harry', 'Olivia Newton-John', 'Elton John', 'Michael J. Fox', 'Axl Rose', 'Emilio Estevez', 'Ralph Macchio', 'Rob Lowe', 'Jennifer Grey'].filter(function (item) {
49
54
  return item.toLowerCase().startsWith(query.toLowerCase());
@@ -105,11 +110,6 @@ var Reader = function Reader() {
105
110
  content: ""
106
111
  }),
107
112
  editor = _useTiptap.editor;
108
- useEffect(function () {
109
- if (editor) {
110
- editor.commands.setAiWriting(true);
111
- }
112
- }, [editor]);
113
113
  return /*#__PURE__*/React.createElement(EditorThemeProvider, {
114
114
  mode: "light"
115
115
  }, /*#__PURE__*/React.createElement(Box, {
@@ -10,6 +10,11 @@ declare module '@tiptap/core' {
10
10
  setAiWriting: (enabled: boolean) => ReturnType;
11
11
  };
12
12
  }
13
+ interface Storage {
14
+ aiWriting: {
15
+ enabled: boolean;
16
+ };
17
+ }
13
18
  }
14
19
  type SuggestionState = {
15
20
  enabled: boolean;
@@ -17,10 +22,12 @@ type SuggestionState = {
17
22
  text: string;
18
23
  decorations: DecorationSet;
19
24
  lastDocText: string;
25
+ lastTriggerPos: number | null;
20
26
  };
21
27
  export declare const AiWritingExtension: (props: {
22
- onAiWritingGetSuggestion?: (({ text }: {
23
- text: string;
28
+ onAiWritingGetSuggestion?: (({ prefix, suffix }: {
29
+ prefix: string;
30
+ suffix: string;
24
31
  }) => Promise<string>) | undefined;
25
32
  }) => Extension<AiWritingOptions, any>;
26
33
  export type { SuggestionState };
@@ -34,16 +34,11 @@ function isAtEndWithNoContentAfter(view) {
34
34
  var selection = state.selection,
35
35
  doc = state.doc;
36
36
  if (!selection.empty) return false;
37
- // 文档需非空
38
- var fullText = getFullText(view);
39
- if (!fullText || fullText.trim().length === 0) return false;
40
- // 光标后文档范围内无可见字符
41
- var afterText = doc.textBetween(selection.from, doc.content.size, '\n', '\n');
42
- if (afterText && afterText.trim().length > 0) return false;
43
- // 同时在当前文本块末尾(不在块中间)
44
- var $from = selection.$from;
45
- var atEndOfBlock = $from.parentOffset === $from.parent.content.size;
46
- return !!atEndOfBlock;
37
+ // 基于当前行:从光标到下一处换行(或文末)之间是否仅为空白
38
+ var suffixFromCursor = doc.textBetween(selection.from, doc.content.size, '\n', '\n');
39
+ var nextNewlineIndex = suffixFromCursor.indexOf('\n');
40
+ var currentLineAfterCursor = nextNewlineIndex >= 0 ? suffixFromCursor.slice(0, nextNewlineIndex) : suffixFromCursor;
41
+ return currentLineAfterCursor.trim().length === 0;
47
42
  }
48
43
  function createSuggestionWidget(text) {
49
44
  var dom = document.createElement('span');
@@ -56,7 +51,7 @@ export var AiWritingExtension = function AiWritingExtension(props) {
56
51
  name: 'aiWriting',
57
52
  addOptions: function addOptions() {
58
53
  return {
59
- minChars: 1,
54
+ minChars: 0,
60
55
  debounceMs: 1000
61
56
  };
62
57
  },
@@ -71,16 +66,13 @@ export var AiWritingExtension = function AiWritingExtension(props) {
71
66
  setAiWriting: function setAiWriting(enabled) {
72
67
  return function (_ref) {
73
68
  var tr = _ref.tr,
74
- state = _ref.state,
75
69
  dispatch = _ref.dispatch;
76
70
  if (dispatch) {
77
71
  var meta = {
78
72
  type: 'setEnabled',
79
73
  enabled: enabled
80
74
  };
81
- dispatch(tr.setMeta(aiWritingPluginKey, meta))
82
- // 同步到 storage,便于外部判断
83
- ;
75
+ dispatch(tr.setMeta(aiWritingPluginKey, meta));
84
76
  _this.storage.enabled = !!enabled;
85
77
  }
86
78
  return true;
@@ -103,7 +95,21 @@ export var AiWritingExtension = function AiWritingExtension(props) {
103
95
  dispatch = _ref2.dispatch;
104
96
  if (!dispatch) return true;
105
97
  var insertPos = state.selection.from;
106
- tr.insertText(text, insertPos);
98
+ var schema = state.schema;
99
+ var segments = text.split('\n');
100
+ var posCursor = insertPos;
101
+ for (var i = 0; i < segments.length; i++) {
102
+ var segment = segments[i];
103
+ if (segment.length > 0) {
104
+ tr.insertText(segment, posCursor);
105
+ posCursor += segment.length;
106
+ }
107
+ if (i < segments.length - 1) {
108
+ var br = schema.nodes.hardBreak.create();
109
+ tr.insert(posCursor, br);
110
+ posCursor += 1;
111
+ }
112
+ }
107
113
  // 接受后清空建议
108
114
  dispatch(tr.setMeta(aiWritingPluginKey, {
109
115
  type: 'clearSuggestion'
@@ -120,7 +126,7 @@ export var AiWritingExtension = function AiWritingExtension(props) {
120
126
  var debouncedRequest = null;
121
127
  var request = /*#__PURE__*/function () {
122
128
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(view) {
123
- var state, text, suggestion, _yield$props$onAiWrit, _props$onAiWritingGet, tr;
129
+ var state, text, from, suggestion, _yield$props$onAiWrit, _props$onAiWritingGet, _ref4, doc, prefix, suffix, tr;
124
130
  return _regeneratorRuntime().wrap(function _callee$(_context) {
125
131
  while (1) switch (_context.prev = _context.next) {
126
132
  case 0:
@@ -144,57 +150,70 @@ export var AiWritingExtension = function AiWritingExtension(props) {
144
150
  }
145
151
  return _context.abrupt("return");
146
152
  case 8:
147
- if (!(state.lastDocText === text)) {
153
+ if (!(!text || text.trim().length === 0)) {
148
154
  _context.next = 10;
149
155
  break;
150
156
  }
151
157
  return _context.abrupt("return");
152
158
  case 10:
159
+ // 避免重复请求同一内容与同一光标位置
160
+ from = view.state.selection.from;
161
+ if (!(state.lastDocText === text && state.lastTriggerPos === from)) {
162
+ _context.next = 13;
163
+ break;
164
+ }
165
+ return _context.abrupt("return");
166
+ case 13:
153
167
  suggestion = '';
154
- _context.prev = 11;
155
- _context.next = 14;
168
+ _context.prev = 14;
169
+ _ref4 = view.state, doc = _ref4.doc;
170
+ prefix = doc.textBetween(0, from, '\n', '\n');
171
+ suffix = doc.textBetween(from, doc.content.size, '\n', '\n');
172
+ _context.next = 20;
156
173
  return (_props$onAiWritingGet = props.onAiWritingGetSuggestion) === null || _props$onAiWritingGet === void 0 ? void 0 : _props$onAiWritingGet.call(props, {
157
- text: text
174
+ prefix: prefix,
175
+ suffix: suffix
158
176
  });
159
- case 14:
177
+ case 20:
160
178
  _context.t1 = _yield$props$onAiWrit = _context.sent;
161
179
  _context.t0 = _context.t1 !== null;
162
180
  if (!_context.t0) {
163
- _context.next = 18;
181
+ _context.next = 24;
164
182
  break;
165
183
  }
166
184
  _context.t0 = _yield$props$onAiWrit !== void 0;
167
- case 18:
185
+ case 24:
168
186
  if (!_context.t0) {
169
- _context.next = 22;
187
+ _context.next = 28;
170
188
  break;
171
189
  }
172
190
  _context.t2 = _yield$props$onAiWrit;
173
- _context.next = 23;
191
+ _context.next = 29;
174
192
  break;
175
- case 22:
193
+ case 28:
176
194
  _context.t2 = '';
177
- case 23:
195
+ case 29:
178
196
  suggestion = _context.t2;
179
- _context.next = 29;
197
+ _context.next = 35;
180
198
  break;
181
- case 26:
182
- _context.prev = 26;
183
- _context.t3 = _context["catch"](11);
199
+ case 32:
200
+ _context.prev = 32;
201
+ _context.t3 = _context["catch"](14);
184
202
  console.error('getSuggestion error', _context.t3);
185
- case 29:
203
+ case 35:
186
204
  tr = view.state.tr.setMeta(aiWritingPluginKey, {
187
205
  type: 'setSuggestion',
188
206
  text: suggestion,
189
207
  pos: view.state.selection.from,
190
- lastDocText: text
208
+ lastDocText: text,
209
+ lastTriggerPos: from
191
210
  });
192
211
  view.dispatch(tr);
193
- case 31:
212
+ case 37:
194
213
  case "end":
195
214
  return _context.stop();
196
215
  }
197
- }, _callee, null, [[11, 26]]);
216
+ }, _callee, null, [[14, 32]]);
198
217
  }));
199
218
  return function request(_x) {
200
219
  return _ref3.apply(this, arguments);
@@ -217,7 +236,8 @@ export var AiWritingExtension = function AiWritingExtension(props) {
217
236
  pos: null,
218
237
  text: '',
219
238
  decorations: DecorationSet.create(state.doc, []),
220
- lastDocText: ''
239
+ lastDocText: '',
240
+ lastTriggerPos: null
221
241
  };
222
242
  },
223
243
  apply: function apply(tr, pluginState, _old, newState) {
@@ -234,7 +254,8 @@ export var AiWritingExtension = function AiWritingExtension(props) {
234
254
  next = _objectSpread(_objectSpread({}, next), {}, {
235
255
  text: '',
236
256
  pos: null,
237
- decorations: DecorationSet.create(newState.doc, [])
257
+ decorations: DecorationSet.create(newState.doc, []),
258
+ lastTriggerPos: null
238
259
  });
239
260
  }
240
261
  }
@@ -250,7 +271,8 @@ export var AiWritingExtension = function AiWritingExtension(props) {
250
271
  enabled: _enabled,
251
272
  text: _enabled ? next.text : '',
252
273
  pos: _enabled ? next.pos : null,
253
- decorations: cleared
274
+ decorations: cleared,
275
+ lastTriggerPos: _enabled ? next.lastTriggerPos : null
254
276
  });
255
277
  }
256
278
  case 'setSuggestion':
@@ -259,12 +281,14 @@ export var AiWritingExtension = function AiWritingExtension(props) {
259
281
  var text = meta.text || '';
260
282
  var pos = typeof meta.pos === 'number' ? meta.pos : null;
261
283
  var lastDocText = (_meta$lastDocText = meta.lastDocText) !== null && _meta$lastDocText !== void 0 ? _meta$lastDocText : next.lastDocText;
284
+ var lastTriggerPos = typeof meta.lastTriggerPos === 'number' ? meta.lastTriggerPos : next.lastTriggerPos;
262
285
  if (!text || pos == null) {
263
286
  return _objectSpread(_objectSpread({}, next), {}, {
264
287
  text: '',
265
288
  pos: null,
266
289
  decorations: DecorationSet.create(newState.doc, []),
267
- lastDocText: lastDocText
290
+ lastDocText: lastDocText,
291
+ lastTriggerPos: lastTriggerPos
268
292
  });
269
293
  }
270
294
  var deco = Decoration.widget(pos, function () {
@@ -278,7 +302,8 @@ export var AiWritingExtension = function AiWritingExtension(props) {
278
302
  text: text,
279
303
  pos: pos,
280
304
  decorations: decoSet,
281
- lastDocText: lastDocText
305
+ lastDocText: lastDocText,
306
+ lastTriggerPos: lastTriggerPos
282
307
  });
283
308
  }
284
309
  case 'clearSuggestion':
@@ -286,7 +311,8 @@ export var AiWritingExtension = function AiWritingExtension(props) {
286
311
  return _objectSpread(_objectSpread({}, next), {}, {
287
312
  text: '',
288
313
  pos: null,
289
- decorations: DecorationSet.create(newState.doc, [])
314
+ decorations: DecorationSet.create(newState.doc, []),
315
+ lastTriggerPos: null
290
316
  });
291
317
  }
292
318
  }
@@ -307,6 +333,13 @@ export var AiWritingExtension = function AiWritingExtension(props) {
307
333
  var handler = function handler() {
308
334
  var state = aiWritingPluginKey.getState(view.state);
309
335
  if (!state || !state.enabled) return;
336
+ var currentPos = view.state.selection.from;
337
+ // 若光标位置与建议位置不同且有建议,先清空(视为拒绝)
338
+ if (state.text && state.pos != null && state.pos !== currentPos) {
339
+ view.dispatch(view.state.tr.setMeta(aiWritingPluginKey, {
340
+ type: 'clearSuggestion'
341
+ }));
342
+ }
310
343
  if (!isAtEndWithNoContentAfter(view)) return;
311
344
  ensureDebounced()(view);
312
345
  };
@@ -1,4 +1,4 @@
1
1
  import { UploadFunction } from "../../type";
2
2
  export declare const FileHandlerExtension: (props: {
3
3
  onUpload?: UploadFunction;
4
- }) => import("@tiptap/core").Extension<Omit<import("@tiptap/extension-file-handler").FileHandlePluginOptions, "editor" | "key">, any>;
4
+ }) => import("@tiptap/core").Extension<Omit<import("@tiptap/extension-file-handler").FileHandlePluginOptions, "key" | "editor">, any>;
@@ -70,8 +70,9 @@ export type EditorFnProps = {
70
70
  onError?: (error: Error) => void;
71
71
  onUpload?: UploadFunction;
72
72
  onTocUpdate?: (toc: TocList) => void;
73
- onAiWritingGetSuggestion?: ({ text }: {
74
- text: string;
73
+ onAiWritingGetSuggestion?: ({ prefix, suffix }: {
74
+ prefix: string;
75
+ suffix: string;
75
76
  }) => Promise<string>;
76
77
  };
77
78
  export type MentionItems = string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctzhian/tiptap",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "基于 Tiptap 二次开发的编辑器组件",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",