@ctzhian/tiptap 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,15 @@
1
1
  import { mergeAttributes, Node } from '@tiptap/core';
2
- import { TextSelection } from '@tiptap/pm/state';
3
2
  import { ReactNodeViewRenderer } from '@tiptap/react';
4
3
  import AlertView from "../component/Alert";
5
4
  export var AlertExtension = Node.create({
6
5
  name: 'alert',
7
6
  group: 'block',
8
- content: 'inline*',
7
+ content: 'block+',
9
8
  defining: true,
10
9
  draggable: true,
11
10
  addOptions: function addOptions() {
12
11
  return {
13
- HTMLAttributes: {
14
- class: 'cq-alert'
15
- }
12
+ HTMLAttributes: {}
16
13
  };
17
14
  },
18
15
  addAttributes: function addAttributes() {
@@ -54,8 +51,6 @@ export var AlertExtension = Node.create({
54
51
  },
55
52
  parseHTML: function parseHTML() {
56
53
  return [{
57
- tag: 'div.cq-alert'
58
- }, {
59
54
  tag: 'div[data-node="alert"]'
60
55
  }];
61
56
  },
@@ -71,50 +66,15 @@ export var AlertExtension = Node.create({
71
66
  setAlert: function setAlert() {
72
67
  var attrs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
73
68
  return function (_ref2) {
74
- var chain = _ref2.chain;
69
+ var commands = _ref2.commands;
75
70
  var id = "alert_".concat(Math.random().toString(36).slice(2));
76
71
  var variant = attrs.variant || 'info';
77
72
  var type = attrs.type || 'icon';
78
- var ok = chain().focus().insertContent({
79
- type: _this.name,
80
- attrs: {
81
- id: id,
82
- variant: variant,
83
- type: type
84
- }
85
- }).run();
86
- if (!ok) {
87
- // 尝试在当前块之后插入
88
- var state = _this.editor.state;
89
- var $from = state.selection.$from;
90
- var afterPos = $from.after($from.depth);
91
- var fallback = _this.editor.commands.insertContentAt(afterPos, {
92
- type: _this.name,
93
- attrs: {
94
- id: id,
95
- variant: variant,
96
- type: type
97
- }
98
- });
99
- if (!fallback) return false;
100
- }
101
-
102
- // 将光标移动到刚插入的 Alert 内部
103
- try {
104
- var doc = _this.editor.state.doc;
105
- var posInside = null;
106
- doc.descendants(function (node, pos) {
107
- if (node.type.name === _this.name && node.attrs.id === id) {
108
- posInside = pos + 1;
109
- return false;
110
- }
111
- return true;
112
- });
113
- if (posInside != null) {
114
- _this.editor.commands.setTextSelection(posInside);
115
- }
116
- } catch (_unused) {}
117
- return true;
73
+ return commands.wrapIn(_this.name, {
74
+ id: id,
75
+ variant: variant,
76
+ type: type
77
+ });
118
78
  };
119
79
  },
120
80
  setAlertVariant: function setAlertVariant(variant) {
@@ -136,83 +96,19 @@ export var AlertExtension = Node.create({
136
96
  toggleAlert: function toggleAlert() {
137
97
  var attrs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
138
98
  return function (_ref5) {
139
- var state = _ref5.state,
140
- dispatch = _ref5.dispatch,
141
- editor = _ref5.editor;
142
- var tr = state.tr,
143
- selection = state.selection,
144
- schema = state.schema;
145
- var $from = selection.$from;
146
- var currentNode = $from.node($from.depth);
147
- var posStart = $from.before($from.depth);
148
- var posEnd = posStart + currentNode.nodeSize;
99
+ var commands = _ref5.commands;
100
+ var id = "alert_".concat(Math.random().toString(36).slice(2));
149
101
  var variant = attrs.variant || 'info';
150
102
  var type = attrs.type || 'icon';
151
-
152
- // 已是 Alert -> 还原为段落
153
- if (currentNode.type === _this.type) {
154
- var paragraph = schema.nodes.paragraph.create(undefined, currentNode.content);
155
- tr.replaceWith(posStart, posEnd, paragraph);
156
- tr.setSelection(TextSelection.near(tr.doc.resolve(posStart + 1)));
157
- if (dispatch) dispatch(tr);
158
- editor.view.focus();
159
- return true;
160
- }
161
-
162
- // 将当前块替换为 Alert,尽量保留 inline 内容
163
- var inlineContent = null;
164
- if (currentNode.isTextblock) {
165
- inlineContent = currentNode.content;
166
- } else {
167
- var textContent = currentNode.textContent || '';
168
- inlineContent = textContent ? schema.text(textContent) : undefined;
169
- }
170
- var id = "alert_".concat(Math.random().toString(36).slice(2));
171
- var alertNode = schema.nodes.alert.create({
103
+ return commands.toggleWrap(_this.name, {
172
104
  id: id,
173
105
  variant: variant,
174
106
  type: type
175
- }, inlineContent);
176
- tr.replaceWith(posStart, posEnd, alertNode);
177
- tr.setSelection(TextSelection.near(tr.doc.resolve(posStart + 1)));
178
- if (dispatch) dispatch(tr);
179
- editor.view.focus();
180
- return true;
107
+ });
181
108
  };
182
109
  }
183
110
  };
184
111
  },
185
- addKeyboardShortcuts: function addKeyboardShortcuts() {
186
- var _this2 = this;
187
- return {
188
- Enter: function Enter() {
189
- if (!_this2.editor.isActive(_this2.name)) return false;
190
- // 按回车退出当前 Alert,并在后面新起一段落
191
- return _this2.editor.chain().command(function (_ref6) {
192
- var state = _ref6.state,
193
- tr = _ref6.tr,
194
- dispatch = _ref6.dispatch;
195
- var $from = state.selection.$from;
196
- // 寻找最近的 alert 节点位置
197
- var pos = $from.before();
198
- for (var depth = $from.depth; depth > 0; depth--) {
199
- var _node = $from.node(depth);
200
- if (_node.type === _this2.type) {
201
- pos = $from.before(depth);
202
- break;
203
- }
204
- }
205
- var node = tr.doc.nodeAt(pos);
206
- if (!node || node.type !== _this2.type) return false;
207
- var insertPos = pos + node.nodeSize;
208
- tr.insert(insertPos, _this2.editor.schema.nodes.paragraph.create());
209
- tr.setSelection(TextSelection.near(tr.doc.resolve(insertPos + 1)));
210
- if (dispatch) dispatch(tr);
211
- return true;
212
- }).run();
213
- }
214
- };
215
- },
216
112
  addNodeView: function addNodeView() {
217
113
  return ReactNodeViewRenderer(AlertView);
218
114
  }
@@ -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>;
@@ -1,4 +1,7 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
3
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
4
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
2
5
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
6
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
7
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
@@ -8,7 +11,7 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
8
11
 
9
12
  import { Extension } from '@tiptap/core';
10
13
  import { Table, TableCell, TableHeader, TableRow } from '@tiptap/extension-table';
11
- import { TextSelection } from '@tiptap/pm/state';
14
+ import { Plugin, TextSelection } from '@tiptap/pm/state';
12
15
  import { createTableContextMenuPlugin } from "../component/Table";
13
16
  export var TableExtension = function TableExtension(_ref) {
14
17
  var editable = _ref.editable;
@@ -169,6 +172,81 @@ export var TableExtension = function TableExtension(_ref) {
169
172
  addProseMirrorPlugins: function addProseMirrorPlugins() {
170
173
  return editable ? [createTableContextMenuPlugin(this.editor)] : [];
171
174
  }
175
+ }),
176
+ // Safari 中文输入 deleteCompositionText 修复
177
+ Extension.create({
178
+ name: 'safariCompositionDeleteFix',
179
+ addProseMirrorPlugins: function addProseMirrorPlugins() {
180
+ if (!editable) return [];
181
+ var ZERO_WIDTH_SPACE = "\u200B";
182
+ var isSafari = function () {
183
+ if (typeof navigator === 'undefined') return false;
184
+ var ua = navigator.userAgent;
185
+ var isAppleMobile = /iP(ad|hone|od)/.test(ua);
186
+ var isMacSafari = /Safari\//.test(ua) && !/Chrome\//.test(ua);
187
+ return isAppleMobile || isMacSafari;
188
+ }();
189
+ if (!isSafari) return [];
190
+
191
+ // 注意:这里不能使用上面从 ProseMirror 导入的 Node 类型,需判断 DOM 文本节点
192
+ var isTextNode = function isTextNode(node) {
193
+ return !!node && node.nodeType === 3;
194
+ };
195
+ return [new Plugin({
196
+ props: {
197
+ handleDOMEvents: {
198
+ beforeinput: function beforeinput(_view, event) {
199
+ // 仅处理 Safari 在中文合成结束后触发的删除合成文本行为
200
+ if (event.inputType !== 'deleteCompositionText') {
201
+ return false;
202
+ }
203
+ var selection = window.getSelection();
204
+ if (!selection || selection.rangeCount === 0) return false;
205
+ var range = selection.getRangeAt(0);
206
+ var startContainer = range.startContainer,
207
+ endContainer = range.endContainer,
208
+ startOffset = range.startOffset,
209
+ endOffset = range.endOffset;
210
+ if (isTextNode(startContainer) && startContainer === endContainer && startOffset === 0 && endOffset === startContainer.length) {
211
+ var _startContainer$paren;
212
+ (_startContainer$paren = startContainer.parentElement) === null || _startContainer$paren === void 0 || _startContainer$paren.insertBefore(document.createTextNode(ZERO_WIDTH_SPACE), startContainer);
213
+ }
214
+ // 让 ProseMirror 照常处理
215
+ return false;
216
+ },
217
+ input: function input(_view, event) {
218
+ if (event.inputType !== 'deleteCompositionText') {
219
+ return false;
220
+ }
221
+ var selection = window.getSelection();
222
+ if (!selection || selection.rangeCount === 0) return false;
223
+ var range = selection.getRangeAt(0);
224
+ var node = range.startContainer;
225
+ var parentEl = (node === null || node === void 0 ? void 0 : node.parentElement) || null;
226
+ if (!parentEl) return false;
227
+ var textNodes = Array.from(parentEl.childNodes).filter(isTextNode);
228
+ var _iterator = _createForOfIteratorHelper(textNodes),
229
+ _step;
230
+ try {
231
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
232
+ var textNode = _step.value;
233
+ if (textNode.textContent === ZERO_WIDTH_SPACE) {
234
+ textNode.remove();
235
+ } else if (textNode.textContent && textNode.textContent.includes(ZERO_WIDTH_SPACE)) {
236
+ textNode.textContent = textNode.textContent.split(ZERO_WIDTH_SPACE).join('');
237
+ }
238
+ }
239
+ } catch (err) {
240
+ _iterator.e(err);
241
+ } finally {
242
+ _iterator.f();
243
+ }
244
+ return false;
245
+ }
246
+ }
247
+ }
248
+ })];
249
+ }
172
250
  })];
173
251
  };
174
252
  export default TableExtension;
package/dist/index.css CHANGED
@@ -14,6 +14,10 @@
14
14
  margin-top: 0 !important;
15
15
  }
16
16
 
17
+ .tiptap.ProseMirror :last-child {
18
+ margin-bottom: 0 !important;
19
+ }
20
+
17
21
  .tiptap.ProseMirror a {
18
22
  text-decoration: underline;
19
23
  }
@@ -25,7 +29,8 @@
25
29
  margin-bottom: 20px;
26
30
  }
27
31
 
28
- .tiptap.ProseMirror blockquote p {
32
+ .tiptap.ProseMirror blockquote p,
33
+ .tiptap.ProseMirror .node-alert p {
29
34
  margin: 0;
30
35
  }
31
36
 
@@ -13,8 +13,6 @@ export type LinewiseTarget = {
13
13
  type: 'taskList';
14
14
  } | {
15
15
  type: 'blockquote';
16
- } | {
17
- type: 'codeBlock';
18
16
  } | {
19
17
  type: 'alert';
20
18
  attrs?: {
@@ -124,11 +124,6 @@ export function buildNodeFromLines(editor, lines, target) {
124
124
  });
125
125
  return schema.nodes.blockquote.create(undefined, Fragment.fromArray(paragraphs));
126
126
  }
127
- case 'codeBlock':
128
- {
129
- var text = lines.join('\n');
130
- return schema.nodes.codeBlock.create(undefined, text ? schema.text(text) : undefined);
131
- }
132
127
  case 'alert':
133
128
  {
134
129
  var attrs = target.attrs || {
@@ -154,17 +149,73 @@ export function buildNodeFromLines(editor, lines, target) {
154
149
  }
155
150
  }
156
151
 
152
+ /**
153
+ * 通用替换与派发
154
+ */
155
+ function replaceRange(editor, from, to, content) {
156
+ var tr = editor.state.tr;
157
+ tr.replaceWith(from, to, content);
158
+ editor.view.dispatch(tr);
159
+ editor.view.focus();
160
+ }
161
+ function isContainerNode(node) {
162
+ var name = node.type.name;
163
+ return name === 'blockquote' || name === 'alert';
164
+ }
165
+ function isContainerTarget(target) {
166
+ return target.type === 'blockquote' || target.type === 'alert';
167
+ }
168
+ function createContainerWithContent(editor, target, content) {
169
+ var schema = editor.schema;
170
+ if (target.type === 'blockquote') {
171
+ return schema.nodes.blockquote.create(undefined, content);
172
+ }
173
+ if (target.type === 'alert') {
174
+ var attrs = target.attrs || {
175
+ variant: 'info',
176
+ type: 'icon'
177
+ };
178
+ return schema.nodes.alert.create(attrs, content);
179
+ }
180
+ throw new Error('createContainerWithContent: invalid container target');
181
+ }
182
+
157
183
  /**
158
184
  * 将给定位置的节点转换为目标类型(按行规则)。
159
185
  */
160
186
  export function convertNodeAt(editor, pos, node, target) {
161
- var lines = extractLinesFromNode(node);
162
- var replacement = buildNodeFromLines(editor, lines, target);
163
187
  var from = pos;
164
188
  var to = pos + node.nodeSize;
165
- var tr = editor.state.tr;
166
- // Fragment 或 Node 均可传入 replaceWith
167
- tr.replaceWith(from, to, replacement);
168
- editor.view.dispatch(tr);
169
- editor.view.focus();
189
+ var nodeType = node.type.name;
190
+
191
+ // 规则 1:目标为容器(blockquote/alert)
192
+ if (isContainerTarget(target)) {
193
+ // 再次点击同容器 -> 拆包
194
+ if (target.type === 'blockquote' && nodeType === 'blockquote' || target.type === 'alert' && nodeType === 'alert') {
195
+ replaceRange(editor, from, to, node.content);
196
+ return;
197
+ }
198
+ // 容器互转:用内部内容构造目标容器,避免嵌套
199
+ var content = isContainerNode(node) ? node.content : Fragment.from(node);
200
+ var wrapper = createContainerWithContent(editor, target, content);
201
+ replaceRange(editor, from, to, wrapper);
202
+ return;
203
+ }
204
+
205
+ // 规则 2:当前为容器,且目标为非容器 -> 子节点逐个转换
206
+ if (isContainerNode(node)) {
207
+ var nodesToInsert = [];
208
+ node.forEach(function (child) {
209
+ var built = buildNodeFromLines(editor, extractLinesFromNode(child), target);
210
+ Fragment.from(built).forEach(function (n) {
211
+ nodesToInsert.push(n);
212
+ });
213
+ });
214
+ replaceRange(editor, from, to, Fragment.fromArray(nodesToInsert));
215
+ return;
216
+ }
217
+
218
+ // 其他:按行构建单节点替换
219
+ var replacement = buildNodeFromLines(editor, extractLinesFromNode(node), target);
220
+ replaceRange(editor, from, to, replacement);
170
221
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctzhian/tiptap",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "基于 Tiptap 二次开发的编辑器组件",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -1,7 +0,0 @@
1
- import { Editor } from '@tiptap/react';
2
- import React from 'react';
3
- interface ListHoverProps {
4
- editor: Editor;
5
- }
6
- declare const ListHover: ({ editor }: ListHoverProps) => React.JSX.Element | null;
7
- export default ListHover;