@dialpad/dialtone-vue 3.122.0 → 3.123.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.
Files changed (33) hide show
  1. package/dist/common/utils.cjs +8 -1
  2. package/dist/common/utils.cjs.map +1 -1
  3. package/dist/common/utils.js +8 -1
  4. package/dist/common/utils.js.map +1 -1
  5. package/dist/lib/message-input.cjs +46 -40
  6. package/dist/lib/message-input.cjs.map +1 -1
  7. package/dist/lib/message-input.js +47 -41
  8. package/dist/lib/message-input.js.map +1 -1
  9. package/dist/lib/rich-text-editor.cjs +173 -11
  10. package/dist/lib/rich-text-editor.cjs.map +1 -1
  11. package/dist/lib/rich-text-editor.js +174 -12
  12. package/dist/lib/rich-text-editor.js.map +1 -1
  13. package/dist/lib/tooltip.cjs +5 -4
  14. package/dist/lib/tooltip.cjs.map +1 -1
  15. package/dist/lib/tooltip.js +5 -4
  16. package/dist/lib/tooltip.js.map +1 -1
  17. package/dist/types/common/utils/index.d.ts.map +1 -1
  18. package/dist/types/components/datepicker/datepicker.vue.d.ts +1 -1
  19. package/dist/types/components/emoji_picker/emoji_picker.vue.d.ts +1 -1
  20. package/dist/types/components/emoji_picker/modules/emoji_selector.vue.d.ts +1 -1
  21. package/dist/types/components/emoji_picker/modules/emoji_skin_selector.vue.d.ts +1 -1
  22. package/dist/types/components/emoji_picker/modules/emoji_tabset.vue.d.ts +1 -1
  23. package/dist/types/components/hovercard/hovercard.vue.d.ts +1 -1
  24. package/dist/types/components/rich_text_editor/extensions/channels/channel.d.ts +2 -1
  25. package/dist/types/components/rich_text_editor/extensions/channels/channel.d.ts.map +1 -1
  26. package/dist/types/components/rich_text_editor/extensions/emoji/emoji.d.ts +1 -0
  27. package/dist/types/components/rich_text_editor/extensions/emoji/emoji.d.ts.map +1 -1
  28. package/dist/types/components/rich_text_editor/extensions/mentions/mention.d.ts +2 -1
  29. package/dist/types/components/rich_text_editor/extensions/mentions/mention.d.ts.map +1 -1
  30. package/dist/types/components/rich_text_editor/rich_text_editor.vue.d.ts +12 -1
  31. package/dist/types/components/rich_text_editor/rich_text_editor.vue.d.ts.map +1 -1
  32. package/dist/types/recipes/conversation_view/message_input/message_input.vue.d.ts +3 -2
  33. package/package.json +24 -24
@@ -19,7 +19,7 @@ import { Node, mergeAttributes, nodeInputRule, nodePasteRule, getMarksBetween, c
19
19
  import { resolveComponent, openBlock, createBlock, withCtx, createVNode, createElementBlock, withDirectives, createElementVNode, Fragment, renderList, normalizeClass, withModifiers, resolveDynamicComponent, vShow, createTextVNode, toDisplayString, markRaw } from "vue";
20
20
  import { _export_sfc } from "../chunks/_plugin-vue_export-helper-caHeSgYY.js";
21
21
  import { DtEmoji } from "./emoji.js";
22
- import { shortcodeToEmojiData, codeToEmojiData } from "../common/emoji.js";
22
+ import { codeToEmojiData } from "../common/emoji.js";
23
23
  import { PluginKey, Plugin } from "@tiptap/pm/state";
24
24
  import Suggestion from "@tiptap/suggestion";
25
25
  import { emojisIndexed } from "@dialpad/dialtone-emojis";
@@ -32,10 +32,10 @@ import { DtLink } from "./link.js";
32
32
  import { DtAvatar } from "./avatar.js";
33
33
  import DtIconHash from "@dialpad/dialtone-icons/vue3/hash";
34
34
  import DtIconLock from "@dialpad/dialtone-icons/vue3/lock";
35
+ import emojiRegex from "emoji-regex";
35
36
  import "./skeleton.js";
36
37
  import "../chunks/icon_constants-Dy4MEUJL.js";
37
38
  import "@dialpad/dialtone-icons/icons.json";
38
- import "emoji-regex";
39
39
  import "../chunks/list_item_constants-u1xcN9Dd.js";
40
40
  import "./item-layout.js";
41
41
  import "./icon.js";
@@ -305,7 +305,7 @@ const suggestionOptions = {
305
305
  };
306
306
  const EmojiPluginKey = new PluginKey("emoji");
307
307
  const inputShortCodeRegex = /:\w+:$/;
308
- const pasteShortCodeRegex = /:\w+:/g;
308
+ const emojiShortCodeRegex = /:\w+:/g;
309
309
  const inputUnicodeRegex = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])$/;
310
310
  const pasteUnicodeRegex = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g;
311
311
  const inputRuleMatch = (match) => {
@@ -318,7 +318,7 @@ const inputRuleMatch = (match) => {
318
318
  }
319
319
  };
320
320
  const shortCodePasteMatch = (text) => {
321
- const matches = [...text.matchAll(pasteShortCodeRegex)];
321
+ const matches = [...text.matchAll(emojiShortCodeRegex)];
322
322
  return matches.filter((match) => codeToEmojiData(match[0])).map((match) => ({
323
323
  index: match.index,
324
324
  text: match[0],
@@ -387,7 +387,7 @@ const Emoji = Node.create({
387
387
  },
388
388
  type: this.type,
389
389
  getAttributes(attrs) {
390
- const unicode = shortcodeToEmojiData(attrs[0]).unicode_output;
390
+ const unicode = codeToEmojiData(attrs[0]).unicode_output;
391
391
  const emoji = String.fromCodePoint(parseInt(unicode, 16));
392
392
  return {
393
393
  code: emoji,
@@ -565,7 +565,7 @@ function autolink(options) {
565
565
  });
566
566
  }
567
567
  const defaultAttributes = {
568
- class: "d-link d-c-text d-d-inline-block",
568
+ class: "d-link d-c-text d-d-inline-block d-wb-break-all",
569
569
  rel: "noopener noreferrer nofollow"
570
570
  };
571
571
  const Link = Mark.create({
@@ -619,6 +619,30 @@ function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
619
619
  });
620
620
  }
621
621
  const MentionComponent = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["render", _sfc_render$4]]);
622
+ const mentionRegex = /@([\w.-]+)[^\w.-]?/g;
623
+ const mentionPasteMatch = (text, suggestions) => {
624
+ const matches = [...text.matchAll(mentionRegex)];
625
+ return matches.filter((match) => suggestions.some(({ id }) => id === match[1].trim())).map((match) => {
626
+ let mention = match[1];
627
+ if (!mention.endsWith(" "))
628
+ mention += " ";
629
+ return {
630
+ index: match.index,
631
+ text: mention,
632
+ match
633
+ };
634
+ });
635
+ };
636
+ const mentionInputMatch = (text, suggestions) => {
637
+ const match = text.match(/@([\w.-]+)[^\w.-]$/);
638
+ if (!match || !suggestions.some(({ id }) => id === match[1]))
639
+ return;
640
+ return {
641
+ index: match.index,
642
+ text: match[0],
643
+ match
644
+ };
645
+ };
622
646
  const MentionPlugin = Mention.extend({
623
647
  addNodeView() {
624
648
  return VueNodeViewRenderer(MentionComponent);
@@ -647,7 +671,38 @@ const MentionPlugin = Mention.extend({
647
671
  return `@${node.attrs.id}`;
648
672
  },
649
673
  renderHTML({ HTMLAttributes }) {
650
- return ["mention-component", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
674
+ return ["mention-component", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
675
+ },
676
+ addInputRules() {
677
+ var _a;
678
+ const suggestions = (_a = this.options.suggestion) == null ? void 0 : _a.items({ query: "" });
679
+ return [
680
+ nodeInputRule({
681
+ find: (text) => mentionInputMatch(text, suggestions),
682
+ type: this.type,
683
+ getAttributes(attrs) {
684
+ return suggestions.find(({ id }) => id === attrs[0].replace("@", "").trim());
685
+ }
686
+ })
687
+ ];
688
+ },
689
+ addPasteRules() {
690
+ var _a;
691
+ const suggestions = (_a = this.options.suggestion) == null ? void 0 : _a.items({ query: "" });
692
+ return [
693
+ nodePasteRule({
694
+ find: (text) => mentionPasteMatch(text, suggestions),
695
+ type: this.type,
696
+ getAttributes(attrs) {
697
+ return suggestions.find(({ id }) => id === attrs[0].trim());
698
+ }
699
+ })
700
+ ];
701
+ }
702
+ }).configure({
703
+ suggestion: {
704
+ char: "@",
705
+ pluginKey: new PluginKey("mentionSuggestion")
651
706
  }
652
707
  });
653
708
  const _sfc_main$3 = {
@@ -679,8 +734,34 @@ function _sfc_render$3(_ctx, _cache, $props, $setup, $data, $options) {
679
734
  });
680
735
  }
681
736
  const ChannelComponent = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["render", _sfc_render$3]]);
737
+ const channelRegex = /#([\w-]+)[^\w-]?/g;
738
+ const channelPasteMatch = (text, suggestions) => {
739
+ const matches = [...text.matchAll(channelRegex)];
740
+ return matches.filter((match) => suggestions.some(({ id }) => id === match[1].trim())).map((match) => {
741
+ let channel = match[1];
742
+ if (!channel.endsWith(" "))
743
+ channel += " ";
744
+ return {
745
+ index: match.index,
746
+ text: channel,
747
+ match
748
+ };
749
+ });
750
+ };
751
+ const channelInputMatch = (text, suggestions) => {
752
+ const match = text.match(/#([\w-]+)[^\w-]$/);
753
+ if (!match || !suggestions.some(({ id }) => id === match[1]))
754
+ return;
755
+ return {
756
+ index: match.index,
757
+ text: match[0],
758
+ match
759
+ };
760
+ };
682
761
  const ChannelPlugin = Mention.extend({
683
762
  name: "channel",
763
+ group: "inline",
764
+ inline: true,
684
765
  addNodeView() {
685
766
  return VueNodeViewRenderer(ChannelComponent);
686
767
  },
@@ -708,7 +789,33 @@ const ChannelPlugin = Mention.extend({
708
789
  return `#${node.attrs.id}`;
709
790
  },
710
791
  renderHTML({ HTMLAttributes }) {
711
- return ["channel-component", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
792
+ return ["channel-component", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
793
+ },
794
+ addInputRules() {
795
+ var _a;
796
+ const suggestions = (_a = this.options.suggestion) == null ? void 0 : _a.items({ query: "" });
797
+ return [
798
+ nodeInputRule({
799
+ find: (text) => channelInputMatch(text, suggestions),
800
+ type: this.type,
801
+ getAttributes(attrs) {
802
+ return suggestions.find(({ id }) => id === attrs[0].replace("#", "").trim());
803
+ }
804
+ })
805
+ ];
806
+ },
807
+ addPasteRules() {
808
+ var _a;
809
+ const suggestions = (_a = this.options.suggestion) == null ? void 0 : _a.items({ query: "" });
810
+ return [
811
+ nodePasteRule({
812
+ find: (text) => channelPasteMatch(text, suggestions),
813
+ type: this.type,
814
+ getAttributes(attrs) {
815
+ return suggestions.find(({ id }) => id === attrs[0].trim());
816
+ }
817
+ })
818
+ ];
712
819
  }
713
820
  }).configure({
714
821
  suggestion: {
@@ -1098,7 +1205,7 @@ const _sfc_main = {
1098
1205
  "input",
1099
1206
  /**
1100
1207
  * Event to sync the value with the parent
1101
- * @event input
1208
+ * @event update:value
1102
1209
  * @type {String|JSON}
1103
1210
  */
1104
1211
  "update:modelValue",
@@ -1118,7 +1225,8 @@ const _sfc_main = {
1118
1225
  data() {
1119
1226
  return {
1120
1227
  editor: null,
1121
- popoverOpened: false
1228
+ popoverOpened: false,
1229
+ internalValue: this.modelValue
1122
1230
  };
1123
1231
  },
1124
1232
  computed: {
@@ -1235,7 +1343,8 @@ const _sfc_main = {
1235
1343
  if (newValue === currentValue) {
1236
1344
  return;
1237
1345
  }
1238
- this.editor.commands.setContent(newValue, false);
1346
+ this.internalValue = newValue;
1347
+ this.insertContent();
1239
1348
  }
1240
1349
  },
1241
1350
  created() {
@@ -1248,7 +1357,6 @@ const _sfc_main = {
1248
1357
  createEditor() {
1249
1358
  this.editor = new Editor({
1250
1359
  autofocus: this.autoFocus,
1251
- content: this.modelValue,
1252
1360
  editable: this.editable,
1253
1361
  extensions: this.extensions,
1254
1362
  editorProps: {
@@ -1258,8 +1366,62 @@ const _sfc_main = {
1258
1366
  }
1259
1367
  }
1260
1368
  });
1369
+ this.insertContent();
1261
1370
  this.addEditorListeners();
1262
1371
  },
1372
+ /**
1373
+ * This function is necessary as tiptap doesn't render the content passed
1374
+ * directly through `editor.commands.setContent` the content passed down to it
1375
+ * should be already parsed. So We're parsing the elements into it's corresponding
1376
+ * HTML version before setting it.
1377
+ */
1378
+ insertContent() {
1379
+ this.parseMentions();
1380
+ this.parseChannels();
1381
+ this.parseEmojis();
1382
+ this.editor.commands.setContent(this.internalValue, true);
1383
+ },
1384
+ parseEmojis() {
1385
+ const matches = [...this.modelValue.matchAll(emojiRegex()), ...this.modelValue.matchAll(emojiShortCodeRegex)];
1386
+ if (!matches)
1387
+ return;
1388
+ matches.forEach((match) => {
1389
+ const emoji = codeToEmojiData(match[0]);
1390
+ if (!emoji)
1391
+ return;
1392
+ this.internalValue = this.internalValue.replace(new RegExp(` ${match[0]}`), ` <emoji-component code="${emoji.shortname}"></emoji-component>`);
1393
+ });
1394
+ },
1395
+ parseChannels() {
1396
+ if (!this.channelSuggestion)
1397
+ return;
1398
+ const suggestions = this.channelSuggestion.items({ query: "" });
1399
+ const matches = [...this.modelValue.matchAll(channelRegex)].filter((match) => suggestions.some(({ id }) => id === match[1]));
1400
+ if (!matches)
1401
+ return;
1402
+ matches.forEach((match) => {
1403
+ const channel = suggestions.find(({ id }) => id === match[1]);
1404
+ this.internalValue = this.internalValue.replace(
1405
+ `#${match[1]}`,
1406
+ /** The space at the beginning is important as tiptap removes that while rendering.
1407
+ * So if multiple mentions, channels or emojis are next to each other it will fail
1408
+ */
1409
+ ` <channel-component name="${channel.name}" id="${channel.id}"></channel-component>`
1410
+ );
1411
+ });
1412
+ },
1413
+ parseMentions() {
1414
+ if (!this.mentionSuggestion)
1415
+ return;
1416
+ const suggestions = this.mentionSuggestion.items({ query: "" });
1417
+ const matches = [...this.modelValue.matchAll(mentionRegex)].filter((match) => suggestions.some(({ id }) => id === match[1]));
1418
+ if (!matches)
1419
+ return;
1420
+ matches.forEach((match) => {
1421
+ const mention = suggestions.find(({ id }) => id === match[1]);
1422
+ this.internalValue = this.internalValue.replace(`@${match[1]}`, ` <mention-component name="${mention.name}" id="${mention.id}"></mention-component>`);
1423
+ });
1424
+ },
1263
1425
  destroyEditor() {
1264
1426
  this.editor.destroy();
1265
1427
  },