put notifications on the right

put notifications on the right

<script lang="coffee">
import AppConfig      from '@/shared/services/app_config'
import EventBus       from '@/shared/services/event_bus'
import AbilityService from '@/shared/services/ability_service'
import AuthModalMixin from '@/mixins/auth_modal'
import Session        from '@/shared/services/session'
import UrlFor         from '@/mixins/url_for'
import {tail, map, last}         from 'lodash'

export default
  mixins: [ AuthModalMixin, UrlFor ]
  data: ->
    title: AppConfig.theme.site_name
    isLoggedIn: Session.isSignedIn()
    group: null
    breadcrumbs: null
    sidebarOpen: false

  methods:
    toggleSidebar: -> EventBus.$emit 'toggleSidebar'
    toggleThreadNav: -> EventBus.$emit 'toggleThreadNav'
    signIn: -> @openAuthModal()
    last: last

  mounted: ->
    EventBus.$on 'sidebarOpen', (val) =>
      @sidebarOpen = val

    EventBus.$on 'signedIn', (user) =>
      @isLoggedIn = true

    EventBus.$on 'currentComponent', (data) =>
      if data.title?
        @title = data.title
      else if data.titleKey?
        @title = @$t(data.titleKey)

      @group = data.group
      @discussion = data.discussion
      @breadcrumbs = data.breadcrumbs

      if data.breadcrumbs
        if data.breadcrumbs[0].isA?('group')
          @group = data.breadcrumbs[0]
          if data.breadcrumbs.length > 1
            data.breadcrumbs = tail(@breadcrumbs)

        @breadcrumbs = map data.breadcrumbs, (model, i) =>
          i: i
          text: model.title || model.name
          to: @urlFor(model)
        last(@breadcrumbs).last = true
        @title = last(@breadcrumbs).text

  computed:
    logo: ->
      AppConfig.theme.app_logo_src
    icon: ->
      AppConfig.theme.icon_src
</script>

<template lang="pug">
v-app-bar(app clipped-right)
  v-btn.navbar__sidenav-toggle(icon v-if="!sidebarOpen" @click="toggleSidebar()")
    v-avatar(tile size="36px")
      v-icon mdi-menu
  v-btn.navbar__group-toggle(icon v-if="group" :to="urlFor(group)")
    v-avatar(tile size="36px")
      img(:src='group.logoUrlMedium')
  v-toolbar-title
    span(v-if="breadcrumbs" v-for="crumb in breadcrumbs" :key="crumb.i")
      router-link(v-if="!crumb.last" :to="crumb.to") {{crumb.text}}
      span(v-if="crumb.last") {{crumb.text}}
      span(v-if="!crumb.last")
        |
        | >
        |
    span(v-if="!breadcrumbs") {{title}}
  v-spacer
  notifications(v-if='isLoggedIn')
  v-toolbar-items
    v-btn.navbar__sign-in(text v-if='!isLoggedIn' v-t="'navbar.sign_in'" @click='signIn()')
</template>

<script lang="coffee">
import AppConfig      from '@/shared/services/app_config'
import EventBus       from '@/shared/services/event_bus'
import AbilityService from '@/shared/services/ability_service'
import AuthModalMixin from '@/mixins/auth_modal'
import Session        from '@/shared/services/session'
import UrlFor         from '@/mixins/url_for'
import {tail, map, last}         from 'lodash'

export default
  mixins: [ AuthModalMixin, UrlFor ]
  data: ->
    title: AppConfig.theme.site_name
    isLoggedIn: Session.isSignedIn()
    group: null
    breadcrumbs: null
    sidebarOpen: false

  methods:
    toggleSidebar: -> EventBus.$emit 'toggleSidebar'
    toggleThreadNav: -> EventBus.$emit 'toggleThreadNav'
    signIn: -> @openAuthModal()
    last: last

  mounted: ->
    EventBus.$on 'sidebarOpen', (val) =>
      @sidebarOpen = val

    EventBus.$on 'signedIn', (user) =>
      @isLoggedIn = true

    EventBus.$on 'currentComponent', (data) =>
      if data.title?
        @title = data.title
      else if data.titleKey?
        @title = @$t(data.titleKey)

      @group = data.group
      @discussion = data.discussion
      @breadcrumbs = data.breadcrumbs

      if data.breadcrumbs
        if data.breadcrumbs[0].isA?('group')
          @group = data.breadcrumbs[0]
          if data.breadcrumbs.length > 1
            data.breadcrumbs = tail(@breadcrumbs)

        @breadcrumbs = map data.breadcrumbs, (model, i) =>
          i: i
          text: model.title || model.name
          to: @urlFor(model)
        last(@breadcrumbs).last = true
        @title = last(@breadcrumbs).text

  computed:
    logo: ->
      AppConfig.theme.app_logo_src
    icon: ->
      AppConfig.theme.icon_src
</script>

<template lang="pug">
v-app-bar(app)
  v-btn.navbar__sidenav-toggle(icon v-if="!sidebarOpen" @click="toggleSidebar()")
    v-avatar(tile size="36px")
      v-icon mdi-menu
  v-btn.navbar__group-toggle(icon v-if="group" :to="urlFor(group)")
    v-avatar(tile size="36px")
      img(:src='group.logoUrlMedium')
  v-toolbar-title
    span(v-if="breadcrumbs" v-for="crumb in breadcrumbs" :key="crumb.i")
      router-link(v-if="!crumb.last" :to="crumb.to") {{crumb.text}}
      span(v-if="crumb.last") {{crumb.text}}
      span(v-if="!crumb.last")
        |
        | >
        |
    span(v-if="!breadcrumbs") {{title}}
  //- v-spacer
  notifications(v-if='isLoggedIn')
  v-toolbar-items
    v-btn.navbar__sign-in(text v-if='!isLoggedIn' v-t="'navbar.sign_in'" @click='signIn()')
</template>


Update url-preview.vue

Update url-preview.vue

<template>
<iframe v-if="youtubeId" type="text/html" height="250"
	:src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
	frameborder="0"/>
<iframe v-else-if="spotifyId"
	:src="`https://open.spotify.com/embed/track/${spotifyId}`"
	frameborder="0" allowtransparency="true" allow="encrypted-media" />
<iframe v-else-if="nicovideoId"
	:src="`https://embed.nicovideo.jp/watch/${nicovideoId}?oldScript=1&referer=${misskeyUrl}&from=${position || '0'}&allowProgrammaticFullScreen=1`"
	frameborder="0" allow="autoplay; encrypted-media" allowfullscreen />

		<div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`">

{{ title }}

{{ description }}

{{ sitename }}

</template>

<template>
<iframe v-if="youtubeId" type="text/html" height="250"
	:src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
	frameborder="0"/>
<iframe v-else-if="spotifyId"
	:src="`https://open.spotify.com/embed/track/${spotifyId}`"
	frameborder="0" allowtransparency="true" allow="encrypted-media" />

		<div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`">

{{ title }}

{{ description }}

{{ sitename }}

</template>


fixed copy-paste erorr in feed

fixed copy-paste erorr in feed

<template>

Caricamento in corso

            <multiselect :options="months" label="label" :multiple="false" :close-on-select="true" v-model="selectedMonth" placeholder="Scegli un mese" />

            <multiselect :options="sources" :multiple="true" :close-on-select="true" v-model="selectedSources" placeholder="Filtra per fonti" label="name" />

                Solo annunci di lavoro <input id="jobs_only" type="checkbox" value="false" v-model="jobsOnly"></input>
                Mostra come tabella <input id="as_list" type="checkbox" value="false" v-model="asList"></input>

            <PostDisplay :posts="filteredPosts" :as-list="asList"></PostDisplay>

</template>

        PostDisplay,
        Multiselect
    }
};

export default Feed;
</script>


Dashboard top panels

Dashboard top panels

<template>

Portfolio

Today's Gains/Losses

Cash Available

Uncleared Deposits

Day

Week

Year

5 Year

    <line-chart :chart-data="graphData" :options="chartOptions"></line-chart>

Put watchlist below here

</template>

<template>

Welcome to Robinhood-Web

Entire Portfolio Value

Day

Week

Year

5 Year

    <line-chart :chart-data="graphData" :options="chartOptions"></line-chart>

Account Equity Stats

       Portfolio Equity ( today)

       Previous Close Equity

Account Balance Stats

       Uncleared Deposits

       Cash Available

</template>


User dropdown widget fixes for use in the table form using material alike look and feel.

<template>
  <span v-if="multiple">
    <MultiSelectDropdown v-model="inputValue" :list-source="ddListSource" :disabled="disabled"
      :md-type="$attrs['md-type']" />
  </span>
  <span v-else>
    <Dropdown v-model="inputValue" :list-source="ddListSource" :disabled="disabled"
      :md-type="$attrs['md-type']" />
  </span>
</template>

<script>
import Dropdown from '@/common/components/Dropdown.vue';
import MultiSelectDropdown from '@/common/components/MultiSelectDropdown.vue';

import http from '@/common/services/HttpClient.js';
import util from '@/common/services/Util.js';
import exprUtil from '@/common/services/ExpressionUtil.js';

export default {
  props: ['modelValue', 'selectProp', 'listSource', 'context', 'multiple', 'disabled'],

  components: {
    Dropdown,
    MultiSelectDropdown
  },

  data() {
    return {
      ddListSource: {
        loadFn: async (opts) => {
          let cache = (this.context && this.context._formCache) || {};
          cache = cache['user-dropdown'] = cache['user-dropdown'] || {};

          opts = opts || {maxResults: 100};
          if (opts.value || opts.value == 0) {
            let id = parseInt(opts.value);
            if (!isNaN(id)) {
              if (!cache[id]) {
                cache[id] = await http.get('users/' + id);
              }

              return [cache[id]];
            }
          }

          let params = Object.assign({searchString: opts.query || ''}, opts || {maxResults: 100});
          let ls = this.listSource || {};
          let qp = ls.queryParams || {};
          if (qp.static) {
            Object.keys(qp.static).forEach(name => params[name] = qp.static[name]);
          }

          if (qp.dynamic && this.context) {
            Object.keys(qp.dynamic).forEach(name => params[name] = exprUtil.eval(this.context, qp.dynamic[name]));
          }

          if (this.listAll) {
            params.listAll = true;
          }

          const qs = util.queryString(params);
          if (!cache[qs]) {
            cache[qs] = await http.get('users', params);
          }

          return cache[qs];
        },
        selectProp: this.selectProp || (this.listSource && this.listSource.selectProp),
        displayProp: (user) => user.firstName + ' ' + user.lastName
      }
    }
  },

  computed: {
    inputValue: {
      get() {
        if (this.modelValue != null && !isNaN(this.modelValue)) {
          return +this.modelValue;
        }

        return this.modelValue;
      },

      set(value) {
        this.$emit('update:modelValue', value);
      }
    }
  }
}
</script>

<template>
  <span v-if="multiple">
    <MultiSelectDropdown v-model="inputValue" :list-source="ddListSource" :disabled="disabled" />
  </span>
  <span v-else>
    <Dropdown v-model="inputValue" :list-source="ddListSource" :disabled="disabled" />
  </span>
</template>

<script>
import Dropdown from '@/common/components/Dropdown.vue';
import MultiSelectDropdown from '@/common/components/MultiSelectDropdown.vue';

import http from '@/common/services/HttpClient.js';
import util from '@/common/services/Util.js';
import exprUtil from '@/common/services/ExpressionUtil.js';

export default {
  props: ['modelValue', 'selectProp', 'listSource', 'context', 'multiple', 'disabled'],

  components: {
    Dropdown,
    MultiSelectDropdown
  },

  data() {
    return {
      ddListSource: {
        loadFn: async (opts) => {
          let cache = (this.context && this.context._formCache) || {};
          cache = cache['user-dropdown'] = cache['user-dropdown'] || {};

          opts = opts || {maxResults: 100};
          if (opts.value || opts.value == 0) {
            let id = parseInt(opts.value);
            if (!isNaN(id)) {
              if (!cache[id]) {
                cache[id] = await http.get('users/' + id);
              }

              return [cache[id]];
            }
          }

          let params = Object.assign({searchString: opts.query || ''}, opts || {maxResults: 100});
          let ls = this.listSource || {};
          let qp = ls.queryParams || {};
          if (qp.static) {
            Object.keys(qp.static).forEach(name => params[name] = qp.static[name]);
          }

          if (qp.dynamic && this.context) {
            Object.keys(qp.dynamic).forEach(name => params[name] = exprUtil.eval(this.context, qp.dynamic[name]));
          }

          if (this.listAll) {
            params.listAll = true;
          }

          const qs = util.queryString(params);
          if (!cache[qs]) {
            cache[qs] = await http.get('users', params);
          }

          return cache[qs];
        },
        selectProp: this.selectProp || (this.listSource && this.listSource.selectProp),
        displayProp: (user) => user.firstName + ' ' + user.lastName
      }
    }
  },

  computed: {
    inputValue: {
      get() {
        if (this.modelValue != null && !isNaN(this.modelValue)) {
          return +this.modelValue;
        }

        return this.modelValue;
      },

      set(value) {
        this.$emit('update:modelValue', value);
      }
    }
  }
}
</script>


ui: 🎨 hide 3 percent staking fee

ui: 🎨 hide 3 percent staking fee

<template>

    <v-row>
      <v-col
        :order="$vuetify.breakpoint.smAndDown ? 'last' : ''"
        cols="12"
        md="8"
        :class="$vuetify.breakpoint.smAndDown ? 'my-10' : 'pr-7'"
      >
        <mew6-white-sheet
          class="pa-md-15"
          :no-shadow="$vuetify.breakpoint.smAndDown"
        >

Compound Rewards

                You give

rETH2

              <v-icon color="greenPrimary">mdi-arrow-right</v-icon>

                You will get

sETH2

            <button-balance :loading="false" :balance="balanceInETH" />
            <mew-input
              type="number"
              :max-btn-obj="{
                title: 'Max',
                disabled: false,
                method: setMax
              }"
              :image="iconStakewise"
              label="Amount to compound"
              placeholder="Enter amount"
              :value="compoundAmount"
            />

How Compounding works

              To increase your staking balance and maximize your rewards, you
              can transfer your rETH2 rewards balance to the sETH2 staking pool.

                View StakeWise guide<v-icon
                  color="greenPrimary"
                  small
                  class="ml-2"
                >
                  mdi-open-in-new
                </v-icon>

          <v-divider class="mt-12 mb-5" />

                  Network Fee

                  Edit priority

0.023422 ETH

$120.98

          <v-divider class="mt-4 mb-9" />

            <mew-checkbox
              label="I have read and agreeed to Stakewise terms of service."
              :link="{
                title: 'Stakewise terms',
                url: 'https://stakewise.io/terms-and-conditions/'
              }"
            />
            <mew-button
              class="mt-8"
              title="Compound Rewards"
              btn-size="xlarge"
            />

        </mew6-white-sheet>
      </v-col>
      <v-col cols="12" md="4">
        <stakewise-apr class="mb-4" />
        <stakewise-staking class="mb-4" />
        <stakewise-rewards />
      </v-col>
    </v-row>

</template>

<template>

    <v-row>
      <v-col
        :order="$vuetify.breakpoint.smAndDown ? 'last' : ''"
        cols="12"
        md="8"
        :class="$vuetify.breakpoint.smAndDown ? 'my-10' : 'pr-7'"
      >
        <mew6-white-sheet
          class="pa-md-15"
          :no-shadow="$vuetify.breakpoint.smAndDown"
        >

Compound Rewards

                You give

rETH2

              <v-icon color="greenPrimary">mdi-arrow-right</v-icon>

                You will get

sETH2

            <button-balance :loading="false" :balance="balanceInETH" />
            <mew-input
              type="number"
              :max-btn-obj="{
                title: 'Max',
                disabled: false,
                method: setMax
              }"
              :image="iconStakewise"
              label="Amount to compound"
              placeholder="Enter amount"
              :value="compoundAmount"
            />

How Compounding works

              To increase your staking balance and maximize your rewards, you
              can transfer your rETH2 rewards balance to the sETH2 staking pool.

                View StakeWise guide<v-icon
                  color="greenPrimary"
                  small
                  class="ml-2"
                >
                  mdi-open-in-new
                </v-icon>

          <v-divider class="mt-12 mb-5" />

                  Network Fee

                  Edit priority

0.023422 ETH

$120.98

                0.3% Staking Fee

0.38421 ETH

$120.98

          <v-divider class="mt-4 mb-9" />

            <mew-checkbox
              label="I have read and agreeed to Stakewise terms of service."
              :link="{
                title: 'Stakewise terms',
                url: 'https://stakewise.io/terms-and-conditions/'
              }"
            />
            <mew-button
              class="mt-8"
              title="Compound Rewards"
              btn-size="xlarge"
            />

        </mew6-white-sheet>
      </v-col>
      <v-col cols="12" md="4">
        <stakewise-apr class="mb-4" />
        <stakewise-staking class="mb-4" />
        <stakewise-rewards />
      </v-col>
    </v-row>

</template>


Removed heart symbol

Removed heart symbol

<template>

{{projectList.owner.name}}

Projects

      There are no public projects here yet.

    <router-link
      v-if='projectList.owner && myProjectList'
      :to='{name: "new-project"}' class='start-new-project'>Start a new project</router-link>

        <router-link class='project-link'
          v-for='project in projectList.projects' :to='{name: "project-overview", params: {projectId: project.id, userId: project.ownerId}}'
          >{{project.title}}</router-link>

    <router-link
      :to='{name: "userPage", params: { userId: myId }}' class='back-to-your-home'
      v-if='!myProjectList'>↰ back to your projects</router-link>

        <router-link to='/' class='logo-text'> Streak αλφα</router-link>

</template>

<template>

{{projectList.owner.name}}

Projects

      There are no public projects here yet.

    <router-link
      v-if='projectList.owner && myProjectList'
      :to='{name: "new-project"}' class='start-new-project'>Start a new project</router-link>

        <router-link class='project-link'
          v-for='project in projectList.projects' :to='{name: "project-overview", params: {projectId: project.id, userId: project.ownerId}}'
          >{{project.title}}</router-link>

    <router-link
      :to='{name: "userPage", params: { userId: myId }}' class='back-to-your-home'
      v-if='!myProjectList'>↰ back to your projects</router-link>

        <router-link to='/' class='logo-text'>❤ Streak αλφα</router-link>

</template>


Button uppercase.

Button uppercase.

<template>

      <slot></slot>

</template>

<template>

      <slot></slot>

</template>


popup: Fix overflow-scrolling setting #311

<template>
  <transition name="vux-popup-animate">
    <div v-show="show" :style="{height:height}" class="vux-popup">
      <slot></slot>
    </div>
  </transition>
</template>

<script>
import Popup from './popup'

export default {
  props: {
    value: Boolean,
    height: {
      type: String,
      default: 'auto'
    },
    hideOnBlur: {
      type: Boolean,
      default: true
    }
  },
  mounted () {
    this.$nextTick(() => {
      const _this = this
      this.popup = new Popup({
        container: _this.$el,
        innerHTML: '',
        hideOnBlur: _this.hideOnBlur,
        onOpen () {
          _this.fixSafariOverflowScrolling('auto')
          _this.show = true
        },
        onClose () {
          _this.show = false
          if (Object.keys(window.__$vuxPopups).length >= 1) return
          _this.fixSafariOverflowScrolling('touch')
        }
      })
      this.$overflowScrollingList = document.querySelectorAll('.vux-fix-safari-overflow-scrolling')
    })
  },
  methods: {
    /**
    * https://github.com/airyland/vux/issues/311
    * https://benfrain.com/z-index-stacking-contexts-experimental-css-and-ios-safari/
    */
    fixSafariOverflowScrolling (type) {
      if (!this.$overflowScrollingList.length) return
      if (!/iphone/i.test(navigator.userAgent)) return
      for (let i = 0; i < this.$overflowScrollingList.length; i++) {
        this.$overflowScrollingList[i].style.webkitOverflowScrolling = type
      }
    }
  },
  data () {
    return {
      hasFirstShow: false,
      show: this.value
    }
  },
  watch: {
    show (val) {
      this.$emit('input', val)
      if (val) {
        this.popup.show()
        this.$emit('on-show')
        this.fixSafariOverflowScrolling('auto')
        if (!this.hasFirstShow) {
          this.$emit('on-first-show')
          this.hasFirstShow = true
        }
      } else {
        this.$emit('on-hide')
        this.fixSafariOverflowScrolling('touch')
        this.show = false
        this.popup.hide(false)
      }
    },
    value (val) {
      this.show = val
    }
  },
  beforeDestroy () {
    this.popup.destroy()
    this.fixSafariOverflowScrolling('touch')
  }
}
</script>

<style>
.vux-popup-dialog,.vux-popup {
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  background: #eee;
  z-index: 501;
  transition-property: transform;
  transition-duration: 300ms;
}
.vux-popup-mask {
  display: block;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  tap-highlight-color: rgba(0,0,0,0);
  z-index: -1;
}
.vux-popup-mask.vux-popup-show {
  opacity: 1;
  z-index: 100;
  transition: opacity 0.3s;
}
.vux-popup-animate-transiton {}
.vux-popup-animate-enter {
  transform: translate3d(0, 100%, 0);
}
.vux-popup-animate-leave-active {
  transform: translate3d(0, 100%, 0);
}
</style>

<template>
  <transition name="vux-popup-animate">
    <div v-show="show" :style="{height:height}" class="vux-popup">
      <slot></slot>
    </div>
  </transition>
</template>

<script>
import Popup from './popup'

export default {
  props: {
    value: Boolean,
    height: {
      type: String,
      default: 'auto'
    },
    hideOnBlur: {
      type: Boolean,
      default: true
    }
  },
  mounted () {
    this.$nextTick(() => {
      const _this = this
      this.popup = new Popup({
        container: _this.$el,
        innerHTML: '',
        hideOnBlur: _this.hideOnBlur,
        onOpen () {
          _this.fixSafariOverflowScrolling('auto')
          _this.show = true
        },
        onClose () {
          _this.show = false
          if (Object.keys(window.__$vuxPopups).length >= 1) return
          _this.fixSafariOverflowScrolling('touch')
        }
      })
      this.$overflowScrollingList = document.querySelectorAll('.vux-fix-safari-overflow-scrolling')
    })
  },
  methods: {
    /**
    * https://github.com/airyland/vux/issues/311
    * https://benfrain.com/z-index-stacking-contexts-experimental-css-and-ios-safari/
    */
    fixSafariOverflowScrolling (type) {
      if (!this.$overflowScrollingList.length) return
      if (!/iphone/i.test(navigator.userAgent)) return
      for (let i = 0; i < this.$overflowScrollingList.length; i++) {
        this.$overflowScrollingList[i].style.webkitOverflowScrolling = type
      }
    }
  },
  data () {
    return {
      hasFirstShow: false,
      show: this.value
    }
  },
  watch: {
    show (val) {
      this.$emit('input', val)
      if (val) {
        this.popup.show()
        this.$emit('on-show')
        if (!this.hasFirstShow) {
          this.$emit('on-first-show')
          this.hasFirstShow = true
        }
      } else {
        this.$emit('on-hide')
        this.show = false
        this.popup.hide(false)
      }
    },
    value (val) {
      this.show = val
    }
  },
  beforeDestroy () {
    this.popup.destroy()
    this.fixSafariOverflowScrolling('touch')
  }
}
</script>

<style>
.vux-popup-dialog,.vux-popup {
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  background: #eee;
  z-index: 501;
  transition-property: transform;
  transition-duration: 300ms;
}
.vux-popup-mask {
  display: block;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  tap-highlight-color: rgba(0,0,0,0);
  z-index: -1;
}
.vux-popup-mask.vux-popup-show {
  opacity: 1;
  z-index: 100;
  transition: opacity 0.3s;
}
.vux-popup-animate-transiton {}
.vux-popup-animate-enter {
  transform: translate3d(0, 100%, 0);
}
.vux-popup-animate-leave-active {
  transform: translate3d(0, 100%, 0);
}
</style>


Complete initial Builder page

Complete initial Builder page

<template>

You are almost there!

          Click

            Build Jetpack
          to download your Jetpack or

            Reset Settings
          to revert to default settings.

        <button type="button" class="btn btn-danger btn-lg btn-block">

          Reset Settings
        </button>

        <button type="button" class="btn btn-success btn-lg btn-block">

          Build Jetpack
        </button>

</template>

<template>

        Jetpack Preview

          <button type="button" class="btn btn-default">

            Reset Settings
          </button>
          <button type="button" class="btn btn-default">
            Build Jetpack

          </button>

              Virtual Machine

          ...

              Development Kits

          ...

</template>


Add schedule to topbar section

Add schedule to topbar section

<template>

          ≡

              <combobox
                class="context-selector"
                :options="openProductionOptions"
                :is-top="true"
                v-model="currentProductionId"
              />

              >

              <combobox
                class="context-selector"
                :options="navigationOptions"
                :is-top="true"
                v-model="currentProjectSection"
              />

              >

              <combobox
                class="context-selector"
                :options="episodeOptions"
                :is-top="true"
                v-model="currentEpisodeId"
                v-if="isEpisodeContext"
              />

          <router-link
            :to="lastSectionPath"
            class="flexrow"
          >
            <chevron-left-icon />
            {{ $t('main.go_productions') }}
          </router-link>

        <notification-bell />

          <people-avatar
            ref="avatar"
            class="avatar"
            :no-cache="true"
            :person="user"
            :is-link="false"
          />
          <people-name
            class="user-name"
            :person="user"
          />

          <router-link to="/profile" @click.native="toggleUserMenu()">
            {{ $t("main.profile") }}
          </router-link>

            {{ $t("main.dark_theme")}}

            {{ $t("main.white_theme")}}

            {{ $t("main.documentation")}}

            Slack

            Roadmap

            {{ $t("main.about")}}

          {{ $t("main.logout") }}

          Kitsu {{ kitsuVersion }}

</template>


Update user.vue

Update user.vue

<template>
<mk-ui>
	<template slot="header" v-if="!fetching">{{ user | userName }}</template>

%fa:exclamation-triangle% %i18n:@is-suspended%

%fa:exclamation-triangle% %i18n:@is-remote%%i18n:@view-remote%

					<mk-follow-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>

{{ user | userName }}

					<mk-acct :user="user"/>
					%i18n:@follows-you%

					<misskey-flavored-markdown v-if="user.description" :text="user.description" :i="$store.state.i"/>

						%fa:map-marker%{{ user.profile.location }}

						%fa:birthday-cake%{{ user.profile.birthday.replace('-', '年').replace('-', '月') + '日' }} ({{ age }}歳)

						{{ user.notesCount | number }}
						%i18n:@notes%

						{{ user.followingCount | number }}
						%i18n:@following%

						{{ user.followersCount | number }}
						%i18n:@followers%

				%fa:home% %i18n:@overview%
				%fa:R comment-alt% %i18n:@timeline%
				%fa:image% %i18n:@media%

			<x-home v-if="page == 'home'" :user="user"/>
			<mk-user-timeline v-if="page == 'notes'" :user="user" key="tl"/>
			<mk-user-timeline v-if="page == 'media'" :user="user" :with-media="true" key="media"/>

</mk-ui>
</template>


Only scroll to conversations when clicking on search results/listed conversations

Signed-off-by: Joas Schilling <ab43a7c9cb5b2380afc4ddf8b3e2583169b39a02@schilljs.com>

<template>
	<AppNavigation :aria-label="t('spreed', 'Conversation list')">

			<SearchBox
				v-model="searchText"
				class="conversations-search"
				:is-searching="isSearching"
				@input="debounceFetchSearchResults"
				@keypress.enter.prevent.stop="onInputEnter"
				@abort-search="abortSearch" />
			<NewGroupConversation
				v-if="canStartConversations" />

		<template #list class="left-sidebar__list">

				<ConversationsList
					ref="conversationsList"
					:conversations-list="conversationsList"
					:initialised-conversations="initialisedConversations"
					:search-text="searchText"
					@click-search-result="handleClickSearchResult"
					@focus="setFocusedIndex" />

			<template v-if="isSearching">
				<template v-if="!listedConversationsLoading && searchResultsListedConversations.length > 0">

					<Conversation
						v-for="item of searchResultsListedConversations"
						:key="item.id"
						:item="item"
						:is-search-result="true"
						@click="joinListedConversation(item)" />
				</template>
				<template v-if="searchResultsUsers.length !== 0">

						<ConversationsOptionsList
							:items="searchResultsUsers"
							@click="createAndJoinConversation" />

				</template>
				<template v-if="!showStartConversationsOptions">

					<Hint v-if="contactsLoading" :hint="t('spreed', 'Loading')" />
					<Hint v-else :hint="t('spreed', 'No search results')" />
				</template>
			</template>
			<template v-if="showStartConversationsOptions">
				<template v-if="searchResultsGroups.length !== 0">

						<ConversationsOptionsList
							:items="searchResultsGroups"
							@click="createAndJoinConversation" />

				</template>

				<template v-if="searchResultsCircles.length !== 0">

						<ConversationsOptionsList
							:items="searchResultsCircles"
							@click="createAndJoinConversation" />

				</template>

				<Hint v-if="contactsLoading" :hint="t('spreed', 'Loading')" />
				<Hint v-else :hint="t('spreed', 'No search results')" />
			</template>
		</template>

		<template #footer>

					<button class="settings-button" @click="showSettings">
						{{ t('spreed', 'Settings') }}
					</button>

		</template>
	</AppNavigation>
</template>


fix UI bug

fix UI bug

<template>

        <el-card class="box-card">

                <el-form ref="form" :inline="true" :model="searchForm" label-width="80px">
                    <el-form-item label="查询类型">
                        <el-select v-model="searchForm.type">
                            <el-option v-for="item in typeList" :label="item.label" :value="item.value"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="关键词">
                        <el-input v-model="searchForm.keywords" :placeholder="typeList[searchForm.type].label+((searchForm.type==6||searchForm.type==7)?' 不持股请输入 \'-\' ':'')"></el-input>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" icon="search" @click="getSeniorList">搜索</el-button>
                    </el-form-item>
                </el-form>

高管信息数据量较大，请手动搜索  |  部分高管一人多职，将显示成两条记录

{{this.result.resLabel}}包含{{this.result.keywords}}的结果

                <el-table
                        :data="seniorList"
                        stripe
                        border
                        style="width: 100%"
                >
                    <el-table-column
                            prop="name"
                            label="姓名"
                            min-width="100">
                    </el-table-column>
                    <el-table-column
                            prop="company"
                            label="所在公司"
                            min-width="120">
                    </el-table-column>
                    <el-table-column
                            prop="code"
                            label="股票代码"
                            min-width="100">
                    </el-table-column>
                    <el-table-column
                            prop="title"
                            label="职位"
                            show-overflow-tooltip="true"
                            min-width="140">
                    </el-table-column>
                    <el-table-column
                            prop="education"
                            label="学历"
                            min-width="120">
                    </el-table-column>
                    <el-table-column
                            prop="birth"
                            label="生日"
                            min-width="160">
                    </el-table-column>
                    <el-table-column
                            prop="hold_stocks"
                            label="持股数"
                            min-width="100"
                    >
                    </el-table-column>
                    <el-table-column
                            prop="stock_ratio"
                            label="持股比例(%)"
                            min-width="120"
                    >
                    </el-table-column>
                    <el-table-column
                            fixed="right"
                            label="操作"
                            width="100"
                    >
                        <template scope="scope">
                            <el-button type="text" @click="showInfo(scope.$index)">个人经历</el-button>
                        </template>
                    </el-table-column>
                </el-table>
                <el-pagination
                        small
                        layout="prev,pager,next,jumper,total"
                        :page-size="12"
                        :total="numOfPage"
                        :current-page="parseInt(this.$route.params.page)||1"
                        @current-change="changePage"
                ></el-pagination>

        </el-card>
        <el-dialog :title="dialog.name+' 个人经历'" v-model="dialogVisible" >
            {{dialog.experience}}
        </el-dialog>

</template>

<template>

        <el-card class="box-card">

                <el-form ref="form" :inline="true" :model="searchForm" label-width="80px">
                    <el-form-item label="查询类型">
                        <el-select v-model="searchForm.type">
                            <el-option v-for="item in typeList" :label="item.label" :value="item.value"></el-option>
                        </el-select>
                    </el-form-item>
                    <el-form-item label="关键词">
                        <el-input v-model="searchForm.keywords" :placeholder="typeList[searchForm.type].label+((searchForm.type==6||searchForm.type==7)?' 不持股请输入 \'-\' ':'')"></el-input>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" icon="search" @click="getSeniorList">搜索</el-button>
                    </el-form-item>
                </el-form>

高管信息数据量较大，请手动搜索  |  部分高管一人多职，将显示成两条记录

{{this.result.resLabel}}包含{{this.result.keywords}}的结果

                <el-table
                        :data="seniorList"
                        stripe
                        border
                        style="width: 100%"
                >
                    <el-table-column
                            prop="name"
                            label="姓名"
                            min-width="100">
                    </el-table-column>
                    <el-table-column
                            prop="company"
                            label="所在公司"
                            min-width="120">
                    </el-table-column>
                    <el-table-column
                            prop="code"
                            label="股票代码"
                            min-width="100">
                    </el-table-column>
                    <el-table-column
                            prop="title"
                            label="职位"
                            show-overflow-tooltip="true"
                            min-width="140">
                    </el-table-column>
                    <el-table-column
                            prop="education"
                            label="学历"
                            min-width="120">
                    </el-table-column>
                    <el-table-column
                            prop="birth"
                            label="生日"
                            min-width="160">
                    </el-table-column>
                    <el-table-column
                            prop="hold_stocks"
                            label="持股数"
                            min-width="100"
                    >
                    </el-table-column>
                    <el-table-column
                            prop="stock_ratio"
                            label="持股比例"
                            min-width="100"
                    >
                    </el-table-column>
                    <el-table-column
                            fixed="right"
                            label="操作"
                            width="100"
                    >
                        <template scope="scope">
                            <el-button type="text" @click="showInfo(scope.$index)">个人经历</el-button>
                        </template>
                    </el-table-column>
                </el-table>
                <el-pagination
                        small
                        layout="prev,pager,next,jumper,total"
                        :page-size="12"
                        :total="numOfPage"
                        :current-page="parseInt(this.$route.params.page)||1"
                        @current-change="changePage"
                ></el-pagination>

        </el-card>
        <el-dialog :title="dialog.name+' 个人经历'" v-model="dialogVisible" >
            {{dialog.experience}}
        </el-dialog>

</template>


Vue JS: Added margin to the search widget so that there is some space above and below the widget in the navbar.

<template>

    <AutoComplete ref="searchOverlay" placeholder="  Search..." v-model="selectedResult"
      :suggestions="searchResults" :delay="searchDelay" @complete="search"
      @item-select="goto($event.originalEvent, $event.value)">

      <template #header v-if="searchResults.length > 20">
        Many matches found. Keep typing...
      </template>

      <template #item="slotProps">

          <os-icon name="graduation-cap" />

            {{slotProps.item.category}}

                <os-icon name="external-link-alt"/>

              {{slotProps.item.props}}

      </template>
    </AutoComplete>

</template>


fix: contact form component had submit defined in data as well as the method itself

<script lang="coffee">
import Session        from '@/shared/services/session'
import Records        from '@/shared/services/records'
import AbilityService from '@/shared/services/ability_service'
import AppConfig      from '@/shared/services/app_config'
import UserHelpService from '@/shared/services/user_help_service'
import EventBus from '@/shared/services/event_bus'
import Flash from '@/shared/services/flash'
import { onError } from '@/shared/helpers/form'

export default
  data: ->
    submitted: false
    message: Records.contactMessages.build()
    isDisabled: false
    helpLink: UserHelpService.helpLink()
    contactEmail: AppConfig.contactEmail

  mounted: ->
    EventBus.$emit 'currentComponent',
      titleKey: 'contact_message_form.contact_us'
      page: 'inboxPage'

  created: ->
    if @isLoggedIn
      @message.name = Session.user().name
      @message.email = Session.user().email
      @message.userId = Session.user().id

  methods:
    submit: ->
      @message.save()
      .then =>
        Flash.success "contact_message_form.new_contact_message"
        @submitted = true
      .catch onError(@message)

  computed:
    isLoggedIn: ->
      Session.isSignedIn()
</script>

<template lang="pug">
v-content
  v-container.contact-page
    v-card.contact-form(v-show='!submitted')
      submit-overlay(:value='message.processing')
      v-card-title
        h1.headline(v-t="'contact_message_form.title'")
      v-card-text
        p(v-html="$t('contact_message_form.read_the_manual', { link: helpLink })")
        p
        div(v-if='!isLoggedIn')
          label(v-t="")
          v-text-field(:label="$t('contact_message_form.name_label')" :placeholder="$t('contact_message_form.name_placeholder')" v-model='message.name')
          validation-errors(:subject='message', field='name')
        div(v-if='!isLoggedIn')
          v-text-field(:label="$t('contact_message_form.email_label')" :placeholder="$t('contact_message_form.email_placeholder')", v-model='message.email')
          validation-errors(:subject='message', field='email')

        v-text-field(:label="$t('contact_message_form.subject_label')" :placeholder="$t('contact_message_form.subject_placeholder')", v-model='message.subject')
        validation-errors(:subject='message', field='subject')

        v-textarea(:label="$t('contact_message_form.message_label')" v-model='message.message', :placeholder="$t('contact_message_form.message_placeholder')")
        validation-errors(:subject='message', field='message')

        p.lmo-hint-text(v-html="$t('contact_message_form.contact_us_email', { email: contactEmail })")
        v-card-actions
          v-spacer
          v-btn(color="primary" @click='submit', v-t="'contact_message_form.send_message'")

    v-card.contact-form__success(v-show='submitted')
      v-card-title
        h1.headline(v-t="$t('contact_message_form.success', { name: message.name })")
</template>

<script lang="coffee">
import Session        from '@/shared/services/session'
import Records        from '@/shared/services/records'
import AbilityService from '@/shared/services/ability_service'
import AppConfig      from '@/shared/services/app_config'
import UserHelpService from '@/shared/services/user_help_service'
import EventBus from '@/shared/services/event_bus'
import Flash from '@/shared/services/flash'
import { onError } from '@/shared/helpers/form'

export default
  data: ->
    submitted: false
    message: Records.contactMessages.build()
    isDisabled: false
    helpLink: UserHelpService.helpLink()
    contactEmail: AppConfig.contactEmail
    submit: null

  mounted: ->
    EventBus.$emit 'currentComponent',
      titleKey: 'contact_message_form.contact_us'
      page: 'inboxPage'

  created: ->
    if @isLoggedIn
      @message.name = Session.user().name
      @message.email = Session.user().email
      @message.userId = Session.user().id

  methods:
    submit: ->
      @message.save()
      .then =>
        Flash.success "contact_message_form.new_contact_message"
        @submitted = true
      .catch onError(@message)

  computed:
    isLoggedIn: ->
      Session.isSignedIn()
</script>

<template lang="pug">
v-content
  v-container.contact-page
    v-card.contact-form(v-show='!submitted')
      submit-overlay(:value='message.processing')
      v-card-title
        h1.headline(v-t="'contact_message_form.title'")
      v-card-text
        p(v-html="$t('contact_message_form.read_the_manual', { link: helpLink })")
        p
        div(v-if='!isLoggedIn')
          label(v-t="")
          v-text-field(:label="$t('contact_message_form.name_label')" :placeholder="$t('contact_message_form.name_placeholder')" v-model='message.name')
          validation-errors(:subject='message', field='name')
        div(v-if='!isLoggedIn')
          v-text-field(:label="$t('contact_message_form.email_label')" :placeholder="$t('contact_message_form.email_placeholder')", v-model='message.email')
          validation-errors(:subject='message', field='email')

        v-text-field(:label="$t('contact_message_form.subject_label')" :placeholder="$t('contact_message_form.subject_placeholder')", v-model='message.subject')
        validation-errors(:subject='message', field='subject')

        v-textarea(:label="$t('contact_message_form.message_label')" v-model='message.message', :placeholder="$t('contact_message_form.message_placeholder')")
        validation-errors(:subject='message', field='message')

        p.lmo-hint-text(v-html="$t('contact_message_form.contact_us_email', { email: contactEmail })")
        v-card-actions
          v-spacer
          v-btn(color="primary" @click='submit', v-t="'contact_message_form.send_message'")

    v-card.contact-form__success(v-show='submitted')
      v-card-title
        h1.headline(v-t="$t('contact_message_form.success', { name: message.name })")
</template>


Alterando Botões para Checkboxes

Alterando Botões para Checkboxes

Alterando os Botões de seleção para checkboxes, facilitando a entrada das informações no Banco de Dados

<template>

Selecione até 5 categorias culinárias de sua preferência.

                        <form class="form" role="form" autocomplete="off" id="formCad" action="">

                            <router-link class="btn btn-outline-secondary btn-lg" role="button" aria-pressed="true" to="/usuario-nao-autenticado">Cancelar</router-link>
                            <router-link class="btn btn-outline-secondary btn-lg" role="button" aria-pressed="true" to="/cadastro-1">Voltar</router-link>
                            <router-link class="btn btn-outline-secondary btn-lg" role="button" aria-pressed="true" to="/cadastro-3">Confirmar</router-link>
                        </form>

</template>

<template>

Selecione até 5 categorias culinárias de sua preferência.

                        <form class="form" role="form" autocomplete="off" id="formCad" action="">

<input type="checkbox" name="chk1" id="item1" value="val1" class="hidden" autocomplete="off">

<input type="checkbox" name="chk2" id="item2" value="val2" class="hidden" autocomplete="off">

<input type="checkbox" name="chk3" id="item3" value="val3" class="hidden" autocomplete="off">

<input type="checkbox" name="chk4" id="item4" value="val1" class="hidden" autocomplete="off">

<input type="checkbox" name="chk2" id="item5" value="val2" class="hidden" autocomplete="off">

<input type="checkbox" name="chk3" id="item6" value="val3" class="hidden" autocomplete="off">

<input type="checkbox" name="chk1" id="item7" value="val1" class="hidden" autocomplete="off">

<input type="checkbox" name="chk2" id="item8" value="val2" class="hidden" autocomplete="off">

<input type="checkbox" name="chk3" id="item9" value="val3" class="hidden" autocomplete="off">

                            <router-link class="btn btn-outline-secondary btn-lg" role="button" aria-pressed="true" to="/usuario-nao-autenticado">Cancelar</router-link>
                            <router-link class="btn btn-outline-secondary btn-lg" role="button" aria-pressed="true" to="/cadastro-1">Voltar</router-link>
                            <router-link class="btn btn-outline-secondary btn-lg" role="button" aria-pressed="true" to="/cadastro-3">Confirmar</router-link>
                        </form>

</template>


Remove unused getter.

Remove unused getter.

<template>

    <grid-item :link="link" :title="title" :kind="kind" :progress="progress">

    </grid-item>

</template>


wip transitioning between login steps

wip transitioning between login steps

<template>
  <transition name="fade-stage">

        <transition name="fade-body">

            <TheAposLoginHeader
              :env="context.env"
              :name="context.name"
              :error="$t(error)"
            />

              <form
                @submit.prevent="submit"
              >
                <AposSchema
                  :schema="schema"
                  v-model="doc"
                />

                <template v-if="loaded">
                  <Component
                    v-for="requirement in beforeSubmitRequirements"
                    :key="requirement.name"
                    :is="requirement.component"
                    v-bind="getRequirementProps(requirement.name)"
                    @done="requirementDone(requirement, $event)"
                  />
                </template>

                <AposButton
                  :busy="busy"
                  :disabled="disabled"
                  type="primary"
                  label="apostrophe:login"
                  button-type="submit"
                  class="apos-login__submit"
                  :modifiers="['gradient-on-hover', 'block']"
                  @click="submit"
                />
              </form>

            <TheAposLoginHeader
              :env="context.env"
              :name="context.name"
              :error="$t(error)"
              :tiny="true"
            />

              <Component
                v-bind="getRequirementProps(activeSoloRequirement.name)"
                :is="activeSoloRequirement.component"
                :success="activeSoloRequirement.success"
                :error="activeSoloRequirement.error"
                @done="requirementDone(activeSoloRequirement, $event)"
                @confirm="requirementConfirmed(activeSoloRequirement)"
              />

        </transition>

      <transition name="fade-footer">

          <AposLogo class="apos-login__logo" />

            Version {{ context.version }}

      </transition>

  </transition>
</template>

<template>
  <transition name="fade-stage">

        <transition name="fade-body">

              <label
                class="apos-login__project apos-login__project-env"
                :class="[`apos-login__project-env--${context.env}`]"
              >
                {{ context.env }}

                {{ context.name }}

                {{ $t(error) }}

              <form
                v-if="phase == 'beforeSubmit'"
                @submit.prevent="submit"
              >
                <AposSchema
                  :schema="schema"
                  v-model="doc"
                />

                <template v-if="loaded">
                  <Component
                    v-for="requirement in beforeSubmitRequirements"
                    :key="requirement.name"
                    :is="requirement.component"
                    v-bind="getRequirementProps(requirement.name)"
                    @done="requirementDone(requirement, $event)"
                  />
                </template>

                <AposButton
                  :busy="busy"
                  :disabled="disabled"
                  type="primary"
                  label="apostrophe:login"
                  button-type="submit"
                  class="apos-login__submit"
                  :modifiers="['gradient-on-hover', 'block']"
                  @click="submit"
                />
              </form>
              <Component
                v-if="activeSoloRequirement && !fetchingRequirementProps"
                v-bind="getRequirementProps(activeSoloRequirement.name)"
                :is="activeSoloRequirement.component"
                :success="activeSoloRequirement.success"
                :error="activeSoloRequirement.error"
                @done="requirementDone(activeSoloRequirement, $event)"
                @confirm="requirementConfirmed(activeSoloRequirement)"
              />

        </transition>

      <transition name="fade-footer">

          <AposLogo class="apos-login__logo" />

            Version {{ context.version }}

      </transition>

  </transition>
</template>


fix: set base message list color to gray

<template>
  <div
    v-if="messages.length"
    class="va-message-list"
    :style="computedStyle"
  >
    <div
      class="va-message-list__message"
      v-for="(message, index) in messages"
      :key="index"
    >
      {{message}}
    </div>
  </div>
</template>

<script>
import { ColorThemeMixin } from '../../../services/ColorThemePlugin'

export default {
  name: 'va-message-list',
  mixins: [ColorThemeMixin],
  data () {
    return {
      colorThemeDefault: 'gray', // mixin override
    }
  },
  props: {
    value: {},
    limit: { type: Number, default: 1 },
  },
  computed: {
    messages () {
      if (!this.value) {
        return []
      }
      if (!Array.isArray(this.value)) {
        return [this.value]
      }
      return this.value.slice(0, this.limit)
    },
    computedStyle () {
      return {
        color: this.colorComputed,
      }
    },
  },
}
</script>

<style lang="scss">
@import "../../vuestic-sass/resources/resources";

.va-message-list {
  &__message {
    vertical-align: middle;
    font-size: $font-size-mini;
  }
}
</style>

<template>
  <div
    v-if="messages.length"
    class="va-message-list"
    :style="computedStyle"
  >
    <div
      class="va-message-list__message"
      v-for="(message, index) in messages"
      :key="index"
    >
      {{message}}
    </div>
  </div>
</template>

<script>
import { ColorThemeMixin } from '../../../services/ColorThemePlugin'

export default {
  name: 'va-message-list',
  mixins: [ColorThemeMixin],
  data () {
    return {
      colorThemeDefault: 'black', // mixin override
    }
  },
  props: {
    value: {},
    limit: { type: Number, default: 1 },
  },
  computed: {
    messages () {
      if (!this.value) {
        return []
      }
      if (!Array.isArray(this.value)) {
        return [this.value]
      }
      return this.value.slice(0, this.limit)
    },
    computedStyle () {
      return {
        color: this.colorComputed,
      }
    },
  },
}
</script>

<style lang="scss">
@import "../../vuestic-sass/resources/resources";

.va-message-list {
  &__message {
    vertical-align: middle;
    font-size: $font-size-mini;
  }
}
</style>


Fixes #4752 -- Fix podcast episode modal failing to open.

<template>
    <modal-form ref="modal" :loading="loading" :title="langTitle" :error="error" :disable-save-button="$v.form.$invalid"
                @submit="doSubmit" @hidden="clearContents">

        <b-tabs content-class="mt-3">
            <episode-form-basic-info :form="$v.form"></episode-form-basic-info>

            <episode-form-media v-model="$v.form.media_file.$model" :record-has-media="record.has_media"
                                :new-media-url="newMediaUrl" :edit-media-url="record.links.media"
                                :download-url="record.links.download"></episode-form-media>

            <podcast-common-artwork v-model="$v.form.artwork_file.$model" :artwork-src="record.art"
                                    :new-art-url="newArtUrl" :edit-art-url="record.links.art"></podcast-common-artwork>
        </b-tabs>

    </modal-form>
</template>

<script>
import {required} from 'vuelidate/dist/validators.min.js';
import BaseEditModal from '~/components/Common/BaseEditModal';
import EpisodeFormBasicInfo from './EpisodeForm/BasicInfo';
import PodcastCommonArtwork from './Common/Artwork';
import EpisodeFormMedia from './EpisodeForm/Media';
import {DateTime} from 'luxon';
import mergeExisting from "~/functions/mergeExisting";

export default {
    name: 'EditModal',
    components: {EpisodeFormMedia, PodcastCommonArtwork, EpisodeFormBasicInfo},
    mixins: [BaseEditModal],
    props: {
        stationTimeZone: String,
        locale: String,
        podcastId: String,
        newArtUrl: String,
        newMediaUrl: String
    },
    data() {
        return {
            uploadPercentage: null,
            record: {
                has_custom_art: false,
                art: null,
                has_media: false,
                media: null,
                links: {},
                artwork_file: null,
                media_file: null
            },
        };
    },
    computed: {
        langTitle () {
            return this.isEditMode
                ? this.$gettext('Edit Episode')
                : this.$gettext('Add Episode');
        },
        langSaveChanges () {
            let baseText = this.$gettext('Save Changes');

            if (null !== this.uploadPercentage) {
                baseText = baseText + ' (' + this.uploadPercentage + '%)';
            }

            return baseText;
        }
    },
    validations: {
        form: {
            'title': { required },
            'link': {},
            'description': { required },
            'publish_date': {},
            'publish_time': {},
            'explicit': {},
            'artwork_file': {},
            'media_file': {}
        }
    },
    methods: {
        resetForm () {
            this.uploadPercentage = null;
            this.record = {
                has_custom_art: false,
                art: null,
                has_media: false,
                media: null,
                links: {
                    art: null,
                    media: null
                }
            };
            this.form = {
                'title': '',
                'link': '',
                'description': '',
                'publish_date': '',
                'publish_time': '',
                'explicit': false,
                'artwork_file': null,
                'media_file': null
            };
        },
        populateForm (d) {
            let publishDate = '';
            let publishTime = '';

            if (d.publish_at !== null) {
                let publishDateTime = DateTime.fromSeconds(d.publish_at);
                publishDate = publishDateTime.toISODate();
                publishTime = publishDateTime.toISOTime({
                    suppressMilliseconds: true,
                    includeOffset: false
                });
            }

            this.record = mergeExisting(this.record, d);
            this.form = mergeExisting(this.form, {
                ...d,
                publish_date: publishDate,
                publish_time: publishTime
            });
        },
        getSubmittableFormData() {
            let modifiedForm = this.form;

            if (modifiedForm.publish_date.length > 0 && modifiedForm.publish_time.length > 0) {
                let publishDateTimeString = modifiedForm.publish_date + 'T' + modifiedForm.publish_time;
                let publishDateTime = DateTime.fromISO(publishDateTimeString);

                modifiedForm.publish_at = publishDateTime.toSeconds();
            }

            return modifiedForm;
        },
    }
};
</script>

<template>
    <modal-form ref="modal" :loading="loading" :title="langTitle" :error="error" :disable-save-button="$v.form.$invalid"
                @submit="doSubmit" @hidden="clearContents">

        <b-tabs content-class="mt-3">
            <episode-form-basic-info :form="$v.form"></episode-form-basic-info>

            <episode-form-media v-model="$v.form.media_file.$model" :record-has-media="record.has_media"
                                :new-media-url="newMediaUrl" :edit-media-url="record.links.media"
                                :download-url="record.links.download"></episode-form-media>

            <podcast-common-artwork v-model="$v.form.artwork_file.$model" :artwork-src="record.art"
                                    :new-art-url="newArtUrl" :edit-art-url="record.links.art"></podcast-common-artwork>
        </b-tabs>

    </modal-form>
</template>

<script>
import {required} from 'vuelidate/dist/validators.min.js';
import BaseEditModal from '~/components/Common/BaseEditModal';
import EpisodeFormBasicInfo from './EpisodeForm/BasicInfo';
import PodcastCommonArtwork from './Common/Artwork';
import EpisodeFormMedia from './EpisodeForm/Media';
import {DateTime} from 'luxon';

export default {
    name: 'EditModal',
    components: {EpisodeFormMedia, PodcastCommonArtwork, EpisodeFormBasicInfo},
    mixins: [BaseEditModal],
    props: {
        stationTimeZone: String,
        locale: String,
        podcastId: String,
        newArtUrl: String,
        newMediaUrl: String
    },
    data() {
        return {
            uploadPercentage: null,
            record: {
                has_custom_art: false,
                art: null,
                has_media: false,
                media: null,
                links: {},
                artwork_file: null,
                media_file: null
            },
        };
    },
    computed: {
        langTitle () {
            return this.isEditMode
                ? this.$gettext('Edit Episode')
                : this.$gettext('Add Episode');
        },
        langSaveChanges () {
            let baseText = this.$gettext('Save Changes');

            if (null !== this.uploadPercentage) {
                baseText = baseText + ' (' + this.uploadPercentage + '%)';
            }

            return baseText;
        }
    },
    validations: {
        form: {
            'title': { required },
            'link': {},
            'description': { required },
            'publish_date': {},
            'publish_time': {},
            'explicit': {},
            'artwork_file': {},
            'media_file': {}
        }
    },
    methods: {
        resetForm () {
            this.uploadPercentage = null;
            this.record = {
                has_custom_art: false,
                art: null,
                has_media: false,
                media: null,
                links: {
                    art: null,
                    media: null
                }
            };
            this.form = {
                'title': '',
                'link': '',
                'description': '',
                'publish_date': '',
                'publish_time': '',
                'explicit': false,
                'artwork_file': null,
                'media_file': null
            };
        },
        populateForm (d) {
            let publishDate = '';
            let publishTime = '';

            if (d.publishAt !== null) {
                let publishDateTime = DateTime.fromSeconds(d.publishAt);
                publishDate = publishDateTime.toISODate();
                publishTime = publishDateTime.toFormat('hh:mm');
            }

            this.record = d;

            this.form = {
                'title': d.title,
                'link': d.link,
                'description': d.description,
                'publish_date': publishDate,
                'publish_time': publishTime,
                'explicit': d.explicit
            };
        },
        getSubmittableFormData() {
            let modifiedForm = this.form;
            if (modifiedForm.publish_date.length > 0 && modifiedForm.publish_time.length > 0) {
                let publishDateTimeString = modifiedForm.publish_date + ' ' + modifiedForm.publish_time;
                let publishDateTime = DateTime.fromFormat(publishDateTimeString, 'YYYY-mm-dd HH:ss');

                modifiedForm.publish_at = publishDateTime.toSeconds();
            }

            return modifiedForm;
        },
    }
};
</script>


Call search before running invoke-save-filter

<template>

    <dropdown v-if="query.recentFilter!=='-' && displayOpts.showRecentFilter">

        <i18n :path="'period.label.'+period.name" />

      <template slot="dropdown">

            <i18n :path="'period.label.'+perobj.name" />
            √

      </template>
    </dropdown>

    <btn
      @click="filterOpen=true"
      size="xs"
      :class="hasQuery?'btn-queried btn-info':'btn-secondary'"
      v-tooltip="hasQuery?$t('Click to edit Search Query'):''"
      v-if="displayOpts.showFilter"
    >

            <i18n :path="'jobquery.title.'+qname" />:
            {{query[qname]}}

      {{$t('search.ellipsis')}}
    </btn>

    <saved-filters
      :query="value"
      :has-query="hasQuery"
      @select_filter="selectFilter($event)"
      v-if="value && displayOpts.showSavedFilters"
      :event-bus="eventBus"
    ></saved-filters>

    <modal
      id="activityFilter"
      v-model="filterOpen"
      :title="$t('Search Activity')"
      size="lg"
      @hide="closing"
    >

                  <i18n path="jobquery.title.jobFilter" />

                <input
                  type="text"
                  name="jobFilter"
                  v-model="query.jobFilter"
                  autofocus="true"
                  class="form-control"
                  :placeholder="$t('jobquery.title.jobFilter')"
                />

                  <i18n path="jobquery.title.jobIdFilter" />

                <input
                  type="text"
                  name="jobIdFilter"
                  v-model="query.jobIdFilter"
                  class="form-control"
                  :placeholder="$t( 'jobquery.title.jobIdFilter')"
                />

                  <i18n path="jobquery.title.userFilter" />

                <input
                  type="text"
                  name="userFilter"
                  v-model="query.userFilter"
                  class="form-control"
                  :placeholder="$t( 'jobquery.title.userFilter')"
                />

                  <i18n path="jobquery.title.filter" />

                <input
                  type="text"
                  name="execnodeFilter"
                  v-model="query.execnodeFilter"
                  class="form-control"
                  :placeholder="$t( 'jobquery.title.filter')"
                />

                  <i18n path="jobquery.title.titleFilter" />

                <input
                  type="text"
                  name="titleFilter"
                  v-model="query.titleFilter"
                  class="form-control"
                  :placeholder="$t( 'jobquery.title.titleFilter')"
                />

                  <i18n path="jobquery.title.statFilter" />

                <select
                  name="statFilter"
                  v-model="query.statFilter"
                  noSelection="['': 'Any']"
                  valueMessagePrefix="status.label"
                  class="form-control"
                >
                  <option value>Any</option>
                  <option>succeed</option>
                  <option>fail</option>
                  <option>cancel</option>
                </select>

                  <i18n path="jobquery.title.recentFilter" />

                  <select name="recentFilter" v-model="query.recentFilter" class="form-control">
                    <option value>Any Time</option>
                    <option :value="val" v-for="(key,val) in recentDateFilters" :key="key">{{key}}</option>
                    <option value="-">Other...</option>
                  </select>

              <date-filter v-model="df.filter">{{$t('jobquery.title.'+df.name)}}</date-filter>

      <template slot="footer">
        <btn @click="filterOpen=false">{{$t('cancel')}}</btn>
        <btn @click="search" type="primary">{{$t('search')}}</btn>
        <btn @click="saveFilter" type="success" class="pull-right">

          {{$t('Save as a Filter...')}}
        </btn>
      </template>
    </modal>

</template>

<template>

    <dropdown v-if="query.recentFilter!=='-' && displayOpts.showRecentFilter">

        <i18n :path="'period.label.'+period.name"/>

      <template slot="dropdown">

          <i18n :path="'period.label.'+perobj.name"/>
          √

      </template>
    </dropdown>

    <btn
      @click="filterOpen=true"
      size="xs"
      :class="hasQuery?'btn-queried btn-info':'btn-secondary'"
      v-tooltip="hasQuery?$t('Click to edit Search Query'):''"
      v-if="displayOpts.showFilter"
    >

            <i18n :path="'jobquery.title.'+qname"/>:
            {{query[qname]}}

      {{$t('search.ellipsis')}}
    </btn>

    <saved-filters :query="value" :has-query="hasQuery" @select_filter="selectFilter($event)" v-if="value && displayOpts.showSavedFilters" :event-bus="eventBus"></saved-filters>

    <modal id="activityFilter" v-model="filterOpen" :title="$t('Search Activity')" size="lg" @hide="closing">

                  <i18n path="jobquery.title.jobFilter"/>

                <input
                  type="text"
                  name="jobFilter"
                  v-model="query.jobFilter"
                  autofocus="true"
                  class="form-control"
                  :placeholder="$t('jobquery.title.jobFilter')"
                >

                  <i18n path="jobquery.title.jobIdFilter"/>

                <input
                  type="text"
                  name="jobIdFilter"
                  v-model="query.jobIdFilter"
                  class="form-control"
                  :placeholder="$t( 'jobquery.title.jobIdFilter')"
                >

                  <i18n path="jobquery.title.userFilter"/>

                <input
                  type="text"
                  name="userFilter"
                  v-model="query.userFilter"
                  class="form-control"
                  :placeholder="$t( 'jobquery.title.userFilter')"
                >

                  <i18n path="jobquery.title.filter"/>

                <input
                  type="text"
                  name="execnodeFilter"
                  v-model="query.execnodeFilter"
                  class="form-control"
                  :placeholder="$t( 'jobquery.title.filter')"
                >

                  <i18n path="jobquery.title.titleFilter"/>

                <input
                  type="text"
                  name="titleFilter"
                  v-model="query.titleFilter"
                  class="form-control"
                  :placeholder="$t( 'jobquery.title.titleFilter')"
                >

                  <i18n path="jobquery.title.statFilter"/>

                <select
                  name="statFilter"
                  v-model="query.statFilter"
                  noSelection="['': 'Any']"
                  valueMessagePrefix="status.label"
                  class="form-control"
                >
                  <option value>Any</option>
                  <option>succeed</option>
                  <option>fail</option>
                  <option>cancel</option>
                </select>

                  <i18n path="jobquery.title.recentFilter"/>

                  <select name="recentFilter" v-model="query.recentFilter" class="form-control">
                    <option value>Any Time</option>
                    <option :value="val" v-for="(key,val) in recentDateFilters" :key="key">{{key}}</option>
                    <option value="-">Other...</option>
                  </select>

                <date-filter v-model="df.filter">
                  {{$t('jobquery.title.'+df.name)}}
                </date-filter>

      <template slot="footer">
        <btn @click="filterOpen=false">{{$t('cancel')}}</btn>
        <btn @click="search" type="primary">{{$t('search')}}</btn>
        <btn @click="saveFilter" type="success" class="pull-right">

          {{$t('Save as a Filter...')}}
        </btn>
      </template>
    </modal>

</template>


fix: v3模式下 vue <map>组件动态修改 longitude，latitude 无效的Bug

<template>
  <uni-map v-on="$listeners">
    <div
      ref="container"
      class="uni-map-container" />
    <v-uni-cover-image
      v-for="(control, index) in mapControls"
      :key="index"
      :src="control.iconPath"
      :style="control.position"
      auto-size
      @click="controlclick(control)"/>
    <div class="uni-map-slot">
      <slot />
    </div>
  </uni-map>
</template>
<script>
import {
  subscriber
} from 'uni-mixins'
import native from '../../mixins/native'

const methods = [
  'getCenterLocation',
  'moveToLocation',
  'getRegion',
  'getScale',
  '$getAppMap'
]

// const events = [
//   'markertap',
//   'callouttap',
//   'controltap',
//   'regionchange',
//   'tap',
//   'updated'
// ]

const attrs = [
  'latitude',
  'longitude',
  'scale',
  'markers',
  'polyline',
  'circles',
  'controls',
  'show-location'
]

const convertCoordinates = (lng, lat, callback) => {
  // plus.maps.Map.convertCoordinates(new plus.maps.Point(lng, lat), {
  //   coordType: 'gcj02'
  // }, callback)
  callback({
    coord: {
      latitude: lat,
      longitude: lng
    }
  })
}

function parseHex (color) {
  if (color.indexOf('#') !== 0) {
    return {
      color,
      opacity: 1
    }
  }
  const opacity = color.substr(7, 2)
  return {
    color: color.substr(0, 7),
    opacity: opacity ? Number('0x' + opacity) / 255 : 1
  }
}

export default {
  name: 'Map',
  mixins: [subscriber, native],
  props: {
    id: {
      type: String,
      default: ''
    },
    latitude: {
      type: [Number, String],
      default: ''
    },
    longitude: {
      type: [Number, String],
      default: ''
    },
    scale: {
      type: [String, Number],
      default: 1
    },
    markers: {
      type: Array,
      default () {
        return []
      }
    },
    polyline: {
      type: Array,
      default () {
        return []
      }
    },
    circles: {
      type: Array,
      default () {
        return []
      }
    },
    controls: {
      type: Array,
      default () {
        return []
      }
    }
  },
  data () {
    return {
      style: {
        top: '0px',
        left: '0px',
        width: '0px',
        height: '0px',
        position: 'static'
      },
      hidden: false
    }
  },
  computed: {
    attrs () {
      const obj = {}
      attrs.forEach(key => {
        let val = this.$props[key]
        val = key === 'src' ? this.$getRealPath(val) : val
        obj[key.replace(/[A-Z]/g, str => '-' + str.toLowerCase())] = val
      })
      return obj
    },
    mapControls () {
      const list = this.controls.map((control) => {
        let position = { position: 'absolute' };
        ['top', 'left', 'width', 'height'].forEach(key => {
          position[key] = control.position[key] + 'px'
        })
        return {
          id: control.id,
          iconPath: this.$getRealPath(control.iconPath),
          position: position
        }
      })
      return list
    }
  },
  watch: {
    hidden (val) {
      this.map && this.map[val ? 'hide' : 'show']()
    }
  },
  mounted () {
    let mapStyle = Object.assign({}, this.attrs, this.position)
    if (this.latitude && this.longitude) {
      mapStyle.center = new plus.maps.Point(this.longitude, this.latitude)
    }
    const map = this.map = plus.maps.create('map' + Date.now(), mapStyle)
    map.__markers__ = {}
    map.__lines__ = []
    map.__circles__ = []
    plus.webview.currentWebview().append(map)
    if (this.hidden) {
      map.hide()
    }
    this.$watch('attrs', () => {
      if (this.map) {
        this.map.setStyles(this.attrs)
        // TODO 临时处理更新 longitude, latitude 无效问题
        this.map.setStyles({
          center: new plus.maps.Point(this.longitude, this.latitude)
        })
      }
    }, {
      deep: true
    })
    this.$watch('position', () => {
      this.map && this.map.setStyles(this.position)
    }, {
      deep: true
    })
    map.onclick((data = {}) => {
      this.$trigger('tap', {}, data)
    })
    map.onstatuschanged((data = {}) => {
      this.$trigger('end', {}, data)
    })
    this._addMarkers(this.markers)
    this._addMapLines(this.polyline)
    this._addMapCircles(this.circles)
  },
  beforeDestroy () {
    delete this.map
  },
  methods: {
    _handleSubscribe ({
      type,
      data = {}
    }) {
      if (!methods.includes(type)) {
        return
      }
      this.map && this[type](data)
    },
    getRegion () {
    },
    getScale () {
    },
    controlclick (e) {
      this.$trigger('controltap', {}, { id: e.id })
    },
    _addMarker (nativeMap, marker) {
      const {
        id,
        // title,
        latitude,
        longitude,
        iconPath,
        // width,
        // height,
        // rotate,
        // alpha,
        callout,
        label
      } = marker
      convertCoordinates(longitude, latitude, res => {
        const {
          latitude,
          longitude
        } = res.coord
        const nativeMarker = new plus.maps.Marker(new plus.maps.Point(longitude, latitude))
        if (iconPath) {
          nativeMarker.setIcon(this.$getRealPath(iconPath))
        }
        if (label && label.content) {
          nativeMarker.setLabel(label.content)
        }
        let nativeBubble = false
        if (callout && callout.content) {
          nativeBubble = new plus.maps.Bubble(callout.content)
        }
        if (nativeBubble) {
          nativeMarker.setBubble(nativeBubble)
        }
        if (id || id === 0) {
          nativeMarker.onclick = (e) => {
            this.$trigger('markertap', {}, {
              id
            })
          }
          if (nativeBubble) {
            nativeBubble.onclick = () => {
              this.$trigger('callouttap', {}, {
                id
              })
            }
          }
        }
        nativeMap.addOverlay(nativeMarker)
        nativeMap.__markers__[id + ''] = nativeMarker
      })
    },
    _addMarkers (markers, clear) {
      if (this.map) {
        if (clear) {
          this.map.clearOverlays()
        }
        markers.forEach(marker => {
          this._addMarker(this.map, marker)
        })
        return {
          errMsg: 'addMapMarkers:ok'
        }
      }
      return {
        errMsg: 'addMapMarkers:fail:请先创建地图元素'
      }
    },
    _translateMapMarker ({
      autoRotate,
      callbackId,
      destination,
      duration,
      markerId
    }) {
      if (this.map) {
        const nativeMarker = this.map.__markers__[markerId + '']
        if (nativeMarker) {
          nativeMarker.setPoint(new plus.maps.Point(destination.longitude, destination.latitude))
        }
      }
      return {
        errMsg: 'translateMapMarker:ok'
      }
    },
    _addMapLines (lines) {
      const nativeMap = this.map
      if (!nativeMap) {
        return {
          errMsg: 'addMapLines:fail:请先创建地图元素'
        }
      }

      if (nativeMap.__lines__.length > 0) {
        nativeMap.__lines__.forEach(circle => {
          nativeMap.removeOverlay(circle)
        })
        nativeMap.__lines__ = []
      }

      lines.forEach(line => {
        const {
          color,
          width
          // dottedLine,
          // arrowLine,
          // arrowIconPath,
          // borderColor,
          // borderWidth
        } = line
        const points = line.points.map(point => new plus.maps.Point(point.longitude, point.latitude))
        const polyline = new plus.maps.Polyline(points)
        if (color) {
          const strokeStyle = parseHex(color)
          polyline.setStrokeColor(strokeStyle.color)
          polyline.setStrokeOpacity(strokeStyle.opacity)
        }
        if (width) {
          polyline.setLineWidth(width)
        }
        nativeMap.addOverlay(polyline)
        nativeMap.__lines__.push(polyline)
      })
      return {
        errMsg: 'addMapLines:ok'
      }
    },
    _addMapCircles (circles) {
      const nativeMap = this.map
      if (!nativeMap) {
        return {
          errMsg: 'addMapCircles:fail:请先创建地图元素'
        }
      }

      if (nativeMap.__circles__.length > 0) {
        nativeMap.__circles__.forEach(circle => {
          nativeMap.removeOverlay(circle)
        })
        nativeMap.__circles__ = []
      }

      circles.forEach(circle => {
        const {
          latitude,
          longitude,
          color,
          fillColor,
          radius,
          strokeWidth
        } = circle
        const nativeCircle = new plus.maps.Circle(new plus.maps.Point(longitude, latitude), radius)
        if (color) {
          const strokeStyle = parseHex(color)
          nativeCircle.setStrokeColor(strokeStyle.color)
          nativeCircle.setStrokeOpacity(strokeStyle.opacity)
        }
        if (fillColor) {
          const fillStyle = parseHex(fillColor)
          nativeCircle.setFillColor(fillStyle.color)
          nativeCircle.setFillOpacity(fillStyle.opacity)
        }
        if (strokeWidth) {
          nativeCircle.setLineWidth(strokeWidth)
        }
        nativeMap.addOverlay(nativeCircle)
        nativeMap.__circles__.push(nativeCircle)
      })
      return {
        errMsg: 'addMapCircles:ok'
      }
    }
  }
}
</script>

<style>
  uni-map {
    width: 300px;
    height: 225px;
    display: inline-block;
    line-height: 0;
    overflow: hidden;
    position: relative;
  }

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

  .uni-map-container {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    overflow: hidden;
    background-color: black;
  }

  .uni-map-slot {
    position: absolute;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    pointer-events: none;
  }
</style>

<template>
  <uni-map v-on="$listeners">
    <div
      ref="container"
      class="uni-map-container" />
    <v-uni-cover-image
      v-for="(control, index) in mapControls"
      :key="index"
      :src="control.iconPath"
      :style="control.position"
      auto-size
      @click="controlclick(control)"/>
    <div class="uni-map-slot">
      <slot />
    </div>
  </uni-map>
</template>
<script>
import {
  subscriber
} from 'uni-mixins'
import native from '../../mixins/native'

const methods = [
  'getCenterLocation',
  'moveToLocation',
  'getRegion',
  'getScale',
  '$getAppMap'
]

// const events = [
//   'markertap',
//   'callouttap',
//   'controltap',
//   'regionchange',
//   'tap',
//   'updated'
// ]

const attrs = [
  'latitude',
  'longitude',
  'scale',
  'markers',
  'polyline',
  'circles',
  'controls',
  'show-location'
]

const convertCoordinates = (lng, lat, callback) => {
  // plus.maps.Map.convertCoordinates(new plus.maps.Point(lng, lat), {
  //   coordType: 'gcj02'
  // }, callback)
  callback({
    coord: {
      latitude: lat,
      longitude: lng
    }
  })
}

function parseHex (color) {
  if (color.indexOf('#') !== 0) {
    return {
      color,
      opacity: 1
    }
  }
  const opacity = color.substr(7, 2)
  return {
    color: color.substr(0, 7),
    opacity: opacity ? Number('0x' + opacity) / 255 : 1
  }
}

export default {
  name: 'Map',
  mixins: [subscriber, native],
  props: {
    id: {
      type: String,
      default: ''
    },
    latitude: {
      type: [Number, String],
      default: ''
    },
    longitude: {
      type: [Number, String],
      default: ''
    },
    scale: {
      type: [String, Number],
      default: 1
    },
    markers: {
      type: Array,
      default () {
        return []
      }
    },
    polyline: {
      type: Array,
      default () {
        return []
      }
    },
    circles: {
      type: Array,
      default () {
        return []
      }
    },
    controls: {
      type: Array,
      default () {
        return []
      }
    }
  },
  data () {
    return {
      style: {
        top: '0px',
        left: '0px',
        width: '0px',
        height: '0px',
        position: 'static'
      },
      hidden: false
    }
  },
  computed: {
    attrs () {
      const obj = {}
      attrs.forEach(key => {
        let val = this.$props[key]
        val = key === 'src' ? this.$getRealPath(val) : val
        obj[key.replace(/[A-Z]/g, str => '-' + str.toLowerCase())] = val
      })
      return obj
    },
    mapControls () {
      const list = this.controls.map((control) => {
        let position = { position: 'absolute' };
        ['top', 'left', 'width', 'height'].forEach(key => {
          position[key] = control.position[key] + 'px'
        })
        return {
          id: control.id,
          iconPath: this.$getRealPath(control.iconPath),
          position: position
        }
      })
      return list
    }
  },
  watch: {
    hidden (val) {
      this.map && this.map[val ? 'hide' : 'show']()
    }
  },
  listeners: {
    '@view-update': '_requestUpdate'
  },
  mounted () {
    this._updateStyle()
    let mapStyle = Object.assign({}, this.attrs, this.style)
    if (this.latitude && this.longitude) {
      mapStyle.center = new plus.maps.Point(this.longitude, this.latitude)
    }
    const map = this.map = plus.maps.create('map' + Date.now(), mapStyle)
    map.__markers__ = {}
    map.__lines__ = []
    map.__circles__ = []
    plus.webview.currentWebview().append(map)
    if (this.hidden) {
      map.hide()
    }
    this.$watch('attrs', () => {
      this.map && this.map.setStyles(this.attrs)
    }, {
      deep: true
    })
    this.$watch('style', () => {
      this.map && this.map.setStyles(this.style)
    }, {
      deep: true
    })
    map.onclick((data = {}) => {
      this.$trigger('tap', {}, data)
    })
    map.onstatuschanged((data = {}) => {
      this.$trigger('end', {}, data)
    })
    this._addMarkers(this.markers)
    this._addMapLines(this.polyline)
    this._addMapCircles(this.circles)
  },
  beforeDestroy () {
    delete this.map
  },
  methods: {
    _handleSubscribe ({
      type,
      data = {}
    }) {
      if (!methods.includes(type)) {
        return
      }
      this.map && this[type](data)
    },
    getRegion () {
      // TODO
      // const region = this.map.getBounds()
    },
    getScale () {
      // TODO
      // const zoom = this.map.getZoom()
    },
    _updateStyle () {
      const rect = this.$refs.container.getBoundingClientRect()
      this.hidden = false;
      ['top', 'left', 'width', 'height'].forEach(key => {
        let val = rect[key]
        val = key === 'top' ? val + (document.documentElement.scrollTop || document.body.scrollTop || 0) : val
        if (!val && (key === 'width' || key === 'height')) {
          this.hidden = true
        }
        this.style[key] = val + 'px'
      })
    },
    _requestUpdate () {
      if (this._animationFrame) {
        cancelAnimationFrame(this._animationFrame)
      }
      if (this.video) {
        this._animationFrame = requestAnimationFrame(() => {
          delete this._animationFrame
          this._updateStyle()
        })
      }
    },
    controlclick (e) {
      this.$trigger('controltap', {}, { id: e.id })
    },
    _addMarker (nativeMap, marker) {
      const {
        id,
        // title,
        latitude,
        longitude,
        iconPath,
        // width,
        // height,
        // rotate,
        // alpha,
        callout,
        label
      } = marker
      convertCoordinates(longitude, latitude, res => {
        const {
          latitude,
          longitude
        } = res.coord
        const nativeMarker = new plus.maps.Marker(new plus.maps.Point(longitude, latitude))
        if (iconPath) {
          nativeMarker.setIcon(this.$getRealPath(iconPath))
        }
        if (label && label.content) {
          nativeMarker.setLabel(label.content)
        }
        let nativeBubble = false
        if (callout && callout.content) {
          nativeBubble = new plus.maps.Bubble(callout.content)
        }
        if (nativeBubble) {
          nativeMarker.setBubble(nativeBubble)
        }
        if (id || id === 0) {
          nativeMarker.onclick = (e) => {
            this.$trigger('markertap', {}, {
              id
            })
          }
          if (nativeBubble) {
            nativeBubble.onclick = () => {
              this.$trigger('callouttap', {}, {
                id
              })
            }
          }
        }
        nativeMap.addOverlay(nativeMarker)
        nativeMap.__markers__[id + ''] = nativeMarker
      })
    },
    _addMarkers (markers, clear) {
      if (this.map) {
        if (clear) {
          this.map.clearOverlays()
        }
        markers.forEach(marker => {
          this._addMarker(this.map, marker)
        })
        return {
          errMsg: 'addMapMarkers:ok'
        }
      }
      return {
        errMsg: 'addMapMarkers:fail:请先创建地图元素'
      }
    },
    _translateMapMarker ({
      autoRotate,
      callbackId,
      destination,
      duration,
      markerId
    }) {
      if (this.map) {
        const nativeMarker = this.map.__markers__[markerId + '']
        if (nativeMarker) {
          nativeMarker.setPoint(new plus.maps.Point(destination.longitude, destination.latitude))
        }
      }
      return {
        errMsg: 'translateMapMarker:ok'
      }
    },
    _addMapLines (lines) {
      const nativeMap = this.map
      if (!nativeMap) {
        return {
          errMsg: 'addMapLines:fail:请先创建地图元素'
        }
      }

      if (nativeMap.__lines__.length > 0) {
        nativeMap.__lines__.forEach(circle => {
          nativeMap.removeOverlay(circle)
        })
        nativeMap.__lines__ = []
      }

      lines.forEach(line => {
        const {
          color,
          width
          // dottedLine,
          // arrowLine,
          // arrowIconPath,
          // borderColor,
          // borderWidth
        } = line
        const points = line.points.map(point => new plus.maps.Point(point.longitude, point.latitude))
        const polyline = new plus.maps.Polyline(points)
        if (color) {
          const strokeStyle = parseHex(color)
          polyline.setStrokeColor(strokeStyle.color)
          polyline.setStrokeOpacity(strokeStyle.opacity)
        }
        if (width) {
          polyline.setLineWidth(width)
        }
        nativeMap.addOverlay(polyline)
        nativeMap.__lines__.push(polyline)
      })
      return {
        errMsg: 'addMapLines:ok'
      }
    },
    _addMapCircles (circles) {
      const nativeMap = this.map
      if (!nativeMap) {
        return {
          errMsg: 'addMapCircles:fail:请先创建地图元素'
        }
      }

      if (nativeMap.__circles__.length > 0) {
        nativeMap.__circles__.forEach(circle => {
          nativeMap.removeOverlay(circle)
        })
        nativeMap.__circles__ = []
      }

      circles.forEach(circle => {
        const {
          latitude,
          longitude,
          color,
          fillColor,
          radius,
          strokeWidth
        } = circle
        const nativeCircle = new plus.maps.Circle(new plus.maps.Point(longitude, latitude), radius)
        if (color) {
          const strokeStyle = parseHex(color)
          nativeCircle.setStrokeColor(strokeStyle.color)
          nativeCircle.setStrokeOpacity(strokeStyle.opacity)
        }
        if (fillColor) {
          const fillStyle = parseHex(fillColor)
          nativeCircle.setFillColor(fillStyle.color)
          nativeCircle.setFillOpacity(fillStyle.opacity)
        }
        if (strokeWidth) {
          nativeCircle.setLineWidth(strokeWidth)
        }
        nativeMap.addOverlay(nativeCircle)
        nativeMap.__circles__.push(nativeCircle)
      })
      return {
        errMsg: 'addMapCircles:ok'
      }
    }
  }
}
</script>

<style>
  uni-map {
    width: 300px;
    height: 225px;
    display: inline-block;
    line-height: 0;
    overflow: hidden;
    position: relative;
  }

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

  .uni-map-container {
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    overflow: hidden;
    background-color: black;
  }

  .uni-map-slot {
    position: absolute;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    pointer-events: none;
  }
</style>


Tools: refetch bookings on create/update fail

If the fail was as a result of a conflict we need to refetch the bookings to see what we clashed with


Auto scrollTo hash

Auto scrollTo hash

<template>

            <router-link active-class="active" :class="{'nav-link': true, active: $route.hash.indexOf(group.hash) === 1}" :to="'#' + group.hash">{{group.name}}</router-link>

                <router-link active-class="active" class="nav-link" :to="'#' + group.hash + '-' + child.hash">{{child.name}}</router-link>

{{$t('document.title')}}

{{document}}

</template>


fix: typo

fix: typo


ssr and video fixes

ssr and video fixes

<template>
	<div class="-item">
		<app-responsive-dimensions
			:class="{
				'-ssr': GJ_IS_SSR,
				'-filled': isFilled,
			}"
			:ratio="mediaItem.width / mediaItem.height"
			:max-width="mediaItem.width"
			:max-height="!GJ_IS_SSR ? Screen.height * 0.45 : undefined"
			@change="onDimensionsChange"
		>
			<app-event-item-media-tags :gif="mediaItem.is_animated" />

			<app-img-responsive
				v-if="!isPostHydrated || !mediaItem.is_animated"
				class="-img"
				:style="
					!GJ_IS_SSR && {
						maxWidth: mediaItem.width + 'px',
						maxHeight: mediaItem.height + 'px',
					}
				"
				:src="mediaItem.mediaserver_url"
				alt=""
				ondragstart="return false"
			/>
			<app-video
				v-else-if="shouldVideoPlay"
				class="-video"
				:style="
					!GJ_IS_SSR && {
						maxWidth: mediaItem.width + 'px',
						maxHeight: mediaItem.height + 'px',
					}
				"
				:poster="mediaItem.mediaserver_url"
				:webm="mediaItem.mediaserver_url_webm"
				:mp4="mediaItem.mediaserver_url_mp4"
				show-loading
			/>
		</app-responsive-dimensions>
	</div>
</template>

<style lang="stylus" scoped>
@require '~styles/variables'
@require '~styles-lib/mixins'

.-item
	position: relative
	display: inline-flex
	justify-content: center
	vertical-align: middle
	width: 100%
	max-width: 100% !important

// The "item" gets the correct dimensions applied, so we want to stretch
// out any image or video in the item to be full width/height.
.-img, .-video
	rounded-corners-lg()
	change-bg('bg-offset')
	display: block
	width: inherit
	margin-left: auto
	margin-right: auto
	position: absolute
	top: 0
	right: 0
	bottom: 0
	left: 0
	overflow: hidden

	.-filled &
		border-radius: 0

	// For SSR responsive-dimensions component doesn't work, so we want
	// to instead just try showing the media however the browser would
	// show it.
	.-ssr &
		position: static
		width: 100%
</style>

<script lang="ts" src="./item"></script>

<template>
	<div class="-item">
		<app-responsive-dimensions
			:class="{
				'-ssr': GJ_IS_SSR,
				'-filled': isFilled,
			}"
			:ratio="mediaItem.width / mediaItem.height"
			:max-width="mediaItem.width"
			:max-height="!GJ_IS_SSR ? Screen.height * 0.45 : undefined"
			@change="onDimensionsChange"
		>
			<app-event-item-media-tags :gif="mediaItem.is_animated" />

			<app-img-responsive
				v-if="!isPostHydrated || !mediaItem.is_animated"
				class="-img"
				:style="
					!GJ_IS_SSR && {
						maxWidth: mediaItem.width + 'px',
						maxHeight: mediaItem.height + 'px',
					}
				"
				:src="mediaItem.mediaserver_url"
				alt=""
				ondragstart="return false"
			/>
			<app-video
				v-else-if="shouldVideoPlay"
				class="-video"
				:style="
					!GJ_IS_SSR && {
						maxWidth: mediaItem.width + 'px',
						maxHeight: mediaItem.height + 'px',
					}
				"
				:poster="mediaItem.mediaserver_url"
				:webm="mediaItem.mediaserver_url_webm"
				:mp4="mediaItem.mediaserver_url_mp4"
				show-loading
			/>
		</app-responsive-dimensions>
	</div>
</template>

<style lang="stylus" scoped>
@require '~styles/variables'
@require '~styles-lib/mixins'

.-item
	position: relative
	display: inline-flex
	justify-content: center
	vertical-align: middle
	width: 100%
	max-width: 100% !important

// The "item" gets the correct dimensions applied, so we want to stretch
// out any image or video in the item to be full width/height.
.-img, .-video
	rounded-corners-lg()
	change-bg('bg-offset')
	display: block
	height: 100%
	width: auto
	margin-left: auto
	margin-right: auto
	position: absolute
	top: 0
	right: 0
	bottom: 0
	left: 0
	overflow: hidden

	.-filled &
		border-radius: 0

	// For SSR responsive-dimensions component doesn't work, so we want
	// to instead just try showing the media however the browser would
	// show it.
	.-ssr &
		position: static
</style>

<script lang="ts" src="./item"></script>


google maps successfully load geojson

google maps successfully load geojson

<template>

        <gmap-map
          ref = "mymap"
          :center="center"
          :zoom="4"
          style="width: 800px; height: 800px"
        >

          <gmap-polygon :paths="paths" :editable="true" @paths_changed="updateEdited($event)">
          </gmap-polygon>
        </gmap-map>

    <button @click="loadControls">
      Load Drawing Controls
    </button>
    <button @click="displayGeoJson">
      Display GeoJSON Data
    </button>
    <textarea cols="300" rows="50" v-model="geojson"></textarea>

</template>

<template>

Hello World

        <gmap-map
          :center="center"
          :zoom="7"
          style="width: 800px; height: 800px"
        >
          <gmap-marker
            :key="index"
            v-for="(m, index) in markers"
            :position="m.position"
            :clickable="true"
            :draggable="true"
            @click="center=m.position"
          ></gmap-marker>
        </gmap-map>

</template>