More work on display pending progress in LoadDatasetsCard

import {StorageType} from "../StorageType";
import {StorageType} from "../StorageType";
<template>
    <card-nav>
        <tab label="Create" :active="true">
            <dataset-form @click="storeCreateDataset()" :peptides="createPeptides" :name="createName" :save="createSave" :loading="pendingStore"></dataset-form>

                <simple-button label="Add to selected datasets" glyphicon="plus"></simple-button>

        </tab>
        <tab label="Sample data">
        </tab>
        <tab label="Pride">

Load data from the PRIDE archive

You can easily load data from the PRIDE data repository. Simply enter an assay id (e.g. 8500) in the field below and click the 'Load PRIDE Dataset' button. The corresponding dataset will then be fetched using the PRIDE API and loaded into the search form on the left.

            <validated-textfield v-model="prideAssay" label="Assay id" placeholder="e.g. 8500" :disabled="prideLoading || pendingStore"></validated-textfield>

                <simple-button v-if="!prideLoading" glyphicon="cloud-download" label="Fetch PRIDE dataset" @click="fetchPrideAssay()"></simple-button>
                <determinate-striped-progress-bar v-if="prideLoading" :progress="prideProgress"></determinate-striped-progress-bar>

            <dataset-form :peptides="pridePeptides" :name="prideName" :save="prideSave" :loading="prideLoading || pendingStore"></dataset-form>

{{ prideLoading }}

                <simple-button @click="storePrideDataset()" label="Add to selected datasets" glyphicon="plus"></simple-button>

        </tab>
        <tab label="Local data">
            <list placeholder="There are currently no datasets present in your browser's local storage.">

                        {{ dataset.getName() }}

                            {{ dataset.getDateFormatted() }}

                            {{ dataset.getAmountOfPeptides() }} peptides

            </list>
        </tab>
    </card-nav>
</template>

import {StorageType} from "../StorageType";
import {StorageType} from "../StorageType";
<template>
    <card-nav>
        <tab label="Create" :active="true">
            <dataset-form @click="storeCreateDataset()" :peptides="createPeptides" :name="createName" :save="createSave" :loading="pendingStore"></dataset-form>

                <simple-button label="Add to selected datasets" glyphicon="plus"></simple-button>

        </tab>
        <tab label="Sample data">
        </tab>
        <tab label="Pride">

Load data from the PRIDE archive

You can easily load data from the PRIDE data repository. Simply enter an assay id (e.g. 8500) in the field below and click the 'Load PRIDE Dataset' button. The corresponding dataset will then be fetched using the PRIDE API and loaded into the search form on the left.

            <validated-textfield v-model="prideAssay" label="Assay id" placeholder="e.g. 8500"></validated-textfield>

                <simple-button glyphicon="cloud-download" label="Fetch PRIDE dataset" @click="fetchPrideAssay()"></simple-button>

            <dataset-form :peptides="pridePeptides" :name="prideName" :save="prideSave" :loading="prideLoading || pendingStore"></dataset-form>

{{ prideLoading }}

                <simple-button v-if="!prideLoading" @click="storePrideDataset()" label="Add to selected datasets" glyphicon="plus"></simple-button>
                <determinate-striped-progress-bar v-if="prideLoading" :progress="prideProgress"></determinate-striped-progress-bar>

        </tab>
        <tab label="Local data">
            <list placeholder="There are currently no datasets present in your browser's local storage.">

                        {{ dataset.getName() }}

                            {{ dataset.getDateFormatted() }}

                            {{ dataset.getAmountOfPeptides() }} peptides

            </list>
        </tab>
    </card-nav>
</template>


add 'onMouseMove' and 'onMouseLeave' handlers

<template lang="pug">
  #browser-tabs(v-sortable="", @dblclick="$electron.remote.getCurrentWindow().maximize()")
    .window-buttons
    .browser-tab(v-for="(page, i) in pages", :class="i == currentPageIndex ? 'active' : ''")
      <transition name="fade">
        div#tab-icons(@click="$parent.onToggleAudio($event, i, !page.isAudioMuted)")
          i.el-icon-loading(v-if="page.isLoading")
          img(v-show="page.favicon", :src="page.favicon", height='16', width='16', v-else)
          icon(name="volume-off", v-if="page.hasMedia && page.isAudioMuted")
          icon(name="volume-up", v-else-if="page.hasMedia && !page.isAudioMuted")
      </transition>
      span(:id="`${i}`", @mousemove.stop="onMouseMove", @mouseleave.stop="onMouseLeave", @click="$parent.onTabClick($event, parseInt($event.target.id))", @contextmenu.prevent="$parent.onTabContextMenu($event, i)")
        | {{ page.title || 'loading' }}
      a.close(@click="onClose")
        icon(name="times")
    a.newtab(@click="$parent.onNewTab()")
</template>

<script>
  import Icon from 'vue-awesome/components/Icon';
  import 'vue-awesome/icons/times';
  import 'vue-awesome/icons/volume-up';
  import 'vue-awesome/icons/volume-off';

  import Sortable from 'sortablejs';

  export default {
    directives: {
      sortable: {
        update(el) {
          Sortable.create(el, {
            draggable: '.browser-tab',
            animation: 150,
            ghostClass: 'ghost',
          });
        },
      },
    },
    components: {
      Icon,
    },
    computed: {
      pages() {
        return this.$store.getters.pages;
      },
      currentPageIndex() {
        return this.$store.getters.currentPageIndex;
      },
    },
    methods: {
      onClose(event) {
        let id = null;
        try {
          id = event.target.previousSibling.id;
        } catch (e) {
          try {
            id = event.target.parentNode.previousSibling.id;
          } catch (e) {
            id = event.target.parentNode.parentNode.previousSibling.id;
          }
        }
        this.$parent.onTabClose(event, parseInt(id, 10));
      },
      onMouseMove(event) {
        const x = event.pageX - event.target.offsetLeft;
        const y = event.pageY - event.target.offsetTop;
        const xy = `${x} ${y}`;

        // eslint-disable-next-line max-len
        const bgWebKit = `-webkit-gradient(radial, ${xy}, 0, ${xy}, 100, from(rgba(255,255,255,0.8)), to(rgba(255,255,255,0.0))), linear-gradient(to bottom, #e5e5e5 90%, #ddd)`;

        event.target.parentNode.style.background = bgWebKit;
      },
      onMouseLeave(event) {
        event.target.parentNode.style.background = '';
      },
    },
    mounted() {
      const ipc = this.$electron.ipcRenderer;
      ipc.on('reload', () => {
        if (this.$parent.onClickRefresh) {
          this.$parent.onClickRefresh();
        }
      });
    },
  };
</script>

<style scoped>
  #browser-tabs {
    display: flex;
    background: linear-gradient(to bottom, #ddd 90%, #bbb);
    font-size: 15px;
    height: 38px;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
  }
  #browser-tabs > div.window-buttons {
    -webkit-app-region: drag;
    padding-right: 80px;
  }
  #browser-tabs > div.browser-tab {
    flex: 1;
    display: flex;
    background: linear-gradient(to bottom, #ddd 90%, #bbb);
    color: #777;
    border-top: 1px solid #ccc;
    border-right: 1px solid #ccc;
    cursor: default;
    white-space: nowrap;
    overflow:hidden;
  }
  #browser-tabs > div.active {
    background: linear-gradient(to bottom, #e5e5e5 90%, #ddd);
    color: #555;
  }
  #browser-tabs > div.ghost {
    opacity: .5;
    color: red;
  }
  #browser-tabs > div > span {
    flex: 1;
    padding: 6px 0 4px 12px;
    line-height: 16px;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .browser-tab {
    align-items: center;
  }
  #tab-icons {
    margin-top: 3px;
  }
  #tab-icons > div, #tab-icons > img {
    margin-left: 10px;
  }
  #tab-icons > i, #tab-icons > svg {
    width: 20px;
    margin-left: 10px;
  }
  #browser-tabs > div > a {
    -webkit-app-region: no-drag;
    display: inline-block;
    padding: 5px 10px;
    margin-right: 5px;
    cursor: pointer;
    float: right;
  }
  #browser-tabs > div > a:hover {
    background: rgb(245, 119, 119);
    color: #fff;
  }
  #browser-tabs > a {
    -webkit-app-region: no-drag;
    display: inline-block;
    color: #888;
    font-size: 11px;
    cursor: pointer;
    margin: 6px 4px;
  }
  #browser-tabs > a.newtab {
    padding: 8px 12px 5px 10px;
    margin: 0;
  }
  #browser-tabs > a.newtab:hover {
    color: #fff;
    background: rgb(99, 190, 229);
  }
  #browser-tabs > a.maximize {
    color: rgb(32, 205, 20);
    text-shadow: 0 0 1px rgba(8, 77, 3, 1);
  }
  #browser-tabs > a.maximize svg {
    font-size: 12.5px
  }
  #browser-tabs > a.minimize {
    color: rgb(255, 213, 0);
    text-shadow: 0 0 1px rgba(65, 42, 2, 1);
  }
  #browser-tabs > a.minimize svg {
    font-size: 12.5px
  }
  #browser-tabs > a.close {
    color: red;
    text-shadow: 0 0 1px rgba(50, 1, 1, 1);
    margin-left: 8px;
  }
  .fade-enter-active, .fade-leave-active {
    transition: opacity .5s
  }
  .fade-enter, .fade-leave-active {
    opacity: 0
  }
</style>

<template lang="pug">
  #browser-tabs(v-sortable="", @dblclick="$electron.remote.getCurrentWindow().maximize()")
    .window-buttons
    .browser-tab(v-for="(page, i) in pages", :class="i == currentPageIndex ? 'active' : ''")
      <transition name="fade">
        div#tab-icons(@click="$parent.onToggleAudio($event, i, !page.isAudioMuted)")
          i.el-icon-loading(v-if="page.isLoading")
          img(v-show="page.favicon", :src="page.favicon", height='16', width='16', v-else)
          icon(name="volume-off", v-if="page.hasMedia && page.isAudioMuted")
          icon(name="volume-up", v-else-if="page.hasMedia && !page.isAudioMuted")
      </transition>
      span(:id="`${i}`", @click="$parent.onTabClick($event, parseInt($event.target.id))", @contextmenu.prevent="$parent.onTabContextMenu($event, i)")
        | {{ page.title || 'loading' }}
      a.close(@click="onClose")
        icon(name="times")
    a.newtab(@click="$parent.onNewTab()")
</template>

<script>
  import Icon from 'vue-awesome/components/Icon';
  import 'vue-awesome/icons/times';
  import 'vue-awesome/icons/volume-up';
  import 'vue-awesome/icons/volume-off';

  import Sortable from 'sortablejs';

  export default {
    directives: {
      sortable: {
        update(el) {
          Sortable.create(el, {
            draggable: '.browser-tab',
            animation: 150,
            ghostClass: 'ghost',
          });
        },
      },
    },
    components: {
      Icon,
    },
    computed: {
      pages() {
        return this.$store.getters.pages;
      },
      currentPageIndex() {
        return this.$store.getters.currentPageIndex;
      },
    },
    methods: {
      onClose(event) {
        let id = null;
        try {
          id = event.target.previousSibling.id;
        } catch (e) {
          try {
            id = event.target.parentNode.previousSibling.id;
          } catch (e) {
            id = event.target.parentNode.parentNode.previousSibling.id;
          }
        }
        this.$parent.onTabClose(event, parseInt(id, 10));
      },
    },
    mounted() {
      const ipc = this.$electron.ipcRenderer;
      ipc.on('reload', () => {
        if (this.$parent.onClickRefresh) {
          this.$parent.onClickRefresh();
        }
      });
    },
  };
</script>

<style scoped>
  #browser-tabs {
    display: flex;
    background: linear-gradient(to bottom, #ddd 90%, #bbb);
    font-size: 15px;
    height: 38px;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
  }
  #browser-tabs > div.window-buttons {
    -webkit-app-region: drag;
    padding-right: 80px;
  }
  #browser-tabs > div.browser-tab {
    flex: 1;
    display: flex;
    background: linear-gradient(to bottom, #ddd 90%, #bbb);
    color: #777;
    border-top: 1px solid #ccc;
    border-right: 1px solid #ccc;
    cursor: default;
    white-space: nowrap;
    overflow:hidden;
  }
  #browser-tabs > div.active {
    background: linear-gradient(to bottom, #e5e5e5 90%, #ddd);
    color: #555;
  }
  #browser-tabs > div.ghost {
    opacity: .5;
    background: #C8EBFB;
  }
  #browser-tabs > div > span {
    flex: 1;
    padding: 6px 0 4px 12px;
    line-height: 16px;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .browser-tab {
    align-items: center;
  }
  #tab-icons {
    margin-top: 3px;
  }
  #tab-icons > div, #tab-icons > img {
    margin-left: 10px;
  }
  #tab-icons > i, #tab-icons > svg {
    width: 20px;
    margin-left: 10px;
  }
  #browser-tabs > div > a {
    -webkit-app-region: no-drag;
    display: inline-block;
    padding: 5px 10px;
    margin-right: 5px;
    cursor: pointer;
    float: right;
  }
  #browser-tabs > div > a:hover {
    background: rgb(245, 119, 119);
    color: #fff;
  }
  #browser-tabs > a {
    -webkit-app-region: no-drag;
    display: inline-block;
    color: #888;
    font-size: 11px;
    cursor: pointer;
    margin: 6px 4px;
  }
  #browser-tabs > a.newtab {
    padding: 8px 12px 5px 10px;
    margin: 0;
  }
  #browser-tabs > a.newtab:hover {
    color: #fff;
    background: rgb(99, 190, 229);
  }
  #browser-tabs > a.maximize {
    color: rgb(32, 205, 20);
    text-shadow: 0 0 1px rgba(8, 77, 3, 1);
  }
  #browser-tabs > a.maximize svg {
    font-size: 12.5px
  }
  #browser-tabs > a.minimize {
    color: rgb(255, 213, 0);
    text-shadow: 0 0 1px rgba(65, 42, 2, 1);
  }
  #browser-tabs > a.minimize svg {
    font-size: 12.5px
  }
  #browser-tabs > a.close {
    color: red;
    text-shadow: 0 0 1px rgba(50, 1, 1, 1);
    margin-left: 8px;
  }
  .fade-enter-active, .fade-leave-active {
    transition: opacity .5s
  }
  .fade-enter, .fade-leave-active {
    opacity: 0
  }
</style>


docs(Home): add newsletter subscribe feature

<template>
  <v-footer
    id="footer"
    class="justify-center align-center py-4"
    color="grey darken-4"
    dark
    height="auto"
  >
    <v-container>
      <v-layout wrap>
        <v-flex xs12>

            <v-layout
              align-center
              column
              justify-center
            >
              <v-img
                contain
                height="64px"
                src="https://cdn.vuetifyjs.com/images/logos/v-alt.svg"
                width="64px"
              />
            </v-layout>

        </v-flex>
        <v-flex xs12>
          <v-layout justify-center mb-3>

              <v-icon
                dark
                v-text="social.icon"
              />

          </v-layout>
        </v-flex>
        <v-flex
          xs12
          sm8
          md5
          mx-auto
        >
          <v-form
            action="https://www.getrevue.co/profile/vuetify/add_subscriber"
            method="post"
            name="revue-form"
            target="_blank"
          >
            <v-text-field
              id="member_email"
              v-model="email"
              class="v-text-field--rounded"
              color="secondary"
              type="email"
              name="member[email]"
              solo
              flat
              hide-details
              label="Email Address"
            >
              <v-btn
                slot="append"
                type="submit"
                color="secondary"
                name="member[subscribe]"
                value="Subscribe"
                @click="email = ''"
              >
                Subscribe
              </v-btn>
            </v-text-field>
          </v-form>

            Subscribe to our monthly Newsletter

        </v-flex>
        <v-flex xs12 />
        <v-flex
          xs12
          my-5
          text-xs-center
        >
          <v-divider
            class="mx-auto"
            style="width: 150px; max-width: 70%;"
          />
        </v-flex>
        <v-flex xs12>
          <v-layout
            column
            text-xs-center
          >

              Released under the

                MIT License

                Copyright © 2016-{{ (new Date()).getFullYear() }} Vuetify, LLC

                Privacy Policy

               •

                Cookie Policy

          </v-layout>
        </v-flex>
      </v-layout>
    </v-container>
  </v-footer>
</template>

<template>
  <v-footer
    id="footer"
    class="justify-center align-center py-4"
    color="grey darken-4"
    dark
    height="auto"
  >
    <v-container>
      <v-layout wrap>
        <v-flex xs12>

            <v-layout
              align-center
              column
              justify-center
            >
              <v-img
                contain
                height="64px"
                src="https://cdn.vuetifyjs.com/images/logos/v-alt.svg"
                width="64px"
              />
            </v-layout>

        </v-flex>
        <v-flex xs12>
          <v-layout justify-center mb-3>

              <v-icon
                dark
                v-text="social.icon"
              />

          </v-layout>
        </v-flex>
        <v-flex xs12>
          <v-layout
            column
            text-xs-center
          >

              Released under the

                MIT License

                Copyright © 2016-{{ (new Date()).getFullYear() }} Vuetify, LLC

                Privacy Policy

               •

                Cookie Policy

          </v-layout>
        </v-flex>
      </v-layout>
    </v-container>
  </v-footer>
</template>


fix a messagebox bug

fix a messagebox bug

<template>

    <transition name="msgbox-bounce">

{{ title }}

{{ message }}

            <input type="text" v-model="inputValue" :placeholder="inputPlaceholder" ref="input" />

{{ editorErrorMessage }}

          <el-button :class="[ cancelButtonClasses ]" v-show="showCancelButton" @click.native="handleAction('cancel')">{{ cancelButtonText }}</el-button>
          <el-button :class="[ confirmButtonClasses ]" v-show="showConfirmButton" @click.native="handleAction('confirm')" type="primary">{{ confirmButtonText }}</el-button>

    </transition>

</template>


wyswietlanie wpisow na mikro

wyswietlanie wpisow na mikro

<template>

      <form :action="url" role="search" ref="search" class="flex-grow-1">
        <input
          ref="input"
          @focus="showDropdown"
          @keydown.esc.prevent="hideDropdown"
          @keyup="completion"
          @keyup.up.prevent="up"
          @keyup.down.prevent="down"
          @keydown.enter.prevent="changeUrl"
          @search="clearInput"
          v-model="value"
          type="search"
          name="q"
          autocomplete="off"
          placeholder="Szukaj (Shift+/)"
        >
      </form>

 0" class="search-dropdown">

          <template v-for="category in categories">

{{ getCategoryLabel(category) }}

              <component :is="makeDecorator(child)" :item="child" :value="value"></component>

          </template>

</template>


updated pk-icon-comment comments view

updated pk-icon-comment comments view

<template>

            <input type="checkbox" name="id" value="{{ comment.id }}">

                    {{ comment.author }}

{{ comment.email }}

                    {{ comment.created | relativeDate }}

                    {{ comment.created | relativeDate }}

{{ comment.content }}

            {{ comment.post.title }}

                 {{ comment.post.comment_count }}

            <form class="uk-form uk-form-stacked" name="editform" v-on="valid: submit">

                        {{ 'Name' | trans }}
                        <input id="form-author" class="uk-width-1-1" name="author" type="text" v-model="editComment.author" v-valid="required">

{{ 'Author cannot be blank.' | trans }}

                        {{ 'E-mail' | trans }}
                        <input id="form-email" class="uk-width-1-1" name="email" type="text" v-model="editComment.email" v-valid="email" lazy>

{{ 'Field must be a valid email address.' | trans }}

                        {{ 'Status' | trans }}
                        <select id="form-status" class="uk-width-1-1" v-model="editComment.status" options="statuses | toOptions"></select>

                        <textarea class="uk-width-1-1" name="content" rows="10" v-model="editComment.content" v-valid="required"></textarea>

{{ 'Content cannot be blank.' | trans }}

                    <button class="uk-button uk-button-primary" type="submit">{{ 'Save' | trans }}</button>
                    <button class="uk-button" v-on="click: cancel">{{ 'Cancel' | trans }}</button>

            </form>

            <form name="replyform" class="uk-form" v-on="valid: submit">

                    <textarea class="uk-width-1-1" name="content" rows="10" v-model="replyComment.content" v-valid="required"></textarea>

{{ 'Content cannot be blank.' | trans }}

                    <button class="uk-button uk-button-primary" type="submit">{{ 'Reply' | trans }}</button>
                    <button class="uk-button" v-on="click: cancel">{{ 'Cancel' | trans }}</button>

            </form>

</template>


this/me typo

this/me typo

<template>

            Filter and Sort

                Sort by

                <template v-if="option.enabled">
                    <input
                        class="is-checkradio"
                        :id="option.id"
                        type="radio"
                        v-model="sortBy"
                        :value="{id: option.id, label: option.label}"
                        name="sortResults">
                    {{ option.label }}
                </template>

                Quick Filters

                <template v-if="option.enabled">
                    <input
                        class="is-checkradio"
                        :id="option.id"
                        type="checkbox"
                        v-model="option.checked"
                        :name="option.id">
                    {{ option.label }}
                </template>

                Apply search term to

                <template v-if="option.enabled">
                    <input
                        class="is-checkradio"
                        :id="option.id"
                        v-model="option.checked"
                        type="checkbox"
                        :name="option.id">
                    {{ option.label }}
                </template>

</template>


fix: controllable does not expose `hasProp` method

<template>

    <slot name="before"/>
    <veui-tree
      ref="tree"
      :class="{
        [$c('menu-tree')]: true,
        [$c('menu-tree-has-icon')]: hasIcon
      }"
      :datasource="realItems"
      :expanded="realExpanded"
      keys="name"
    >
      <template
        slot="item"
        slot-scope="link"
      >
        <div
          :ref="`item-${link.name}`"
          :class="{
            [$c('menu-item')]: true,
            ...itemClass(link)
          }"
          @click.stop="handleItemClick(link)"
          @mouseenter="show(link)"
        >
          <slot
            name="item"
            v-bind="link"
          >

              <slot
                name="icon"
                v-bind="link"
              >
                <veui-icon :name="link.icon"/>
              </slot>

            <slot
              name="item-label"
              v-bind="link"
            >
              <veui-link
                :class="$c('menu-link')"
                :disabled="!!link.disabled"
                v-bind="pickLinkProps(link)"
              >
                {{
                  link.label
                }}
              </veui-link>
            </slot>
            <veui-button
              v-if="
                !realCollapsed &&
                  !link.disabled &&
                  link.children &&
                  link.children.length
              "
              :ui="uiParts.toggle"
              tabindex="-1"
              :class="$c('menu-item-toggle')"
              @click.stop="toggleExpanded(link)"
            >
              <veui-icon
                :class="$c('menu-toggle-icon')"
                :name="link.expanded ? icons.collapse : icons.expand"
              />
            </veui-button>
          </slot>

      </template>
    </veui-tree>
    <slot name="after"/>

  <veui-overlay
    v-if="realCollapsed"
    :target="refTarget"
    :open.sync="open"
    :options="realOverlayOptions"
    position="right-start"
    :overlay-class="$c('menu-overlay')"
    trigger="hover"
  >
    <veui-option-group
      v-outside="{
        refs: outsideRefs,
        delay: 100,
        trigger: 'hover',
        handler: close
      }"
      :overlay-class="$c('menu-overlay')"
      :options="currentOptions"
      position="popup"
      trigger="hover"
      option-tag="div"
    >
      <template
        slot="option"
        slot-scope="link"
      >
        <slot
          name="item"
          v-bind="link"
        >

            <slot
              name="item-label"
              v-bind="link"
            >
              <veui-link
                :class="$c('menu-link')"
                v-bind="pickLinkProps(link)"
              >
                {{ link.label }}
              </veui-link>
            </slot>

        </slot>
      </template>
      <template
        slot="option-group-label"
        slot-scope="link"
      >
        <slot
          name="item"
          v-bind="link"
        >

            <slot
              name="item-label"
              v-bind="link"
            >
              <veui-link
                :class="$c('menu-link')"
                v-bind="pickLinkProps(link)"
                @click="handleSelect(link.name, link.closeMenu)"
              >
                {{ link.label }}
              </veui-link>
            </slot>

        </slot>
      </template>
    </veui-option-group>
  </veui-overlay>

    <veui-button
      :ui="uiParts.toggle"
      :class="$c('menu-toggle')"
      @click="toggleCollapsed"
    >
      <veui-icon
        :class="$c('menu-toggle-icon')"
        :name="realCollapsed ? icons.expand : icons.collapse"
      />
    </veui-button>

</template>


another bug fix

another bug fix

<script lang="coffee">
import EventBus from '@/shared/services/event_bus'
import Records from '@/shared/services/records'
import Session from '@/shared/services/session'
import {map, debounce, without, filter, uniq, uniqBy, find} from 'lodash'
import RecipientsNotificationsCount from '@/components/common/recipients_notifications_count'
import AbilityService from '@/shared/services/ability_service'

export default
  components: {
    RecipientsNotificationsCount
  }

  props:
    autofocus: Boolean
    label: String
    placeholder: String
    hint: String
    reset: Boolean
    model: Object
    excludedAudiences:
      type: Array
      default: -> []
    excludedUserIds:
      type: Array
      default: -> [Session.user().id]
    initialRecipients:
      type: Array
      default: -> []

  data: ->
    query: ''
    suggestions: []
    recipients: []
    loading: false

  mounted: ->
    @recipients = @initialRecipients
    @fetchAndUpdateSuggestions()

  watch:
    'model.groupId': (groupId) ->
      @newRecipients(@initialRecipients)
      @fetchAndUpdateSuggestions()

    reset: ->
      @query = ''
      @recipients = @initialRecipients
      @fetchAndUpdateSuggestions()

    recipients: (val) ->
      @newRecipients(val)
      @$emit('new-recipients', val)
      @updateSuggestions()

    query: (q) ->
      @$emit('new-query', q)
      @fetchAndUpdateSuggestions()

  methods:
    fetchAndUpdateSuggestions: ->
      @fetchMemberships()
      @updateSuggestions()

    fetchMemberships: debounce ->
      return unless @query

      emails = uniq(@query.match(/[^\s:,;'"`<>]+?@[^\s:,;'"`<>]+\.[^\s:,;'"`<>]+/g) || [])
      return if emails.length

      @loading = true

      Records.memberships.fetch
        params:
          exclude_types: 'group inviter'
          q: @query
          subgroups: 'all'
          per: 20
          group_id: @model.group().parentOrSelf().id
      .then =>
        @updateSuggestions()
      .finally =>
        @loading = false
    , 300

    newRecipients: (val) ->
      @model.recipientAudience = (find(val, (o) -> o.type == 'audience') || {}).id
      @model.recipientUserIds = map filter(val, (o) -> o.type == 'user'), 'id'
      @model.recipientEmails = map filter(val, (o) -> o.type == 'email'), 'name'

    findUsers: ->
      # return [] unless @query
      chain = Records.users.collection.chain()

      if @model.group().id
        chain = chain.find(id: {$in: @model.group().parentAndSelfMemberIds()})

      chain = chain.find(emailVerified: true)
      chain = chain.find(id: {$nin: @excludedUserIds})

      chain = chain.find
        $or: [
          {name: {'$regex': ["^#{@query}", "i"]}}
          {username: {'$regex': ["^#{@query}", "i"]}}
          {name: {'$regex': [" #{@query}", "i"]}}
        ]

      chain.data()

    notificationsCount: ->
      sum(@recipients.map((r) => r.size || 1))

    remove: (item) ->
      @recipients = filter @recipients, (r) ->
        !(r.id == item.id && r.type == item.type)

    emailToRecipient: (email) ->
      id: email
      type: 'email'
      icon: 'mdi-email-outline'
      name: email

    updateSuggestions: ->
      if @query
        emails = uniq(@query.match(/[^\s:,;'"`<>]+?@[^\s:,;'"`<>]+\.[^\s:,;'"`<>]+/g) || [])

        # catch paste of multiple email addresses, or failure to press enter after an email address
        if emails.length > 1 or (emails.length == 1 && [',', ' '].includes(@query.slice(-1)))
          objs = uniqBy @recipients.concat(emails.map(@emailToRecipient)), 'id'
          @recipients = objs
          @suggestions = objs
          @query = ''
          return
        else if emails.length == 1
          @suggestions = @recipients.concat emails.map(@emailToRecipient)
          return

      members = @findUsers().map (u) ->
        id: u.id
        type: 'user'
        name: u.nameOrEmail()
        user: u

      audiences = @audiences.map (a) ->
        id: a.id
        type: 'audience'
        icon: 'mdi-account-group'
        name: a.name
        size: a.size

      @suggestions = @recipients.concat(audiences).concat(members)

  computed:
    modelName: -> @model.constructor.singular

    audiences: ->
      ret = []
      canAnnounce = !!(@model.group().adminsInclude(Session.user()) || @model.group().membersCanAnnounce)
      if @recipients.length == 0
        if @model.groupId && canAnnounce
          ret.push
            id: 'group'
            name: @$t('announcement.audiences.group', name: @model.group().name)
            size: @model.group().acceptedMembershipsCount
            icon: 'mdi-account-group'

        if @model.discussion && (@model.discussion() || {}).id && AbilityService.canAnnounceTo(@model.discussion())
          ret.push
            id: 'discussion_group'
            name: @$t('announcement.audiences.discussion_group')
            size: @model.discussion().membersCount
            icon: 'mdi-forum'

        if @model.poll && @model.poll()
          if @model.poll().votersCount > 1
            ret.push
              id: 'voters'
              name: @$t('announcement.audiences.voters', pollType: @model.poll().translatedPollType())
              size: @model.poll().votersCount
              icon: 'mdi-forum'

          if @model.poll().decidedVotersCount > 0
            ret.push
              id: 'decided_voters'
              name: @$t('announcement.audiences.decided_voters')
              size: @model.poll().decidedVotersCount
              icon: 'mdi-forum'

          if @model.poll().decidedVotersCount > 0 && @model.poll().undecidedVotersCount > 0
            ret.push
              id: 'undecided_voters'
              name: @$t('announcement.audiences.undecided_voters')
              size: @model.poll().undecidedVotersCount
              icon: 'mdi-forum'

        # non voters
        # also subgroups

      ret.filter (a) =>
        !@excludedAudiences.includes(a.id) &&
        ((@query && a.name.match(new RegExp(@query, 'i'))) || true)

</script>

<template lang="pug">
div
  //- chips attribute is messing with e2es; no behaviour change noticed
  v-autocomplete.announcement-form__input(
    multiple
    return-object
    auto-select-first
    hide-no-data
    hide-selected
    v-model='recipients'
    @change="query = null"
    :search-input.sync="query"
    item-text='name'
    :loading="loading"
    :label="label"
    :hint="hint"
    :placeholder="placeholder"
    :items='suggestions'
    )
    template(v-slot:selection='data')
      v-chip.chip--select-multi(:value='data.selected' close @click:close='remove(data.item)')
        span
          user-avatar.mr-1(v-if="data.item.type == 'user'" :user="data.item.user" size="small" no-link)
          v-icon.mr-1(v-else small) {{data.item.icon}}
        span {{ data.item.name }}
    template(v-slot:item='data')
      v-list-item-avatar
        user-avatar(v-if="data.item.type == 'user'" :user="data.item.user" size="small" no-link)
        v-icon.mr-1(v-else small) {{data.item.icon}}
      v-list-item-content.announcement-chip__content
        v-list-item-title {{data.item.name}}
  //- recipients-notifications-count(:model="model")
</template>

<script lang="coffee">
import EventBus from '@/shared/services/event_bus'
import Records from '@/shared/services/records'
import Session from '@/shared/services/session'
import {map, debounce, without, filter, uniq, uniqBy, find} from 'lodash'
import RecipientsNotificationsCount from '@/components/common/recipients_notifications_count'

export default
  components: {
    RecipientsNotificationsCount
  }

  props:
    autofocus: Boolean
    label: String
    placeholder: String
    hint: String
    reset: Boolean
    model: Object
    excludedAudiences:
      type: Array
      default: -> []
    excludedUserIds:
      type: Array
      default: -> [Session.user().id]
    initialRecipients:
      type: Array
      default: -> []

  data: ->
    query: ''
    suggestions: []
    recipients: []
    loading: false

  mounted: ->
    @recipients = @initialRecipients
    @fetchAndUpdateSuggestions()

  watch:
    'model.groupId': (groupId) ->
      @newRecipients(@initialRecipients)
      @fetchAndUpdateSuggestions()

    reset: ->
      @query = ''
      @recipients = @initialRecipients
      @fetchAndUpdateSuggestions()

    recipients: (val) ->
      @newRecipients(val)
      @$emit('new-recipients', val)
      @updateSuggestions()

    query: (q) ->
      @$emit('new-query', q)
      @fetchAndUpdateSuggestions()

  methods:
    fetchAndUpdateSuggestions: ->
      @fetchMemberships()
      @updateSuggestions()

    fetchMemberships: debounce ->
      return unless @query

      emails = uniq(@query.match(/[^\s:,;'"`<>]+?@[^\s:,;'"`<>]+\.[^\s:,;'"`<>]+/g) || [])
      return if emails.length

      @loading = true

      Records.memberships.fetch
        params:
          exclude_types: 'group inviter'
          q: @query
          subgroups: 'all'
          per: 20
          group_id: @model.group().parentOrSelf().id
      .then =>
        @updateSuggestions()
      .finally =>
        @loading = false
    , 300

    newRecipients: (val) ->
      @model.recipientAudience = (find(val, (o) -> o.type == 'audience') || {}).id
      @model.recipientUserIds = map filter(val, (o) -> o.type == 'user'), 'id'
      @model.recipientEmails = map filter(val, (o) -> o.type == 'email'), 'name'

    findUsers: ->
      # return [] unless @query
      chain = Records.users.collection.chain()

      if @model.group().id
        chain = chain.find(id: {$in: @model.group().parentAndSelfMemberIds()})

      chain = chain.find(emailVerified: true)
      chain = chain.find(id: {$nin: @excludedUserIds})

      chain = chain.find
        $or: [
          {name: {'$regex': ["^#{@query}", "i"]}}
          {username: {'$regex': ["^#{@query}", "i"]}}
          {name: {'$regex': [" #{@query}", "i"]}}
        ]

      chain.data()

    notificationsCount: ->
      sum(@recipients.map((r) => r.size || 1))

    remove: (item) ->
      @recipients = filter @recipients, (r) ->
        !(r.id == item.id && r.type == item.type)

    emailToRecipient: (email) ->
      id: email
      type: 'email'
      icon: 'mdi-email-outline'
      name: email

    updateSuggestions: ->
      if @query
        emails = uniq(@query.match(/[^\s:,;'"`<>]+?@[^\s:,;'"`<>]+\.[^\s:,;'"`<>]+/g) || [])

        # catch paste of multiple email addresses, or failure to press enter after an email address
        if emails.length > 1 or (emails.length == 1 && [',', ' '].includes(@query.slice(-1)))
          objs = uniqBy @recipients.concat(emails.map(@emailToRecipient)), 'id'
          @recipients = objs
          @suggestions = objs
          @query = ''
          return
        else if emails.length == 1
          @suggestions = @recipients.concat emails.map(@emailToRecipient)
          return

      members = @findUsers().map (u) ->
        id: u.id
        type: 'user'
        name: u.nameOrEmail()
        user: u

      audiences = @audiences.map (a) ->
        id: a.id
        type: 'audience'
        icon: 'mdi-account-group'
        name: a.name
        size: a.size

      @suggestions = @recipients.concat(audiences).concat(members)

  computed:
    modelName: -> @model.constructor.singular

    audiences: ->
      ret = []
      canAnnounce = !!(@model.group().adminsInclude(Session.user()) || @model.group().membersCanAnnounce)
      if @recipients.length == 0
        if @model.groupId && canAnnounce
          ret.push
            id: 'group'
            name: @$t('announcement.audiences.group', name: @model.group().name)
            size: @model.group().acceptedMembershipsCount
            icon: 'mdi-account-group'

        if @model.discussion &&
           (@model.discussion() || {}).id &&
           AbilityService.canAnnounceTo(@model.discussion())

          ret.push
            id: 'discussion_group'
            name: @$t('announcement.audiences.discussion_group')
            size: @model.discussion().membersCount
            icon: 'mdi-forum'

        if @model.poll && @model.poll()
          if @model.poll().votersCount > 1
            ret.push
              id: 'voters'
              name: @$t('announcement.audiences.voters', pollType: @model.poll().translatedPollType())
              size: @model.poll().votersCount
              icon: 'mdi-forum'

          if @model.poll().decidedVotersCount > 0
            ret.push
              id: 'decided_voters'
              name: @$t('announcement.audiences.decided_voters')
              size: @model.poll().decidedVotersCount
              icon: 'mdi-forum'

          if @model.poll().decidedVotersCount > 0 && @model.poll().undecidedVotersCount > 0
            ret.push
              id: 'undecided_voters'
              name: @$t('announcement.audiences.undecided_voters')
              size: @model.poll().undecidedVotersCount
              icon: 'mdi-forum'

        # non voters
        # also subgroups

      ret.filter (a) =>
        !@excludedAudiences.includes(a.id) &&
        ((@query && a.name.match(new RegExp(@query, 'i'))) || true)

</script>

<template lang="pug">
div
  //- chips attribute is messing with e2es; no behaviour change noticed
  v-autocomplete.announcement-form__input(
    multiple
    return-object
    auto-select-first
    hide-no-data
    hide-selected
    v-model='recipients'
    @change="query = null"
    :search-input.sync="query"
    item-text='name'
    :loading="loading"
    :label="label"
    :hint="hint"
    :placeholder="placeholder"
    :items='suggestions'
    )
    template(v-slot:selection='data')
      v-chip.chip--select-multi(:value='data.selected' close @click:close='remove(data.item)')
        span
          user-avatar.mr-1(v-if="data.item.type == 'user'" :user="data.item.user" size="small" no-link)
          v-icon.mr-1(v-else small) {{data.item.icon}}
        span {{ data.item.name }}
    template(v-slot:item='data')
      v-list-item-avatar
        user-avatar(v-if="data.item.type == 'user'" :user="data.item.user" size="small" no-link)
        v-icon.mr-1(v-else small) {{data.item.icon}}
      v-list-item-content.announcement-chip__content
        v-list-item-title {{data.item.name}}
  //- recipients-notifications-count(:model="model")
</template>


Remove footnote css.

Remove footnote css.

<template>

      {{index+1}}

<slot></slot>

</template>


Select: Fix default-first-option behavior when typing Chinese (#15431) (#15553)

* Select: Fix default-first-option behavior when typing Chinese (#15431)

* Select: Fix missing param transmit

* Select: callback style modify

<template>

        <el-tag
          :closable="!selectDisabled"
          :size="collapseTagSize"
          :hit="selected[0].hitState"
          type="info"
          @close="deleteTag($event, selected[0])"
          disable-transitions>
          {{ selected[0].currentLabel }}
        </el-tag>
        <el-tag
          v-if="selected.length > 1"
          :closable="false"
          :size="collapseTagSize"
          type="info"
          disable-transitions>
          + {{ selected.length - 1 }}
        </el-tag>

      <transition-group @after-leave="resetInputHeight" v-if="!collapseTags">
        <el-tag
          v-for="item in selected"
          :key="getValueKey(item)"
          :closable="!selectDisabled"
          :size="collapseTagSize"
          :hit="item.hitState"
          type="info"
          @close="deleteTag($event, item)"
          disable-transitions>
          {{ item.currentLabel }}
        </el-tag>
      </transition-group>

      <input
        type="text"
        class="el-select__input"
        :class="[selectSize ? `is-${ selectSize }` : '']"
        :disabled="selectDisabled"
        :autocomplete="autoComplete || autocomplete"
        @focus="handleFocus"
        @blur="softFocus = false"
        @click.stop
        @keyup="managePlaceholder"
        @keydown="resetInputState"
        @keydown.down.prevent="navigateOptions('next')"
        @keydown.up.prevent="navigateOptions('prev')"
        @keydown.enter.prevent="selectOption"
        @keydown.esc.stop.prevent="visible = false"
        @keydown.delete="deletePrevTag"
        @compositionstart="handleComposition"
        @compositionupdate="handleComposition"
        @compositionend="handleComposition"
        v-model="query"
        @input="debouncedQueryChange"
        v-if="filterable"
        :style="{ 'flex-grow': '1', width: inputLength / (inputWidth - 32) + '%', 'max-width': inputWidth - 42 + 'px' }"
        ref="input">

    <el-input
      ref="reference"
      v-model="selectedLabel"
      type="text"
      :placeholder="currentPlaceholder"
      :name="name"
      :id="id"
      :autocomplete="autoComplete || autocomplete"
      :size="selectSize"
      :disabled="selectDisabled"
      :readonly="readonly"
      :validate-event="false"
      :class="{ 'is-focus': visible }"
      @focus="handleFocus"
      @blur="handleBlur"
      @keyup.native="debouncedOnInputChange"
      @keydown.native.down.stop.prevent="navigateOptions('next')"
      @keydown.native.up.stop.prevent="navigateOptions('prev')"
      @keydown.native.enter.prevent="selectOption"
      @keydown.native.esc.stop.prevent="visible = false"
      @keydown.native.tab="visible = false"
      @paste.native="debouncedOnInputChange"
      @mouseenter.native="inputHovering = true"
      @mouseleave.native="inputHovering = false">
      <template slot="prefix" v-if="$slots.prefix">
        <slot name="prefix"></slot>
      </template>
      <template slot="suffix">

      </template>
    </el-input>
    <transition
      name="el-zoom-in-top"
      @before-enter="handleMenuEnter"
      @after-leave="doDestroy">
      <el-select-menu
        ref="popper"
        :append-to-body="popperAppendToBody"
        v-show="visible && emptyText !== false">
        <el-scrollbar
          tag="ul"
          wrap-class="el-select-dropdown__wrap"
          view-class="el-select-dropdown__list"
          ref="scrollbar"
          :class="{ 'is-empty': !allowCreate && query && filteredOptionsCount === 0 }"
          v-show="options.length > 0 && !loading">
          <el-option
            :value="query"
            created
            v-if="showNewOption">
          </el-option>
          <slot></slot>
        </el-scrollbar>
        <template v-if="emptyText && (!allowCreate || loading || (allowCreate && options.length === 0 ))">
          <slot name="empty" v-if="$slots.empty"></slot>

            {{ emptyText }}

        </template>
      </el-select-menu>
    </transition>

</template>


Select: fix clear on remote mode

Select: fix clear on remote mode

<template>

      <transition-group @after-leave="resetInputHeight">
        <el-tag
          v-for="item in selected"
          :key="item"
          closable
          :hit="item.hitState"
          type="primary"
          @close="deleteTag($event, item)"
          close-transition>{{ item.currentLabel }}</el-tag>
      </transition-group>
      <input
        type="text"
        class="el-select__input"
        @focus="visible = true"
        @keyup="managePlaceholder"
        @keydown="resetInputState"
        @keydown.down.prevent="navigateOptions('next')"
        @keydown.up.prevent="navigateOptions('prev')"
        @keydown.enter.prevent="selectOption"
        @keydown.esc.prevent="visible = false"
        @keydown.delete="deletePrevTag"
        v-model="query"
        :debounce="remote ? 300 : 0"
        v-if="filterable"
        :style="{ width: inputLength + 'px', 'max-width': inputWidth - 42 + 'px' }"
        ref="input">

    <el-input
      ref="reference"
      v-model="selectedLabel"
      type="text"
      :placeholder="currentPlaceholder"
      :name="name"
      :disabled="disabled"
      :readonly="!filterable || multiple"
      @focus="toggleMenu"
      @click="toggleMenu"
      @mousedown.native="handleMouseDown"
      @keyup.native="debouncedOnInputChange"
      @keydown.native.down.prevent="navigateOptions('next')"
      @keydown.native.up.prevent="navigateOptions('prev')"
      @keydown.native.enter.prevent="selectOption"
      @keydown.native.esc.prevent="visible = false"
      @keydown.native.tab="visible = false"
      @mouseenter.native="inputHovering = true"
      @mouseleave.native="inputHovering = false"
      :icon="iconClass">
    </el-input>
    <transition name="md-fade-bottom" @after-leave="doDestroy">
      <el-select-menu
        ref="popper"
        v-show="visible && emptyText !== false">

 0 && filteredOptionsCount > 0 && !loading">
          <slot></slot>

{{ emptyText }}

      </el-select-menu>
    </transition>

</template>


fix select.vue selected option

fix select.vue selected option

<template>
	<div class="ui-select" v-clickout="handleClose">
		<ui-input
    ref="inputRef"
    :placeholder="currentPlaceholder"
    :icon="iconClass"
    @click="handleIconClick"
    @focus="handleFocus"
    @mousedown.native="handleMouseDown"
    ></ui-input>
    <transition name="ui-zoom-in-down">
      <ui-select-dropdown
       ref="popper" v-show="visible">
        <ui-scroll-bar>
          <slot></slot>
        </ui-scroll-bar>
      </ui-select-dropdown>
    </transition>
	</div>
</template>
<script>
  import emit from "../../../src/mixins/emit.js";
  import UiInput from "../../input/src/input.vue";
  import UiOption from "../../option/src/option.vue";
  import UiOptionGroup from "../../option-group/src/option-group.vue";
  import UiSelectDropdown from "../../select-dropdown/src/select-dropdown.vue";
  import UiScrollBar from "../../scroll-bar/src/scroll-bar.js";
  import {hasClass,addClass,removeClass} from "../../../src/utils/dom.js";
  import clickout from "../../../src/utils/clickout.js";
  import {addResizeListener,removeResizeListener} from "../../../src/utils/resize-event.js"
	export default{
        name:"UiSelect",
        componentName:"UiSelect",
        mixins:[emit],
        directives:{clickout},
        props:{
          value:{},
          placeholder:{
            type:String,
            default:""
          },
          popperClass:String,
          multiple:Boolean
        },
        data(){
          return{
            currentPlaceholder:this.placeholder,
            visible:false,
            inputWidth:0,
            isSelect:true,
            selected:this.multiple?[]:{},
            options:[],
            optionsAllDisabled:false
          }
        },
        watch:{
          // placeholder(val){
          //   this.currentPlaceholder = val;
          // }
          visible(val){
            if(!val){
              this.handleIconPostive();
              this.broadcast('UiSelectDropdown','destoryPopper');
            }else{
              this.handleIconNegative();
              this.broadcast('UiSelectDropdown','updatePopper');
            }
          },
          options(val){
            if(this.$isServer) return;
            this.optionsAllDisabled=val.length===val.filter(item =>item.disabled===true).length;
          }

          let inputs=this.$el.querySelector("input");
          if([].indexOf.call(inputs,document.activeElement)===-1){
            this.setSelected();
          }

        },
        computed:{
           iconClass:function(){
            return "arrowUp"
           }
        },
        methods:{
          handleIconClick:function(){
             this.visible=!this.visible;
          },
          handleIconPostive:function(){
             let icon = this.$el.querySelector(".ui-input-icon");
             if(icon){
              removeClass(icon,"is-reverse")
             }
          },
          handleIconNegative:function(){
             let icon = this.$el.querySelector(".ui-input-icon");
             if(icon){
                addClass(icon,"is-reverse")
             }
          },
          handleOpen(){
            this.visible = true;
          },
          handleClose(){
            this.visible = false;
          },
          handleFocus(){
            this.visible=true;
          },
          handleMouseDown(e){
            if(e.target.tagName !== "INPUT") return;
              this.visible = !this.visible;
              e.preventDefault();

          },
          resetInputWidth(){
            this.inputWidth=this.$refs.inputRef.$el.getBoundingClientRect().width;
          },
          resetInputHeight(){

          },
          handleResize(){
            this.resetInputWidth();
          },
          setSelected(){

          },
          handleOptionClick(option){
              if(!this.multiple){
                this.$emit("input",option.value);
                console.log(this.value);
                this.visible=false;
              }
          }
        },
        created(){
          this.$on("handleOptionClick",this.handleOptionClick);
        },
        mounted(){
            addResizeListener(this.$el,this.handleResize);
        },
        components:{
          UiInput,
          UiOption,
          UiOptionGroup,
          UiSelectDropdown,
          UiScrollBar
        }
	}
</script>

<template>
	<div class="ui-select" v-clickout="handleClose">
		<ui-input
    ref="inputRef"
    :placeholder="currentPlaceholder"
    :icon="iconClass"
    @click="handleIconClick"
    @focus="handleFocus"
    @mousedown.native="handleMouseDown"
    ></ui-input>
    <transition name="ui-zoom-in-down">
      <ui-select-dropdown
       ref="popper" v-show="visible">
        <ui-scroll-bar>
          <slot></slot>
        </ui-scroll-bar>
      </ui-select-dropdown>
    </transition>
	</div>
</template>
<script>
  import emit from "../../../src/mixins/emit.js";
  import UiInput from "../../input/src/input.vue";
  import UiOption from "../../option/src/option.vue";
  import UiOptionGroup from "../../option-group/src/option-group.vue";
  import UiSelectDropdown from "../../select-dropdown/src/select-dropdown.vue";
  import UiScrollBar from "../../scroll-bar/src/scroll-bar.js";
  import {hasClass,addClass,removeClass} from "../../../src/utils/dom.js";
  import clickout from "../../../src/utils/clickout.js";
  import {addResizeListener,removeResizeListener} from "../../../src/utils/resize-event.js"
	export default{
        name:"UiSelect",
        componentName:"UiSelect",
        mixins:[emit],
        directives:{clickout},
        props:{
          value:{},
          placeholder:{
            type:String,
            default:""
          },
          popperClass:String,
          multiple:Boolean
        },
        data(){
          return{
            currentPlaceholder:this.placeholder,
            visible:false,
            inputWidth:0,
            isSelect:true,
            selected:this.multiple?[]:{}
          }
        },
        watch:{
          // placeholder(val){
          //   this.currentPlaceholder = val;
          // }
          visible(val){
            if(!val){
              this.handleIconPostive();
              this.broadcast('UiSelectDropdown','destoryPopper');
            }else{
              this.handleIconNegative();
              this.broadcast('UiSelectDropdown','updatePopper');
            }
          }
        },
        computed:{
           iconClass:function(){
            return "arrowUp"
           }
        },
        methods:{
          handleIconClick:function(){
             this.visible=!this.visible;
          },
          handleIconPostive:function(){
             let icon = this.$el.querySelector(".ui-input-icon");
             if(icon){
              removeClass(icon,"is-reverse")
             }
          },
          handleIconNegative:function(){
             let icon = this.$el.querySelector(".ui-input-icon");
             if(icon){
                addClass(icon,"is-reverse")
             }
          },
          handleOpen(){
            this.visible = true;
          },
          handleClose(){
            this.visible = false;
          },
          handleFocus(){
            this.visible=true;
          },
          handleMouseDown(e){
            if(e.target.tagName !== "INPUT") return;
              this.visible = !this.visible;
              e.preventDefault();

          },
          resetInputWidth(){
            this.inputWidth=this.$refs.inputRef.$el.getBoundingClientRect().width;
          },
          handleResize(){
            this.resetInputWidth();
          },
          handleOptionClick(option){
              if(!this.multiple){
                this.$emit("input",option.value);
                this.visible=false;
              }
          }
        },
        created(){
          this.$on("handleOptionClick",this.handleOptionClick);
        },
        mounted(){
            addResizeListener(this.$el,this.handleResize);
        },
        components:{
          UiInput,
          UiOption,
          UiOptionGroup,
          UiSelectDropdown,
          UiScrollBar
        }
	}
</script>


fix:js console errors if no project motd is defined

<template>

        <button type="button" class="close" @click="dismissMessage" data-dismiss="alert">×</button>

Message of The Day

</template>


refactor modal data loader to use composition api

<template>
  <v-dialog :width="width" v-model="dialog">
    <v-card>
      <v-card-title>

{{ modalTitle }}

        <reload-button id="reload-modal" :action="load"/>

          <v-btn icon @click.native="close">
            <v-icon>mdi-close</v-icon>
          </v-btn>

      </v-card-title>
      <v-divider/>

      <v-card-text>
        <loader :request-state="requestState">
          <print-pretty :caption="modalSubtitle"
                        :document="data"
                        :initial-height="calculatedHeight()"
                        :resizable="false"/>
        </loader>
      </v-card-text>
    </v-card>
  </v-dialog>
</template>

<template>
  <v-dialog :width="width" v-model="dialog">
    <v-card>
      <v-card-title>

{{modalTitle}}

        <reload-button id="reload-modal" :action="() => this.$refs.dataLoader.loadData()"/>

          <v-btn icon @click.native="close">
            <v-icon>mdi-close</v-icon>
          </v-btn>

      </v-card-title>
      <v-divider/>

      <v-card-text>
        <data-loader ref="dataLoader" :method="method" :method-params="methodParams" render-content-while-loading>
          <template v-slot:default="data">
            <slot name="content">
              <print-pretty :caption="modalSubtitle"
                            :document="data.body"
                            :initial-height="calculatedHeight()"
                            :resizable="false"/>
            </slot>
          </template>
        </data-loader>
      </v-card-text>
    </v-card>
  </v-dialog>
</template>


fix: swiper switch fixed #2985

fix: swiper switch fixed #2985

<script>
import touchtrack from 'uni-mixins/touchtrack'
import { deepClone } from 'uni-shared'

export default {
  name: 'Swiper',
  mixins: [touchtrack],
  props: {
    indicatorDots: {
      type: [Boolean, String],
      default: false
    },
    vertical: {
      type: [Boolean, String],
      default: false
    },
    autoplay: {
      type: [Boolean, String],
      default: false
    },
    circular: {
      type: [Boolean, String],
      default: false
    },
    interval: {
      type: [Number, String],
      default: 5e3
    },
    duration: {
      type: [Number, String],
      default: 500
    },
    current: {
      type: [Number, String],
      default: 0
    },
    indicatorColor: {
      type: String,
      default: ''
    },
    indicatorActiveColor: {
      type: String,
      default: ''
    },
    previousMargin: {
      type: String,
      default: ''
    },
    nextMargin: {
      type: String,
      default: ''
    },
    currentItemId: {
      type: String,
      default: ''
    },
    skipHiddenItemLayout: {
      type: [Boolean, String],
      default: false
    },
    displayMultipleItems: {
      type: [Number, String],
      default: 1
    },
    disableTouch: {
      type: [Boolean, String],
      default: false
    }
  },
  data () {
    return {
      currentSync: Math.round(this.current) || 0,
      currentItemIdSync: this.currentItemId || '',
      userTracking: false,
      currentChangeSource: '',
      items: []
    }
  },
  computed: {
    intervalNumber () {
      const interval = Number(this.interval)
      return isNaN(interval) ? 5e3 : interval
    },
    durationNumber () {
      const duration = Number(this.duration)
      return isNaN(duration) ? 500 : duration
    },
    displayMultipleItemsNumber () {
      const displayMultipleItems = Math.round(this.displayMultipleItems)
      return isNaN(displayMultipleItems) ? 1 : displayMultipleItems
    },
    slidesStyle () {
      var style = {}
      if (this.nextMargin || this.previousMargin) {
        style = this.vertical ? {
          left: 0,
          right: 0,
          top: this._upx2px(this.previousMargin),
          bottom: this._upx2px(this.nextMargin)
        } : {
          top: 0,
          bottom: 0,
          left: this._upx2px(this.previousMargin),
          right: this._upx2px(this.nextMargin)
        }
      }
      return style
    },
    slideFrameStyle () {
      var value = Math.abs(100 / this.displayMultipleItemsNumber) + '%'
      return {
        width: this.vertical ? '100%' : value,
        height: !this.vertical ? '100%' : value
      }
    },
    circularEnabled () {
      return this.circular && this.items.length > this.displayMultipleItemsNumber
    }
  },
  watch: {
    vertical () {
      this._resetLayout()
    },
    circular () {
      this._resetLayout()
    },
    intervalNumber (val) {
      if (this._timer) {
        this._cancelSchedule()
        this._scheduleAutoplay()
      }
    },
    current (val) {
      this._currentCheck()
    },
    currentSync (val, oldVal) {
      this._currentChanged(val, oldVal)
      this.$emit('update:current', val)
    },
    currentItemId (val) {
      this._currentCheck()
    },
    currentItemIdSync (val) {
      this.$emit('update:currentItemId', val)
    },
    displayMultipleItemsNumber () {
      this._resetLayout()
    }
  },
  created () {
    this._invalid = true
    this._viewportPosition = 0
    this._viewportMoveRatio = 1
    this._animating = null
    this._requestedAnimation = false
    this._userDirectionChecked = false
    this._contentTrackViewport = 0
    this._contentTrackSpeed = 0
    this._contentTrackT = 0
  },
  mounted () {
    this._currentCheck()
    this.touchtrack(this.$refs.slidesWrapper, '_handleContentTrack', true)
    this._resetLayout()
    this.$watch(() => {
      return this.autoplay && !this.userTracking
    }, this._inintAutoplay)
    this._inintAutoplay(this.autoplay && !this.userTracking)
    this.$watch('items.length', this._resetLayout)
  },
  beforeDestroy () {
    this._cancelSchedule()
    cancelAnimationFrame(this._animationFrame)
  },
  methods: {
    _inintAutoplay (enable) {
      if (enable) {
        this._scheduleAutoplay()
      } else {
        this._cancelSchedule()
      }
    },
    /**
     * 页面变更检查和同步
     */
    _currentCheck () {
      var current = -1
      if (this.currentItemId) {
        for (let i = 0, items = this.items; i < items.length; i++) {
          const componentInstance = items[i].componentInstance
          if (componentInstance && componentInstance.itemId === this.currentItemId) {
            current = i
            break
          }
        }
      }
      if (current < 0) {
        current = Math.round(this.current) || 0
      }
      current = current < 0 ? 0 : current
      if (this.currentSync !== current) {
        this.currentChangeSource = ''
        this.currentSync = current
      }
    },
    _itemReady (vnode, callback) {
      if (vnode.componentInstance && vnode.componentInstance._isMounted) {
        callback()
      } else {
        vnode._callbacks = vnode._callbacks || []
        vnode._callbacks.push(callback)
      }
    },
    /**
     * 当前页面变更
     */
    _currentChanged (current, history) {
      var source = this.currentChangeSource
      this.currentChangeSource = ''
      if (!source) {
        const length = this.items.length
        this._animateViewport(current, '', this.circularEnabled && history + (length - current) % length > length / 2 ? 1 : 0)
      }
      var item = this.items[current]
      if (item) {
        this._itemReady(item, () => {
          var currentItemId = this.currentItemIdSync = item.componentInstance.itemId || ''
          this.$trigger('change', {}, {
            current: this.currentSync,
            currentItemId,
            source
          })
        })
      }
    },
    /**
     * 自动播放
     */
    _scheduleAutoplay () {
      var self = this
      this._cancelSchedule()
      function timer () {
        self._timer = null
        self.currentChangeSource = 'autoplay'
        if (self.circularEnabled) {
          self.currentSync = self._normalizeCurrentValue(self.currentSync + 1)
        } else {
          self.currentSync = self.currentSync + self.displayMultipleItemsNumber < self.items.length ? self.currentSync + 1 : 0
        }
        self._animateViewport(self.currentSync, 'autoplay', self.circularEnabled ? 1 : 0)
        self._timer = setTimeout(timer, self.intervalNumber)
      }
      if (!(!this._isMounted || this._invalid || this.items.length <= this.displayMultipleItemsNumber)) {
        this._timer = setTimeout(timer, this.intervalNumber)
      }
    },
    /**
     * 清除定时器
     */
    _cancelSchedule () {
      if (this._timer) {
        clearTimeout(this._timer)
        this._timer = null
      }
    },
    /**
     * 检查当前页值
     */
    _normalizeCurrentValue (current) {
      var length = this.items.length
      if (!length) {
        return -1
      }
      var index = (Math.round(current) % length + length) % length
      if (this.circularEnabled) {
        if (length <= this.displayMultipleItemsNumber) {
          return 0
        }
      } else if (index > length - this.displayMultipleItemsNumber) {
        return length - this.displayMultipleItemsNumber
      }
      return index
    },
    _upx2px (val) {
      if (/\d+[ur]px$/i.test(val)) {
        val.replace(/\d+[ur]px$/i, text => {
          return `${uni.upx2px(parseFloat(text))}px`
        })
      }
      return val || ''
    },
    /**
     * 重新布局
     */
    _resetLayout () {
      if (this._isMounted) {
        this._cancelSchedule()
        this._endViewportAnimation()
        var items = this.items

        for (var i = 0; i < items.length; i++) {
          this._updateItemPos(i, i)
        }
        this._viewportMoveRatio = 1
        if (this.displayMultipleItemsNumber === 1 && items.length) {
          var itemRect = items[0].componentInstance.$el.getBoundingClientRect()
          var slideFrameRect = this.$refs.slideFrame.getBoundingClientRect()
          this._viewportMoveRatio = itemRect.width / slideFrameRect.width
          if (!(this._viewportMoveRatio > 0 && this._viewportMoveRatio < 1)) {
            this._viewportMoveRatio = 1
          }
        }
        var position = this._viewportPosition
        this._viewportPosition = -2
        var current = this.currentSync
        if (current >= 0) {
          this._invalid = false
          if (this.userTracking) {
            this._updateViewport(position + current - this._contentTrackViewport)
            this._contentTrackViewport = current
          } else {
            this._updateViewport(current)
            if (this.autoplay) {
              this._scheduleAutoplay()
            }
          }
        } else {
          this._invalid = true
          this._updateViewport(-this.displayMultipleItemsNumber - 1)
        }
      }
    },
    _checkCircularLayout (e) {
      if (!this._invalid) {
        for (var items = this.items, n = items.length, i = e + this.displayMultipleItemsNumber, r = 0; r < n; r++) {
          var item = items[r]
          var _position = item._position
          var s = Math.floor(e / n) * n + r
          var l = s + n
          var c = s - n
          var u = Math.max(e - (s + 1), s - i, 0)
          var d = Math.max(e - (l + 1), l - i, 0)
          var h = Math.max(e - (c + 1), c - i, 0)
          var p = Math.min(u, d, h)
          var f = [s, l, c][[u, d, h].indexOf(p)]
          if (_position !== f) {
            this._updateItemPos(r, f)
          }
        }
      }
    },
    _updateItemPos (current, position) {
      var x = this.vertical ? '0' : 100 * position + '%'
      var y = this.vertical ? 100 * position + '%' : '0'
      var transform = 'translate(' + x + ', ' + y + ') translateZ(0)'
      var item = this.items[current]
      this._itemReady(item, () => {
        var el = item.componentInstance.$el
        el.style['-webkit-transform'] = transform
        el.style.transform = transform
        el._position = position
      })
    },
    _updateViewport (index) {
      if (!(Math.floor(2 * this._viewportPosition) === Math.floor(2 * index) && Math.ceil(2 * this._viewportPosition) === Math.ceil(2 * index))) {
        if (this.circularEnabled) {
          this._checkCircularLayout(index)
        }
      }

      var x = this.vertical ? '0' : 100 * -index * this._viewportMoveRatio + '%'
      var y = this.vertical ? 100 * -index * this._viewportMoveRatio + '%' : '0'
      var transform = 'translate(' + x + ', ' + y + ') translateZ(0)'
      var slideFrame = this.$refs.slideFrame
      if (slideFrame) {
        slideFrame.style['-webkit-transform'] = transform
        slideFrame.style.transform = transform
      }
      this._viewportPosition = index
      if (!this._transitionStart) {
        if (index % 1 === 0) {
          return
        }
        this._transitionStart = index
      }
      index -= Math.floor(this._transitionStart)
      if (index <= -(this.items.length - 1)) {
        index += this.items.length
      } else if (index >= this.items.length) {
        index -= this.items.length
      }
      index = this._transitionStart % 1 > 0.5 || this._transitionStart < 0 ? index - 1 : index
      this.$trigger('transition', {}, {
        dx: this.vertical ? 0 : index * slideFrame.offsetWidth,
        dy: this.vertical ? index * slideFrame.offsetHeight : 0
      })
    },
    _animateFrameFuncProto () {
      if (!this._animating) {
        this._requestedAnimation = false
        return
      }
      var _animating = this._animating
      var toPos = _animating.toPos
      var acc = _animating.acc
      var endTime = _animating.endTime
      var source = _animating.source
      var time = endTime - Date.now()
      if (time <= 0) {
        this._updateViewport(toPos)
        this._animating = null
        this._requestedAnimation = false
        this._transitionStart = null
        var item = this.items[this.currentSync]
        if (item) {
          this._itemReady(item, () => {
            var currentItemId = item.componentInstance.itemId || ''
            this.$trigger('animationfinish', {}, {
              current: this.currentSync,
              currentItemId,
              source
            })
          })
        }
        return
      }
      var s = acc * time * time / 2
      var l = toPos + s
      this._updateViewport(l)
      this._animationFrame = requestAnimationFrame(this._animateFrameFuncProto.bind(this))
    },
    _animateViewport (current, source, n) {
      this._cancelViewportAnimation()
      var duration = this.durationNumber
      var length = this.items.length
      var position = this._viewportPosition
      if (this.circularEnabled) {
        if (n < 0) {
          for (; position < current;) {
            position += length
          }
          for (; position - length > current;) {
            position -= length
          }
        } else if (n > 0) {
        /**
         * current -> 1
         * position -> 2
         * length -> 3
         *
         * position -> -1
         */
          for (; position > current;) {
            position -= length
          }
          for (; position + length < current;) {
            position += length
          }
          if (position + length - current < current - position) {
            position += length
          }
        } else {
          for (; position + length < current;) {
            position += length
          }
          for (; position - length > current;) {
            position -= length
          }
          if (position + length - current < current - position) {
            position += length
          }
        }
      }

      this._animating = {
        toPos: current,
        acc: 2 * (position - current) / (duration * duration),
        endTime: Date.now() + duration,
        source: source
      }
      if (!this._requestedAnimation) {
        this._requestedAnimation = true
        this._animationFrame = requestAnimationFrame(this._animateFrameFuncProto.bind(this))
      }
    },
    _cancelViewportAnimation () {
      this._animating = null
    },
    /**
     * 结束动画
     */
    _endViewportAnimation () {
      if (this._animating) {
        this._updateViewport(this._animating.toPos)
        this._animating = null
      }
    },
    _handleTrackStart () {
      this._cancelSchedule()
      this._contentTrackViewport = this._viewportPosition
      this._contentTrackSpeed = 0
      this._contentTrackT = Date.now()
      this._cancelViewportAnimation()
    },
    _handleTrackMove (data) {
      var self = this
      var contentTrackT = this._contentTrackT
      this._contentTrackT = Date.now()
      var length = this.items.length
      var other = length - this.displayMultipleItemsNumber
      function calc (val) {
        return 0.5 - 0.25 / (val + 0.5)
      }

      function move (oldVal, newVal) {
        var val = self._contentTrackViewport + oldVal
        self._contentTrackSpeed = 0.6 * self._contentTrackSpeed + 0.4 * newVal
        if (!self.circularEnabled) {
          if (val < 0 || val > other) {
            if (val < 0) {
              val = -calc(-val)
            } else {
              if (val > other) {
                val = other + calc(val - other)
              }
            }
            self._contentTrackSpeed = 0
          }
        }
        self._updateViewport(val)
      }
      var time = (this._contentTrackT - contentTrackT) || 1
      if (this.vertical) {
        move(-data.dy / this.$refs.slideFrame.offsetHeight, -data.ddy / time)
      } else {
        move(-data.dx / this.$refs.slideFrame.offsetWidth, -data.ddx / time)
      }
    },
    _handleTrackEnd (isCancel) {
      this.userTracking = false
      var t = this._contentTrackSpeed / Math.abs(this._contentTrackSpeed)
      var n = 0
      if (!isCancel && Math.abs(this._contentTrackSpeed) > 0.2) {
        n = 0.5 * t
      }
      var current = this._normalizeCurrentValue(this._viewportPosition + n)
      if (isCancel) {
        this._updateViewport(this._contentTrackViewport)
      } else {
        this.currentChangeSource = 'touch'
        this.currentSync = current
        this._animateViewport(current, 'touch', n !== 0 ? n : current === 0 && (this.circularEnabled && this._viewportPosition >= 1) ? 1 : 0)
      }
    },
    _handleContentTrack (e) {
      if (this.disableTouch || !this.items.length) {
        return
      }
      if (!this._invalid) {
        if (e.detail.state === 'start') {
          this.userTracking = true
          this._userDirectionChecked = false
          return this._handleTrackStart()
        }
        // fixed by xxxxxx
        if (e.detail.state === 'end') {
          return this._handleTrackEnd(false)
        }
        if (e.detail.state === 'cancel') {
          return this._handleTrackEnd(true)
        }
        if (this.userTracking) {
          if (!this._userDirectionChecked) {
            this._userDirectionChecked = true
            var t = Math.abs(e.detail.dx)
            var n = Math.abs(e.detail.dy)
            if (t >= n && this.vertical) {
              this.userTracking = false
            } else {
              if (t <= n && !this.vertical) {
                this.userTracking = false
              }
            }
            if (!this.userTracking) {
              if (this.autoplay) {
                this._scheduleAutoplay()
              }
              return
            }
          }
          this._handleTrackMove(e.detail)
          return false
        }
      }
    }
  },
  render (createElement) {
    var slidesDots = []
    var swiperItems = []
    if (this.$slots.default) {
      deepClone(this.$slots.default, createElement).forEach(vnode => {
        if (vnode.componentOptions && vnode.componentOptions.tag === 'v-uni-swiper-item') {
          swiperItems.push(vnode)
        }
      })
    }
    for (let index = 0, length = swiperItems.length; index < length; index++) {
      const currentSync = this.currentSync
      slidesDots.push(createElement('div', {
        on: {
          click: () => {
            this._animateViewport(this.currentSync = index, this.currentChangeSource = 'click', this.circularEnabled ? 1 : 0)
          }
        },
        class: {
          'uni-swiper-dot': true,
          'uni-swiper-dot-active': (index < currentSync + this.displayMultipleItemsNumber && index >= currentSync) || (index < currentSync + this.displayMultipleItemsNumber - length)
        },
        style: {
          background: index === currentSync ? this.indicatorActiveColor : this.indicatorColor
        }
      }))
    }
    this.items = swiperItems
    var slidesWrapperChild = [createElement('div', {
      ref: 'slides',
      style: this.slidesStyle,
      class: 'uni-swiper-slides'
    }, [
      createElement('div', {
        ref: 'slideFrame',
        class: 'uni-swiper-slide-frame',
        style: this.slideFrameStyle
      }, swiperItems)
    ])]
    if (this.indicatorDots) {
      slidesWrapperChild.push(createElement('div', {
        ref: 'slidesDots',
        class: ['uni-swiper-dots', this.vertical ? 'uni-swiper-dots-vertical' : 'uni-swiper-dots-horizontal']
      }, slidesDots))
    }

    return createElement(
      'uni-swiper', {
        on: this.$listeners
      }, [createElement('div', {
        ref: 'slidesWrapper',
        class: 'uni-swiper-wrapper'
      }, slidesWrapperChild)]
    )
  }
}
</script>
<style>
uni-swiper {
  display: block;
  height: 150px;
}

uni-swiper[hidden] {
  display: none;
}

uni-swiper .uni-swiper-wrapper {
  overflow: hidden;
  position: relative;
  width: 100%;
  height: 100%;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}

uni-swiper .uni-swiper-slides {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
}

uni-swiper .uni-swiper-slide-frame {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  will-change: transform;
}

uni-swiper .uni-swiper-dots {
  position: absolute;
  font-size: 0;
}

uni-swiper .uni-swiper-dots-horizontal {
  left: 50%;
  bottom: 10px;
  text-align: center;
  white-space: nowrap;
  -webkit-transform: translate(-50%, 0);
  transform: translate(-50%, 0);
}

uni-swiper .uni-swiper-dots-horizontal .uni-swiper-dot {
  margin-right: 8px;
}

uni-swiper .uni-swiper-dots-horizontal .uni-swiper-dot:last-child {
  margin-right: 0;
}

uni-swiper .uni-swiper-dots-vertical {
  right: 10px;
  top: 50%;
  text-align: right;
  -webkit-transform: translate(0, -50%);
  transform: translate(0, -50%);
}

uni-swiper .uni-swiper-dots-vertical .uni-swiper-dot {
  display: block;
  margin-bottom: 9px;
}

uni-swiper .uni-swiper-dots-vertical .uni-swiper-dot:last-child {
  margin-bottom: 0;
}

uni-swiper .uni-swiper-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  cursor: pointer;
  transition-property: background-color;
  transition-timing-function: ease;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 50%;
}

uni-swiper .uni-swiper-dot-active {
  background-color: #000000;
}
</style>

<script>
import touchtrack from 'uni-mixins/touchtrack'
import { deepClone } from 'uni-shared'

export default {
  name: 'Swiper',
  mixins: [touchtrack],
  props: {
    indicatorDots: {
      type: [Boolean, String],
      default: false
    },
    vertical: {
      type: [Boolean, String],
      default: false
    },
    autoplay: {
      type: [Boolean, String],
      default: false
    },
    circular: {
      type: [Boolean, String],
      default: false
    },
    interval: {
      type: [Number, String],
      default: 5e3
    },
    duration: {
      type: [Number, String],
      default: 500
    },
    current: {
      type: [Number, String],
      default: 0
    },
    indicatorColor: {
      type: String,
      default: ''
    },
    indicatorActiveColor: {
      type: String,
      default: ''
    },
    previousMargin: {
      type: String,
      default: ''
    },
    nextMargin: {
      type: String,
      default: ''
    },
    currentItemId: {
      type: String,
      default: ''
    },
    skipHiddenItemLayout: {
      type: [Boolean, String],
      default: false
    },
    displayMultipleItems: {
      type: [Number, String],
      default: 1
    },
    disableTouch: {
      type: [Boolean, String],
      default: false
    }
  },
  data () {
    return {
      currentSync: Math.round(this.current) || 0,
      currentItemIdSync: this.currentItemId || '',
      userTracking: false,
      currentChangeSource: '',
      items: []
    }
  },
  computed: {
    intervalNumber () {
      const interval = Number(this.interval)
      return isNaN(interval) ? 5e3 : interval
    },
    durationNumber () {
      const duration = Number(this.duration)
      return isNaN(duration) ? 500 : duration
    },
    displayMultipleItemsNumber () {
      const displayMultipleItems = Math.round(this.displayMultipleItems)
      return isNaN(displayMultipleItems) ? 1 : displayMultipleItems
    },
    slidesStyle () {
      var style = {}
      if (this.nextMargin || this.previousMargin) {
        style = this.vertical ? {
          left: 0,
          right: 0,
          top: this._upx2px(this.previousMargin),
          bottom: this._upx2px(this.nextMargin)
        } : {
          top: 0,
          bottom: 0,
          left: this._upx2px(this.previousMargin),
          right: this._upx2px(this.nextMargin)
        }
      }
      return style
    },
    slideFrameStyle () {
      var value = Math.abs(100 / this.displayMultipleItemsNumber) + '%'
      return {
        width: this.vertical ? '100%' : value,
        height: !this.vertical ? '100%' : value
      }
    },
    circularEnabled () {
      return this.circular && this.items.length > this.displayMultipleItemsNumber
    }
  },
  watch: {
    vertical () {
      this._resetLayout()
    },
    circular () {
      this._resetLayout()
    },
    intervalNumber (val) {
      if (this._timer) {
        this._cancelSchedule()
        this._scheduleAutoplay()
      }
    },
    current (val) {
      this._currentCheck()
    },
    currentSync (val, oldVal) {
      this._currentChanged(val, oldVal)
      this.$emit('update:current', val)
    },
    currentItemId (val) {
      this._currentCheck()
    },
    currentItemIdSync (val) {
      this.$emit('update:currentItemId', val)
    },
    displayMultipleItemsNumber () {
      this._resetLayout()
    }
  },
  created () {
    this._invalid = true
    this._viewportPosition = 0
    this._viewportMoveRatio = 1
    this._animating = null
    this._requestedAnimation = false
    this._userDirectionChecked = false
    this._contentTrackViewport = 0
    this._contentTrackSpeed = 0
    this._contentTrackT = 0
  },
  mounted () {
    this._currentCheck()
    this.touchtrack(this.$refs.slidesWrapper, '_handleContentTrack', true)
    this._resetLayout()
    this.$watch(() => {
      return this.autoplay && !this.userTracking
    }, this._inintAutoplay)
    this._inintAutoplay(this.autoplay && !this.userTracking)
    this.$watch('items.length', this._resetLayout)
  },
  beforeDestroy () {
    this._cancelSchedule()
    cancelAnimationFrame(this._animationFrame)
  },
  methods: {
    _inintAutoplay (enable) {
      if (enable) {
        this._scheduleAutoplay()
      } else {
        this._cancelSchedule()
      }
    },
    /**
     * 页面变更检查和同步
     */
    _currentCheck () {
      var current = -1
      if (this.currentItemId) {
        for (let i = 0, items = this.items; i < items.length; i++) {
          const componentInstance = items[i].componentInstance
          if (componentInstance && componentInstance.itemId === this.currentItemId) {
            current = i
            break
          }
        }
      }
      if (current < 0) {
        current = Math.round(this.current) || 0
      }
      current = current < 0 ? 0 : current
      if (this.currentSync !== current) {
        this.currentChangeSource = ''
        this.currentSync = current
      }
    },
    _itemReady (vnode, callback) {
      if (vnode.componentInstance && vnode.componentInstance._isMounted) {
        callback()
      } else {
        vnode._callbacks = vnode._callbacks || []
        vnode._callbacks.push(callback)
      }
    },
    /**
     * 当前页面变更
     */
    _currentChanged (current, history) {
      var source = this.currentChangeSource
      this.currentChangeSource = ''
      if (!source) {
        const length = this.items.length
        this._animateViewport(current, '', this.circularEnabled && history + (length - current) % length > length / 2 ? 1 : 0)
      }
      var item = this.items[current]
      if (item) {
        this._itemReady(item, () => {
          var currentItemId = this.currentItemIdSync = item.componentInstance.itemId || ''
          this.$trigger('change', {}, {
            current: this.currentSync,
            currentItemId,
            source
          })
        })
      }
    },
    /**
     * 自动播放
     */
    _scheduleAutoplay () {
      var self = this
      this._cancelSchedule()
      function timer () {
        self._timer = null
        self.currentChangeSource = 'autoplay'
        if (self.circularEnabled) {
          self.currentSync = self._normalizeCurrentValue(self.currentSync + 1)
        } else {
          self.currentSync = self.currentSync + self.displayMultipleItemsNumber < self.items.length ? self.currentSync + 1 : 0
        }
        self._animateViewport(self.currentSync, 'autoplay', self.circularEnabled ? 1 : 0)
        self._timer = setTimeout(timer, self.intervalNumber)
      }
      if (!(!this._isMounted || this._invalid || this.items.length <= this.displayMultipleItemsNumber)) {
        this._timer = setTimeout(timer, this.intervalNumber)
      }
    },
    /**
     * 清除定时器
     */
    _cancelSchedule () {
      if (this._timer) {
        clearTimeout(this._timer)
        this._timer = null
      }
    },
    /**
     * 检查当前页值
     */
    _normalizeCurrentValue (current) {
      var length = this.items.length
      if (!length) {
        return -1
      }
      var index = (Math.round(current) % length + length) % length
      if (this.circularEnabled) {
        if (length <= this.displayMultipleItemsNumber) {
          return 0
        }
      } else if (index > length - this.displayMultipleItemsNumber) {
        return length - this.displayMultipleItemsNumber
      }
      return index
    },
    _upx2px (val) {
      if (/\d+[ur]px$/i.test(val)) {
        val.replace(/\d+[ur]px$/i, text => {
          return `${uni.upx2px(parseFloat(text))}px`
        })
      }
      return val || ''
    },
    /**
     * 重新布局
     */
    _resetLayout () {
      if (this._isMounted) {
        this._cancelSchedule()
        this._endViewportAnimation()
        var items = this.items

        for (var i = 0; i < items.length; i++) {
          this._updateItemPos(i, i)
        }
        this._viewportMoveRatio = 1
        if (this.displayMultipleItemsNumber === 1 && items.length) {
          var itemRect = items[0].componentInstance.$el.getBoundingClientRect()
          var slideFrameRect = this.$refs.slideFrame.getBoundingClientRect()
          this._viewportMoveRatio = itemRect.width / slideFrameRect.width
          if (!(this._viewportMoveRatio > 0 && this._viewportMoveRatio < 1)) {
            this._viewportMoveRatio = 1
          }
        }
        var position = this._viewportPosition
        this._viewportPosition = -2
        var current = this.currentSync
        if (current >= 0) {
          this._invalid = false
          if (this.userTracking) {
            this._updateViewport(position + current - this._contentTrackViewport)
            this._contentTrackViewport = current
          } else {
            this._updateViewport(current)
            if (this.autoplay) {
              this._scheduleAutoplay()
            }
          }
        } else {
          this._invalid = true
          this._updateViewport(-this.displayMultipleItemsNumber - 1)
        }
      }
    },
    _checkCircularLayout (e) {
      if (!this._invalid) {
        for (var items = this.items, n = items.length, i = e + this.displayMultipleItemsNumber, r = 0; r < n; r++) {
          var item = items[r]
          var _position = item._position
          var s = Math.floor(e / n) * n + r
          var l = s + n
          var c = s - n
          var u = Math.max(e - (s + 1), s - i, 0)
          var d = Math.max(e - (l + 1), l - i, 0)
          var h = Math.max(e - (c + 1), c - i, 0)
          var p = Math.min(u, d, h)
          var f = [s, l, c][[u, d, h].indexOf(p)]
          if (_position !== f) {
            this._updateItemPos(r, f)
          }
        }
      }
    },
    _updateItemPos (current, position) {
      var x = this.vertical ? '0' : 100 * position + '%'
      var y = this.vertical ? 100 * position + '%' : '0'
      var transform = 'translate(' + x + ', ' + y + ') translateZ(0)'
      var item = this.items[current]
      this._itemReady(item, () => {
        var el = item.componentInstance.$el
        el.style['-webkit-transform'] = transform
        el.style.transform = transform
        el._position = position
      })
    },
    _updateViewport (index) {
      if (!(Math.floor(2 * this._viewportPosition) === Math.floor(2 * index) && Math.ceil(2 * this._viewportPosition) === Math.ceil(2 * index))) {
        if (this.circularEnabled) {
          this._checkCircularLayout(index)
        }
      }

      var x = this.vertical ? '0' : 100 * -index * this._viewportMoveRatio + '%'
      var y = this.vertical ? 100 * -index * this._viewportMoveRatio + '%' : '0'
      var transform = 'translate(' + x + ', ' + y + ') translateZ(0)'
      var slideFrame = this.$refs.slideFrame
      if (slideFrame) {
        slideFrame.style['-webkit-transform'] = transform
        slideFrame.style.transform = transform
      }
      this._viewportPosition = index
      if (!this._transitionStart) {
        if (index % 1 === 0) {
          return
        }
        this._transitionStart = index
      }
      index -= Math.floor(this._transitionStart)
      if (index <= -(this.items.length - 1)) {
        index += this.items.length
      } else if (index >= this.items.length) {
        index -= this.items.length
      }
      index = this._transitionStart % 1 > 0.5 || this._transitionStart < 0 ? index - 1 : index
      this.$trigger('transition', {}, {
        dx: this.vertical ? 0 : index * slideFrame.offsetWidth,
        dy: this.vertical ? index * slideFrame.offsetHeight : 0
      })
    },
    _animateFrameFuncProto () {
      if (!this._animating) {
        this._requestedAnimation = false
        return
      }
      var _animating = this._animating
      var toPos = _animating.toPos
      var acc = _animating.acc
      var endTime = _animating.endTime
      var source = _animating.source
      var time = endTime - Date.now()
      if (time <= 0) {
        this._updateViewport(toPos)
        this._animating = null
        this._requestedAnimation = false
        this._transitionStart = null
        var item = this.items[this.currentSync]
        if (item) {
          this._itemReady(item, () => {
            var currentItemId = item.componentInstance.itemId || ''
            this.$trigger('animationfinish', {}, {
              current: this.currentSync,
              currentItemId,
              source
            })
          })
        }
        return
      }
      var s = acc * time * time / 2
      var l = toPos + s
      this._updateViewport(l)
      this._animationFrame = requestAnimationFrame(this._animateFrameFuncProto.bind(this))
    },
    _animateViewport (current, source, n) {
      this._cancelViewportAnimation()
      var duration = this.durationNumber
      var length = this.items.length
      var position = this._viewportPosition
      if (this.circularEnabled) {
        if (n < 0) {
          for (; position < current;) {
            position += length
          }
          for (; position - length > current;) {
            position -= length
          }
        } else if (n > 0) {
          for (; position > current;) {
            position -= length
          }
          for (; position + length < current;) {
            position += length
          }
        } else {
          for (; position + length < current;) {
            position += length
          }
          for (; position - length > current;) {
            position -= length
          }
          if (position + length - current < current - position) {
            position += length
          }
        }
      }

      this._animating = {
        toPos: current,
        acc: 2 * (position - current) / (duration * duration),
        endTime: Date.now() + duration,
        source: source
      }
      if (!this._requestedAnimation) {
        this._requestedAnimation = true
        this._animationFrame = requestAnimationFrame(this._animateFrameFuncProto.bind(this))
      }
    },
    _cancelViewportAnimation () {
      this._animating = null
    },
    /**
     * 结束动画
     */
    _endViewportAnimation () {
      if (this._animating) {
        this._updateViewport(this._animating.toPos)
        this._animating = null
      }
    },
    _handleTrackStart () {
      this._cancelSchedule()
      this._contentTrackViewport = this._viewportPosition
      this._contentTrackSpeed = 0
      this._contentTrackT = Date.now()
      this._cancelViewportAnimation()
    },
    _handleTrackMove (data) {
      var self = this
      var contentTrackT = this._contentTrackT
      this._contentTrackT = Date.now()
      var length = this.items.length
      var other = length - this.displayMultipleItemsNumber
      function calc (val) {
        return 0.5 - 0.25 / (val + 0.5)
      }

      function move (oldVal, newVal) {
        var val = self._contentTrackViewport + oldVal
        self._contentTrackSpeed = 0.6 * self._contentTrackSpeed + 0.4 * newVal
        if (!self.circularEnabled) {
          if (val < 0 || val > other) {
            if (val < 0) {
              val = -calc(-val)
            } else {
              if (val > other) {
                val = other + calc(val - other)
              }
            }
            self._contentTrackSpeed = 0
          }
        }
        self._updateViewport(val)
      }
      var time = (this._contentTrackT - contentTrackT) || 1
      if (this.vertical) {
        move(-data.dy / this.$refs.slideFrame.offsetHeight, -data.ddy / time)
      } else {
        move(-data.dx / this.$refs.slideFrame.offsetWidth, -data.ddx / time)
      }
    },
    _handleTrackEnd (isCancel) {
      this.userTracking = false
      var t = this._contentTrackSpeed / Math.abs(this._contentTrackSpeed)
      var n = 0
      if (!isCancel && Math.abs(this._contentTrackSpeed) > 0.2) {
        n = 0.5 * t
      }
      var current = this._normalizeCurrentValue(this._viewportPosition + n)
      if (isCancel) {
        this._updateViewport(this._contentTrackViewport)
      } else {
        this.currentChangeSource = 'touch'
        this.currentSync = current
        this._animateViewport(current, 'touch', n !== 0 ? n : current === 0 && (this.circularEnabled && this._viewportPosition >= 1) ? 1 : 0)
      }
    },
    _handleContentTrack (e) {
      if (this.disableTouch || !this.items.length) {
        return
      }
      if (!this._invalid) {
        if (e.detail.state === 'start') {
          this.userTracking = true
          this._userDirectionChecked = false
          return this._handleTrackStart()
        }
        // fixed by xxxxxx
        if (e.detail.state === 'end') {
          return this._handleTrackEnd(false)
        }
        if (e.detail.state === 'cancel') {
          return this._handleTrackEnd(true)
        }
        if (this.userTracking) {
          if (!this._userDirectionChecked) {
            this._userDirectionChecked = true
            var t = Math.abs(e.detail.dx)
            var n = Math.abs(e.detail.dy)
            if (t >= n && this.vertical) {
              this.userTracking = false
            } else {
              if (t <= n && !this.vertical) {
                this.userTracking = false
              }
            }
            if (!this.userTracking) {
              if (this.autoplay) {
                this._scheduleAutoplay()
              }
              return
            }
          }
          this._handleTrackMove(e.detail)
          return false
        }
      }
    }
  },
  render (createElement) {
    var slidesDots = []
    var swiperItems = []
    if (this.$slots.default) {
      deepClone(this.$slots.default, createElement).forEach(vnode => {
        if (vnode.componentOptions && vnode.componentOptions.tag === 'v-uni-swiper-item') {
          swiperItems.push(vnode)
        }
      })
    }
    for (let index = 0, length = swiperItems.length; index < length; index++) {
      const currentSync = this.currentSync
      slidesDots.push(createElement('div', {
        on: {
          click: () => {
            this._animateViewport(this.currentSync = index, this.currentChangeSource = 'click', this.circularEnabled ? 1 : 0)
          }
        },
        class: {
          'uni-swiper-dot': true,
          'uni-swiper-dot-active': (index < currentSync + this.displayMultipleItemsNumber && index >= currentSync) || (index < currentSync + this.displayMultipleItemsNumber - length)
        },
        style: {
          background: index === currentSync ? this.indicatorActiveColor : this.indicatorColor
        }
      }))
    }
    this.items = swiperItems
    var slidesWrapperChild = [createElement('div', {
      ref: 'slides',
      style: this.slidesStyle,
      class: 'uni-swiper-slides'
    }, [
      createElement('div', {
        ref: 'slideFrame',
        class: 'uni-swiper-slide-frame',
        style: this.slideFrameStyle
      }, swiperItems)
    ])]
    if (this.indicatorDots) {
      slidesWrapperChild.push(createElement('div', {
        ref: 'slidesDots',
        class: ['uni-swiper-dots', this.vertical ? 'uni-swiper-dots-vertical' : 'uni-swiper-dots-horizontal']
      }, slidesDots))
    }

    return createElement(
      'uni-swiper', {
        on: this.$listeners
      }, [createElement('div', {
        ref: 'slidesWrapper',
        class: 'uni-swiper-wrapper'
      }, slidesWrapperChild)]
    )
  }
}
</script>
<style>
uni-swiper {
  display: block;
  height: 150px;
}

uni-swiper[hidden] {
  display: none;
}

uni-swiper .uni-swiper-wrapper {
  overflow: hidden;
  position: relative;
  width: 100%;
  height: 100%;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}

uni-swiper .uni-swiper-slides {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
}

uni-swiper .uni-swiper-slide-frame {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  will-change: transform;
}

uni-swiper .uni-swiper-dots {
  position: absolute;
  font-size: 0;
}

uni-swiper .uni-swiper-dots-horizontal {
  left: 50%;
  bottom: 10px;
  text-align: center;
  white-space: nowrap;
  -webkit-transform: translate(-50%, 0);
  transform: translate(-50%, 0);
}

uni-swiper .uni-swiper-dots-horizontal .uni-swiper-dot {
  margin-right: 8px;
}

uni-swiper .uni-swiper-dots-horizontal .uni-swiper-dot:last-child {
  margin-right: 0;
}

uni-swiper .uni-swiper-dots-vertical {
  right: 10px;
  top: 50%;
  text-align: right;
  -webkit-transform: translate(0, -50%);
  transform: translate(0, -50%);
}

uni-swiper .uni-swiper-dots-vertical .uni-swiper-dot {
  display: block;
  margin-bottom: 9px;
}

uni-swiper .uni-swiper-dots-vertical .uni-swiper-dot:last-child {
  margin-bottom: 0;
}

uni-swiper .uni-swiper-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  cursor: pointer;
  transition-property: background-color;
  transition-timing-function: ease;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 50%;
}

uni-swiper .uni-swiper-dot-active {
  background-color: #000000;
}
</style>