Removing unused keys

Removing unused keys

<template>

  <router-link :to="link" class="content-card" :style="{ backgroundColor: $themeTokens.surface }">

    <CardThumbnail
      class="thumbnail"
      :thumbnail="thumbnail"
      :kind="kind"
      :isMobile="true"
    />

        {{ title }}

        {{ message }}

      <TextTruncator
        :text="description"
        :maxHeight="80"
        class="description"
      />
      <CoachContentLabel
        class="coach-content-label"
        :value="numCoachContents"
        :isTopic="isTopic"
      />

        <KRouterLink
          v-if="!isTopic"
          :text="$tr('viewLabel')"
          :to="link"
        />

  </router-link>

</template>


bug fix

bug fix

<template>

Characters

        <router-link class="card" :to="'/character/' + character.id" v-for="(character, index) in createdCharacters" v-bind:key="index">

{{ character.name }}

{{ character.className }}

        </router-link>

= 5">You've hit max characters, kill one to give another life!

{{ cClass.name }}

            {{ cClass.flavorText }}

          <router-link :to="'/characters/new/' + cClass.id" class="ui primary button" v-if="characterCount <= 4">Create {{ cClass.name }}</router-link>

</template>

<template>

Characters

        <router-link class="card" :to="'/character/' + character.id" v-for="(character, index) in createdCharacters" v-bind:key="index">

{{ character.name }}

{{ character.className }}

        </router-link>

= 5">You've hit max characters, kill one to give another life!

{{ cClass.name }}

            {{ cClass.flavorText }}

          <router-link :to="'/characters/new/' + cClass.id" class="ui primary button" v-if="characterCount < 4">Create {{ cClass.name }}</router-link>

</template>


Fix open productions page layout when there is a lot of productions

<template>

      <spinner />

 0">

        {{ $t('productions.home.title') }}

 4
        }"
      >

          <router-link
            :to="getPath(production)"
          >

                {{ generateAvatar(production) }}

              {{ production.name }}

          </router-link>
          <router-link
            class="secondary"
            :to="assetsPath(production)"
          >
            {{ $t('assets.title') }}
          </router-link>
          -
          <router-link
            class="secondary"
            :to="shotsPath(production)"
          >
            {{ $t('shots.title') }}
          </router-link>

          {{ $t('productions.home.create_new') }}

        {{ $t('productions.home.welcome') }}

          {{ $t('productions.home.empty') }}

          <button-simple
            class="level-item big-button"
            :text="$t('productions.home.create_new')"
            :is-responsive="false"
            @click="showNewModal"
          />

          {{ $t('productions.home.no_prod_for_client') }}

          {{ $t('productions.home.no_task') }}

    <edit-production-modal
      :active="modals.isNewDisplayed"
      :is-loading="editProduction.isLoading"
      :is-error="editProduction.isError"
      @confirm="confirmEditProduction"
      @cancel="hideNewModal"
    />

</template>


Support for string on "close" attributes

<script>
  export default {
    props: {
      noLinkClass: Boolean,
      noFastclick: Boolean,

      external: Boolean,
      color: String,
      bg: String,
      theme: String,
      text: String,
      iconOnly: Boolean,
      icon: String,
      iconMaterial: String,
      iconIon: String,
      iconFa: String,
      iconF7: String,
      iconIfMaterial: String,
      iconIfIos: String,
      iconSize: [String, Number],
      rippleColor: String,
      href: {
        type: String,
        default: '#'
      },

      // Button
      round: Boolean,
      fill: Boolean,
      big: Boolean,
      raised: Boolean,

      // Router
      force: Boolean,
      reload: Boolean,
      animatePages: Boolean,
      ignoreCache: Boolean,
      pageName: String,
      template: String,

      // View
      view: String,

      // Panel
      openPanel: [String, Boolean],
      closePanel: Boolean,

      // Popup
      openPopup: [String, Boolean],
      closePopup: [String, Boolean],

      // Popover
      openPopover: [String, Boolean],
      closePopover: [String, Boolean],

      // Login Screen
      openLoginScreen: [String, Boolean],
      closeLoginScreen: [String, Boolean],

      // Picker
      openPicker: [String, Boolean],
      closePicker: [String, Boolean],

      // Tab
      tabLink: [Boolean, String],

      // Sortable
      openSortable: [String, Boolean],
      closeSortable: [String, Boolean],
      toggleSortable: [String, Boolean],

      // Active
      active: Boolean,

      // Badge
      badge: [String, Number],
      iconBadge: [String, Number],
      badgeColor: [String],

      // Back Link
      back: Boolean
    },
    computed: {
      attrsObject: function () {
        var self = this;
        var ao = {
          href: self.href
        };
        var pd = self.$options.propsData;
        if ('force' in pd) ao['data-force'] = self.force;
        if ('reload' in pd) ao['data-reload'] = 'true';
        if ('animatePages' in pd) ao['data-animate-pages'] = 'true';
        if ('ignoreCache' in pd) ao['data-ignore-cache'] = 'true';
        if (self.pageName) ao['data-page-name'] = self.pageName;
        if (self.template) ao['data-template'] = self.template;
        if (self.view) ao['data-view'] = self.view;

        if (typeof self.openPanel === 'string') ao['data-panel'] = self.openPanel;
        if (typeof self.openPopup === 'string') ao['data-popup'] = self.openPopup;
        if (typeof self.openPopover === 'string') ao['data-popover'] = self.openPopover;
        if (typeof self.openPicker === 'string') ao['data-picker'] = self.openPicker;
        if (typeof self.openLoginScreen === 'string') ao['data-login-screen'] = self.openLoginScreen;
        if (typeof self.openSortable === 'string') ao['data-sortable'] = self.openSortable;
        if (typeof self.toggleSortable === 'string') ao['data-sortable'] = self.toggleSortable;

        if (typeof self.closePopup === 'string') ao['data-popup'] = self.closePopup;
        if (typeof self.closePopover === 'string') ao['data-popover'] = self.closePopover;
        if (typeof self.closePicker === 'string') ao['data-picker'] = self.closePicker;
        if (typeof self.closeLoginScreen === 'string') ao['data-login-screen'] = self.closeLoginScreen;
        if (typeof self.closeSortable === 'string') ao['data-sortable'] = self.closeSortable;

        if (typeof self.tabLink === 'string') ao['data-tab'] = self.tabLink;
        return ao;
      },
      classesObject: function () {
        var self = this;
        var co = {};
        var pd = self.$options.propsData;
        if (self.rippleColor) co['ripple-color-' + self.rippleColor] = true;
        if (self.color) co['color-' + self.color] = true;
        if (self.theme) co['theme-' + self.theme] = true;
        if (self.bg) co['bg-' + self.bg] = true;

        co['back'] = self.back;
        co['external'] = self.external;
        co['no-fastclick'] = self.noFastclick;

        // Button
        ['round', 'fill', 'big', 'raised'].forEach(function (prop, index) {
          if (self[prop]) co['button-' + prop] = true;
        });

        // Active
        co['active'] = self.active;

        // Panel
        if (self.closePanel) co['close-panel'] = true;
        if (self.openPanel) co['open-panel'] = true;

        // Popup
        if (self.closePopup) co['close-popup'] = true;
        if (self.openPopup) co['open-popup'] = true;

        // Popover
        if (self.closePopover) co['close-popover'] = true;
        if (self.openPopover) co['open-popover'] = true;

        // Picker
        if (self.closePicker) co['close-picker'] = true;
        if (self.openPicker) co['open-picker'] = true;

        // Login Screen
        if (self.closeLoginScreen) co['close-login-screen'] = true;
        if (self.openLoginScreen) co['open-login-screen'] = true;

        // Sortable
        if (self.closeSortable) co['close-sortable'] = true;
        if (self.openSortable) co['open-sortable'] = true;
        if (self.toggleSortable) co['toggle-sortable'] = true;

        // Tab
        if (self.tabLink) co['tab-link'] = true;

        return co;
      }
    },
    methods: {
      onClick: function (event) {
        this.$emit('click', event);
      }
    }
  }
</script>

<script>
  export default {
    props: {
      noLinkClass: Boolean,
      noFastclick: Boolean,

      external: Boolean,
      color: String,
      bg: String,
      theme: String,
      text: String,
      iconOnly: Boolean,
      icon: String,
      iconMaterial: String,
      iconIon: String,
      iconFa: String,
      iconF7: String,
      iconIfMaterial: String,
      iconIfIos: String,
      rippleColor: String,
      href: {
        type: String,
        default: '#'
      },

      // Button
      round: Boolean,
      fill: Boolean,
      big: Boolean,
      raised: Boolean,

      // Router
      force: Boolean,
      reload: Boolean,
      animatePages: Boolean,
      ignoreCache: Boolean,
      pageName: String,
      template: String,

      // View
      view: String,

      // Panel
      openPanel: [String, Boolean],
      closePanel: Boolean,

      // Popup
      openPopup: [String, Boolean],
      closePopup: Boolean,

      // Popover
      openPopover: [String, Boolean],
      closePopover: Boolean,

      // Login Screen
      openLoginScreen: [String, Boolean],
      closeLoginScreen: Boolean,

      // Picker
      openPicker: [String, Boolean],
      closePicker: Boolean,

      // Tab
      tabLink: [Boolean, String],

      // Sortable
      openSortable: [String, Boolean],
      closeSortable: Boolean,
      toggleSortable: [String, Boolean],

      // Active
      active: Boolean,

      // Badge
      badge: [String, Number],
      iconBadge: [String, Number],
      badgeColor: [String],

      // Back Link
      back: Boolean
    },
    computed: {
      attrsObject: function () {
        var self = this;
        var ao = {
          href: self.href
        };
        var pd = self.$options.propsData;
        if ('force' in pd) ao['data-force'] = self.force;
        if ('reload' in pd) ao['data-reload'] = 'true';
        if ('animatePages' in pd) ao['data-animate-pages'] = 'true';
        if ('ignoreCache' in pd) ao['data-ignore-cache'] = 'true';
        if (self.pageName) ao['data-page-name'] = self.pageName;
        if (self.template) ao['data-template'] = self.template;
        if (self.view) ao['data-view'] = self.view;

        if (typeof self.openPanel === 'string') ao['data-panel'] = self.openPanel;
        if (typeof self.openPopup === 'string') ao['data-popup'] = self.openPopup;
        if (typeof self.openPopover === 'string') ao['data-popover'] = self.openPopover;
        if (typeof self.openPicker === 'string') ao['data-picker'] = self.openPicker;
        if (typeof self.openLoginScreen === 'string') ao['data-login-screen'] = self.openLoginScreen;
        if (typeof self.openSortable === 'string') ao['data-sortable'] = self.openSortable;
        if (typeof self.toggleSortable === 'string') ao['data-sortable'] = self.toggleSortable;
        if (typeof self.tabLink === 'string') ao['data-tab'] = self.tabLink;
        return ao;
      },
      classesObject: function () {
        var self = this;
        var co = {};
        var pd = self.$options.propsData;
        if (self.rippleColor) co['ripple-color-' + self.rippleColor] = true;
        if (self.color) co['color-' + self.color] = true;
        if (self.theme) co['theme-' + self.theme] = true;
        if (self.bg) co['bg-' + self.bg] = true;

        co['back'] = self.back;
        co['external'] = self.external;
        co['no-fastclick'] = self.noFastclick;

        // Button
        ['round', 'fill', 'big', 'raised'].forEach(function (prop, index) {
          if (self[prop]) co['button-' + prop] = true;
        });

        // Active
        co['active'] = self.active;

        // Panel
        if (self.closePanel) co['close-panel'] = true;
        if (self.openPanel) co['open-panel'] = true;

        // Popup
        if (self.closePopup) co['close-popup'] = true;
        if (self.openPopup) co['open-popup'] = true;

        // Popover
        if (self.closePopover) co['close-popover'] = true;
        if (self.openPopover) co['open-popover'] = true;

        // Picker
        if (self.closePicker) co['close-picker'] = true;
        if (self.openPicker) co['open-picker'] = true;

        // Login Screen
        if (self.closeLoginScreen) co['close-login-screen'] = true;
        if (self.openLoginScreen) co['open-login-screen'] = true;

        // Sortable
        if (self.closeSortable) co['close-sortable'] = true;
        if (self.openSortable) co['open-sortable'] = true;
        if (self.toggleSortable) co['toggle-sortable'] = true;

        // Tab
        if (self.tabLink) co['tab-link'] = true;

        return co;
      }
    },
    methods: {
      onClick: function (event) {
        this.$emit('click', event);
      }
    }
  }
</script>


Adding single-quote wrap around intermixed user-generated strings.

<template>

      {{ content.title }}

      <template v-if="description">

          {{ $tr('descriptionDataHeader') }}

          '{{ description }}'

      </template>
      <template v-if="author">

          {{ $tr('authorDataHeader') }}

          '{{ content.author }}'

      </template>
      <template v-if="license">

          {{ $tr('licenseDataHeader') }}

          '{{ content.license_name }}'

      </template>
      <template v-if="copyrightHolder">

          {{ $tr('copyrightHolderDataHeader') }}

          '{{ content.license_owner }}'

      </template>

</template>

<template>

      {{ content.title }}

      <template v-if="description">

          {{ $tr('descriptionDataHeader') }}

          {{ description }}

      </template>
      <template v-if="author">

          {{ $tr('authorDataHeader') }}

          {{ content.author }}

      </template>
      <template v-if="license">

          {{ $tr('licenseDataHeader') }}

          {{ content.license_name }}

      </template>
      <template v-if="copyrightHolder">

          {{ $tr('copyrightHolderDataHeader') }}

          {{ content.license_owner }}

      </template>

</template>


Fix demo

Fix demo

<template lang="pug">
.group.is-wrap
  mixin btn-basic(style)
    button.btn(class=style) 戻る
    button.btn.is-primary(class=style) 決定
    button.btn.is-success(class=style) 登録
    button.btn.is-danger(class=style) 削除
    button.btn.is-disable(class=style) 不可
    button.btn.is-primary(class=style)
      i.fas.fa-flag(aria-hidden="true")
      span.text FontAwesome
    button.btn.is-success(class=style)
      span.text FontAwesome
      i.fas.fa-angle-right.is-fit(aria-hidden="true")
    button.btn.is-primary(class=style)
      i.material-icons face
      span.text Material Icons
    button.btn.is-success(class=style)
      span.text Material Icons
      i.material-icons keyboard_arrow_right
  mixin btn-icon(style)
    button.btn(class=style)
      i.fas.fa-angle-left(aria-hidden="true")
    button.btn(class=style).is-primary
      i.fab.fa-twitter(aria-hidden="true")
    button.btn(class=style).is-success
      i.fas.fa-save(aria-hidden="true")
    button.btn(class=style).is-danger
      i.fas.fa-times(aria-hidden="true")
    button.btn(class=style).is-disable
      i.fas.fa-plus(aria-hidden="true")
  .inner
    .group.is-space(v-if="demo === 'plain'")
      .btns
        +btn-basic("is-plain")
    .group.is-space(v-else-if="demo === 'melt'")
      .btns
        +btn-basic("is-melt")
    .group.is-space(v-else-if="demo === 'outline'")
      .btns
        +btn-basic("is-outline")
    .group.is-space(v-else-if="demo === 'ghost'")
      .group.is-wrap.is-paint
        .btns
          +btn-basic("is-ghost")
    .group.is-space(v-else-if="demo === 'floating'")
      .btns
        +btn-basic("is-plain is-floating")
    .group.is-space(v-else-if="demo === 'round'")
      .btns
        +btn-basic("is-plain is-round")
      .group.is-wrap.is-paint
        .btns
          +btn-basic("is-ghost is-round")
    .group.is-space(v-else-if="demo === 'circle'")
      .btns
        +btn-icon("is-plain is-circle")
        +btn-icon("is-outline is-circle")
      .group.is-wrap.is-paint
        .btns
          +btn-icon("is-ghost is-circle")
    .group.is-space(v-else-if="demo === 'size'")
      .btns
        button.btn.is-plain.is-xxs 極小
        button.btn.is-plain.is-xs 小さい
        button.btn.is-plain.is-sm 小さめ
        button.btn.is-plain.is-md 中くらい
        button.btn.is-plain.is-lg 大きめ
        button.btn.is-plain.is-xl 大きい
        button.btn.is-plain.is-xxl 特大
      .btns.is-xxs
        - for (var i = 1; i <= 5; i++)
          button.btn.is-plain まとめて極小
    .group.is-space(v-else-if="demo === 'flex'")
      .btns
        +btn-basic("is-plain is-mobile-full is-tablet-0")
      .btns
        +btn-basic("is-outline is-mobile-full is-tablet-0")
      .group.is-wrap.is-paint
        .btns
          +btn-basic("is-ghost is-mobile-full is-tablet-0")
    .group.is-space(v-else-if="demo === 'position'")
      each val in ["left", "center", "right"]
        .btns(class="is-"+val)
          - for (var i = 1; i <= 3; i++)
            button.btn.is-plain= ".is-" + val
    .group.is-space(v-else-if="demo === 'bar'")
      .btns.is-bar.is-sm
        button.btn.is-plain
          i.fas.fa-align-left(aria-hidden="true")
          span.text 左寄せ
        button.btn.is-plain
          i.fas.fa-align-center(aria-hidden="true")
          span.text 中央寄せ
        button.btn.is-plain
          i.fas.fa-align-right(aria-hidden="true")
          span.text 右寄せ
      .btns.is-bar.is-sm
        button.btn.is-outline.is-mobile-0
          i.fas.fa-align-left(aria-hidden="true")
          span.text 左寄せ
        button.btn.is-outline.is-mobile-0
          i.fas.fa-align-center(aria-hidden="true")
          span.text 中央寄せ
        button.btn.is-outline.is-mobile-0
          i.fas.fa-align-right(aria-hidden="true")
          span.text 右寄せ
      .btns.is-bar.is-sm
        button.btn.is-outline.is-round.is-mobile-0 ラウンド
        button.btn.is-outline.is-round.is-mobile-0 ラウンド
        button.btn.is-outline.is-round.is-mobile-0 ラウンド
      .group.is-wrap.is-paint
        .btns
          .btns.is-bar.is-sm
            button.btn.is-ghost 松
            button.btn.is-ghost 桜
            button.btn.is-ghost 芒
            button.btn.is-ghost 雨
            button.btn.is-ghost 桐
          .btns.is-bar.is-sm
            button.btn.is-ghost 線と角丸
            button.btn.is-ghost 自動調整
          .btns.is-bar.is-sm
            button.btn.is-ghost.is-round ラウンド
            button.btn.is-ghost.is-round ラウンド
</template>

<script>
export default {
  props: {
    demo: {
      type: String,
      default: ""
    }
  }
};
</script>

<template lang="pug">
.group.is-wrap
  mixin btn-basic(style)
    button.btn(class=style) 戻る
    button.btn.is-primary(class=style) 決定
    button.btn.is-success(class=style) 登録
    button.btn.is-danger(class=style) 削除
    button.btn.is-disable(class=style) 不可
    button.btn.is-primary(class=style)
      i.fas.fa-flag(aria-hidden="true")
      span.text FontAwesome
    button.btn.is-success(class=style)
      span.text FontAwesome
      i.fas.fa-angle-right.is-fit(aria-hidden="true")
    button.btn.is-primary(class=style)
      i.material-icons face
      span.text Material Icons
    button.btn.is-success(class=style)
      span.text Material Icons
      i.material-icons keyboard_arrow_right
  mixin btn-icon(style)
    button.btn(class=style)
      i.fas.fa-angle-left(aria-hidden="true")
    button.btn(class=style).is-primary
      i.fab.fa-twitter(aria-hidden="true")
    button.btn(class=style).is-success
      i.fas.fa-save(aria-hidden="true")
    button.btn(class=style).is-danger
      i.fas.fa-times(aria-hidden="true")
    button.btn(class=style).is-disable
      i.fas.fa-plus(aria-hidden="true")
  .inner
    .group.is-space(v-if="demo === 'plain'")
      .btns
        +btn-basic("is-plain")
    .group.is-space(v-else-if="demo === 'melt'")
      .btns
        +btn-basic("is-melt")
    .group.is-space(v-else-if="demo === 'outline'")
      .btns
        +btn-basic("is-outline")
    .group.is-space(v-else-if="demo === 'ghost'")
      .group.is-wrap.is-paint
        .btns
          +btn-basic("is-ghost")
    .group.is-space(v-else-if="demo === 'floating'")
      .btns
        +btn-basic("is-plain is-floating")
    .group.is-space(v-else-if="demo === 'round'")
      .btns
        +btn-basic("is-plain is-round")
      .group.is-wrap.is-paint
        .btns
          +btn-basic("is-ghost is-round")
    .group.is-space(v-else-if="demo === 'circle'")
      .btns
        +btn-icon("is-plain is-circle")
        +btn-icon("is-outline is-circle")
      .group.is-wrap.is-paint
        .btns
          +btn-icon("is-ghost is-circle")
    .group.is-space(v-else-if="demo === 'size'")
      .btns
        button.btn.is-plain.is-xxs 極小
        button.btn.is-plain.is-xs 小さい
        button.btn.is-plain.is-sm 小さめ
        button.btn.is-plain.is-md 中くらい
        button.btn.is-plain.is-lg 大きめ
        button.btn.is-plain.is-xl 大きい
        button.btn.is-plain.is-xxl 特大
      .btns.is-xxs
        - for (var i = 1; i <= 5; i++)
          button.btn.is-plain.is-xxs まとめて極小
    .group.is-space(v-else-if="demo === 'flex'")
      .btns
        +btn-basic("is-plain is-mobile-full is-tablet-0")
      .btns
        +btn-basic("is-outline is-mobile-full is-tablet-0")
      .group.is-wrap.is-paint
        .btns
          +btn-basic("is-ghost is-mobile-full is-tablet-0")
    .group.is-space(v-else-if="demo === 'position'")
      each val in ["left", "center", "right"]
        .btns(class="is-"+val)
          - for (var i = 1; i <= 3; i++)
            button.btn.is-plain= ".is-" + val
    .group.is-space(v-else-if="demo === 'bar'")
      .btns.is-bar.is-sm
        button.btn.is-plain
          i.fas.fa-align-left(aria-hidden="true")
          span.text 左寄せ
        button.btn.is-plain
          i.fas.fa-align-center(aria-hidden="true")
          span.text 中央寄せ
        button.btn.is-plain
          i.fas.fa-align-right(aria-hidden="true")
          span.text 右寄せ
      .btns.is-bar.is-sm
        button.btn.is-outline.is-mobile-0
          i.fas.fa-align-left(aria-hidden="true")
          span.text 左寄せ
        button.btn.is-outline.is-mobile-0
          i.fas.fa-align-center(aria-hidden="true")
          span.text 中央寄せ
        button.btn.is-outline.is-mobile-0
          i.fas.fa-align-right(aria-hidden="true")
          span.text 右寄せ
      .btns.is-bar.is-sm
        button.btn.is-outline.is-round.is-mobile-0 ラウンド
        button.btn.is-outline.is-round.is-mobile-0 ラウンド
        button.btn.is-outline.is-round.is-mobile-0 ラウンド
      .group.is-wrap.is-paint
        .btns
          .btns.is-bar.is-sm
            button.btn.is-ghost 松
            button.btn.is-ghost 桜
            button.btn.is-ghost 芒
            button.btn.is-ghost 雨
            button.btn.is-ghost 桐
          .btns.is-bar.is-sm
            button.btn.is-ghost 線と角丸
            button.btn.is-ghost 自動調整
          .btns.is-bar.is-sm
            button.btn.is-ghost.is-round ラウンド
            button.btn.is-ghost.is-round ラウンド
</template>

<script>
export default {
  props: {
    demo: {
      type: String,
      default: ""
    }
  }
};
</script>


update aliases

update aliases

<template>

        <componentdoc :component="meta.component" :events="meta.events" :slots="meta.slots"></componentdoc>

                Trying to get native browser events working on your component? Use the
                .native
                modifier to capture browser native events such as:
                @click.native="...",
                @mouseover.native="...", etc. See the the official
                Vue.js documentation
                for more information.

        <componentdoc :component="component" :key="component" v-for="component in meta.components"></componentdoc>

</template>


hrrm css oddities

hrrm css oddities

<script lang="coffee">
import Records from '@/shared/services/records'
import Session from '@/shared/services/session'
import AppConfig from '@/shared/services/app_config'
import FileUploader from '@/shared/services/file_uploader'
import FilesList from './files_list.vue'
import EventBus from '@/shared/services/event_bus'
import I18n from '@/i18n'
import { convertToMd } from '@/shared/services/format_converter'

import Blockquote from '@tiptap/extension-blockquote'
import Bold from '@tiptap/extension-bold'
import BulletList from '@tiptap/extension-bullet-list'
import CodeBlock from '@tiptap/extension-code-block'
import Code from '@tiptap/extension-code'
import CharacterCount from '@tiptap/extension-character-count'
import Document from '@tiptap/extension-document'
import Dropcursor from '@tiptap/extension-dropcursor'
import GapCursor from '@tiptap/extension-gapcursor'
import HardBreak from '@tiptap/extension-hard-break'
import Heading from '@tiptap/extension-heading'
import Highlight from '@tiptap/extension-highlight'
import History from '@tiptap/extension-history'
import HorizontalRule from '@tiptap/extension-horizontal-rule'
import Italic from '@tiptap/extension-italic'
import Link from '@tiptap/extension-link'
import ListItem from '@tiptap/extension-list-item'
import OrderedList from '@tiptap/extension-ordered-list'
import Paragraph from '@tiptap/extension-paragraph'
import Placeholder from '@tiptap/extension-placeholder'
import Strike from '@tiptap/extension-strike'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
import TableRow from '@tiptap/extension-table-row'
import Table from '@tiptap/extension-table'
import TaskList from '@tiptap/extension-task-list'
import {CustomTaskItem} from './extension_custom_task_item'
import TextStyle from '@tiptap/extension-text-style'
import TextAlign from '@tiptap/extension-text-align'
import Text from '@tiptap/extension-text'
import Typography from '@tiptap/extension-typography'
import Underline from '@tiptap/extension-underline'
import {CustomMention} from './extension_mention'
import {CustomImage} from './extension_image'
import {TextColor} from './extension_text_color'
import {Iframe} from './extension_iframe'

import { Editor, EditorContent, VueRenderer } from '@tiptap/vue-2'

import {getEmbedLink} from '@/shared/helpers/embed_link.coffee'

import { CommonMentioning, HtmlMentioning, MentionPluginConfig } from './mentioning.coffee'
import SuggestionList from './suggestion_list'
import Attaching from './attaching.coffee'
import {compact, uniq, throttle, difference, reject, uniqBy} from 'lodash'
import TextHighlightBtn from './text_highlight_btn'
import TextAlignBtn from './text_align_btn'

export default
  mixins: [CommonMentioning, HtmlMentioning, Attaching]
  props:
    model: Object
    field: String
    label: String
    placeholder: String
    maxLength: Number
    shouldReset: Boolean
    autofocus: Boolean

  components: {
    EditorContent
    TextAlignBtn
    TextHighlightBtn
    SuggestionList
    FilesList
  }

  data: ->
    loading: true
    socket: null
    count: 0
    editor: null
    expanded: true
    closeEmojiMenu: false
    linkUrl: ""
    iframeUrl: ""
    linkDialogIsOpen: false
    iframeDialogIsOpen: false
    fetchedUrls: []

  computed:
    format: ->
      @model["#{@field}Format"]

  mounted: ->
    # @expanded = Session.user().experiences['html-editor.expanded']
    @editor = new Editor
      editorProps:
        scrollThreshold: 100
        scrollMargin: 100
      extensions: [
        Blockquote
        Bold
        BulletList
        CodeBlock
        CustomMention.configure(MentionPluginConfig.bind(@)())
        CustomImage.configure({attachFile: @attachFile, attachImageFile: @attachImageFile}),
        Document
        Dropcursor
        GapCursor
        Heading
        Highlight.configure(multicolor: true)
        History
        HorizontalRule
        Italic
        Iframe
        Link
        ListItem
        OrderedList
        Paragraph
        Strike
        Text
        Table
        TableHeader
        TableRow
        TableCell
        TaskList
        CustomTaskItem
        Typography
        TextAlign
        TextStyle
        TextColor
        Underline
      ]
      content: @model[@field]
      onUpdate: @updateModel

  watch:
    'shouldReset': 'reset'

  methods:
    setCount: (count) ->
      @count = count

    tiptapAddress: ->
      if @model.isNew()
        compact([AppConfig.theme.channels_uri, 'tiptap', @model.constructor.singular, 'new', @model.groupId, @model.discussionId, @model.parentId, Session.user().secretToken]).join('/')
      else
        [AppConfig.theme.channels_uri, 'tiptap', @model.constructor.singular, @model.id, (@model.secretToken || Session.user().secretToken)].join('/')

    selectedText: ->
      state = @editor.state
      selection = @editor.state.selection
      { from, to } = selection
      state.doc.textBetween(from, to, ' ')

    reset: ->
      @editor.chain().clearContent().run()
      @resetFiles()

    convertToMd: ->
      if confirm I18n.t('formatting.markdown_confirm')
        convertToMd(@model, @field)
        Records.users.saveExperience('html-editor.uses-markdown')

    toggleExpanded: ->
      if !@expanded
        @expanded = true
        Records.users.saveExperience('html-editor.expanded')
      else
        @expanded = false
        Records.users.removeExperience('html-editor.expanded')

    setLinkUrl: ->
      if @linkUrl
        @linkUrl = "http://".concat(@linkUrl) unless @linkUrl.includes("://")
        @editor.chain().setLink(href: @linkUrl).focus().run()
        @fetchLinkPreviews([@linkUrl])
        @linkUrl = null
      @linkDialogIsOpen = false

    setIframeUrl: () ->
      @editor.chain().setIframe(src: getEmbedLink(@iframeUrl)).focus().run()
      @iframeUrl = null
      @iframeDialogIsOpen = false

    emojiPicked: (shortcode, unicode) ->
      @editor.chain()
          .insertContent(unicode)
          .focus()
          .run()
      @closeEmojiMenu = false

    updateModel: ->
      @model[@field] = @editor.getHTML()
      @updateFiles()
      @scrapeLinkPreviews() if @model.isNew()

    removeLinkPreview: (url) ->
      @model.linkPreviews = reject(@model.linkPreviews, (p) -> p.url == url)

    scrapeLinkPreviews: throttle ->
      parser = new DOMParser()
      doc = parser.parseFromString(@model[@field], 'text/html')
      @fetchLinkPreviews difference((Array.from(doc.querySelectorAll('a')).map (el) => el.href), @fetchedUrls)
    ,
      500

    fetchLinkPreviews: (urls) ->
      if urls.length
        @fetchedUrls = uniq @fetchedUrls.concat(urls)
        Records.remote.post('link_previews', {urls: urls}).then (data) =>
          @model.linkPreviews = uniqBy(@model.linkPreviews.concat(data.previews), 'url')

  beforeDestroy: ->
    # @editor.destroy() if @editor
    # @socket.close() if @socket

</script>

<template lang="pug">
div
  //- template(v-if="!editor || loading")
  //-   | Connecting to socket server …
  .editor.mb-3(v-if="editor")
    editor-content.html-editor__textarea(ref="editor" :editor='editor').lmo-markdown-wrapper
    .menubar
      div
        v-layout(align-center v-if="editor.isActive('table')")
          v-btn(icon @click="editor.chain().deleteTable().focus().run()" :title="$t('formatting.remove_table')")
            v-icon mdi-table-remove
          v-btn(icon @click="editor.chain().addColumnBefore().focus().run()" :title="$t('formatting.add_column_before')")
            v-icon mdi-table-column-plus-before
          v-btn(icon @click="editor.chain().addColumnAfter().focus().run()" :title="$t('formatting.add_column_after')")
            v-icon mdi-table-column-plus-after
          v-btn(icon @click="editor.chain().deleteColumn().focus().run()" :title="$t('formatting.remove_column')")
            v-icon mdi-table-column-remove
          v-btn(icon @click="editor.chain().addRowBefore().focus().run()" :title="$t('formatting.add_row_before')")
            v-icon mdi-table-row-plus-before
          v-btn(icon @click="editor.chain().addRowAfter().focus().run()" :title="$t('formatting.add_row_after')")
            v-icon mdi-table-row-plus-after
          v-btn(icon @click="editor.chain().deleteRow().focus().run()" :title="$t('formatting.remove_row')")
            v-icon mdi-table-row-remove
          v-btn(icon @click="editor.chain().mergeOrSplit().focus().run()" :title="$t('formatting.merge_selected')")
            v-icon mdi-table-merge-cells

        v-layout.py-2.justify-space-between.flex-wrap(align-center)
          section.d-flex.flex-wrap(:aria-label="$t('formatting.formatting_tools')")
            //- attach
            v-btn(icon @click='$refs.filesField.click()' :title="$t('formatting.attach')")
              v-icon mdi-paperclip

            v-btn(icon @click='$refs.imagesField.click()' :title="$t('formatting.insert_image')")
              v-icon mdi-image

            //- link
            v-menu(:close-on-content-click="!selectedText()" v-model="linkDialogIsOpen" min-width="320px")
              template(v-slot:activator="{on, attrs}")
                template(v-if="editor.isActive('link')")
                  v-btn(icon @click="editor.chain().toggleLink().focus().run()" outlined :title="$t('formatting.link')")
                    v-icon mdi-link-variant
                template(v-else)
                  v-btn(icon v-on="on" v-bind="attrs" :title="$t('formatting.link')")
                    v-icon mdi-link-variant
              v-card
                template(v-if="selectedText()")
                  v-card-title.title(v-t="'text_editor.insert_link'")
                  v-card-text
                    v-text-field(type="url" label="https://www.example.com" v-model="linkUrl" autofocus ref="focus" v-on:keyup.enter="setLinkUrl()")
                  v-card-actions
                    v-spacer
                    v-btn(color="primary" @click="setLinkUrl()" v-t="'common.action.apply'")
                template(v-else)
                  v-card-title(v-t="'text_editor.select_text_to_link'")

            //- emoji
            v-menu(:close-on-content-click="false" v-model="closeEmojiMenu")
              template(v-slot:activator="{on, attrs}")
                v-btn.emoji-picker__toggle(v-on="on" v-bind="attrs" icon :title="$t('formatting.insert_emoji')")
                  v-icon mdi-emoticon-outline
              emoji-picker(:insert="emojiPicked")

            template(v-if="expanded")
              //- v-btn(icon @click='editor.chain().focus().setParagraph().run()' :outlined="editor.isActive('paragraph')" :title="$t('formatting.paragraph')")
              //-   v-icon mdi-format-pilcrow
              template(v-for="i in 3")
                v-btn(icon @click='editor.chain().focus().toggleHeading({ level: i }).run()' :outlined="editor.isActive('heading', { level: i })" :title="$t('formatting.heading'+i)")
                  v-icon {{'mdi-format-header-'+i}}

            //- bold
            v-btn(icon v-if="expanded" @click='editor.chain().toggleBold().focus().run()' :outlined="editor.isActive('bold')" :title="$t('formatting.bold')")
              v-icon mdi-format-bold

            //- italic
            v-btn(icon v-if="expanded" @click='editor.chain().toggleItalic().focus().run()' :outlined="editor.isActive('italic')" :title="$t('formatting.italicize')")
              v-icon mdi-format-italic
            //-
            //- //- strikethrough
            //- v-btn(icon v-if="expanded" @click='editor.chain().toggleStrike().focus().run()' :outlined="editor.isActive('strike')"  :title="$t('formatting.strikethrough')")
            //-   v-icon mdi-format-strikethrough
            //- //- underline
            v-btn(icon v-if="expanded" @click='editor.chain().toggleUnderline().focus().run()' :outlined="editor.isActive('underline')",  :title="$t('formatting.underline')")
              v-icon mdi-format-underline
            //-
            text-highlight-btn(v-if="expanded" :editor="editor")
            text-align-btn(v-if="expanded" :editor="editor")

            v-btn(icon v-if="expanded" @click='editor.chain().focus().toggleTaskList().run()' :outlined="editor.isActive('taskList')"  :title="$t('formatting.formatting.check_list')")
              v-icon mdi-format-list-checks

            //- list menu (always a menu)
            v-menu(v-if="expanded")
              template(v-slot:activator="{ on, attrs }")
                v-btn.pl-1.drop-down-button(
                    icon

                    :outlined="editor.isActive('bulletList') || editor.isActive('orderedList')"
                    v-on="on"
                    v-bind="attrs"
                    :title="$t('formatting.lists')")
                  v-icon mdi-format-list-bulleted
                  v-icon.menu-down-arrow mdi-menu-down
              v-list(dense)
                //- v-list-item(@click='commands.todo_list')
                //-   v-list-item-icon
                //-     v-icon mdi-format-list-checks
                //-   v-list-item-title(v-t="'formatting.check_list'")
                v-list-item(@click='editor.chain().toggleBulletList().focus().run()' :class="{ 'v-list-item--active': editor.isActive('bulletList') }")
                  v-list-item-icon
                    v-icon mdi-format-list-bulleted
                  v-list-item-title(v-t="'formatting.bullet_list'")
                v-list-item(@click='editor.chain().toggleOrderedList().focus().run()'  :class="{ 'v-list-item--active': editor.isActive('orderedList') }")
                  v-list-item-icon
                    v-icon mdi-format-list-numbered
                  v-list-item-title(v-t="'formatting.number_list'")

            //- extra text marks
            template(v-if="expanded")
              //- strikethrough
              v-menu(:close-on-content-click="false" v-model="iframeDialogIsOpen" min-width="320px")
                template(v-slot:activator="{on}")
                  v-btn(icon v-on="on" :title="$t('formatting.embed')")
                    v-icon mdi-youtube
                v-card
                  v-card-title.title(v-t="'text_editor.insert_embedded_url'")
                  v-card-text
                    v-text-field(type="url" label="e.g. https://www.youtube.com/watch?v=Zlzuqsunpxc" v-model="iframeUrl" ref="focus" autofocus v-on:keyup.enter="setIframeUrl()")
                  v-card-actions
                    v-spacer
                    v-btn(color="primary" @click="setIframeUrl()" v-t="'common.action.apply'")
              //- blockquote
              v-btn(icon @click='editor.chain().toggleBlockquote().focus().run()' :outlined="editor.isActive('blockquote')"  :title="$t('formatting.blockquote')")
                v-icon mdi-format-quote-close
              //- //- code block
              v-btn(icon @click='editor.chain().toggleCodeBlock().focus().run()' :outlined="editor.isActive('codeBlock')"  :title="$t('formatting.code_block')")
                v-icon mdi-code-braces
              //- embded
              v-btn(icon @click='editor.chain().setHorizontalRule().focus().run()'  :title="$t('formatting.divider')")
                v-icon mdi-minus
              //- table
              v-btn(icon @click='editor.chain().insertTable({rows: 3, cols: 3, withHeaderRow: false }).focus().run()' :title="$t('formatting.add_table')" :outlined="editor.isActive('table')")
                v-icon mdi-table
              //- markdown (save experience)
              v-btn(icon @click="convertToMd" :title="$t('formatting.edit_markdown')")
                v-icon mdi-language-markdown-outline

            v-btn.html-editor__expand(v-if="!expanded" icon @click="toggleExpanded" :title="$t('formatting.expand')")
              v-icon mdi-chevron-right

            v-btn.html-editor__expand(v-if="expanded" icon @click="toggleExpanded" :title="$t('formatting.collapse')")
              v-icon mdi-chevron-left
          //- save button?
          v-spacer
          slot(v-if="!expanded" name="actions")
    div.d-flex(v-if="expanded" name="actions")
      v-spacer
      slot(name="actions")
    //-
    //- v-alert(v-if="maxLength && model[field] && model[field].length > maxLength" color='error')
    //-   span( v-t="'poll_common.too_long'")

  link-previews(:model="model" :remove="removeLinkPreview")
  suggestion-list(:query="query" :loading="fetchingMentions" :mentionable="mentionable" :positionStyles="suggestionListStyles" :navigatedUserIndex="navigatedUserIndex" @select-user="selectUser")
  files-list(:files="files" v-on:removeFile="removeFile")

  form(style="display: block" @change="fileSelected")
    input(ref="filesField" type="file" name="files" multiple=true)

  form(style="display: block" @change="imageSelected")
    input(ref="imagesField" type="file" name="files" multiple=true)
</template>
<style lang="sass">

.ProseMirror-widget
  position: absolute
  width: 0.1px
  /*border-style: solid;*/

.bv-row
  padding-top: 20px

.ProseMirror [contenteditable="false"]
  white-space: normal

.ProseMirror [contenteditable="true"]
  white-space: pre-wrap

.ProseMirror
  outline: none

progress
  width: 50%!important
  -webkit-appearance: none
  appearance: none
  background-color: #fff
  border: 1px solid #ccc
progress::-webkit-progress-bar
  background-color: #fff
  border: 1px solid #ccc
progress::-webkit-progress-value
  background-color: lightblue
  border: 0
  transition: width 120ms ease-out, opacity 60ms 60ms ease-in
progress::-moz-progress-bar
  background-color: lightblue
  border: 0
  transition: width 120ms ease-out, opacity 60ms 60ms ease-in

.menubar
  position: sticky
  bottom: 0

.menubar
  .drop-down-button
    width: 40px !important
  .menu-down-arrow
    margin-left: -10px
  .v-btn--icon
    width: 32px
    height: 32px

  .v-btn.v-btn--icon
    min-width: 0
    margin-left: 0
    margin-right: 0
    max-width: 32px
    .v-icon
      font-size: 16px

.html-editor__textarea .ProseMirror
  border-bottom: 1px solid #999
  padding: 4px 0px
  margin: 4px 0px
  outline: none
  overflow-y: scroll
  overflow: visible

.html-editor__textarea .ProseMirror:focus
  border-bottom: 1px solid var(--v-primary-base)

.lmo-textarea p.is-empty:first-child::before
  content: attr(data-empty-text)
  float: left
  color: #aaa
  pointer-events: none
  height: 0

.lmo-textarea p.is-empty
  font-size: 16px
  padding-bottom: 16px

input[type="file"]
  display: none

.iframe-container
  position: relative
  padding-bottom: 100/16*9%
  height: 0
  overflow: hidden
  width: 100%
  height: auto
  &.ProseMirror-selectednode
    outline: 3px solid #68CEF8
  iframe
    position: absolute
    top: 0
    left: 0
    width: 100%
    height: 100%

</style>

<script lang="coffee">
import Records from '@/shared/services/records'
import Session from '@/shared/services/session'
import AppConfig from '@/shared/services/app_config'
import FileUploader from '@/shared/services/file_uploader'
import FilesList from './files_list.vue'
import EventBus from '@/shared/services/event_bus'
import I18n from '@/i18n'
import { convertToMd } from '@/shared/services/format_converter'

import Blockquote from '@tiptap/extension-blockquote'
import Bold from '@tiptap/extension-bold'
import BulletList from '@tiptap/extension-bullet-list'
import CodeBlock from '@tiptap/extension-code-block'
import Code from '@tiptap/extension-code'
import CharacterCount from '@tiptap/extension-character-count'
import Document from '@tiptap/extension-document'
import Dropcursor from '@tiptap/extension-dropcursor'
import GapCursor from '@tiptap/extension-gapcursor'
import HardBreak from '@tiptap/extension-hard-break'
import Heading from '@tiptap/extension-heading'
import Highlight from '@tiptap/extension-highlight'
import History from '@tiptap/extension-history'
import HorizontalRule from '@tiptap/extension-horizontal-rule'
import Italic from '@tiptap/extension-italic'
import Link from '@tiptap/extension-link'
import ListItem from '@tiptap/extension-list-item'
import OrderedList from '@tiptap/extension-ordered-list'
import Paragraph from '@tiptap/extension-paragraph'
import Placeholder from '@tiptap/extension-placeholder'
import Strike from '@tiptap/extension-strike'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
import TableRow from '@tiptap/extension-table-row'
import Table from '@tiptap/extension-table'
import TaskList from '@tiptap/extension-task-list'
import {CustomTaskItem} from './extension_custom_task_item'
import TextStyle from '@tiptap/extension-text-style'
import TextAlign from '@tiptap/extension-text-align'
import Text from '@tiptap/extension-text'
import Typography from '@tiptap/extension-typography'
import Underline from '@tiptap/extension-underline'
import {CustomMention} from './extension_mention'
import {CustomImage} from './extension_image'
import {TextColor} from './extension_text_color'
import {Iframe} from './extension_iframe'

import { Editor, EditorContent, VueRenderer } from '@tiptap/vue-2'

import {getEmbedLink} from '@/shared/helpers/embed_link.coffee'

import { CommonMentioning, HtmlMentioning, MentionPluginConfig } from './mentioning.coffee'
import SuggestionList from './suggestion_list'
import Attaching from './attaching.coffee'
import {compact, uniq, throttle, difference, reject, uniqBy} from 'lodash'
import TextHighlightBtn from './text_highlight_btn'
import TextAlignBtn from './text_align_btn'

export default
  mixins: [CommonMentioning, HtmlMentioning, Attaching]
  props:
    model: Object
    field: String
    label: String
    placeholder: String
    maxLength: Number
    shouldReset: Boolean
    autofocus: Boolean

  components: {
    EditorContent
    TextAlignBtn
    TextHighlightBtn
    SuggestionList
    FilesList
  }

  data: ->
    loading: true
    socket: null
    count: 0
    editor: null
    expanded: true
    closeEmojiMenu: false
    linkUrl: ""
    iframeUrl: ""
    linkDialogIsOpen: false
    iframeDialogIsOpen: false
    fetchedUrls: []

  computed:
    format: ->
      @model["#{@field}Format"]

  mounted: ->
    # @expanded = Session.user().experiences['html-editor.expanded']
    @editor = new Editor
      editorProps:
        scrollThreshold: 100
        scrollMargin: 100
      extensions: [
        Blockquote
        Bold
        BulletList
        CodeBlock
        CustomMention.configure(MentionPluginConfig.bind(@)())
        CustomImage.configure({attachFile: @attachFile, attachImageFile: @attachImageFile}),
        Document
        Dropcursor
        GapCursor
        Heading
        Highlight.configure(multicolor: true)
        History
        HorizontalRule
        Italic
        Iframe
        Link
        ListItem
        OrderedList
        Paragraph
        Strike
        Text
        Table
        TableHeader
        TableRow
        TableCell
        TaskList
        CustomTaskItem
        Typography
        TextAlign
        TextStyle
        TextColor
        Underline
      ]
      content: @model[@field]
      onUpdate: @updateModel

  watch:
    'shouldReset': 'reset'

  methods:
    setCount: (count) ->
      @count = count

    tiptapAddress: ->
      if @model.isNew()
        compact([AppConfig.theme.channels_uri, 'tiptap', @model.constructor.singular, 'new', @model.groupId, @model.discussionId, @model.parentId, Session.user().secretToken]).join('/')
      else
        [AppConfig.theme.channels_uri, 'tiptap', @model.constructor.singular, @model.id, (@model.secretToken || Session.user().secretToken)].join('/')

    selectedText: ->
      state = @editor.state
      selection = @editor.state.selection
      { from, to } = selection
      state.doc.textBetween(from, to, ' ')

    reset: ->
      @editor.chain().clearContent().run()
      @resetFiles()

    convertToMd: ->
      if confirm I18n.t('formatting.markdown_confirm')
        convertToMd(@model, @field)
        Records.users.saveExperience('html-editor.uses-markdown')

    toggleExpanded: ->
      if !@expanded
        @expanded = true
        Records.users.saveExperience('html-editor.expanded')
      else
        @expanded = false
        Records.users.removeExperience('html-editor.expanded')

    setLinkUrl: ->
      if @linkUrl
        @linkUrl = "http://".concat(@linkUrl) unless @linkUrl.includes("://")
        @editor.chain().setLink(href: @linkUrl).focus().run()
        @fetchLinkPreviews([@linkUrl])
        @linkUrl = null
      @linkDialogIsOpen = false

    setIframeUrl: () ->
      @editor.chain().setIframe(src: getEmbedLink(@iframeUrl)).focus().run()
      @iframeUrl = null
      @iframeDialogIsOpen = false

    emojiPicked: (shortcode, unicode) ->
      @editor.chain()
          .insertContent(unicode)
          .focus()
          .run()
      @closeEmojiMenu = false

    updateModel: ->
      @model[@field] = @editor.getHTML()
      @updateFiles()
      @scrapeLinkPreviews() if @model.isNew()

    removeLinkPreview: (url) ->
      @model.linkPreviews = reject(@model.linkPreviews, (p) -> p.url == url)

    scrapeLinkPreviews: throttle ->
      parser = new DOMParser()
      doc = parser.parseFromString(@model[@field], 'text/html')
      @fetchLinkPreviews difference((Array.from(doc.querySelectorAll('a')).map (el) => el.href), @fetchedUrls)
    ,
      500

    fetchLinkPreviews: (urls) ->
      if urls.length
        @fetchedUrls = uniq @fetchedUrls.concat(urls)
        Records.remote.post('link_previews', {urls: urls}).then (data) =>
          @model.linkPreviews = uniqBy(@model.linkPreviews.concat(data.previews), 'url')

  beforeDestroy: ->
    # @editor.destroy() if @editor
    # @socket.close() if @socket

</script>

<template lang="pug">
div
  //- template(v-if="!editor || loading")
  //-   | Connecting to socket server …
  .editor.mb-3(v-if="editor")
    editor-content.html-editor__textarea(ref="editor" :editor='editor').lmo-markdown-wrapper
    .menubar
      div
        v-layout(align-center v-if="editor.isActive('table')")
          v-btn(icon @click="editor.chain().deleteTable().focus().run()" :title="$t('formatting.remove_table')")
            v-icon mdi-table-remove
          v-btn(icon @click="editor.chain().addColumnBefore().focus().run()" :title="$t('formatting.add_column_before')")
            v-icon mdi-table-column-plus-before
          v-btn(icon @click="editor.chain().addColumnAfter().focus().run()" :title="$t('formatting.add_column_after')")
            v-icon mdi-table-column-plus-after
          v-btn(icon @click="editor.chain().deleteColumn().focus().run()" :title="$t('formatting.remove_column')")
            v-icon mdi-table-column-remove
          v-btn(icon @click="editor.chain().addRowBefore().focus().run()" :title="$t('formatting.add_row_before')")
            v-icon mdi-table-row-plus-before
          v-btn(icon @click="editor.chain().addRowAfter().focus().run()" :title="$t('formatting.add_row_after')")
            v-icon mdi-table-row-plus-after
          v-btn(icon @click="editor.chain().deleteRow().focus().run()" :title="$t('formatting.remove_row')")
            v-icon mdi-table-row-remove
          v-btn(icon @click="editor.chain().mergeOrSplit().focus().run()" :title="$t('formatting.merge_selected')")
            v-icon mdi-table-merge-cells

        v-layout.py-2.justify-space-between.flex-wrap(align-center)
          section.d-flex.flex-wrap(:aria-label="$t('formatting.formatting_tools')")
            //- attach
            v-btn(icon @click='$refs.filesField.click()' :title="$t('formatting.attach')")
              v-icon mdi-paperclip

            v-btn(icon @click='$refs.imagesField.click()' :title="$t('formatting.insert_image')")
              v-icon mdi-image

            //- link
            v-menu(:close-on-content-click="!selectedText()" v-model="linkDialogIsOpen" min-width="320px")
              template(v-slot:activator="{on, attrs}")
                template(v-if="editor.isActive('link')")
                  v-btn(icon @click="editor.chain().toggleLink().focus().run()" outlined :title="$t('formatting.link')")
                    v-icon mdi-link-variant
                template(v-else)
                  v-btn(icon v-on="on" v-bind="attrs" :title="$t('formatting.link')")
                    v-icon mdi-link-variant
              v-card
                template(v-if="selectedText()")
                  v-card-title.title(v-t="'text_editor.insert_link'")
                  v-card-text
                    v-text-field(type="url" label="https://www.example.com" v-model="linkUrl" autofocus ref="focus" v-on:keyup.enter="setLinkUrl()")
                  v-card-actions
                    v-spacer
                    v-btn(color="primary" @click="setLinkUrl()" v-t="'common.action.apply'")
                template(v-else)
                  v-card-title(v-t="'text_editor.select_text_to_link'")

            //- emoji
            v-menu(:close-on-content-click="false" v-model="closeEmojiMenu")
              template(v-slot:activator="{on, attrs}")
                v-btn.emoji-picker__toggle(v-on="on" v-bind="attrs" icon :title="$t('formatting.insert_emoji')")
                  v-icon mdi-emoticon-outline
              emoji-picker(:insert="emojiPicked")

            template(v-if="expanded")
              //- v-btn(icon @click='editor.chain().focus().setParagraph().run()' :outlined="editor.isActive('paragraph')" :title="$t('formatting.paragraph')")
              //-   v-icon mdi-format-pilcrow
              template(v-for="i in 3")
                v-btn(icon @click='editor.chain().focus().toggleHeading({ level: i }).run()' :outlined="editor.isActive('heading', { level: i })" :title="$t('formatting.heading'+i)")
                  v-icon {{'mdi-format-header-'+i}}

            //- bold
            v-btn(icon v-if="expanded" @click='editor.chain().toggleBold().focus().run()' :outlined="editor.isActive('bold')" :title="$t('formatting.bold')")
              v-icon mdi-format-bold

            //- italic
            v-btn(icon v-if="expanded" @click='editor.chain().toggleItalic().focus().run()' :outlined="editor.isActive('italic')" :title="$t('formatting.italicize')")
              v-icon mdi-format-italic
            //-
            //- //- strikethrough
            //- v-btn(icon v-if="expanded" @click='editor.chain().toggleStrike().focus().run()' :outlined="editor.isActive('strike')"  :title="$t('formatting.strikethrough')")
            //-   v-icon mdi-format-strikethrough
            //- //- underline
            v-btn(icon v-if="expanded" @click='editor.chain().toggleUnderline().focus().run()' :outlined="editor.isActive('underline')",  :title="$t('formatting.underline')")
              v-icon mdi-format-underline
            //-
            text-highlight-btn(v-if="expanded" :editor="editor")
            text-align-btn(v-if="expanded" :editor="editor")

            v-btn(icon v-if="expanded" @click='editor.chain().focus().toggleTaskList().run()' :outlined="editor.isActive('taskList')"  :title="$t('formatting.formatting.check_list')")
              v-icon mdi-format-list-checks

            //- list menu (always a menu)
            v-menu(v-if="expanded")
              template(v-slot:activator="{ on, attrs }")
                v-btn.pl-1.drop-down-button(
                    icon

                    :outlined="editor.isActive('bulletList') || editor.isActive('orderedList')"
                    v-on="on"
                    v-bind="attrs"
                    :title="$t('formatting.lists')")
                  v-icon mdi-format-list-bulleted
                  v-icon.menu-down-arrow mdi-menu-down
              v-list(dense)
                //- v-list-item(@click='commands.todo_list')
                //-   v-list-item-icon
                //-     v-icon mdi-format-list-checks
                //-   v-list-item-title(v-t="'formatting.check_list'")
                v-list-item(@click='editor.chain().toggleBulletList().focus().run()' :class="{ 'v-list-item--active': editor.isActive('bulletList') }")
                  v-list-item-icon
                    v-icon mdi-format-list-bulleted
                  v-list-item-title(v-t="'formatting.bullet_list'")
                v-list-item(@click='editor.chain().toggleOrderedList().focus().run()'  :class="{ 'v-list-item--active': editor.isActive('orderedList') }")
                  v-list-item-icon
                    v-icon mdi-format-list-numbered
                  v-list-item-title(v-t="'formatting.number_list'")

            //- extra text marks
            template(v-if="expanded")
              //- strikethrough
              v-menu(:close-on-content-click="false" v-model="iframeDialogIsOpen" min-width="320px")
                template(v-slot:activator="{on}")
                  v-btn(icon v-on="on" :title="$t('formatting.embed')")
                    v-icon mdi-youtube
                v-card
                  v-card-title.title(v-t="'text_editor.insert_embedded_url'")
                  v-card-text
                    v-text-field(type="url" label="e.g. https://www.youtube.com/watch?v=Zlzuqsunpxc" v-model="iframeUrl" ref="focus" autofocus v-on:keyup.enter="setIframeUrl()")
                  v-card-actions
                    v-spacer
                    v-btn(color="primary" @click="setIframeUrl()" v-t="'common.action.apply'")
              //- blockquote
              v-btn(icon @click='editor.chain().toggleBlockquote().focus().run()' :outlined="editor.isActive('blockquote')"  :title="$t('formatting.blockquote')")
                v-icon mdi-format-quote-close
              //- //- code block
              v-btn(icon @click='editor.chain().toggleCodeBlock().focus().run()' :outlined="editor.isActive('codeBlock')"  :title="$t('formatting.code_block')")
                v-icon mdi-code-braces
              //- embded
              v-btn(icon @click='editor.chain().setHorizontalRule().focus().run()'  :title="$t('formatting.divider')")
                v-icon mdi-minus
              //- table
              v-btn(icon @click='editor.chain().insertTable({rows: 3, cols: 3, withHeaderRow: false }).focus().run()' :title="$t('formatting.add_table')" :outlined="editor.isActive('table')")
                v-icon mdi-table
              //- markdown (save experience)
              v-btn(icon @click="convertToMd" :title="$t('formatting.edit_markdown')")
                v-icon mdi-language-markdown-outline

            v-btn.html-editor__expand(v-if="!expanded" icon @click="toggleExpanded" :title="$t('formatting.expand')")
              v-icon mdi-chevron-right

            v-btn.html-editor__expand(v-if="expanded" icon @click="toggleExpanded" :title="$t('formatting.collapse')")
              v-icon mdi-chevron-left
          //- save button?
          v-spacer
          slot(v-if="!expanded" name="actions")
    div.d-flex(v-if="expanded" name="actions")
      v-spacer
      slot(name="actions")
    //-
    //- v-alert(v-if="maxLength && model[field] && model[field].length > maxLength" color='error')
    //-   span( v-t="'poll_common.too_long'")

  link-previews(:model="model" :remove="removeLinkPreview")
  suggestion-list(:query="query" :loading="fetchingMentions" :mentionable="mentionable" :positionStyles="suggestionListStyles" :navigatedUserIndex="navigatedUserIndex" @select-user="selectUser")
  files-list(:files="files" v-on:removeFile="removeFile")

  form(style="display: block" @change="fileSelected")
    input(ref="filesField" type="file" name="files" multiple=true)

  form(style="display: block" @change="imageSelected")
    input(ref="imagesField" type="file" name="files" multiple=true)
</template>
<style lang="sass">

// .count
//   display: flex
//   align-items: center
//   font-weight: bold
//   color: rgba(#000, 0.5)
//   color: #27b127
//   margin-bottom: 1rem
//   text-transform: uppercase
//   font-size: 0.7rem
//   line-height: 1
//   &:before
//     content: ''
//     display: inline-flex
//     background-color: #27b127
//     width: 0.4rem
//     height: 0.4rem
//     border-radius: 50%
//     margin-right: 0.3rem

.cursor
  color: #222
  text-align: center
  border-radius: 6px 6px 6px 0px
  padding: 5px
  margin-left: -4.5px
  position: absolute
  z-index: 1
  bottom: 5px
  left: -50%
  opacity: 0.85
  white-space: nowrap
  -webkit-touch-callout: none
  -webkit-user-select: none
  -khtml-user-select: none
  -moz-user-select: none
  -ms-user-select: none
  user-select: none
  &.me
    display: none
    &::after
      display: none
      border-color: inherit
  &.inactive
    opacity: 0.5
    &::after
      opacity: inherit
      border-color: inherit
  &::after
    content: ""
    position: absolute
    top: 100%
    left: 0%
    border-width: 5px
    border-style: solid
    border-color: inherit
    color: transparent

.ProseMirror-widget
  position: absolute
  width: 0.1px
  /*border-style: solid;*/

.bv-row
  padding-top: 20px

.ProseMirror [contenteditable="false"]
  white-space: normal

.ProseMirror [contenteditable="true"]
  white-space: pre-wrap

.ProseMirror
  outline: none

progress
  width: 50%!important
  -webkit-appearance: none
  appearance: none
  background-color: #fff
  border: 1px solid #ccc
progress::-webkit-progress-bar
  background-color: #fff
  border: 1px solid #ccc
progress::-webkit-progress-value
  background-color: lightblue
  border: 0
  transition: width 120ms ease-out, opacity 60ms 60ms ease-in
progress::-moz-progress-bar
  background-color: lightblue
  border: 0
  transition: width 120ms ease-out, opacity 60ms 60ms ease-in

.menubar
  position: sticky
  bottom: 0

.menubar
  .drop-down-button
    width: 40px !important
  .menu-down-arrow
    margin-left: -10px
  .v-btn--icon
    width: 32px
    height: 32px

  .v-btn.v-btn--icon
    min-width: 0
    margin-left: 0
    margin-right: 0
    max-width: 32px
    .v-icon
      font-size: 16px

.html-editor__textarea .ProseMirror
  border-bottom: 1px solid #999
  padding: 4px 0px
  margin: 4px 0px
  outline: none
  overflow-y: scroll
  overflow: visible

.html-editor__textarea .ProseMirror:focus
  border-bottom: 1px solid var(--v-primary-base)

.lmo-textarea p.is-empty:first-child::before
  content: attr(data-empty-text)
  float: left
  color: #aaa
  pointer-events: none
  height: 0

.lmo-textarea p.is-empty
  font-size: 16px
  padding-bottom: 16px

input[type="file"]
  display: none

.iframe-container
  position: relative
  padding-bottom: 100/16*9%
  height: 0
  overflow: hidden
  width: 100%
  height: auto
  &.ProseMirror-selectednode
    outline: 3px solid #68CEF8
  iframe
    position: absolute
    top: 0
    left: 0
    width: 100%
    height: 100%

</style>


edit atlas

edit atlas

<template>

  <foxgis-layout>

          search
          <input id="search" type="text" placeholder="搜索" @keyup.enter="search"></input>

        <mdl-button raised accent v-mdl-ripple-effect class="search-button" @click="search">搜索</mdl-button>

            主题词：
            <a v-for="tag in theme_tags" v-if="$index<6"
                  @click="conditionClick($event,1)">{{ tag }}

            制图区域：
            <a v-for="location in location_tags" v-if="$index<6"
                  @click="conditionClick($event,2)">{{ location.data }}
                  ({{ location.num }})

            制图年份：
            <a v-for="year in year_tags | orderBy" v-if="$index<6"
                  @click="conditionClick($event,3)">{{ year.data }}
                  ({{ year.num }})

          <div v-for='u in pageConfig.page_item_num' v-if="((pageConfig.current_page-1)*pageConfig.page_item_num+$index) < displayUploads.length" track-by="$index">
            <foxgis-card>

{{displayUploads[(pageConfig.current_page-1)*pageConfig.page_item_num+$index].name}}

制图区域：{{displayUploads[(pageConfig.current_page-1)*pageConfig.page_item_num+$index].location}}

制图年份：{{displayUploads[(pageConfig.current_page-1)*pageConfig.page_item_num+$index].year}}

                  <mdl-anchor-button @click="showPreview($event, (pageConfig.current_page-1)*pageConfig.page_item_num+$index)">预览</mdl-anchor-button>

                  <mdl-anchor-button @click="downloadUpload(displayUploads[(pageConfig.current_page-1)*pageConfig.page_item_num+$index].upload_id)">下载</mdl-anchor-button>

            </foxgis-card>

0?true:false">

 1?'':'disabled'">
            navigate_before

{{ pageConfig.first_page + page }}

 1)&&(pageConfig.current_page < parseInt(total_items/pageConfig.page_item_num)+1)?'':'disabled'">
            navigate_next

  </foxgis-layout>

</template>


change avator path

change avator path

<template>

            <Card :bordered="false">

                    <Icon type="log-in"></Icon>
                    欢迎登录

                    <Form ref="loginForm" :model="form" :rules="rules">
                        <FormItem prop="userName">
                            <Input v-model="form.userName">

                                    <Icon :size="16" type="person"></Icon>

                            </Input>
                        </FormItem>
                        <FormItem prop="password">
                            <Input v-model="form.password">

                                    <Icon :size="14" type="locked"></Icon>

                            </Input>
                        </FormItem>
                        <FormItem>
                            <Button @click="handleSubmit" type="primary" long>登录</Button>
                        </FormItem>
                    </Form>

输入任意用户名和密码即可

            </Card>

</template>


修改popup组件，将组件渲染时插入到body中，解决ios因为容器定位原因不渲染容器以外的内容

<template>
  <transition :name="currentTransition">
    <div
      v-show="currentValue"
      class="mint-popup mt-bg-white"
      :class="[position ? 'mint-popup-' + position : '']"
      :id="pid"
    >
      <slot></slot>
    </div>
  </transition>
</template>

<style>
@component-namespace mint {
  @component popup {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate3d(-50%, -50%, 0);
    backface-visibility: hidden;
    transition: 0.2s ease-out;

    @descendent top {
      top: 0;
      right: auto;
      bottom: auto;
      left: 50%;
      transform: translate3d(-50%, 0, 0);
    }

    @descendent right {
      top: 50%;
      right: 0;
      bottom: auto;
      left: auto;
      transform: translate3d(0, -50%, 0);
    }

    @descendent bottom {
      top: auto;
      right: auto;
      bottom: 0;
      left: 50%;
      width: 100%;
      transform: translate3d(-50%, 0, 0);
    }

    @descendent left {
      top: 50%;
      right: auto;
      bottom: auto;
      left: 0;
      transform: translate3d(0, -50%, 0);
    }
  }
}

.popup-slide-top-enter,
.popup-slide-top-leave-active {
  transform: translate3d(-50%, -100%, 0);
}

.popup-slide-right-enter,
.popup-slide-right-leave-active {
  transform: translate3d(100%, -50%, 0);
}

.popup-slide-bottom-enter,
.popup-slide-bottom-leave-active {
  transform: translate3d(-50%, 100%, 0);
}

.popup-slide-left-enter,
.popup-slide-left-leave-active {
  transform: translate3d(-100%, -50%, 0);
}

.popup-fade-enter,
.popup-fade-leave-active {
  opacity: 0;
}
</style>

<script type="text/babel">
import Popup from "bh-mint-ui2/src/utils/popup";
import Vue from "vue";
if (!Vue.prototype.$isServer) {
  require("bh-mint-ui2/src/style/popup.css");
}

export default {
  name: "mt-popup",

  mixins: [Popup],

  props: {
    popupTransition: {
      type: String,
      default: "popup-slide",
    },

    position: {
      type: String,
      default: "",
    },

    pid: {
      type: String,
      default: null,
    },
  },

  data() {
    return {
      currentValue: false,
      currentTransition: this.popupTransition,
    };
  },

  watch: {
    currentValue(val) {
      if (!val) {
        this.$emit("maskCallback");
        if (this.pid) {
          document.getElementById(this.pid).style.overflow = null;
          document.getElementById(this.pid).style.height = null;
        }
      }
      this.$emit("input", val);
    },

    value(val) {
      this.currentValue = val;
    },
  },
  beforeMount() {
    if (this.popupTransition !== "popup-fade") {
      this.currentTransition = `popup-slide-${this.position}`;
    }
  },

  mounted() {
    //将popup组件默认添加到body中，解决ios中组件pop组件不渲染问题 王敏   YJS-6563
    this.$nextTick(() => {
      const body = document.querySelector("body");
      if (body.append) {
        body.append(this.$el);
      } else {
        body.appendChild(this.$el);
      }
    });
    //-------------
    if (this.value) {
      this.rendered = true;
      this.currentValue = true;
      this.open();
    }
  },
};
</script>

<template>
  <transition :name="currentTransition">
    <div v-show="currentValue" class="mint-popup mt-bg-white" :class="[position ? 'mint-popup-' + position : '']" :id="pid">
      <slot></slot>
    </div>
  </transition>
</template>

<style>
  @component-namespace mint {
    @component popup {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate3d(-50%, -50%, 0);
      backface-visibility: hidden;
      transition:  .2s ease-out;

      @descendent top {
        top: 0;
        right: auto;
        bottom: auto;
        left: 50%;
        transform: translate3d(-50%, 0, 0);
      }

      @descendent right {
        top: 50%;
        right: 0;
        bottom: auto;
        left: auto;
        transform: translate3d(0, -50%, 0);
      }

      @descendent bottom {
        top: auto;
        right: auto;
        bottom: 0;
        left: 50%;
        width: 100%;
        transform: translate3d(-50%, 0, 0);
      }

      @descendent left {
        top: 50%;
        right: auto;
        bottom: auto;
        left: 0;
        transform: translate3d(0, -50%, 0);
      }
    }
  }

  .popup-slide-top-enter,
  .popup-slide-top-leave-active {
    transform: translate3d(-50%, -100%, 0);
  }

  .popup-slide-right-enter,
  .popup-slide-right-leave-active {
    transform: translate3d(100%, -50%, 0);
  }

  .popup-slide-bottom-enter,
  .popup-slide-bottom-leave-active {
    transform: translate3d(-50%, 100%, 0);
  }

  .popup-slide-left-enter,
  .popup-slide-left-leave-active {
    transform: translate3d(-100%, -50%, 0);
  }

  .popup-fade-enter,
  .popup-fade-leave-active {
    opacity: 0;
  }
</style>

<script type="text/babel">
  import Popup from 'bh-mint-ui2/src/utils/popup';
  import Vue from 'vue';
  if (!Vue.prototype.$isServer) {
    require('bh-mint-ui2/src/style/popup.css');
  }

  export default {
    name: 'mt-popup',

    mixins: [Popup],

    props: {
      popupTransition: {
        type: String,
        default: 'popup-slide'
      },

      position: {
        type: String,
        default: ''
      },

      pid:{
        type: String,
        default: null
      }
    },

    data() {
      return {
        currentValue: false,
        currentTransition: this.popupTransition
      };
    },

    watch: {
      currentValue(val) {
        if(!val){
          this.$emit("maskCallback");
          if(this.pid){
            document.getElementById(this.pid).style.overflow = null;
            document.getElementById(this.pid).style.height = null;
          }
        };
        this.$emit('input', val);
      },

      value(val) {
        this.currentValue = val;
      }
    },
    beforeMount() {
      if (this.popupTransition !== 'popup-fade') {
        this.currentTransition = `popup-slide-${ this.position }`;
      }
    },

    mounted() {
      if (this.value) {
        this.rendered = true;
        this.currentValue = true;
        this.open();
      }
    }
  };
</script>


renamed popover's maxwidth property to match the rest

<template>

        <slot></slot>

                    <slot name="content"></slot>

</template>


Changed Submit Button

Changed Submit Button

<template lang='html'>

  <md-whiteframe md-elevation="2">
    <md-toolbar>

Add New Asset

    </md-toolbar>
  </md-whiteframe>

  <md-layout md-gutter class="main-content">
    <md-layout md-flex="35" class="form-content">
      <md-layout md-column>

        Organization Name
        <el-input placeholder="Please input" v-model="organization_name"></el-input>

        Name
        <el-input placeholder="Please input" v-model="name"></el-input>

        Time Of Day Start
        <el-time-select
          v-model="time_start"
          :picker-options="{
            start: '08:30',
            step: '00:15',
            end: '18:30'
          }"
          placeholder="Select time">
        </el-time-select>

        Time Of Day End
        <el-time-select
          v-model="time_end"
          :picker-options="{
            start: '08:30',
            step: '00:15',
            end: '18:30'
          }"
          placeholder="Select time">
        </el-time-select>

        Effecteive Datetime Start
        <el-date-picker
          v-model="datetime_start"
          type="datetime"
          placeholder="Select date and time">
        </el-date-picker>

        Effecteive Datetime End
        <el-date-picker
          v-model="datetime_end"
          type="datetime"
          placeholder="Select date and time">
        </el-date-picker>

        Day Of Week Restrictions
        <el-select v-model="value5" multiple placeholder="Select">
          <el-option
            v-for="item in day_options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
          </el-option>
        </el-select>

        Holiday Restrictions
        <el-select v-model="holiday_restrictions" placeholder="Select">
          <el-option
            v-for="item in holiday_restriction_options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
          </el-option>
        </el-select>

        Ownership
        <el-select v-model="ownership" placeholder="Select">
          <el-option
            v-for="item in ownership_options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
          </el-option>
        </el-select>

        <button v-on:click="submit">Submit</button>

      </md-layout>
    </md-layout>

    <md-layout md-flex="65" class="page-content map">
    </md-layout>
  </md-layout>

</template>

<template lang='html'>

  <md-whiteframe md-elevation="2">
    <md-toolbar>

Add New Asset

    </md-toolbar>
  </md-whiteframe>

  <md-layout md-gutter class="main-content">
    <md-layout md-flex="35" class="form-content">
      <md-layout md-column>

        Organization Name
        <el-input placeholder="Please input" v-model="organization_name"></el-input>

        Name
        <el-input placeholder="Please input" v-model="name"></el-input>

        Time Of Day Start
        <el-time-select
          v-model="time_start"
          :picker-options="{
            start: '08:30',
            step: '00:15',
            end: '18:30'
          }"
          placeholder="Select time">
        </el-time-select>

        Time Of Day End
        <el-time-select
          v-model="time_end"
          :picker-options="{
            start: '08:30',
            step: '00:15',
            end: '18:30'
          }"
          placeholder="Select time">
        </el-time-select>

        Effecteive Datetime Start
        <el-date-picker
          v-model="datetime_start"
          type="datetime"
          placeholder="Select date and time">
        </el-date-picker>

        Effecteive Datetime End
        <el-date-picker
          v-model="datetime_end"
          type="datetime"
          placeholder="Select date and time">
        </el-date-picker>

        Day Of Week Restrictions
        <el-select v-model="value5" multiple placeholder="Select">
          <el-option
            v-for="item in day_options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
          </el-option>
        </el-select>

        Holiday Restrictions
        <el-select v-model="holiday_restrictions" placeholder="Select">
          <el-option
            v-for="item in holiday_restriction_options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
          </el-option>
        </el-select>

        Ownership
        <el-select v-model="ownership" placeholder="Select">
          <el-option
            v-for="item in ownership_options"
            :key="item.value"
            :label="item.label"
            :value="item.value">
          </el-option>
        </el-select>

        <el-button type="submit">Submit</el-button>

      </md-layout>
    </md-layout>

    <md-layout md-flex="65" class="page-content map">
    </md-layout>
  </md-layout>

</template>


better import private data

better import private data

<template>
  <Layout>
    <template #sidebar>
      <template v-if="!isDatasetLoaded">
        <SkeletonSpinner
          height-square="20px"
          squares-rows="1"
          squares="3"
        />
        <SkeletonSpinner
          squares="0"
          lines="4"
        />
      </template>
      <Sidebar
        v-else
        :active-tab="activeSidebarTab"
        v-bind="attrs"
        @active-tab="activeSidebarTab = $event"
        @update="handleUpdate"
      />
    </template>

    <SkeletonSpinner
      v-if="!isDatasetLoaded"
      height-square="200px"
      squares-rows="3"
      squares="1"
    />
    <keep-alive v-else>
      <router-view
        :key="$route.params.id"
        :datasets="parsedSubsetItems"
        :page-title="pageTitle"
      />
    </keep-alive>
  </Layout>
</template>

<script>
import { SkeletonSpinner } from "lib/vue/components";
import Layout from "./../layouts/Layout.vue";
import Sidebar from "./../components/Sidebar.vue";
import { CategoriesMixin } from "./../../lib/mixins/categories.mixin";
import { DatasetFactoryMixin } from "./../../lib/factories/datasets";
import { translate } from "lib/vue/filters";
import CONFIGURATION from "../../lib/filters.conf"
const { availableFilters: fields } = CONFIGURATION

export default {
  name: "Main",
  components: {
    Layout,
    Sidebar,
    SkeletonSpinner
  },
  mixins: [CategoriesMixin, DatasetFactoryMixin],
  data() {
    return {
      activeSidebarTab: 0,
      pageTitle: "",
      attrs: {},
      items: [],
      isDatasetLoaded: false
    };
  },
  computed: {
    categories() {
      // We need to extract the human-readable elements from the dictionary
      const { attributes: { vocabulary_terms: categories = [] } = {} } =
        this.attrs.metadata.find(
          ({ attributes: { uid } = {} }) => uid === "category"
        ) || {};

      return categories;
    },
    frequencies() {
      // We need to extract the human-readable elements from the dictionary
      const { attributes: { vocabulary_terms: frequencies = [] } = {} } =
        this.attrs.metadata.find(
          ({ attributes: { uid } = {} }) => uid === "frequency"
        ) || {};

      return frequencies;
    },
    parsedSubsetItems() {
      return this.items.map(
        ({
          id,
          attributes: {
            slug,
            name,
            description,
            data_updated_at,
            category: category_id,
            frequency: frequency_id
          }
        }) => {
          let category = null;
          let frequency = null;

          if (category_id) {
            // convert into arrays
            const selectedCategories = Array.isArray(category_id)
              ? category_id
              : [category_id];
            // get only the translated strings, separated by commas
            category = (
              this.categories.reduce((acc, { id, name_translations }) => {
                if (selectedCategories.includes(id.toString())) {
                  acc.push(translate(name_translations));
                }
                return acc;
              }, []) || []
            ).join(", ");
          }

          if (frequency_id) {
            const selectedFrequencies = Array.isArray(frequency_id)
              ? frequency_id
              : [frequency_id];

            frequency = (
              this.frequencies.reduce((acc, { id, name_translations }) => {
                if (selectedFrequencies.includes(id.toString())) {
                  acc.push(translate(name_translations));
                }
                return acc;
              }, []) || []
            ).join(", ");
          }

          return {
            id,
            slug,
            name,
            description,
            data_updated_at,
            category,
            frequency
          };
        }
      );
    }
  },
  watch: {
    $route(to) {
      if (to.name === "Dataset") {
        this.activeSidebarTab = 1;
      }
    }
  },
  async created() {
    this.pageTitle = document.title;

    const [
      {
        data: { data: items = [] }
      },
      {
        data: { data: metadata = [], meta: stats = {} }
      }
    ] = await Promise.all([
      this.getDatasets(),
      this.getDatasetsMetadata({ stats: true })
    ]);

    this.attrs = {
      items,
      fields,
      metadata,
      stats,
    };

    this.isDatasetLoaded = true;
  },
  methods: {
    handleUpdate(items) {
      this.items = items;
    }
  }
};
</script>

<template>
  <Layout>
    <template #sidebar>
      <template v-if="!isDatasetLoaded">
        <SkeletonSpinner
          height-square="20px"
          squares-rows="1"
          squares="3"
        />
        <SkeletonSpinner
          squares="0"
          lines="4"
        />
      </template>
      <Sidebar
        v-else
        :active-tab="activeSidebarTab"
        v-bind="attrs"
        @active-tab="activeSidebarTab = $event"
        @update="handleUpdate"
      />
    </template>

    <SkeletonSpinner
      v-if="!isDatasetLoaded"
      height-square="200px"
      squares-rows="3"
      squares="1"
    />
    <keep-alive v-else>
      <router-view
        :key="$route.params.id"
        :datasets="parsedSubsetItems"
        :page-title="pageTitle"
      />
    </keep-alive>
  </Layout>
</template>

<script>
import { SkeletonSpinner } from "lib/vue/components";
import Layout from "./../layouts/Layout.vue";
import Sidebar from "./../components/Sidebar.vue";
import { CategoriesMixin } from "./../../lib/mixins/categories.mixin";
import { DatasetFactoryMixin } from "./../../lib/factories/datasets";
import { translate } from "lib/vue/filters";

export default {
  name: "Main",
  components: {
    Layout,
    Sidebar,
    SkeletonSpinner
  },
  mixins: [CategoriesMixin, DatasetFactoryMixin],
  data() {
    return {
      activeSidebarTab: 0,
      pageTitle: "",
      attrs: {},
      items: [],
      isDatasetLoaded: false
    };
  },
  computed: {
    categories() {
      // We need to extract the human-readable elements from the dictionary
      const { attributes: { vocabulary_terms: categories = [] } = {} } =
        this.attrs.metadata.find(
          ({ attributes: { uid } = {} }) => uid === "category"
        ) || {};

      return categories;
    },
    frequencies() {
      // We need to extract the human-readable elements from the dictionary
      const { attributes: { vocabulary_terms: frequencies = [] } = {} } =
        this.attrs.metadata.find(
          ({ attributes: { uid } = {} }) => uid === "frequency"
        ) || {};

      return frequencies;
    },
    parsedSubsetItems() {
      return this.items.map(
        ({
          id,
          attributes: {
            slug,
            name,
            description,
            data_updated_at,
            category: category_id,
            frequency: frequency_id
          }
        }) => {
          let category = null;
          let frequency = null;

          if (category_id) {
            // convert into arrays
            const selectedCategories = Array.isArray(category_id)
              ? category_id
              : [category_id];
            // get only the translated strings, separated by commas
            category = (
              this.categories.reduce((acc, { id, name_translations }) => {
                if (selectedCategories.includes(id.toString())) {
                  acc.push(translate(name_translations));
                }
                return acc;
              }, []) || []
            ).join(", ");
          }

          if (frequency_id) {
            const selectedFrequencies = Array.isArray(frequency_id)
              ? frequency_id
              : [frequency_id];

            frequency = (
              this.frequencies.reduce((acc, { id, name_translations }) => {
                if (selectedFrequencies.includes(id.toString())) {
                  acc.push(translate(name_translations));
                }
                return acc;
              }, []) || []
            ).join(", ");
          }

          return {
            id,
            slug,
            name,
            description,
            data_updated_at,
            category,
            frequency
          };
        }
      );
    }
  },
  watch: {
    $route(to) {
      if (to.name === "Dataset") {
        this.activeSidebarTab = 1;
      }
    }
  },
  async created() {
    this.pageTitle = document.title;

    const [
      {
        data: { data: items = [] }
      },
      {
        data: { data: metadata = [], meta: stats = {} }
      }
    ] = await Promise.all([
      this.getDatasets(),
      this.getDatasetsMetadata({ stats: true })
    ]);

    // TODO: completar los fields de manera adecuada
    this.attrs = {
      items,
      metadata,
      stats,
      fields: [
        {
          id: "category",
          flat: true
        },
        {
          id: "frequency",
          flat: true
        }
      ]
    };

    this.isDatasetLoaded = true;
  },
  methods: {
    handleUpdate(items) {
      this.items = items;
    }
  }
};
</script>


coach-reports-group-lessons-diffQs primary-color & back_arrow

<template>

  <CoreBase
    :immersivePage="true"
    :immersivePagePrimary="true"
    immersivePageIcon="arrow_back"
    :immersivePageRoute="toolbarRoute"
    :appBarTitle="title"
    :pageTitle="title"
    :authorized="userIsAuthorized"
    authorizedRole="adminOrCoach"
  >
    <QuestionLearnersReport
      @navigate="handleNavigation"
    />
  </CoreBase>

</template>

<script>

  import { mapState } from 'vuex';
  import commonCoach from '../common';
  import QuestionLearnersReport from '../common/QuestionLearnersReport';

  export default {
    name: 'ReportsGroupReportLessonExerciseQuestionPage',
    components: {
      QuestionLearnersReport,
    },
    mixins: [commonCoach],
    computed: {
      ...mapState('questionDetail', ['title']),
      toolbarRoute() {
        return this.classRoute('ReportsGroupReportLessonExerciseQuestionListPage', {});
      },
    },
    methods: {
      handleNavigation(params) {
        this.$router.push({
          name: this.name,
          params: {
            classId: this.$route.params.classId,
            groupId: this.$route.params.groupId,
            lessonId: this.$route.params.lessonId,
            ...params,
          },
        });
      },
    },
    $trs: {
      allQuestionsLabel: 'All questions',
      summary:
        '{count, number, integer} {count, plural, one {learner} other {learners}} got this question incorrect',
    },
  };

</script>

<style lang="scss" scoped></style>

<template>

  <CoreBase
    :immersivePage="true"
    :immersivePageRoute="toolbarRoute"
    :appBarTitle="title"
    :pageTitle="title"
    :authorized="userIsAuthorized"
    authorizedRole="adminOrCoach"
  >
    <QuestionLearnersReport
      @navigate="handleNavigation"
    />
  </CoreBase>

</template>

<script>

  import { mapState } from 'vuex';
  import commonCoach from '../common';
  import QuestionLearnersReport from '../common/QuestionLearnersReport';

  export default {
    name: 'ReportsGroupReportLessonExerciseQuestionPage',
    components: {
      QuestionLearnersReport,
    },
    mixins: [commonCoach],
    computed: {
      ...mapState('questionDetail', ['title']),
      toolbarRoute() {
        return this.classRoute('ReportsGroupReportLessonExerciseQuestionListPage', {});
      },
    },
    methods: {
      handleNavigation(params) {
        this.$router.push({
          name: this.name,
          params: {
            classId: this.$route.params.classId,
            groupId: this.$route.params.groupId,
            lessonId: this.$route.params.lessonId,
            ...params,
          },
        });
      },
    },
    $trs: {
      allQuestionsLabel: 'All questions',
      summary:
        '{count, number, integer} {count, plural, one {learner} other {learners}} got this question incorrect',
    },
  };

</script>

<style lang="scss" scoped></style>


Cleanup MOTD indicator

Cleanup MOTD indicator


Removed label of actions in KCard

Removed label of actions in KCard

<template>
  <q-card>
    <!--
      Content section
    -->
    <q-card-main class="bg-light">
      <q-item>
        <q-item-side><avatar :username="item.name" :size="36" /></q-item-side>
        <q-item-main>
          <q-item-tile label>{{ item.name }}</q-item-tile>
          <q-item-tile sublabel>{{ item.description }}</q-item-tile>
        </q-item-main>
      </q-item>
    </q-card-main>
    <!--
      Actions section
    -->
    <q-card-separator />
    <q-card-actions>
      <template v-for="action in actions">
        <q-btn flat color="primary" :icon="action.icon" @click="onActionTriggered(action.handler, item)" />
      </template>
    </q-card-actions>
  </q-card>
</template>

<script>
import { QCard, QCardTitle, QCardActions, QCardSeparator, QCardMain, QItem, QItemMain, QItemTile, QItemSide, QBtn } from 'quasar'
import Avatar from 'vue-avatar/dist/Avatar'
import mixins from '../../mixins'

export default {
  name: 'k-card',
  mixins: [mixins.baseItem],
  components: {
    QCard,
    QCardTitle,
    QCardActions,
    QCardSeparator,
    QCardMain,
    QItem,
    QItemMain,
    QItemTile,
    QItemSide,
    QBtn,
    Avatar
  }
}
</script>

<template>
  <q-card>
    <!--
      Content section
    -->
    <q-card-main class="bg-light">
      <q-item>
        <q-item-side><avatar :username="item.name" :size="36" /></q-item-side>
        <q-item-main>
          <q-item-tile label>{{ item.name }}</q-item-tile>
          <q-item-tile sublabel>{{ item.description }}</q-item-tile>
        </q-item-main>
      </q-item>
    </q-card-main>
    <!--
      Actions section
    -->
    <q-card-separator />
    <q-card-actions>
      <template v-for="action in actions">
        <q-btn flat color="primary" :icon="action.icon" @click="onActionTriggered(action.handler, item)">
          <small>{{ action.label }}</small>
        </q-btn>
      </template>
    </q-card-actions>
  </q-card>
</template>

<script>
import { QCard, QCardTitle, QCardActions, QCardSeparator, QCardMain, QItem, QItemMain, QItemTile, QItemSide, QBtn } from 'quasar'
import Avatar from 'vue-avatar/dist/Avatar'
import mixins from '../../mixins'

export default {
  name: 'k-card',
  mixins: [mixins.baseItem],
  components: {
    QCard,
    QCardTitle,
    QCardActions,
    QCardSeparator,
    QCardMain,
    QItem,
    QItemMain,
    QItemTile,
    QItemSide,
    QBtn,
    Avatar
  }
}
</script>


convert data page to purecss grids

convert data page to purecss grids

<template>

{{$tr('pageHeading')}}

      {{$tr('pageSubHeading')}}

{{$tr('detailsHeading')}}

        {{$tr('detailsSubHeading')}}

      <form :action="sessionlogurl" method="get">
        <icon-button :text="$tr('download')">
          <mat-svg category="file" name="file_download"/>
        </icon-button>
      </form>

        {{$tr('note')}}: {{$tr('detailsInfo')}}.

{{$tr('summaryHeading')}}

        {{$tr('summarySubHeading')}}

      <form :action="summarylogurl" method="get">
        <icon-button :text="$tr('download')">
          <mat-svg category="file" name="file_download"/>
        </icon-button>
      </form>

        {{$tr('note')}}: {{$tr('summaryInfo')}}

</template>


Restored a more sensible default number of items per page

Refs #78


adds missing wrapper div

adds missing wrapper div

<template>

    <v-menu v-model="menuOpen" offset-y :close-on-content-click="false">
      <template v-slot:activator="{ on, attrs }">
        <v-btn v-if="activeInstance" id="instance_selection_toggle" v-bind="attrs"
               v-on="on"
               :title="`Connected to '${activeInstance.name}' (${activeInstance.uri})`" aria-label="Instance selection"
               class="text-none">
          <svg :title="`Cluster health: ${activeInstance.status}`" class="mr-2" height="14" width="14">
            <circle :class="`health--${activeInstance.status}`" cx="7" cy="9" r="5"/>
          </svg>
          {{ activeInstance.name }}
          <v-icon v-if="menuOpen">mdi-menu-up</v-icon>
          <v-icon v-else>mdi-menu-down</v-icon>
        </v-btn>
        <v-btn v-else v-bind="attrs" v-on="on" class="text-none">
          Chose instance
          <v-icon v-if="menuOpen">mdi-menu-up</v-icon>
          <v-icon v-else>mdi-menu-down</v-icon>
        </v-btn>
      </template>

      <v-card>
        <elasticsearch-instances-table/>
      </v-card>
    </v-menu>

</template>

<template>

    <v-menu v-model="menuOpen" offset-y :close-on-content-click="false">
      <template v-slot:activator="{ on, attrs }">
        <v-btn v-if="activeInstance" id="instance_selection_toggle" v-bind="attrs"
               v-on="on"
               :title="`Connected to '${activeInstance.name}' (${activeInstance.uri})`" aria-label="Instance selection"
               class="text-none">
          <svg :title="`Cluster health: ${activeInstance.status}`" class="mr-2" height="14" width="14">
            <circle :class="`health--${activeInstance.status}`" cx="7" cy="9" r="5"/>
          </svg>
          {{ activeInstance.name }}
          <v-icon v-if="menuOpen">mdi-menu-up</v-icon>
          <v-icon v-else>mdi-menu-down</v-icon>
        </v-btn>
        <v-btn v-else v-bind="attrs" v-on="on" class="text-none">
          Chose instance
          <v-icon v-if="menuOpen">mdi-menu-up</v-icon>
          <v-icon v-else>mdi-menu-down</v-icon>
        </v-btn>
      </template>

      <elasticsearch-instances-table/>
    </v-menu>

</template>


Cursor to 'grab' for pan tool

Cursor to 'grab' for pan tool

<template lang="html">
  <v-list-tile id="tool-tile">
    <v-tooltip
      id="tooltip"
      right
      open-delay="700"
    >
      <v-btn
        id="pan"
        slot="activator"
        flat
        block
        @click.native="initialiseTool"
      >
        <v-icon
          v-if="active"
          class="blue--text text--darken-1">
          lock_open
        </v-icon>

        <v-icon
          v-else
          class="grey--text text--darken-2">
          lock_outline
        </v-icon>
      </v-btn>
      <span> Lock View Tool [space] </span>
    </v-tooltip>
  </v-list-tile>
</template>

<script>
export default {
  props: {
    active: {
      type: Boolean,
      default: false
    }
  },

  data () {
    return {
      toolPan: null
    }
  },

  mounted () {
    this.initialiseTool()
  },

  methods: {
    initialiseTool () {
      // Relinquish control of PaperJS interface
      document
        .getElementById('annotation-canvas')
        .setAttribute('class', 'pointers-no')

      document.getElementById('osd-canvas').style.cursor = 'grab'
    }

  }
}
</script>

<style lang='css'>
#tooltip {
  width: 100%;
}

#pan {
  min-width: 0px;
}

#tool-tile {
  padding: 0px;
  min-width: 0px;
}
</style>

<template lang="html">
  <v-list-tile id="tool-tile">
    <v-tooltip
      id="tooltip"
      right
      open-delay="700"
    >
      <v-btn
        id="pan"
        slot="activator"
        flat
        block
        @click.native="initialiseTool"
      >
        <v-icon
          v-if="active"
          class="blue--text text--darken-1">
          lock_open
        </v-icon>

        <v-icon
          v-else
          class="grey--text text--darken-2">
          lock_outline
        </v-icon>
      </v-btn>
      <span> Lock View Tool [space] </span>
    </v-tooltip>
  </v-list-tile>
</template>

<script>
// TODO: Add a keyboard short cut that can be held down to activate
// pan and zoom controls.
export default {
  props: {
    active: {
      type: Boolean,
      default: false
    }
  },

  data () {
    return {
      toolPan: null
    }
  },

  mounted () {
    this.initialiseTool()
  },

  methods: {
    initialiseTool () {
      // Relinquish control of PaperJS interface
      document
        .getElementById('annotation-canvas')
        .setAttribute('class', 'pointers-no')
    }
  }
}
</script>

<style lang='css'>
#tooltip {
  width: 100%;
}

#pan {
  min-width: 0px;
}

#tool-tile {
  padding: 0px;
  min-width: 0px;
}
</style>