web-dev-qa-db-fra.com

Vue 2 contentEditable avec v-model

J'essaie de créer un éditeur de texte similaire à Medium. J'utilise une balise de paragraphe modifiable de contenu et stocke chaque élément dans un tableau et le rend chacun avec v-for. Cependant, j'ai des problèmes avec la liaison du texte avec le tableau à l'aide de v-model. Il semble qu'il y ait un conflit avec v-model et la propriété contenteditable. Voici mon code:

<div id="editbar">
     <button class="toolbar" v-on:click.prevent="stylize('bold')">Bold</button>
</div>
<div v-for="(value, index) in content">
     <p v-bind:id="'content-'+index" v-bind:ref="'content-'+index" v-model="content[index].value" v-on:keyup="emit_content($event)" v-on:keyup.delete="remove_content(index)" contenteditable></p>
</div>

et dans mon script:

export default { 
   data() {
      return {
         content: [{ value: ''}]
      }
   },
   methods: {
      stylize(style) {
         document.execCommand(style, false, null);
      },
      remove_content(index) {
         if(this.content.length > 1 && this.content[index].value.length == 0) {
            this.content.splice(index, 1);
         }
      }
   }
}

Je n'ai trouvé aucune réponse en ligne pour cela.

12
Soubriquet

J'ai pensé que je pourrais contribuer parce que je ne pense pas que les solutions données soient les plus élégantes ou concises pour répondre clairement à ce qui est nécessaire ou qu'elles ne fournissent pas la meilleure utilisation de Vue. Certains se rapprochent, mais ont finalement besoin d'un peu de peaufinage pour être vraiment efficaces. Première note, le <p> le paragraphe ne prend pas en charge le modèle v. Le contenu se trouve dans le innerHTML et n'est ajouté qu'en utilisant {{content}} à l'intérieur de la fente d'élément. Ce contenu n'est pas modifié après l'insertion. Vous pouvez lui donner un contenu initial, mais chaque fois que vous actualisez le contenu, le curseur d'édition de contenu est réinitialisé à l'avant (pas une expérience de frappe naturelle). Cela conduit à ma solution finale:

...
<p class="m-0 p-3" :contenteditable="manage" @input="handleInput">
        {{ content }}
</p>
...
  props: {
    content: {type:String,defalut:"fill content"},
    manage: { type: Boolean, default: false },
...
  data: function() {
    return {
      bioContent: this.content
...
methods: {
    handleInput: function(e) {
      this.bioContent = e.target.innerHTML.replace(/(?:^(?:&nbsp;)+)|(?:(?:&nbsp;)+$)/g, '');
    },
...

Ma suggestion est de mettre une valeur de contenu statique initiale dans le <p> slot, puis un @input déclencheur pour mettre à jour une deuxième variable de contenu active avec ce qui est placé dans le innerHTML à partir de l'action contenteditable. Vous voudrez également couper le blanc de format HTML de fin créé par le <p> élément, sinon vous obtiendrez une chaîne brute à la fin si vous avez un espace.

S'il existe une autre solution, plus efficace, je n'en ai pas connaissance mais je suis la bienvenue aux suggestions. C'est ce que j'ai utilisé pour mon code et je suis convaincu qu'il sera performant et répondra à mes besoins.

0
Marcus Smith

Vous pouvez utiliser le composant v-model pour créer contentEditable dans Vue.

Vue.component('editable', {
  template: `<p
v-bind:innerHTML.prop="value"
contentEditable="true" 
@input="updateCode"
@keyup.ctrl.delete="$emit('delete-row')"
></p>`,
  props: ['value'],
  methods: {
    updateCode: function($event) {
      //below code is a hack to prevent updateDomProps
      this.$vnode.child._vnode.data.domProps['innerHTML'] = $event.target.innerHTML;
      this.$emit('input', $event.target.innerHTML);
    }
  }
});

new Vue({
  el: '#app',
  data: {
    len: 3,
    content: [{
        value: 'paragraph 1'
      },
      {
        value: 'paragraph 2'
      },
      {
        value: 'paragraph 3'
      },
    ]
  },
  methods: {
    stylize: function(style, ui, value) {
      var inui = false;
      var ivalue = null;
      if (arguments[1]) {
        inui = ui;
      }
      if (arguments[2]) {
        ivalue = value;
      }
      document.execCommand(style, inui, ivalue);
    },
    createLink: function() {
      var link = Prompt("Enter URL", "https://codepen.io");
      document.execCommand('createLink', false, link);
    },
    deleteThisRow: function(index) {
      this.content.splice(index, 1);
    },
    add: function() {
      ++this.len;
      this.content.Push({
        value: 'paragraph ' + this.len
      });
    },
  }
});
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<div id="app">
  <button class="toolbar" v-on:click.prevent="add()">ADD PARAGRAPH</button>
  <button class="toolbar" v-on:click.prevent="stylize('bold')">BOLD</button>
  <button class="toolbar" v-on:click.prevent="stylize('italic')">ITALIC</button>
  <button class="toolbar" v-on:click.prevent="stylize('justifyLeft')">LEFT ALIGN</button>
  <button class="toolbar" v-on:click.prevent="stylize('justifyCenter')">CENTER</button>
  <button class="toolbar" v-on:click.prevent="stylize('justifyRight')">RIGHT ALIGN</button>
  <button class="toolbar" v-on:click.prevent="stylize('insertOrderedList')">ORDERED LIST</button>
  <button class="toolbar" v-on:click.prevent="stylize('insertUnorderedList')">UNORDERED LIST</button>
  <button class="toolbar" v-on:click.prevent="stylize('backColor',false,'#FFFF66')">HEIGHLIGHT</button>
  <button class="toolbar" v-on:click.prevent="stylize('foreColor',false,'red')">RED TEXT</button>
  <button class="toolbar" v-on:click.prevent="createLink()">CREATE LINK</button>
  <button class="toolbar" v-on:click.prevent="stylize('unlink')">REMOVE LINK</button>
  <button class="toolbar" v-on:click.prevent="stylize('formatBlock',false,'H1')">H1</button>
  <button class="toolbar" v-on:click.prevent="stylize('underline')">UNDERLINE</button>
  <button class="toolbar" v-on:click.prevent="stylize('strikeThrough')">STRIKETHROUGH</button>
  <button class="toolbar" v-on:click.prevent="stylize('superscript')">SUPERSCRIPT</button>
  <button class="toolbar" v-on:click.prevent="stylize('subscript')">SUBSCRIPT</button>
  <button class="toolbar" v-on:click.prevent="stylize('indent')">INDENT</button>
  <button class="toolbar" v-on:click.prevent="stylize('outdent')">OUTDENT</button>
  <button class="toolbar" v-on:click.prevent="stylize('insertHorizontalRule')">HORIZONTAL LINE</button>
  <button class="toolbar" v-on:click.prevent="stylize('insertParagraph')">INSERT PARAGRAPH</button>
  <button class="toolbar" v-on:click.prevent="stylize('formatBlock',false,'BLOCKQUOTE')">BLOCK QUOTE</button>
  <button class="toolbar" v-on:click.prevent="stylize('selectAll')">SELECT ALL</button>
  <button class="toolbar" v-on:click.prevent="stylize('removeFormat')">REMOVE FORMAT</button>
  <button class="toolbar" v-on:click.prevent="stylize('undo')">UNDO</button>
  <button class="toolbar" v-on:click.prevent="stylize('redo')">REDO</button>

  <editable v-for="(item, index) in content" :key="index" v-on:delete-row="deleteThisRow(index)" v-model="item.value"></editable>

  <pre>
    {{content}}
    </pre>
</div>
0
Muthu Kumar