@ctzhian/tiptap 1.12.21 → 1.12.23

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,22 +4,165 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
4
4
  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; }
5
5
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
6
6
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
7
+ function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
8
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
9
+ function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
10
+ function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
7
11
  function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
8
12
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
9
13
  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); }
10
14
  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; }
11
15
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
12
16
  function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
13
- import { Paper } from '@mui/material';
17
+ import { Box, InputAdornment, Paper, Tab, Tabs, TextField, useTheme } from '@mui/material';
14
18
  import Grid from '@mui/material/Grid';
15
- import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
19
+ import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
20
+ // 每行显示的 emoji 数量
21
+ var EMOJIS_PER_ROW = 8;
22
+
23
+ // 分类定义:中文显示名 -> 英文搜索关键词数组
24
+ var EMOJI_CATEGORIES = [{
25
+ label: '表情',
26
+ keywords: ['face', 'smile', 'grinning', 'laugh', 'cry', 'angry', 'sad', 'happy', 'wink', 'kiss', 'love', 'joy']
27
+ }, {
28
+ label: '手势',
29
+ keywords: ['hand', 'thumbs', 'finger', 'wave', 'clap', 'fist', 'victory', 'peace', 'ok']
30
+ }, {
31
+ label: '人物',
32
+ keywords: ['person', 'man', 'woman', 'baby', 'boy', 'girl', 'family', 'people', 'adult', 'child', 'prince', 'princess', 'superhero', 'angel', 'santa']
33
+ }, {
34
+ label: '动物',
35
+ keywords: ['cat', 'dog', 'bird', 'fish', 'bear', 'lion', 'tiger', 'panda', 'monkey', 'pig', 'cow', 'horse', 'mouse', 'rabbit', 'fox', 'elephant', 'dolphin', 'whale', 'butterfly', 'bug', 'bee', 'spider', 'snake', 'turtle']
36
+ }, {
37
+ label: '食物',
38
+ keywords: ['food', 'apple', 'banana', 'pizza', 'cake', 'coffee', 'tea', 'beer', 'wine', 'bread', 'meat', 'egg', 'rice', 'sushi', 'ice-cream', 'cookie', 'chocolate', 'candy']
39
+ }, {
40
+ label: '活动',
41
+ keywords: ['sport', 'ball', 'game', 'music', 'dance', 'party', 'fireworks', 'soccer', 'basketball', 'football', 'tennis', 'video-game']
42
+ }, {
43
+ label: '旅行',
44
+ keywords: ['car', 'bus', 'train', 'plane', 'ship', 'bike', 'airplane', 'helicopter', 'rocket', 'luggage', 'hotel', 'beach']
45
+ }, {
46
+ label: '物品',
47
+ keywords: ['phone', 'computer', 'book', 'gift', 'money', 'key', 'lock', 'light', 'camera', 'watch', 'clock', 'scissors']
48
+ }, {
49
+ label: '符号',
50
+ keywords: ['heart', 'arrow', 'check', 'cross', 'question', 'exclamation', 'plus', 'minus', 'star', 'circle', 'square', 'flag']
51
+ }, {
52
+ label: '自然',
53
+ keywords: ['sun', 'moon', 'star', 'cloud', 'rain', 'snow', 'tree', 'flower', 'leaf', 'fire', 'water', 'mountain']
54
+ }];
16
55
  export var EmojiList = /*#__PURE__*/forwardRef(function (props, ref) {
56
+ var theme = useTheme();
17
57
  var _useState = useState(0),
18
58
  _useState2 = _slicedToArray(_useState, 2),
19
59
  selectedIndex = _useState2[0],
20
60
  setSelectedIndex = _useState2[1];
61
+ var _useState3 = useState(''),
62
+ _useState4 = _slicedToArray(_useState3, 2),
63
+ searchQuery = _useState4[0],
64
+ setSearchQuery = _useState4[1];
65
+ var _useState5 = useState(0),
66
+ _useState6 = _slicedToArray(_useState5, 2),
67
+ activeTab = _useState6[0],
68
+ setActiveTab = _useState6[1];
69
+
70
+ // 使用传入的 query 或内部的 searchQuery
71
+ var effectiveQuery = (props.query || searchQuery).trim();
72
+ var hasQuery = effectiveQuery.length > 0;
73
+
74
+ // 根据搜索查询和分类过滤 emoji
75
+ var filteredItems = useMemo(function () {
76
+ var _props$editor;
77
+ if (!((_props$editor = props.editor) !== null && _props$editor !== void 0 && (_props$editor = _props$editor.storage) !== null && _props$editor !== void 0 && (_props$editor = _props$editor.emoji) !== null && _props$editor !== void 0 && _props$editor.emojis)) {
78
+ return props.items;
79
+ }
80
+ var allEmojis = props.editor.storage.emoji.emojis;
81
+ var currentCategory = EMOJI_CATEGORIES[activeTab];
82
+
83
+ // 确定搜索查询:优先使用传入的 query 或内部的 searchQuery,否则使用当前分类的关键词
84
+ var query = '';
85
+ if (hasQuery) {
86
+ // 有搜索查询,使用搜索查询
87
+ query = effectiveQuery;
88
+ } else if (currentCategory.keywords.length > 0) {
89
+ // 没有搜索查询,使用当前分类的关键词
90
+ query = currentCategory.keywords.join(' ');
91
+ }
92
+ if (query) {
93
+ var normalizedQuery = query.toLowerCase().trim();
94
+ var keywords = normalizedQuery.split(' ').filter(function (k) {
95
+ return k.length > 0;
96
+ });
97
+
98
+ // 使用和 emoji.ts 相同的搜索逻辑:优先匹配开头,其次匹配包含
99
+ return allEmojis.map(function (item) {
100
+ var _item$shortcodes = item.shortcodes,
101
+ shortcodes = _item$shortcodes === void 0 ? [] : _item$shortcodes,
102
+ _item$tags = item.tags,
103
+ tags = _item$tags === void 0 ? [] : _item$tags;
104
+ var score = 0;
105
+
106
+ // 检查 shortcodes 和 tags
107
+ var allMatches = [].concat(_toConsumableArray(shortcodes.map(function (s) {
108
+ return s.toLowerCase();
109
+ })), _toConsumableArray(tags.map(function (t) {
110
+ return t.toLowerCase();
111
+ })));
112
+
113
+ // 如果有关键词数组,检查是否匹配任一关键词
114
+ if (keywords.length > 0) {
115
+ keywords.forEach(function (keyword) {
116
+ var exactStart = allMatches.some(function (m) {
117
+ return m === keyword;
118
+ });
119
+ var startsWith = allMatches.some(function (m) {
120
+ return m.startsWith(keyword);
121
+ });
122
+ var includes = allMatches.some(function (m) {
123
+ return m.includes(keyword);
124
+ });
125
+ if (exactStart) score += 3;else if (startsWith) score += 2;else if (includes) score += 1;
126
+ });
127
+ } else {
128
+ // 单个查询的匹配逻辑
129
+ var exactStart = allMatches.some(function (m) {
130
+ return m === normalizedQuery;
131
+ });
132
+ var startsWith = allMatches.some(function (m) {
133
+ return m.startsWith(normalizedQuery);
134
+ });
135
+ var includes = allMatches.some(function (m) {
136
+ return m.includes(normalizedQuery);
137
+ });
138
+ if (exactStart) score = 3;else if (startsWith) score = 2;else if (includes) score = 1;
139
+ }
140
+ return {
141
+ item: item,
142
+ score: score
143
+ };
144
+ }).filter(function (_ref) {
145
+ var score = _ref.score;
146
+ return score > 0;
147
+ }).sort(function (a, b) {
148
+ return b.score - a.score;
149
+ }) // 按分数排序
150
+ .map(function (_ref2) {
151
+ var item = _ref2.item;
152
+ return item;
153
+ }).slice(0, 160); // 限制结果数量
154
+ }
155
+
156
+ // 没有搜索查询且没有分类关键词时,使用传入的 items
157
+ return props.items;
158
+ }, [effectiveQuery, hasQuery, props.items, props.editor, activeTab]);
159
+
160
+ // 扁平化所有 emoji 用于索引
161
+ var flatItems = useMemo(function () {
162
+ return filteredItems;
163
+ }, [filteredItems]);
21
164
  var selectItem = function selectItem(index) {
22
- var item = props.items[index];
165
+ var item = flatItems[index];
23
166
  if (item) {
24
167
  props.command({
25
168
  name: item.name
@@ -27,17 +170,49 @@ export var EmojiList = /*#__PURE__*/forwardRef(function (props, ref) {
27
170
  }
28
171
  };
29
172
  var upHandler = function upHandler() {
30
- setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length);
173
+ setSelectedIndex((selectedIndex + flatItems.length - 1) % flatItems.length);
31
174
  };
32
175
  var downHandler = function downHandler() {
33
- setSelectedIndex((selectedIndex + 1) % props.items.length);
176
+ setSelectedIndex((selectedIndex + 1) % flatItems.length);
177
+ };
178
+ var leftHandler = function leftHandler() {
179
+ var newIndex = Math.max(0, selectedIndex - 1);
180
+ setSelectedIndex(newIndex);
181
+ };
182
+ var rightHandler = function rightHandler() {
183
+ var newIndex = Math.min(flatItems.length - 1, selectedIndex + 1);
184
+ setSelectedIndex(newIndex);
34
185
  };
35
186
  var enterHandler = function enterHandler() {
36
187
  selectItem(selectedIndex);
37
188
  };
189
+ var handleTabChange = function handleTabChange(_event, newValue) {
190
+ setActiveTab(newValue);
191
+ // 点击分类 tab 时,清空搜索框(分类会通过 activeTab 自动搜索)
192
+ setSearchQuery('');
193
+ };
194
+
195
+ // 当 query 变化时,同步到内部的 searchQuery(用于搜索框显示)
38
196
  useEffect(function () {
39
- return setSelectedIndex(0);
40
- }, [props.items]);
197
+ if (props.query !== undefined) {
198
+ setSearchQuery(props.query);
199
+ }
200
+ }, [props.query]);
201
+ useEffect(function () {
202
+ setSelectedIndex(0);
203
+ }, [flatItems.length, searchQuery, activeTab, props.query]);
204
+
205
+ // 滚动到选中的 emoji
206
+ useEffect(function () {
207
+ if (flatItems.length === 0) return;
208
+ var selectedElement = document.querySelector("[data-emoji-index=\"".concat(selectedIndex, "\"]"));
209
+ if (selectedElement) {
210
+ selectedElement.scrollIntoView({
211
+ behavior: 'smooth',
212
+ block: 'nearest'
213
+ });
214
+ }
215
+ }, [selectedIndex, flatItems.length]);
41
216
  useImperativeHandle(ref, function () {
42
217
  return {
43
218
  onKeyDown: function onKeyDown(x) {
@@ -49,6 +224,14 @@ export var EmojiList = /*#__PURE__*/forwardRef(function (props, ref) {
49
224
  downHandler();
50
225
  return true;
51
226
  }
227
+ if (x.event.key === 'ArrowLeft') {
228
+ leftHandler();
229
+ return true;
230
+ }
231
+ if (x.event.key === 'ArrowRight') {
232
+ rightHandler();
233
+ return true;
234
+ }
52
235
  if (x.event.key === 'Enter') {
53
236
  enterHandler();
54
237
  return true;
@@ -56,49 +239,214 @@ export var EmojiList = /*#__PURE__*/forwardRef(function (props, ref) {
56
239
  return false;
57
240
  }
58
241
  };
59
- }, [upHandler, downHandler, enterHandler]);
242
+ }, [upHandler, downHandler, leftHandler, rightHandler, enterHandler]);
60
243
  return /*#__PURE__*/React.createElement(Paper, {
244
+ elevation: 8,
61
245
  sx: {
62
246
  position: 'relative',
247
+ display: 'flex',
248
+ flexDirection: 'column',
249
+ borderRadius: 2,
250
+ width: 280,
251
+ maxHeight: 320,
252
+ boxShadow: '0 4px 20px rgba(0, 0, 0, 0.15)',
253
+ overflow: 'hidden'
254
+ }
255
+ }, !hasQuery && /*#__PURE__*/React.createElement(Box, {
256
+ sx: {
257
+ p: 1,
258
+ borderBottom: '1px solid',
259
+ borderColor: 'divider'
260
+ }
261
+ }, /*#__PURE__*/React.createElement(TextField, {
262
+ fullWidth: true,
263
+ size: "small",
264
+ placeholder: "\u641C\u7D22 emoji...",
265
+ value: searchQuery,
266
+ onChange: function onChange(e) {
267
+ setSearchQuery(e.target.value);
268
+ },
269
+ autoFocus: false,
270
+ InputProps: {
271
+ startAdornment: /*#__PURE__*/React.createElement(InputAdornment, {
272
+ position: "start"
273
+ }, /*#__PURE__*/React.createElement(Box, {
274
+ sx: {
275
+ fontSize: '0.875rem'
276
+ }
277
+ }, "\uD83D\uDD0D"))
278
+ },
279
+ sx: {
280
+ '& .MuiOutlinedInput-root': {
281
+ borderRadius: 1,
282
+ backgroundColor: 'action.hover',
283
+ fontSize: '0.875rem',
284
+ '& fieldset': {
285
+ borderColor: 'divider'
286
+ },
287
+ '&:hover fieldset': {
288
+ borderColor: 'primary.main'
289
+ },
290
+ '&.Mui-focused fieldset': {
291
+ borderColor: 'primary.main'
292
+ }
293
+ }
294
+ }
295
+ })), !hasQuery && /*#__PURE__*/React.createElement(Box, {
296
+ sx: {
297
+ borderBottom: 1,
298
+ borderColor: 'divider',
299
+ backgroundColor: 'background.paper',
300
+ position: 'sticky',
301
+ top: 0,
302
+ zIndex: 1
303
+ }
304
+ }, /*#__PURE__*/React.createElement(Tabs, {
305
+ value: activeTab,
306
+ onChange: handleTabChange,
307
+ variant: "scrollable",
308
+ scrollButtons: "auto",
309
+ allowScrollButtonsMobile: true,
310
+ sx: {
311
+ minHeight: 32,
312
+ height: 32,
313
+ '& .MuiTabs-scrollButtons': {
314
+ width: 28,
315
+ '&.Mui-disabled': {
316
+ opacity: 0.3
317
+ }
318
+ },
319
+ '& .MuiTab-root': {
320
+ minHeight: 32,
321
+ height: 32,
322
+ fontSize: '0.75rem',
323
+ textTransform: 'none',
324
+ fontWeight: 500,
325
+ px: 1,
326
+ py: 0,
327
+ minWidth: 'auto',
328
+ color: 'text.secondary',
329
+ transition: 'all 0.2s ease-in-out',
330
+ '&:hover': {
331
+ color: 'primary.main',
332
+ backgroundColor: 'action.hover'
333
+ },
334
+ '&.Mui-selected': {
335
+ color: 'primary.main',
336
+ fontWeight: 600
337
+ }
338
+ },
339
+ '& .MuiTabs-indicator': {
340
+ height: 2,
341
+ borderRadius: '2px 2px 0 0',
342
+ backgroundColor: "".concat(theme.palette.primary.main, " !important")
343
+ }
344
+ }
345
+ }, EMOJI_CATEGORIES.map(function (category) {
346
+ return /*#__PURE__*/React.createElement(Tab, {
347
+ key: category.label,
348
+ label: category.label
349
+ });
350
+ }))), /*#__PURE__*/React.createElement(Box, {
351
+ sx: {
352
+ flex: 1,
63
353
  overflowY: 'auto',
354
+ overflowX: 'hidden',
64
355
  p: 1,
65
- borderRadius: 'var(--mui-shape-borderRadius)',
66
- maxWidth: 300,
67
- maxHeight: 300
356
+ '&::-webkit-scrollbar': {
357
+ width: '4px'
358
+ },
359
+ '&::-webkit-scrollbar-track': {
360
+ background: 'transparent'
361
+ },
362
+ '&::-webkit-scrollbar-thumb': {
363
+ background: 'rgba(0, 0, 0, 0.2)',
364
+ borderRadius: '2px',
365
+ '&:hover': {
366
+ background: 'rgba(0, 0, 0, 0.3)'
367
+ }
368
+ }
369
+ }
370
+ }, flatItems.length === 0 ? /*#__PURE__*/React.createElement(Box, {
371
+ sx: {
372
+ display: 'flex',
373
+ flexDirection: 'column',
374
+ alignItems: 'center',
375
+ justifyContent: 'center',
376
+ py: 4,
377
+ color: 'text.secondary',
378
+ width: '100%'
379
+ }
380
+ }, /*#__PURE__*/React.createElement(Box, {
381
+ sx: {
382
+ fontSize: '3rem',
383
+ mb: 1
68
384
  }
69
- }, /*#__PURE__*/React.createElement(Grid, {
385
+ }, "\uD83D\uDE15"), /*#__PURE__*/React.createElement(Box, {
386
+ sx: {
387
+ fontSize: '0.875rem'
388
+ }
389
+ }, "\u672A\u627E\u5230\u5339\u914D\u7684 emoji")) : /*#__PURE__*/React.createElement(Grid, {
70
390
  container: true,
71
- spacing: 1
72
- }, props.items.map(function (item, index) {
391
+ spacing: 0.5
392
+ }, flatItems.map(function (item, index) {
393
+ var isSelected = index === selectedIndex;
73
394
  return /*#__PURE__*/React.createElement(Grid, {
74
- size: 1,
75
- key: index,
395
+ size: 12 / EMOJIS_PER_ROW,
396
+ key: "".concat(item.name, "-").concat(index),
397
+ "data-emoji-index": index,
76
398
  onClick: function onClick() {
77
399
  return selectItem(index);
78
400
  },
79
401
  sx: _objectSpread({
80
402
  cursor: 'pointer',
81
- lineHeight: 1,
82
- fontSize: '1rem',
83
- width: '2rem',
84
- height: '2rem',
85
403
  display: 'flex',
86
404
  justifyContent: 'center',
87
405
  alignItems: 'center',
88
- borderRadius: 'var(--mui-shape-borderRadius)',
89
- img: {
90
- width: '1.25rem',
91
- height: '1.25rem'
92
- },
93
- transition: 'background-color 0.2s ease-in-out',
406
+ width: "".concat(100 / EMOJIS_PER_ROW, "%"),
407
+ aspectRatio: '1',
408
+ minWidth: 0,
409
+ borderRadius: 1,
410
+ transition: 'all 0.15s ease-in-out',
411
+ position: 'relative',
94
412
  '&:hover': {
95
- backgroundColor: 'action.hover'
413
+ backgroundColor: 'action.hover',
414
+ transform: 'scale(1.1)',
415
+ zIndex: 1
416
+ }
417
+ }, isSelected && {
418
+ backgroundColor: 'action.selected',
419
+ transform: 'scale(1.15)',
420
+ zIndex: 2,
421
+ boxShadow: '0 2px 6px rgba(0, 0, 0, 0.2)',
422
+ '&::after': {
423
+ content: '""',
424
+ position: 'absolute',
425
+ inset: -1,
426
+ borderRadius: 1,
427
+ border: '1.5px solid',
428
+ borderColor: 'primary.main',
429
+ pointerEvents: 'none'
96
430
  }
97
- }, index === selectedIndex && {
98
- backgroundColor: 'action.selected'
99
431
  })
432
+ }, /*#__PURE__*/React.createElement(Box, {
433
+ sx: {
434
+ fontSize: '1.25rem',
435
+ lineHeight: 1,
436
+ display: 'flex',
437
+ alignItems: 'center',
438
+ justifyContent: 'center',
439
+ width: '100%',
440
+ height: '100%',
441
+ img: {
442
+ width: '1.25rem',
443
+ height: '1.25rem',
444
+ objectFit: 'contain'
445
+ }
446
+ }
100
447
  }, item.fallbackImage ? /*#__PURE__*/React.createElement("img", {
101
- src: item.fallbackImage
102
- }) : item.emoji);
103
- })));
448
+ src: item.fallbackImage,
449
+ alt: item.name
450
+ }) : item.emoji));
451
+ }))));
104
452
  });