1

所以我的问题是我似乎无法在 vuetify-nuxt 项目中使用tiptap 的“提及”功能。

原始示例可以在这里找到

更多有用信息:

  • 来自tiptap github的示例实现在这里

  • 在这里提出了类似的问题,但没有完全回答

  • 在此处的 vuetify 集成库中提出了类似的问题

为此,我正在尝试结合文档示例。

我想在我尝试使用“tippy”时它会出错,这是用于显示实际列出用户从中选择的弹出窗口的 css 库(在 之后@),但我似乎无法理解真正的问题。

因此,当我键入@keydown/up 事件侦听器正在运行时,但是tippy 似乎没有成功绑定弹出窗口(它没有显示),并且发生以下错误:

Editor.vue?6cd8:204 Uncaught TypeError: Cannot read property '0' of undefined
    at VueComponent.enterHandler (Editor.vue?6cd8:204)
    at onKeyDown (Editor.vue?6cd8:175)
    at Plugin.handleKeyDown (extensions.esm.js?f23d:788)
    at eval (index.es.js?f904:3298)
    at EditorView.someProp (index.es.js?f904:4766)
    at editHandlers.keydown (index.es.js?f904:3298)

这是我的tippy.js nuxt 插件:

import Vue from "vue";
import VueTippy, { TippyComponent } from "vue-tippy";

Vue.use(VueTippy, {
    interactive: true,
    theme: "light",
    animateFill: false,
    arrow: true,
    arrowType: "round",
    placement: "bottom",
    trigger: "click",
    // appendTo: () => document.getElementById("app")
});

Vue.component("tippy", TippyComponent);

这是我试图在其中显示编辑器和建议/提及功能的组件:

<template>
  <div>
    <div class="popup">
      aaaa
    </div>
    <editor-menu-bar v-slot="{ commands }" :editor="editor">
      <div class="menubar">
        <v-btn class="menubar__button" @click="commands.mention({ id: 1, label: 'Fred Kühn' })">
          <v-icon left>@</v-icon>
          <span>Mention</span>
        </v-btn>
      </div>
    </editor-menu-bar>

    <tiptap-vuetify v-model="localValue" :extensions="extensions" :native-extensions="nativeExtensions" :toolbar-attributes="{ color: 'grey' }" @init="onInit" />
  </div>
</template>

<script>
// import the component and the necessary extensions
import {
  TiptapVuetify,
  Heading,
  Bold,
  Italic,
  Strike,
  Underline,
  Code,
  CodeBlock,
  Image,
  Paragraph,
  BulletList,
  OrderedList,
  ListItem,
  Link,
  Blockquote,
  HardBreak,
  HorizontalRule,
  History,
} from "tiptap-vuetify";

// TESTING
import { EditorMenuBar, Editor } from "tiptap";
import { Mention } from "tiptap-extensions";
import tippy, { sticky } from "tippy.js";

export default {
  components: { TiptapVuetify, EditorMenuBar },
  props: {
    value: {
      type: String,
      default: "",
    },
  },
  data: () => ({
    editor: null,
    extensions: null,
    nativeExtensions: null,

    // TESTING
    query: null,
    suggestionRange: null,
    filteredUsers: [],
    navigatedUserIndex: 0,
    insertMention: () => {},
    popup: null,
  }),
  computed: {
    localValue: {
      get() {
        return this.value;
      },
      set(value) {
        this.$emit("input", value);
      },
    },

    // TESTING
    hasResults() {
      return this.filteredUsers.length;
    },
    showSuggestions() {
      return this.query || this.hasResults;
    },
  },
  created() {
    this.extensions = [
      History,
      Blockquote,
      Link,
      Underline,
      Strike,
      Italic,
      ListItem,
      BulletList,
      OrderedList,
      [
        Heading,
        {
          options: {
            levels: [1, 2, 3],
          },
        },
      ],
      Bold,
      Link,
      Code,
      CodeBlock,
      Image,
      HorizontalRule,
      Paragraph,
      HardBreak,
    ];
    this.nativeExtensions = [
      // https://github.com/ueberdosis/tiptap/blob/main/examples/Components/Routes/Suggestions/index.vue
      new Mention({
        // a list of all suggested items
        items: async () => {
          await new Promise((resolve) => {
            setTimeout(resolve, 500);
          });
          return [
            { id: 1, name: "Sven Adlung" },
            { id: 2, name: "Patrick Baber" },
            { id: 3, name: "Nick Hirche" },
            { id: 4, name: "Philip Isik" },
            { id: 5, name: "Timo Isik" },
            { id: 6, name: "Philipp Kühn" },
            { id: 7, name: "Hans Pagel" },
            { id: 8, name: "Sebastian Schrama" },
          ];
        },
        // When @ is pressed, we enter here
        onEnter: ({ items, query, range, command, virtualNode }) => {
          this.query = query; // the field that the @ queries? currently empty
          this.filteredUsers = items;
          this.suggestionRange = range;

          this.renderPopup(virtualNode); // render popup - failing

          this.insertMention = command; // this is saved to be able to call it from within the popup
        },

        // probably when value after @ is changed
        onChange: ({ items, query, range, virtualNode }) => {
          this.query = query;
          this.filteredUsers = items;
          this.suggestionRange = range;
          this.navigatedUserIndex = 0;
          this.renderPopup(virtualNode);
        },

        // mention canceled
        onExit: () => {
          // reset all saved values
          this.query = null;
          this.filteredUsers = [];
          this.suggestionRange = null;
          this.navigatedUserIndex = 0;
          this.destroyPopup();
        },

        // any key down during mention typing
        onKeyDown: ({ event }) => {
          if (event.key === "ArrowUp") {
            this.upHandler();
            return true;
          }
          if (event.key === "ArrowDown") {
            this.downHandler();
            return true;
          }
          if (event.key === "Enter") {
            this.enterHandler();
            return true;
          }
          return false;
        },

        // there may be built-in filtering, not sure
        onFilter: async (items, query) => {
          await console.log("on filter");
        },
      }),
    ];
  },
  methods: {
    // TESTING
    // navigate to the previous item
    // if it's the first item, navigate to the last one
    upHandler() {
      this.navigatedUserIndex =
        (this.navigatedUserIndex + this.filteredUsers.length - 1) %
        this.filteredUsers.length;
    },
    // navigate to the next item
    // if it's the last item, navigate to the first one
    downHandler() {
      this.navigatedUserIndex =
        (this.navigatedUserIndex + 1) % this.filteredUsers.length;
    },
    enterHandler() {
      const user = this.filteredUsers[this.navigatedUserIndex];
      if (user) {
        this.selectUser(user);
      }
    },
    // we have to replace our suggestion text with a mention
    // so it's important to pass also the position of your suggestion text
    selectUser(user) {
      this.insertMention({
        range: this.suggestionRange,
        attrs: {
          id: user.id,
          label: user.name,
        },
      });
      this.editor.focus();
    },
    renderPopup(node) {
      if (this.popup) {
        return;
      }
      // ref: https://atomiks.github.io/tippyjs/v6/all-props/
      this.popup = tippy(".page", {
        getReferenceClientRect: node.getBoundingClientRect, // input location
        appendTo: () => document.body, // must be issue
        interactive: true,
        sticky: true, // make sure position of tippy is updated when content changes
        plugins: [sticky],
        content: this.$refs.suggestions,
        trigger: "mouseenter", // manual
        showOnCreate: true,
        theme: "dark",
        placement: "top-start",
        inertia: true,
        duration: [400, 200],
      });
    },

    destroyPopup() {
      if (this.popup) {
        this.popup[0].destroy();
        this.popup = null;
      }
    },

    beforeDestroy() {
      this.destroyPopup();
    },

    /**
     * NOTE: destructure the editor!
     */
    onInit({ editor }) {
      this.editor = editor;
    },
  },
};
</script>

如何在上述设置中获得“建议”项目显示?

4

0 回答 0