<template>
  <div v-if="editor" class="rich-text-editor">
    <div class="editor-controls">
      <!-- NOTE: this button shouldn't be here, but without it,
        doubling clicking text result in the first button to be executed (bold in this case) -->
      <button style="display: none" />
      <template v-for="({ type, icon, title, action, isActive, isDisabled }, index) in controls">
        <div v-if="type === 'divider'" class="divider" :key="`divider-${index}`" />
        <button
          v-else-if="shouldShowButton(type)"
          @click="action"
          :class="[{ 'is-active': isActive ? isActive() : null }, { unlink: icon?.includes('unlink') || icon?.includes('close') }]"
          :disabled="isDisabled ? isDisabled() : null"
          :key="`control-${index}`"
          :title="title"
        >
          <component :is="icon ?? type"></component>
        </button>
      </template>
      <div v-if="!!editorControls" class="divider"></div>
      <slot name="custom-controls" />
    </div>

    <editor-content :editor="editor" class="editor-content" />

    <modal ref="link-modal">
      <rich-text-link-modal-content
        v-if="isEditingLink"
        :existingLinkHref="existingLinkHref"
        :existingPageLinkType="existingPageLinkType"
        :existingLinkOpenInTab="existingLinkOpenInTab"
        @submitLink="submitAndCloseLinkModal"
        @deleteLink="removeAndCloseLinkModal"
        @cancel="closeLinkModal"
      />
    </modal>
  </div>
</template>

<script>
import { Editor, EditorContent } from '@tiptap/vue-2';
import { StarterKit } from '@tiptap/starter-kit';
import { Link } from '@tiptap/extension-link';
import { Underline } from '@tiptap/extension-underline';
import { PageLink } from '../rich-text/custom-extensions/extension-page-link';
import { Placeholder } from '@tiptap/extension-placeholder';
import { NonBreakingSpace } from '../rich-text/custom-extensions/extension-non-breaking-space';
import richTextLinkModalContent from '../rich-text/rich-text-link-modal-content.vue';
import Modal from '../modal.vue';

import {
  Bold,
  DoubleQuotes,
  ExternalLink,
  FormatClear,
  Heading1,
  Heading2,
  Heading3,
  Heading4,
  Heading5,
  Heading6,
  Italic,
  Redo,
  ListOrdered,
  ListUnordered,
  Separator,
  Space,
  StrikeThrough,
  UnderlineIcon,
  Undo
} from '../icons';

export default {
  components: {
    Modal,
    EditorContent,
    richTextLinkModalContent,
    Bold,
    DoubleQuotes,
    ExternalLink,
    FormatClear,
    Heading1,
    Heading2,
    Heading3,
    Heading4,
    Heading5,
    Heading6,
    Italic,
    ListOrdered,
    ListUnordered,
    Redo,
    Separator,
    Space,
    StrikeThrough,
    UnderlineIcon,
    Undo
  },
  props: {
    value: {
      required: true,
      validator: (value) => value === null || value === undefined || typeof value === 'string',
      demoValue: ' - Hallo!'
    },
    placeholder: {
      type: String,
      default: '(leeg)'
    },
    editorControls: {
      type: Array,
      demoValue: () => ['bold', 'italic'],
      docs: 'Optie om alleen de knoppen uit dit array te tonen.'
    }
  },
  computed: {
    canSubmitLink() {
      if (this.pageLinkType === 'internal') {
        return this.internalPageLinkGuid !== null;
      } else {
        return this.externalLinkUrl !== null && this.regExpTestUrl(this.externalLinkUrl);
      }
    }
  },
  data() {
    return {
      collapsed: true,
      editor: null,
      isEditingLink: false,
      existingPageLinkType: null,
      existingLinkOpenInTab: true,
      existingLinkHref: null,
      controls: [
        {
          type: 'bold',
          title: 'Bold Ctrl+B',
          action: () => this.editor.chain().focus().toggleBold().run(),
          isActive: () => this.editor.isActive('bold'),
          isDisabled: () => !this.editor.can().chain().focus().toggleBold().run()
        },
        {
          type: 'italic',
          title: 'Italic Ctrl+I',
          action: () => this.editor.chain().focus().toggleItalic().run(),
          isActive: () => this.editor.isActive('italic'),
          isDisabled: () => !this.editor.can().chain().focus().toggleItalic().run()
        },
        {
          type: 'underline-icon',
          title: 'Underline Ctrl+U',
          action: () => this.editor.chain().focus().toggleUnderline().run(),
          isActive: () => this.editor.isActive('underline'),
          isDisabled: () => !this.editor.can().chain().focus().toggleUnderline().run()
        },
        {
          type: 'strike',
          icon: 'strike-through',
          title: 'Tekst doorhalen Ctrl+Shift+X',
          action: () => this.editor.chain().focus().toggleStrike().run(),
          isActive: () => this.editor.isActive('strike'),
          isDisabled: () => !this.editor.can().chain().focus().toggleStrike().run()
        },
        {
          type: 'divider'
        },
        {
          type: 'heading-1',
          title: 'Heading 1',
          action: () => this.editor.chain().focus().toggleHeading({ level: 1 }).run(),
          isActive: () => this.editor.isActive('heading', { level: 1 })
        },
        {
          type: 'heading-2',
          title: 'Heading 2',
          action: () => this.editor.chain().focus().toggleHeading({ level: 2 }).run(),
          isActive: () => this.editor.isActive('heading', { level: 2 })
        },
        {
          type: 'heading-3',
          title: 'Heading 3',
          action: () => this.editor.chain().focus().toggleHeading({ level: 3 }).run(),
          isActive: () => this.editor.isActive('heading', { level: 3 })
        },
        {
          type: 'heading-4',
          title: 'Heading 4',
          action: () => this.editor.chain().focus().toggleHeading({ level: 4 }).run(),
          isActive: () => this.editor.isActive('heading', { level: 4 })
        },
        {
          type: 'heading-5',
          title: 'Heading 5',
          action: () => this.editor.chain().focus().toggleHeading({ level: 5 }).run(),
          isActive: () => this.editor.isActive('heading', { level: 5 })
        },
        {
          type: 'heading-6',
          title: 'Heading 6',
          action: () => this.editor.chain().focus().toggleHeading({ level: 6 }).run(),
          isActive: () => this.editor.isActive('heading', { level: 6 })
        },
        {
          type: 'divider'
        },
        {
          type: 'link',
          icon: 'external-link',
          title: 'Hyperlink',
          action: this.openLinkModal,
          isActive: () => this.editor.isActive('link') || this.editor.isActive('pageLink')
        },
        {
          type: 'divider'
        },
        {
          type: 'bulletList',
          icon: 'list-unordered',
          title: 'Bulleted List',
          action: () => this.editor.chain().focus().toggleBulletList().run(),
          isActive: () => this.editor.isActive('bulletList')
        },
        {
          type: 'orderedList',
          icon: 'list-ordered',
          title: 'Numbered List',
          action: () => this.editor.chain().focus().toggleOrderedList().run(),
          isActive: () => this.editor.isActive('orderedList')
        },
        {
          type: 'blockquote',
          icon: 'double-quotes',
          title: 'Quote',
          action: () => this.editor.chain().focus().toggleBlockquote().run(),
          isActive: () => this.editor.isActive('blockquote')
        },
        {
          type: 'horizontalRule',
          icon: 'separator',
          title: 'Horizontal Rule',
          action: () => this.editor.chain().focus().setHorizontalRule().run(),
          isActive: () => this.editor.isActive('horizontalRule')
        },
        {
          type: 'divider'
        },
        {
          type: 'nbsp',
          icon: 'space',
          title: 'Non-breaking Space &amp;nbsp; Ctrl+Space',
          action: () => this.insertNbsp(),
          isActive: () => this.editor.isActive('nbsp')
        },
        {
          type: 'format-clear',
          title: 'Verwijder alle opmaak',
          action: () => this.editor.chain().focus().clearNodes().unsetAllMarks().run()
        },
        {
          type: 'divider'
        },
        {
          type: 'undo',
          title: 'Undo Ctrl+Z',
          action: () => this.editor.chain().focus().undo().run(),
          isDisabled: () => !this.editor.can().chain().focus().undo().run()
        },
        {
          type: 'redo',
          title: 'Redo Ctrl+Shift+Z',
          action: () => this.editor.chain().focus().redo().run(),
          isDisabled: () => !this.editor.can().chain().focus().redo().run()
        }
      ]
    };
  },
  mounted() {
    this.editor = new Editor({
      extensions: [
        StarterKit,
        Link.configure({
          openOnClick: false
        }),
        PageLink,
        Underline,
        NonBreakingSpace,
        Placeholder.configure({
          placeholder: this.placeholder
        })
      ],
      content: this.value ? JSON.parse(this.value) : this.value,
      onUpdate: () => {
        this.$emit('input', this.editor.isEmpty ? '' : JSON.stringify(this.editor.getJSON()));
      }
    });
  },
  methods: {
    insertNbsp() {
      this.editor.chain().focus().setNonBreakingSpace().run();
      return;
    },
    shouldShowButton(type) {
      return !this.editorControls || this.editorControls.indexOf(type) !== -1;
    },
    openLinkModal() {
      this.resetPageLinkState();
      const link = this.editor.getAttributes('link');
      if (link.href) {
        this.existingLinkHref = link.href;
        this.existingLinkOpenInTab = link.target === '_blank';
        this.existingPageLinkType = 'external';
      } else {
        const pageLink = this.editor.getAttributes('pageLink');
        if (pageLink.href) {
          this.existingLinkHref = pageLink.href;
          this.existingPageLinkType = 'internal';
          this.existingLinkOpenInTab = pageLink.target === '_blank';
        }
      }
      this.$refs['link-modal'].show();
      this.isEditingLink = true;
    },
    closeLinkModal() {
      this.$refs['link-modal'].hide();
      this.resetPageLinkState();
    },
    submitAndCloseLinkModal({ href, type, target }) {
      if (type == 'internal') {
        this.editor.chain().focus().extendMarkRange('pageLink').setPageLink({ href, target }).run();
      } else {
        this.editor.chain().focus().extendMarkRange('link').setLink({ href, target }).run();
      }
      this.closeLinkModal();
    },
    removeAndCloseLinkModal() {
      this.editor.chain().focus().extendMarkRange('link').unsetLink().run();
      this.editor.chain().focus().extendMarkRange('pageLink').unsetPageLink().run();
      this.closeLinkModal();
    },
    resetPageLinkState() {
      this.existingPageLinkType = 'internal';
      this.existingLinkHref = null;
      this.existingLinkOpenInTab = true;
    },
    regExpTestUrl(value) {
      if (!value) return true;
      return new RegExp(/^(http|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&amp;:/~+#-]*[\w@?^=%&amp;/~+#-])?/g).test(value);
    }
  },
  beforeUnmount() {
    this.editor.destroy();
  }
};
</script>

<style lang="scss">
.rich-text-editor {
  position: relative;

  .tiptap {
    padding: 0.5rem 0.75rem;

    &:not(.ProseMirror-focused) {
      background: #f5f5f5;

      .is-editor-empty:first-child::before {
        content: attr(data-placeholder);
        color: #55595c; // similar to .form-control from Bootstrap
        float: left;
        pointer-events: none;
        height: 0;
      }
    }
  }

  .editor-controls {
    align-items: center;
    background: #0d0d0d;
    border-top-left-radius: 0.25rem;
    border-top-right-radius: 0.25rem;
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    padding: 0.25rem;

    button {
      font-size: inherit;
      font-family: inherit;
      background: transparent;
      color: #fff;
      cursor: pointer;
      margin: 0.1rem;
      margin-right: 0.25rem;
      border: none;
      border-radius: 0.4rem;
      height: 1.75rem;
      width: 1.75rem;
      padding: 0.25rem;
      accent-color: black;

      svg {
        vertical-align: baseline;
        fill: currentColor;
        height: 100%;
        width: 100%;
      }

      &:disabled,
      &:hover {
        background-color: #303030;
      }

      &:disabled {
        opacity: 0.75;
      }

      &.is-active {
        background: #0069d9;
      }

      &.is-active.unlink {
        background: #c82333;
      }
    }

    .divider {
      background-color: #ffffff40;
      height: 1.25rem;
      margin-inline: 0.5rem;
      width: 1px;

      + .divider {
        display: none;
      }
    }
  }

  .editor-content {
    background-color: #fff;
    border: 1px solid #ced4da;
    border-bottom-left-radius: 0.25rem;
    border-bottom-right-radius: 0.25rem;
    resize: vertical;
    overflow: auto;

    /* Basic editor styles */
    .ProseMirror {
      p:only-child {
        margin-bottom: 0;
        padding-bottom: 1rem;
      }

      > * + * {
        margin-top: 0.75em;
      }

      ul,
      ol {
        padding: 0 1rem;
      }

      h1,
      h2,
      h3,
      h4,
      h5,
      h6 {
        line-height: 1.1;
      }

      code {
        position: relative;
        background-color: rgba(#616161, 0.1);
        color: #616161;
      }

      pre {
        background: #0d0d0d;
        color: #fff;
        font-family: 'JetBrainsMono', monospace;
        padding: 0.75rem 1rem;
        border-radius: 0.5rem;

        code {
          position: relative;
          color: inherit;
          padding: 0;
          background: none;
          font-size: 0.8rem;
        }
      }

      img {
        max-width: 100%;
        height: auto;
      }

      blockquote {
        padding-left: 1rem;
        border-left: 2px solid rgba(#0d0d0d, 0.1);
      }

      hr {
        border: none;
        border-top: 2px solid rgba(#0d0d0d, 0.1);
        margin: 2rem 0;
      }

      &:focus {
        outline: none;
      }
    }
  }
}
</style>
