web-dev-qa-db-ja.com

Vue 2 contentEditable with v-model

Mediumに似たテキストエディタを作成しようとしています。コンテンツ編集可能な段落タグを使用して、各アイテムを配列に格納し、それぞれをv-forでレンダリングします。しかし、v-modelを使用してテキストを配列にバインドする際に問題があります。 v-modelとcontenteditableプロパティが競合しているようです。これが私のコードです:

<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>

そして私のスクリプトでは:

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);
         }
      }
   }
}

これに対するオンラインでの回答は見つかりませんでした。

12
Soubriquet

与えられたソリューションが必要なものを明確に答えるのに最もエレガントまたは簡潔であるとは思わないか、Vueの最適な使用を提供していないので、私は貢献するかもしれないと思いました。近づく人もいますが、実際に効果を上げるには、最終的に少し調整が必要です。まず、<p>段落はv-modelをサポートしていません。コンテンツはinnerHTMLにあり、要素スロット内の{{content}}を使用してのみ追加されます。挿入後、そのコンテンツは編集されません。初期コンテンツを指定できますが、コンテンツを更新するたびに、コンテンツ編集カーソルが前面にリセットされます(自然なタイピングエクスペリエンスではありません)。これは私の最終的な解決策につながります:

...
<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, '');
    },
...

私の提案は、最初の静的コンテンツ値を<p>スロットに入れ、次に@inputトリガーを使用して、2番目のactiveコンテンツ変数をinnerHTMLに入れられたもので更新しますcontenteditableアクションから。また、<p>要素によって作成された最後のHTML形式の空白を削除することもできます。そうしないと、スペースがある場合、最後に文字列全体が表示されます。

別のより効果的な解決策がある場合、私はそれを認識していませんが、提案を歓迎します。これは私のコードに使用したものであり、パフォーマンスが高く、自分のニーズに合うと確信しています。

0
Marcus Smith

コンポーネントv-modelを使用して、VueでcontentEditableを作成できます。

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