Tiptap에서 댓글 달기와 같은 Google 문서를 구현한 방법

24093 단어
Tiptap에는 사람들이 자신이 만든 확장 프로그램을 공유하는 저장소의 모든 커뮤니티 확장 목록과 함께a GitHub issue 있습니다. Someone mentioned 그들은 Google Docs의 댓글 기능과 유사한 Tiptap의 댓글 기능이 필요하다고 말했습니다.

그래서 여기에 내가 같은 것을 만들 기회가 있었던 방법이 있습니다.


고요한 세레나데 / tiptap-comment-extension


Tiptap 2용 댓글 솔루션과 같은 Google 문서.





tiptap-comment-extension 💬


TipTap에서 댓글 달기와 같은 Google 문서를 사용하기 위한 확장 프로그램입니다. 내가 하는 일을 👍/❤️하면 레포에 ⭐️를 주세요.
참고: 이것은 vue 3 버전입니다. React 구현을 찾는 사람들을 위해 저장소는 here 입니다.

데모(상세 정보):


  • https://tiptap-comment-extension.vercel.app/에서 사용해 볼 수 있습니다.




  • out-comment.mp4





    사용하는 방법


    extensions 폴더에 comment.ts 파일을 복사합니다. TypeScript 사용 여부에 따라 확장자를 변경해야 할 수도 있습니다. 영어는 그만하고 코드에 대해 이야기해 봅시다.
    물론 농담이지만 ​​이것에 대한 문서가 필요하다고 생각하지 않습니다. 예제 구현에 대해 Tiptap.vue을 살펴보고 자신의 구현을 생각해 보십시오(또는 StackOverflow에서 찾은 솔루션인 것처럼 복사 붙여넣기). 질문이나 문제가 있는 경우 Bar(Withcer 시즌 2 참조)에 있습니다.…

    View on GitHub

    프레임워크에 구애받지 않는 Tiptap 확장의 예시 구현일 뿐이기 때문에 처음에는 Vue3용으로만 만들었습니다. 그리고 request of community 옆에는 implementation in React 도 있습니다. 코드로 이동하기 전에 확인하려면 여기a demo.를 참조하십시오.

    좋습니다. 컨텍스트가 충분합니다. 어떻게 만들어졌는지 자세히 살펴보겠습니다.

    작업을 시작했을 때 가장 먼저 Google에 관련 논의가 있는지 확인했고 당시 Prosemirror에 대한 지식이 부족하여 완전히 이해할 수 없었던 this thread을 찾았습니다. 외부 데이터 구조에 주석 범위를 저장하고 Prosemirror 장식으로 문서의 주석을 강조 표시한다고 언급한 다른 솔루션과 마크로 주석을 구현하는 장단점을 알게 되었지만 방법을 알아내야 합니다. 한 문서에서 콘텐츠를 복사하여 다른 문서에 붙여넣을 때 주석을 전송합니다.

    결국 나는 이미 마크에 익숙했기 때문에 댓글을 마크로 한 번 시도하기로 결정했습니다. 그래서 새 Mark를 만들었습니다. 여기에 the file이 있습니다.

    export interface CommentOptions {
      HTMLAttributes: Record<string, any>,
    }
    
    export const Comment = Mark.create<CommentOptions>({
      name: 'comment',
    
      addOptions() {
        return {
          HTMLAttributes: {},
        };
      },
    
      parseHTML() {
        return [
          {
            tag: 'span',
          },
        ];
      },
    
      renderHTML({ HTMLAttributes }) {
        return ['span', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
      },
    });
    


    바로 지금, 주석 표시를 생성했습니다. 이 표시는 범위를 렌더링하지만 주석을 어딘가에 저장해야 합니다. 그때 data-comment 속성이 들어옵니다.

    export const Comment = Mark.create<CommentOptions>({
      // ... rest of code
      addAttributes() {
        return {
          comment: {
            default: null,
            parseHTML: (el) => (el as HTMLSpanElement).getAttribute('data-comment'),
            renderHTML: (attrs) => ({ 'data-comment': attrs.comment }),
          },
        };
      },
    
      parseHTML() {
        return [
          {
            tag: 'span[data-comment]',
            getAttrs: (el) => !!(el as HTMLSpanElement).getAttribute('data-comment')?.trim() && null,
          },
        ];
      },
      // ... rest of code
    })
    


    이제 span 요소가 있는 data-comment 속성을 렌더링하고 Tiptap-Attribute를 추가하면 마크의 comment 속성을 정의할 수 있으므로 DOM을 통해 검색하여 data-comment 속성을 가져오는 대신 프로그래밍 방식으로 액세스할 수 있습니다.

    주석 표시를 설정하려면 명령이 필요합니다.

    // ... rest of code
      addCommands() {
        return {
          setComment: (comment: string) => ({ commands }) => commands.setMark('comment', { comment }),
          toggleComment: () => ({ commands }) => commands.toggleMark('comment'),
          unsetComment: () => ({ commands }) => commands.unsetMark('comment'),
        };
      },
    // ... rest of code
    


    확장 부분은 여기까지입니다. 이제 주석을 구조화하고 저장하는 방법을 살펴보고 구현된 Vue 부분으로 이동합니다here.

    interface CommentInstance {
      uuid?: string
      comments?: any[]
    }
    
    // to store currently active instance
    const activeCommentsInstance = ref<CommentInstance>({})
    
    const allComments = ref<any[]>([]) // to store all comments
    


    편집기가 생성되고 업데이트될 때 모든 주석을 로드하고 편집기가 업데이트될 때 현재 활성화된 주석을 설정합니다.

    const findCommentsAndStoreValues = (editor: Editor) => {
      const tempComments: any[] = []
    
      // to get the comments from comment mark.
      editor.state.doc.descendants((node, pos) => {
        const { marks } = node
    
        marks.forEach((mark) => {
          if (mark.type.name === 'comment') {
            const markComments = mark.attrs.comment; // accessing the comment attribute programmatically as mentioned before
    
            const jsonComments = markComments ? JSON.parse(markComments) : null;
    
            if (jsonComments !== null) {
              tempComments.push({
                node,
                jsonComments,
                from: pos,
                to: pos + (node.text?.length || 0),
                text: node.text,
              });
            }
          }
        })
      })
    
      allComments.value = tempComments
    }
    
    const setCurrentComment = (editor: Editor) => {
      const newVal = editor.isActive('comment')
    
      if (newVal) {
        setTimeout(() => (showCommentMenu.value = newVal), 50)
    
        showAddCommentSection.value = !editor.state.selection.empty
    
        const parsedComment = JSON.parse(editor.getAttributes('comment').comment)
    
        parsedComment.comment = typeof parsedComment.comments === 'string' ? JSON.parse(parsedComment.comments) : parsedComment.comments
    
        activeCommentsInstance.value = parsedComment
      } else {
        activeCommentsInstance.value = {}
      }
    }
    
    const tiptapEditor = useEditor({
      content: `
        <p> Comment here. </p>
      `,
    
      extensions: [StarterKit, Comment],
    
      onUpdate({ editor }) {
        findCommentsAndStoreValues(editor)
    
        setCurrentComment(editor)
      },
    
      onSelectionUpdate({ editor }) {
        setCurrentComment(editor)
    
        isTextSelected.value = !!editor.state.selection.content().size
      },
    
      onCreate({ editor }) {
        findCommentsAndStoreValues(editor)
      },
    
      editorProps: {
        attributes: {
          spellcheck: 'false',
        },
      },
    })
    


    이것이 Tiptap에서 주석을 가져와 Vue에 저장하여 편집기 범위 밖에서 사용할 수 있도록 하는 핵심 논리였습니다.

    다음은 나에게 가장 의미 있는 댓글의 구조입니다. 그러나 data-comment 속성의 문자열일 뿐이므로 JSON, XML, YAML 또는 원하는 형식을 가질 수 있으므로 올바르게 구문 분석해야 합니다.

    {
      "uuid": "d1858137-e0d8-48ac-9f38-ae778b56c719",
      "comments": [
        {
          "userName": "sereneinserenade",
          "time": 1648338852939,
          "content": "First comment"
        },
        {
          "userName": "sereneinserenade",
          "time": 1648338857073,
          "content": "Following Comment"
        }
      ]
    }
    


    논리의 다른 부분(비핵심 논리라고 생각함), 즉 새 댓글 만들기, 댓글 모드 켜기/끄기 전환 및 표시the comments outside는 저장소에서 찾을 수 있습니다.

    좋은 하루 되세요. 질문/제안 사항이 있으시면 댓글 섹션에 남겨두겠습니다.

    좋은 웹페이지 즐겨찾기