Vue 2 contentEditable с v-моделью - PullRequest
       9

Vue 2 contentEditable с v-моделью

0 голосов
/ 23 декабря 2018

Я пытаюсь сделать текстовый редактор похожим на Medium.Я использую тег абзаца, который можно редактировать, и сохраняю каждый элемент в массиве и отображаю каждый элемент с помощью v-for.Однако у меня возникают проблемы с привязкой текста к массиву с использованием v-модели.Кажется, что есть конфликт с v-моделью и 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);
         }
      }
   }
}

Я не нашел ни одного ответа онлайн для этого.

Ответы [ 3 ]

0 голосов
/ 24 декабря 2018

Я понял это вчера!Поселились на этом решении.В основном я просто вручную отслеживаю innerHTML в моем массиве content, обновляя информацию о любом возможном событии и перерисовывая, вручную присваивая соответствующие элементы с динамическими ссылками, например content-0, content-1, ... Работает прекрасно:

<template>
   <div id="editbar">
       <button class="toolbar" v-on:click.prevent="stylize('bold')">Bold</button>
   </div>
   <div>
      <div v-for="(value, index) in content">
          <p v-bind:id="'content-'+index" class="content" v-bind:ref="'content-'+index" v-on:keydown.enter="prevent_nl($event)" v-on:keyup.enter="add_content(index)" v-on:keyup.delete="remove_content(index)" contenteditable></p>
      </div>
   </div>
</template>
<script>
export default {
   data() {
      return {
         content: [{
            html: ''
         }]
      }
   },
   methods: {
      add_content(index) {
        //append to array
      },
      remove_content(index) {
        //first, check some edge conditions and remove from array

        //then, update innerHTML of each element by ref
        for(var i = 0; i < this.content.length; i++) {
           this.$refs['content-'+i][0].innerHTML = this.content[i].html;
        }
      },
      stylize(style){
         document.execCommand(style, false, null);
         for(var i = 0; i < this.content.length; i++) {
            this.content[i].html = this.$refs['content-'+i][0].innerHTML;
         }
      }
   }
}
</script>
0 голосов
/ 20 августа 2019

Я думаю, что, возможно, придумал еще более простое решение.Смотрите фрагмент ниже:

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
</head>
<body>
    <main id="app">
        <div class="container-fluid">
            <div class="row">
                <div class="col-8 bg-light visual">
                    <span class="text-dark m-0" v-html="content"></span>
                </div>
                <div class="col-4 bg-dark form">
                    <button v-on:click="bold_text">Bold</button>
                    <span class="bg-light p-2" contenteditable @input="handleInput">Change me!</span>
                </div>
            </div>
        </div>
    </main>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>

    <script>
        new Vue({
            el: '#app',
            data: {
                content: 'Change me!',
            },
            methods: {
                handleInput: function(e){
                    this.content = e.target.innerHTML
                },
                bold_text: function(){
                    document.execCommand('bold')
                }
            }
        })

    </script>
</body>
</html>

Объяснение:

Вы можете редактировать диапазон, как я добавил тег contenteditable.Обратите внимание, что в input я буду вызывать функцию handleInput, которая устанавливает значение innerHtml содержимого в соответствии с тем, что вы вставили в редактируемый диапазон.Затем, чтобы добавить функциональность полужирный , вы просто выбираете то, что хотите выделять жирным шрифтом, и нажимаете кнопку полужирного.

Дополнительный бонус!Он также работает с cmd + b;)

Надеюсь, это кому-нибудь поможет!

Удачное кодирование

Обратите внимание, что я ввел загрузочный css для стилизации и vue через CDNтак что он будет работать во фрагменте.

0 голосов
/ 23 декабря 2018

Я попробовал пример, и eslint-plugin-vue сообщил, что v-model не поддерживается на p элементах.См. Правило valid-v-model .

На момент написания статьи не похоже, что то, что вы хотите, напрямую поддерживается в Vue.Я представлю два общих решения:

Использование входных событий непосредственно в редактируемом элементе

<template>
  <p
    contenteditable
    @input="onInput"
  >
    {{ content }}
  </p>
</template>

<script>
export default {
  data() {
    return { content: 'hello world' };
  },
  methods: {
    onInput(e) {
      console.log(e.target.innerText);
    },
  },
};
</script>

Создание повторно используемого редактируемого компонента

Редактируемый.vue

<template>
  <p
    ref="editable"
    contenteditable
    v-on="listeners"
  />
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  computed: {
    listeners() {
      return { ...this.$listeners, input: this.onInput };
    },
  },
  mounted() {
    this.$refs.editable.innerText = this.value;
  },
  methods: {
    onInput(e) {
      this.$emit('input', e.target.innerText);
    },
  },
};
</script>

index.vue

<template>
  <Editable v-model="content" />
</template>

<script>
import Editable from '~/components/Editable';

export default {
  components: { Editable },
  data() {
    return { content: 'hello world' };
  },
};
</script>

Индивидуальное решение для вашей конкретной проблемы

После большого количестваитерациями, я обнаружил, что для вашего случая использования было проще получить рабочее решение с помощью , а не с использованием отдельного компонента.Кажется, что элементы contenteditable чрезвычайно сложны, особенно при отображении в списке.Я обнаружил, что мне пришлось вручную обновлять innerText каждого p после удаления, чтобы он работал правильно.Я также обнаружил, что использование идентификаторов работало, но использование ссылок не помогло.

Возможно, существует способ получить полную двустороннюю привязку между моделью и содержимым, но я думаю, что для этого потребуется манипулировать расположением курсора.после каждого изменения.

<template>
  <div>
    <p
      v-for="(value, index) in content"
      :id="`content-${index}`"
      :key="index"
      contenteditable
      @input="event => onInput(event, index)"
      @keyup.delete="onRemove(index)"
    />
  </div>
</template>

<script>
export default {
  data() {
    return {
      content: [
        { value: 'paragraph 1' },
        { value: 'paragraph 2' },
        { value: 'paragraph 3' },
      ],
    };
  },
  mounted() {
    this.updateAllContent();
  },
  methods: {
    onInput(event, index) {
      const value = event.target.innerText;
      this.content[index].value = value;
    },
    onRemove(index) {
      if (this.content.length > 1 && this.content[index].value.length === 0) {
        this.$delete(this.content, index);
        this.updateAllContent();
      }
    },
    updateAllContent() {
      this.content.forEach((c, index) => {
        const el = document.getElementById(`content-${index}`);
        el.innerText = c.value;
      });
    },
  },
};
</script>
...