如何将来自 Tiptap 文本编辑器的内容放入 v-model?

Posted

技术标签:

【中文标题】如何将来自 Tiptap 文本编辑器的内容放入 v-model?【英文标题】:How can i put content from tiptap text editor into a v-model? 【发布时间】:2021-01-02 19:39:43 【问题描述】:

我尝试将editor.content 直接绑定到this.newTutorial.content,但还没有成功..

在控制台中:

这是我的代码:

<style scoped>
img.preview 
  width:200px;

.v-btn 
    height: 50px !important;
    min-width: 50px !important;


</style>

<template>

<div id="app">
    <v-dialog v-model="dialog" >
        <template  v-slot:activator=" on, attrs ">
            <v-btn style="z-index:9;" color="blue lighten-1" dark rounded v-bind="attrs" v-on="on" fixed left>
                <v-tooltip right >
                    <template  v-slot:activator=" on, attrs ">
                        <v-icon fab dark v-bind="attrs" v-on="on">
                            mdi-plus
                        </v-icon>
                    </template>
                    <img class="monk-ico" src="https://celfonica.s3-us-west-1.amazonaws.com/logos/monk-circle+50px.png">
                    <span style="display:inline;">
                      Add Tutorial
                    </span>
                </v-tooltip>
            </v-btn>
        </template>
        <div class="left">
            <v-btn color="primary" @click="dialog = false" >
                <v-icon>
                    mdi-close
                </v-icon>
            </v-btn>
        </div>
        <div class="panel-heading">
        </div>
        <div>
            <h1>Tutorial form</h1>
            <h3> create one</h3>
            <form id="form" class="form-inline" v-on:submit.prevent="addTutorial">
                <v-divider class="m-tb-20"></v-divider>
                <h4>Author details</h4>
                <div class="form-group">
                    <v-text-field :rules="nameRules" required label="First Name" type="text" id="tutorialFirst" class="form-control" v-model="newTutorial.first">
                    </v-text-field>
                </div>
                <div class="form-group">
                    <v-text-field :rules="nameRules" required label="Last Name" type="text" id="tutorialLast" class="form-control" v-model="newTutorial.last">
                    </v-text-field>
                </div>
                <div class="form-group">
                    <v-text-field :rules="emailRules" required label="Email" type="text" id="tutorialEmail" class="form-control" v-model="newTutorial.email">
                    </v-text-field>
                </div>
                <div class="form-goup">
                  <!-- Img upload input field-->
                  <div>
                    <h4 class="m-tb-20">Upload tutorial picture:</h4>
                  <input class="form-control"  type="file" @change="previewImage" accept="image/+">
                  <br><v-btn class="m-tb-20" @click=" onUpload();"><v-icon>mdi-upload</v-icon></v-btn>
                </div>
                <div>
                  <p> Progress: uploadValue.toFixed()+"%"
                    <progress :value="uploadValue" max="100"></progress>
                  </p>
                </div>
              </div>

                <v-divider class="m-tb-20"></v-divider>
                <h4>Tutorial content</h4>
                <div class="form-group">
                    <v-select required label="Language"
                    id="tutorialLanguage" v-model="newTutorial.language"
                    multiple type="text" autocomplete tags :items="languages" class="form-control">
                        <template slot="selection" slot-scope="data">
                            <v-btn>
                                 data.item 
                            </v-btn>
                        </template>
                    </v-select>
                </div>
                <div class="form-group">
                    <v-text-field :rules="titleRules" required label="Tutorial Title" type="text" id="tutorialTitle" class="form-control" v-model="newTutorial.title">
                    </v-text-field>
                </div>
  <!--tiptap-->   <v-card >
                  <div >
                    <editor-menu-bar v-on:submit.prevent="addTutorial" :editor="editor" v-slot=" commands, isActive ">
                      <div>
                      <v-btn :class=" 'is-active': isActive.bold() " @click="commands.bold">
                      <v-icon class="mdi mdi-format-bold mdi-24px"> </v-icon>
                    </v-btn>
                      <v-btn :class=" 'is-active': isActive.italic() " @click="commands.italic">
                        <v-icon class="mdi mdi-format-italic mdi-24px "> </v-icon>
                      </v-btn>
                      <v-btn :class=" 'is-active': isActive.underline() " @click="commands.underline">
                        <v-icon class="mdi mdi-format-underline mdi-24px "> </v-icon>
                      </v-btn>
                      <v-btn :class=" 'is-active': isActive.code() " @click="commands.code">
                        <v-icon class="mdi mdi-code-tags mdi-24px "> </v-icon>
                      </v-btn>
                      <v-btn :class=" 'is-active': isActive.link() " @click="commands.link">
                        <v-icon class="mdi mdi-link mdi-24px"> </v-icon>
                      </v-btn>

                      <v-divider></v-divider>
                    </div>
                    </editor-menu-bar>
                    <editor-content :editor="editor" />
                  </div>
                  </v-card>
                <div class="form-group">
                    <v-textarea :rules="contentRules" required label="Tutorial content" type="text" id="tutorialContent" class="form-control">
                    </v-textarea>
                </div>
                <div class="form-group">
                    <v-text-field required label="Date" class="form-control" type='date' v-model='newTutorial.date'>
                    </v-text-field>
                </div>
                <div class="form-group">
                    <v-text-field  required label="Tutorial Sample Code Link" type="url" id="tutorialCode" class="form-control" v-model="newTutorial.code">
                    </v-text-field>
                </div>

              <div>

                <br>

              </div>
                <v-divider class="m-tb-20"></v-divider>
                <h4> Preview </h4>
                <v-card class="m-tb-20" v-model="newTutorial">
                  <img class="preview " :src="picture"><br>
                    <v-card-title class="center"> newTutorial.title  </v-card-title>
                    <v-card-subtitle>  newTutorial.first   newTutorial.last  </v-card-subtitle>
                    <v-divider class="m-tb-20"></v-divider>
                    <v-card-text> newTutorial.content </v-card-text>

                    <v-card-text>
                        <h5> newTutorial.language </h5>
                        <h5> newTutorial.email </h5>
                        <h5> newTutorial.date </h5>
                    </v-card-text>

                </v-card>

                <!-- Form push btn -->
                <v-btn class="m-tb-20" @click="markcompleted();" type="submit" small color="primary" dark>
                     displayText 
                </v-btn>
            </form>
        </div>
    </v-dialog>
</div>

</template>

<script>

import firebase from '../plugins/firebase'

import toastr from 'toastr';
// to debug multiple Fire apps
//if (!firebase.apps.length) 
//    firebase.initializeApp(config);
//    this.newTutorial.userID= uid;
//
import  Editor, EditorContent, EditorMenuBar  from 'tiptap'
import 
  Blockquote,
  CodeBlock,
  HardBreak,
  Heading,
  OrderedList,
  BulletList,
  ListItem,
  TodoItem,
  TodoList,
  Bold,
  Code,
  Italic,
  Link,
  Strike,
  Underline,
  History,
 from 'tiptap-extensions'

let db = firebase.database();

let messagesRef = db.ref('tutorials');

export default 
    name: 'tutform',
    firebase: 
        tutorials: messagesRef
    ,
    components: 
      EditorMenuBar,
      EditorContent,
    ,
    data() 
        return 
          editor: new Editor(
            extensions: [
              new Blockquote(),
              new CodeBlock(),
              new HardBreak(),
              new Heading( levels: [1, 2, 3] ),
              new BulletList(),
              new OrderedList(),
              new ListItem(),
              new TodoItem(),
              new TodoList(),
              new Bold(),
              new Code(),
              new Italic(),
              new Link(),
              new Strike(),
              new Underline(),
              new History(),
            ],
            content: '',
            ),
            imageData:null,
            picture:null,
            uploadValue: 0,
            dialog: false,
            displayText: 'Push me!',
            newTutorial: 
                first: '',
                content: '',
                email: '',
                last: '',
                language: [],
                title: '',
                date: '',
                picture:'',
                code: '',
            ,
            languages: [
                'html', 'CSS', 'VUE', 'React', 'Ruby', 'JS', 'SASS', 'Python','php','C#','JAVA','Other',
            ],
            nameRules: [
                v => !!v || 'you must type something',
                v => v.length <= 10 || 'hum.. this monk smelling somthing strange... must be less than 10 characters',
            ],
            emailRules: [
                v => !!v || 'E-mail is required',
                v => /.+@.+/.test(v) || 'Please enter a valid email containing @ ',
            ],
            contentRules: [
                v => !!v || 'Content is required amigo!'
            ],
            titleRules: [
                v => !!v || 'Tittle is required buddy!',
                v => v.length <= 100 || 'Woots!, Lets try making this one shorter'
            ]
        
    ,

    methods: 
        previewImage(event)
            this.uploadValue=0;
            this.picture=null;
            this.imageData=event.target.files[0];
        ,
        onUpload() 
          this.picture=null;
          const storageRef=firebase.storage().ref(`tutorials/images/$this.imageData.name`).put(this.imageData);
          storageRef.on(`state_changed`, snapshot=>
            this.uploadValue=(snapshot.bytesTransferred/snapshot.totalBytes)*100;
          , error=>console.log(error.message),
          ()=>this.uploadValue=100;
          storageRef.snapshot.ref.getDownloadURL().then((url)=>
            this.picture=url;
            this.newTutorial.picture = url;
            console.log(this.picture);
            toastr.success('Image Uploaded successfully');
          )
        )
        ,
        addTutorial: function() 
            messagesRef.child(this.newTutorial.userID).push(this.newTutorial);
            this.newTutorial.first = '';
            this.newTutorial.last = '';
            this.newTutorial.content = '';
            this.newTutorial.email = '';
            this.newTutorial.language = '';
            this.newTutorial.title = '';
            this.newTutorial.date = '',
            this.newTutorial.picture= '',
            this.newTutorial.code= '',
            toastr.success('Horray! message sent successfully');
            this.displayText = 'Nice job!';
            this.nameRules = true;
            this.emailRules = true;
            this.contentRules = true;
            this.titleRules = true;
        ,
        markcompleted: function() 
            this.displayText = 'hum.. somthing still missing';
        
    ,
    // this functions trow in uid from user in data valu to uid
    created: function()
      var user = firebase.auth().currentUser;
      var uid;
      if (user != null) 
        uid = user.uid; // The user's ID, unique to the Firebase project. Do NOT use
                         // this value to authenticate with your backend server, if
                         // you have one. Use User.getToken() instead.
      
      this.newTutorial.userID = uid;
    ,
    beforeDestroy() 
      this.editor.destroy()
    


</script>

【问题讨论】:

【参考方案1】:

我认为您可以监听update 事件,然后发出input 事件以使其成为双向绑定。

新建组件(原here):

export default 
  props: 
    editor: 
      default: null,
      type: Object
    ,
    value: 
      default: "",
      type: String
    
  ,

  watch: 
    editor: 
      immediate: true,
      handler(editor) 
        if (!editor || !editor.element) return;

        this.editor.setContent(this.value);
        this.editor.on("update", ( getHTML ) => 
          this.$emit("input", getHTML());
        );

        this.$nextTick(() => 
          this.$el.appendChild(editor.element.firstChild);
          editor.setParentComponent(this);
        );
      
    ,
    value: 
      handler(value) 
        this.editor.setContent(value);
      
    
  ,

  render(createElement) 
    return createElement("div");
  ,

  beforeDestroy() 
    this.editor.element = this.$el;
  
;

然后使用组件:

<template>
  <div>
    <editor-content :editor="editor" v-model="content"/>
  </div>
</template>

<script>
import  Editor  from "tiptap";
import EditorContent from "./components/EditorContent";

export default 
  components: 
    EditorContent
  ,

  data: () => (
    editor: new Editor(),
    content: "<p>Hello</p>"
  ),

  beforeDestroy() 
    this.editor.destroy();
  
;
</script>

工作示例here。

【讨论】:

我认为你是对的,这对我有用,我从这个回复和例子中学到了更多的东西,tnx。

以上是关于如何将来自 Tiptap 文本编辑器的内容放入 v-model?的主要内容,如果未能解决你的问题,请参考以下文章

基于Vue的无渲染的富文本编辑器——tiptap!

TipTap 编辑器中无法正确识别空格

Vue中富文本编辑器的使用

vim文本编辑器批量操作

如何调整滚动视图内容的大小并根据其文本大小将文本视图放入其中? [复制]

linux Vi 编辑器 如何复制整行n内容,这样才能粘贴到文本中