0

I am trying to convert from pell rich text editor to vue component. I have downloaded pell.js and converted it to vue component but I meet some issues now. I transfer all datas and methods from pell to vue component.

And I called this.init function in created() method. And it shows that this.defaultActions which defined in datas() is not defined in init functions.

Please give me any advice. Thanks..

Here is my vue component

<template>
    <div class="content">
      <h1>pell</h1>
      <div id="editor" class="pell"></div>
      <div style="margin-top:20px;">
        <h3>Text output:</h3>
        <div id="text-output"></div>
      </div>
      <div style="margin-top:20px;">
        <h3>HTML output:</h3>
        <pre id="html-output"></pre>
      </div>
    </div>
</template>

<script>

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
export default {
    data: ()=> ({
        defaultParagraphSeparatorString : 'defaultParagraphSeparator',
        formatBlock : 'formatBlock',
        defaultActions: {
            bold: {
                icon: '<b>B</b>',
                title: 'Bold',
                state: function state() {
                    return this.queryCommandState('bold');
                },
                result: function result() {
                    return this.exec('bold');
                }
            },
            italic: {
                icon: '<i>I</i>',
                title: 'Italic',
                state: function state() {
                    return this.queryCommandState('italic');
                },
                result: function result() {
                    return this.exec('italic');
                }
            },
            underline: {
                icon: '<u>U</u>',
                title: 'Underline',
                state: function state() {
                    return this.queryCommandState('underline');
                },
                result: function result() {
                    return this.exec('underline');
                }
            },
            strikethrough: {
                icon: '<strike>S</strike>',
                title: 'Strike-through',
                state: function state() {
                return this.queryCommandState('strikeThrough');
                },
                result: function result() {
                return this.exec('strikeThrough');
                }
            },
            heading1: {
                icon: '<b>H<sub>1</sub></b>',
                title: 'Heading 1',
                result: function result() {
                return this.exec('formatBlock', '<h1>');
                }
            },
            heading2: {
                icon: '<b>H<sub>2</sub></b>',
                title: 'Heading 2',
                result: function result() {
                return this.exec('formatBlock', '<h2>');
                }
            },
            paragraph: {
                icon: '&#182;',
                title: 'Paragraph',
                result: function result() {
                return this.exec('formatBlock', '<p>');
                }
            },
            quote: {
                icon: '&#8220; &#8221;',
                title: 'Quote',
                result: function result() {
                return this.exec('formatBlock', '<blockquote>');
                }
            },
            olist: {
                icon: '&#35;',
                title: 'Ordered List',
                result: function result() {
                return this.exec('insertOrderedList');
                }
            },
            ulist: {
                icon: '&#8226;',
                title: 'Unordered List',
                result: function result() {
                return this.exec('insertUnorderedList');
                }
            },
            code: {
                icon: '&lt;/&gt;',
                title: 'Code',
                result: function result() {
                return this.exec('formatBlock', '<pre>');
                }
            },
            line: {
                icon: '&#8213;',
                title: 'Horizontal Line',
                result: function result() {
                return this.exec('insertHorizontalRule');
                }
            },
            link: {
                icon: '&#128279;',
                title: 'Link',
                result: function result() {
                var url = window.prompt('Enter the link URL');
                if (url) this.exec('createLink', url);
                }
            },
            image: {
                icon: '&#128247;',
                title: 'Image',
                result: function result() {
                var url = window.prompt('Enter the image URL');
                if (url) this.exec('insertImage', url);
                }
            }
        },

        defaultClasses: {
            actionbar: 'pell-actionbar',
            button: 'pell-button',
            content: 'pell-content',
            selected: 'pell-button-selected'
        },
    }),

    created(){
        console.log("this.defaultActions", this.defaultActions);
        this.init(
            {   
                element: document.getElementById('editor'),
                defaultParagraphSeparator: 'p',
                // actions: [
                //     'bold',
                //     'italic',
                //     'underline',
                //     'strikethrough'
                // ],
                onChange: function (html) {
                    document.getElementById('text-output').innerHTML = html
                    document.getElementById('html-output').textContent = html
                }
            }
        );
    },

    methods:{
        addEventListener(parent, type, listener) {
            return parent.addEventListener(type, listener);
        },
        appendChild(parent, child) {
            return parent.appendChild(child);
        },
        createElement(tag) {
            return document.createElement(tag);
        },
        queryCommandState(command) {
            return document.queryCommandState(command);
        },
        queryCommandValue(command) {
            return document.queryCommandValue(command);
        },
        exec(command) {
            var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
            return document.execCommand(command, false, value);
        },

        init(settings){
            Object.keys(this.defaultActions).map(function (action) {
                console.log("action", action)
            });
            var actions = settings.actions ? settings.actions.map(function (action) {
                if (typeof action === 'string') return this.defaultActions[action];
                else if (this.defaultActions[action.name]) return _extends({}, this.defaultActions[action.name], action);
                return action;
            }) : Object.keys(this.defaultActions).map(function (action) {
                console.log("action", action)
                console.log("sss", this.defaultActions)
                // return this.defaultActions[action];
            });

            var classes = _extends({}, this.defaultClasses, settings.classes);

            var defaultParagraphSeparator = settings[this.defaultParagraphSeparatorString] || 'div';

            var actionbar = this.createElement('div');
            actionbar.className = classes.actionbar;
            this.appendChild(settings.element, actionbar);

            var content = settings.element.content = this.createElement('div');
            content.contentEditable = true;
            content.className = classes.content;
            content.oninput = function (_ref) {
                var firstChild = _ref.target.firstChild;

                if (firstChild && firstChild.nodeType === 3) this.exec(this.formatBlock, '<' + defaultParagraphSeparator + '>');else if (content.innerHTML === '<br>') content.innerHTML = '';
                settings.onChange(content.innerHTML);
            };
            content.onkeydown = function (event) {
                if (event.key === 'Enter' && this.queryCommandValue(this.formatBlock) === 'blockquote') {
                setTimeout(function () {
                    return this.exec(this.formatBlock, '<' + defaultParagraphSeparator + '>');
                }, 0);
                }
            };
            this.appendChild(settings.element, content);

            actions.forEach(function (action) {
                var button = this.createElement('button');
                button.className = classes.button;
                button.innerHTML = action.icon;
                button.title = action.title;
                button.setAttribute('type', 'button');
                button.onclick = function () {
                return action.result() && content.focus();
                };

                if (action.state) {
                var handler = function handler() {
                    return button.classList[action.state() ? 'add' : 'remove'](classes.selected);
                };
                this.addEventListener(content, 'keyup', handler);
                this.addEventListener(content, 'mouseup', handler);
                this.addEventListener(button, 'click', handler);
                }

                this.appendChild(actionbar, button);
            });

            if (settings.styleWithCSS) this.exec('styleWithCSS');
            this.exec(this.defaultParagraphSeparatorString, defaultParagraphSeparator);

            return settings.element;
        }
    }
}
</script>
    
<style>
    .content {
        box-sizing: border-box;
        margin: 0 auto;
        max-width: 600px;
        padding: 20px;
      }

      #html-output {
        white-space: pre-wrap;
      }
      .pell {
  border: 1px solid rgba(10, 10, 10, 0.1);
  box-sizing: border-box; }

.pell-content {
  box-sizing: border-box;
  height: 300px;
  outline: 0;
  overflow-y: auto;
  padding: 10px; }

.pell-actionbar {
  background-color: #FFF;
  border-bottom: 1px solid rgba(10, 10, 10, 0.1); }

.pell-button {
  background-color: transparent;
  border: none;
  cursor: pointer;
  height: 30px;
  outline: 0;
  width: 30px;
  vertical-align: bottom; }

.pell-button-selected {
  background-color: #F0F0F0; }

</style>
Rockstar0711
  • 47
  • 1
  • 8

2 Answers2

1

You should use arrow functions in the block mapping actions, preserves this from the surrounding scope

var actions = settings.actions
  ? settings.actions.map(action => {                            // arrow function here
      if (typeof action === "string") return this.defaultActions[action];
      else if (this.defaultActions[action.name])
        return _extends({}, this.defaultActions[action.name], action);
      return action
    })
  : Object.keys(this.defaultActions).map(action => {            // arrow function here
      console.log("action", action)
      console.log("sss", this.defaultActions);
      // return this.defaultActions[action];
    });
  • Thanks... It works like charm.. Excuse me, could let me know method that call method in data define part in vue if you have no problem? I stuck this problem now. :) – Rockstar0711 Apr 27 '21 at 20:54
  • Which bit is it? The next problem I see is with `appendChild` method. –  Apr 27 '21 at 21:07
  • To fix that one, change the hook from `created()` to `mounted()`, because you can't manipulate the elements until they exist. –  Apr 27 '21 at 21:11
  • well. yes. you are right. appendchild function has two params - settings.element and content. and setting.element is null because init function is called in created life cycle. I fixed by change it to mounted(). – Rockstar0711 Apr 27 '21 at 21:13
  • Next problem is `actions.forEach(function(action) {` change it to `actions.forEach(action => {` for same reason as original question. –  Apr 27 '21 at 21:16
  • The next problem maybe call method in data. – Rockstar0711 Apr 27 '21 at 21:17
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231674/discussion-between-sammie-and-steve-zodiac). – Rockstar0711 Apr 27 '21 at 21:17
0

Just for reference, you can also use the sample Vue code given in the pell repository
(or parts thereof, e.g styles)

<template>
  <div>
    <h6>Editor:</h6>
    <div id="pell" class="pell" />
    <h6>HTML Output:</h6>
    <pre id="pell-html-output"></pre>
  </div>
</template>

<script>
import pell from 'pell'

export default {
  methods: {
    ensureHTTP: str => /^https?:\/\//.test(str) && str || `http://${str}`
  },
  mounted () {
    pell.init({
      element: document.getElementById('pell'),
      onChange: html => {
        window.document.getElementById('pell-html-output').textContent = html
      },
      actions: [
        'bold', 'italic', 'underline', 'strikethrough', 'heading1', 'heading2',
        'paragraph', 'quote', 'olist', 'ulist', 'code', 'line',
        {
          name: 'image',
          result: () => {
            const url = window.prompt('Enter the image URL')
            if (url) pell.exec('insertImage', this.ensureHTTP(url))
          }
        },
        {
          name: 'link',
          result: () => {
            const url = window.prompt('Enter the link URL')
            if (url) pell.exec('createLink', this.ensureHTTP(url))
          }
        }
      ]
    })
  }
}
</script>

<style>
.pell {
  border: 2px solid #000;
  border-radius: 0;
  box-shadow: none;
}

#pell-html-output {
  margin: 0;
  white-space: pre-wrap;
}
</style>