Bugfix: removed min-width to avoid overflow on small screens.

<template>
    <div id="dumonda-me-mobile-bottom-nav">
        <v-bottom-navigation color="primary" app light grow v-show="$vuetify.breakpoint.mdAndDown"
                             v-model="bottomNav">

            <!--Needed to deselect icons-->
            <v-btn value="none" v-show="false">
                <v-icon>$vuetify.icons.mdiHome</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'index'})" value="home" v-if="!isAuthenticated">
                <v-icon>$vuetify.icons.mdiCardBulletedOutline</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'topics'})" value="topics">
                <v-icon>$vuetify.icons.mdiBookOutline</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'harvestingEvents'})" value="harvestingEvents"
                   v-show="$vuetify.breakpoint.width < 700">
                <v-icon>$vuetify.icons.mdiBriefcaseOutline</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'index'})" value="feed" v-if="isAuthenticated">
                <v-icon>$vuetify.icons.mdiRss</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'question'})" value="feed" v-else>
                <v-icon>$vuetify.icons.mdiRss</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'user'})" value="account" v-if="isAuthenticated">
                <v-icon>$vuetify.icons.mdiAccountOutline</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'notifications'})" value="notifications" v-if="isAuthenticated">
                <v-badge v-if="numberOfUnreadNotifications > 0" overlap :left="$vuetify.breakpoint.width < 700"
                         color="secondary">
                    <template v-slot:badge> {{numberOfUnreadNotifications}}</template>
                    <v-icon>$vuetify.icons.mdiBellOutline</v-icon>
                </v-badge>
                <v-icon v-else>$vuetify.icons.mdiBellOutline</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'login'})" value="login" v-if="!isAuthenticated">
                <v-icon>$vuetify.icons.mdiLogin</v-icon>
            </v-btn>
        </v-bottom-navigation>
    </div>
</template>

<script>
    import {mapGetters} from 'vuex';

    export default {
        name: "DumondaMeMobileBottomNav",
        data() {
            return {bottomNav: ''};
        },
        mounted() {
            this.setBottomNav();
        },
        computed: {
            isAuthenticated() {
                return this.$store.state.auth.userIsAuthenticated
            },
            ...mapGetters({
                numberOfUnreadNotifications: 'notification/numberOfUnreadNotifications'
            })
        },
        methods: {
            setBottomNav() {
                if (this.$route.name === 'index' || this.$route.name === 'question' || this.$route.name === 'commitment'
                    || this.$route.name === 'event') {
                    this.bottomNav = 'feed';
                } else if (this.$route.name === 'user') {
                    this.bottomNav = 'account';
                } else if (this.$route.name === 'notifications') {
                    this.bottomNav = 'notifications';
                } else if (this.$route.name === 'login') {
                    this.bottomNav = 'login';
                } else if (this.$route.name === 'topics') {
                    this.bottomNav = 'topics';
                } else if (this.$route.name === 'harvestingEvents') {
                    this.bottomNav = 'harvestingEvents';
                } else {
                    this.bottomNav = 'none';
                }
            }
        },
        watch: {
            $route() {
                this.setBottomNav();
            }
        }
    }
</script>

<style lang="scss">
    #dumonda-me-mobile-bottom-nav {
        width: 100%;

        .v-bottom-navigation.v-item-group.v-bottom-navigation--grow {
            width: 100%;

            .v-btn {
                min-width: 0;
            }
        }
    }
</style>

<template>
    <div id="dumonda-me-mobile-bottom-nav">
        <v-bottom-navigation color="primary" app light grow v-show="$vuetify.breakpoint.mdAndDown"
                             v-model="bottomNav">

            <!--Needed to deselect icons-->
            <v-btn value="none" v-show="false">
                <v-icon>$vuetify.icons.mdiHome</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'index'})" value="home" v-if="!isAuthenticated">
                <v-icon>$vuetify.icons.mdiCardBulletedOutline</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'topics'})" value="topics">
                <v-icon>$vuetify.icons.mdiBookOutline</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'harvestingEvents'})" value="harvestingEvents"
                   v-show="$vuetify.breakpoint.width < 700">
                <v-icon>$vuetify.icons.mdiBriefcaseOutline</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'index'})" value="feed" v-if="isAuthenticated">
                <v-icon>$vuetify.icons.mdiRss</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'question'})" value="feed" v-else>
                <v-icon>$vuetify.icons.mdiRss</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'user'})" value="account" v-if="isAuthenticated">
                <v-icon>$vuetify.icons.mdiAccountOutline</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'notifications'})" value="notifications" v-if="isAuthenticated">
                <v-badge v-if="numberOfUnreadNotifications > 0" overlap :left="$vuetify.breakpoint.width < 700"
                         color="secondary">
                    <template v-slot:badge> {{numberOfUnreadNotifications}}</template>
                    <v-icon>$vuetify.icons.mdiBellOutline</v-icon>
                </v-badge>
                <v-icon v-else>$vuetify.icons.mdiBellOutline</v-icon>
            </v-btn>
            <v-btn @click="$router.push({name: 'login'})" value="login" v-if="!isAuthenticated">
                <v-icon>$vuetify.icons.mdiLogin</v-icon>
            </v-btn>
        </v-bottom-navigation>
    </div>
</template>

<script>
    import {mapGetters} from 'vuex';

    export default {
        name: "DumondaMeMobileBottomNav",
        data() {
            return {bottomNav: ''};
        },
        mounted() {
            this.setBottomNav();
        },
        computed: {
            isAuthenticated() {
                return this.$store.state.auth.userIsAuthenticated
            },
            ...mapGetters({
                numberOfUnreadNotifications: 'notification/numberOfUnreadNotifications'
            })
        },
        methods: {
            setBottomNav() {
                if (this.$route.name === 'index' || this.$route.name === 'question' || this.$route.name === 'commitment'
                    || this.$route.name === 'event') {
                    this.bottomNav = 'feed';
                } else if (this.$route.name === 'user') {
                    this.bottomNav = 'account';
                } else if (this.$route.name === 'notifications') {
                    this.bottomNav = 'notifications';
                } else if (this.$route.name === 'login') {
                    this.bottomNav = 'login';
                } else if (this.$route.name === 'topics') {
                    this.bottomNav = 'topics';
                } else if (this.$route.name === 'harvestingEvents') {
                    this.bottomNav = 'harvestingEvents';
                } else {
                    this.bottomNav = 'none';
                }
            }
        },
        watch: {
            $route() {
                this.setBottomNav();
            }
        }
    }
</script>

<style lang="scss">
    #dumonda-me-mobile-bottom-nav {
    }
</style>


style(): adjust modal buttons

style(): adjust modal buttons

<template>

    <HubHeader></HubHeader>

    <Modal
      :title="$t('publish.generate_appx')"
      ref="appxModal"
      @modalSubmit="onSubmitAppxModal"
      @cancel="onCancelAppxModal"
      v-on:modalOpened="modalOpened()"
      v-on:modalClosed="modalClosed()"
      v-if="appxForm"
    >

          {{ $t('publish.enter_your') }}
          {{ $t('publish.dev_center') }}
          {{ $t('publish.publisher_details') }}

          {{ $t('publish.label_publisher') }}

        <input
          class="l-generator-input l-generator-input--largest"
          :placeholder="$t('publish.placeholder_publisher')"
          type="text"
          v-model="appxForm.publisher"
          requied
        >

          {{ $t('publish.label_identity') }}
          {{ $t('publish.label_publisher_id') }}

        <input
          class="l-generator-input l-generator-input--largest"
          :placeholder="$t('publish.placeholder_identity')"
          type="text"
          v-model="appxForm.publisher_id"
          requied
        >

          {{ $t('publish.label_package') }}

        <input
          class="l-generator-input l-generator-input--largest"
          :placeholder="$t('publish.placeholder_package')"
          type="text"
          v-model="appxForm.package"
          requied
        >

          {{ $t('publish.label_version') }}

        <input
          class="l-generator-input l-generator-input--largest"
          :placeholder="$t('publish.placeholder_version')"
          type="text"
          v-model="appxForm.version"
          requied
        >

          {{ $t(appxError) }}

    </Modal>

      <button @click="closeAndroidModal()" id="closeAndroidPlatButton">

      </button>

            You can choose to package your PWA as a
            Trusted Web Activity
            or in a
            traditional Webview.

            Note: For Trusted Web Activities you will need Android Studio to associate your PWA with your TWA

          <Download
            :showMessage="true"
            id="androidDownloadButton"
            platform="androidTWA"
            message="Download TWA"
          />
          <Download
            :showMessage="true"
            id="androidDownloadButton"
            class="webviewButton"
            platform="android"
            message="Download WebView"
          />

      <button @click="closeAndroidModal()" id="closeAndroidPlatButton">

      </button>

            You'll get a side-loadable version of your PWA (requires Win10 in dev mode) to test your PWA right away.
            The Generate Appx button can be used to generate a PWA package to submit to the Microsoft Store.

          <Download
            id="androidDownloadButton"
            platform="windows10"
            :message="$t('publish.download')"
            :showMessage="true"
          />
          <button
            id="androidDownloadButton"
            @click="openAppXModal();  $awa( { 'referrerUri': 'https://www.pwabuilder.com/publish/windows10-appx' })"
          >Generate</button>

Everything you need to make your PWA

            If you haven’t already, download the content below and publish it to your website.
            Making these changes to your website is all you need to become a PWA.
            You may also want to publish your PWAs to the different app markets,
            you will find the packages for each of these on the right.

Progressive Web App

                <Download
                  class="platformDownloadButton"
                  platform="web"
                  message="Download your PWA files"
                />

                If you haven’t already, download these files and publish it to your website.
                Making these changes to your website is all you need to become a PWA.

Android

                <button class="platformDownloadButton" @click="openAndroidModal()">

                </button>

PWAs are available through the browser on Android, however your PWA can also be submitted to the play store by submitting the package you get below.

Windows

                <button class="platformDownloadButton" @click="openWindowsModal()">

                </button>

You'll get a side-loadable version of your PWA (requires Win10 in dev mode) to test your PWA right away. To generate an AppX PWA package and submit to the Microsoft Store, click here

MacOS

                <Download class="platformDownloadButton" platform="macos" message="Download"/>

You can use Xcode to build this package to produce an app that runs on MacOS.

iOS

                <Download class="platformDownloadButton" platform="ios" message="Download"/>

PWAs are available through the browser. You can submit this app package to the iOS App Store

Scope out rad PWAs

Pinterest, Spotify, and more built some PWAs and they are like whoa! Check them out by clicking on the image or logos. Love doing PWAs? Submit your own!

        I will hold things

</template>


Initial work on preference selection

Initial work on preference selection

<template>

        {{ error }}

Add Merit Badge Preferences for {{ scout.fullname }}

        Please select the top six merit badges that {{ scout.firstname}} would
        like to attend classes for. We will do our best to accommodate
        preferences.

      <template v-for="(preference, index) in preferences">

                {{ index + 1 }}:

                <select class="form-control"
                        :id="'registration-rank' + index"
                        v-model="preference.offering">
                  <option v-for="option in offerings" :value="option.id">
                    {{ option.name }}
                  </option>
                </select>

      </template>

      <button class="button is-primary"
              :class="{ 'is-disabled is-loading': creating }"
              @click="registerScout()">
        Register Scout
      </button>
      <button class="button"
              @click="cancel()">
        Cancel
      </button>

</template>

<template>

        {{ error }}

Add Merit Badge Preferences for {{ scout.fullname }}

    <button class="button is-primary"
            :class="{ 'is-disabled is-loading': creating }"
            @click="registerScout()">
      Register Scout
    </button>
    <button class="button"
            @click="cancel()">
      Cancel
    </button>

</template>


fix finishPullDown 回退速度问题

fix finishPullDown 回退速度问题

<template>

      <slot>

{{item}}

      </slot>
      <slot name="pullup"
            :pullUpLoad="pullUpLoad"
            :isPullUpLoad="isPullUpLoad"
      >

            加载更多

            <loading></loading>

      </slot>

    <slot name="pulldown"
          :pullDownRefresh="pullDownRefresh"
          :pullDownStyle="pullDownStyle"
          :beforePullDown="beforePullDown"
          :pulling="pulling"
          :bubbleY="bubbleY"
    >

          <bubble :y="bubbleY"></bubble>

            <loading></loading>

刷新成功

    </slot>

</template>


Pages position bug

Pages position bug

<template>
    <input
        class="form-control form-control-sm"
        min="0"
        type="number"
        v-model="model.position"
        @change="$parent.$emit('update-position', model)"
    />
</template>

<script>
export default {
    props: {
        model: {
            type: Object,
            required: true,
        },
    },
};
</script>

<template>
    <input
        class="form-control form-control-sm"
        min="0"
        type="number"
        name="position"
        v-model="model.position"
        @change="$parent.$emit('update-position', model)"
    />
</template>

<script>
export default {
    props: {
        model: {
            type: Object,
            required: true,
        },
    },
};
</script>


better loading indicator

better loading indicator

<script lang="coffee">
import Records from '@/shared/services/records'
import Session from '@/shared/services/session'
import AbilityService from '@/shared/services/ability_service'
import Flash   from '@/shared/services/flash'
import EventBus          from '@/shared/services/event_bus'
import { onError } from '@/shared/helpers/form'
import { orderBy, debounce } from 'lodash-es'

export default
  data: ->
    selectedDiscussion: null
    searchFragment: ''
    searchResults: []
    groupId: @discussion.groupId
    groups: Session.user().groups()
    loading: false

  props:
    discussion: Object
    close: Function

  methods:
    resetSourceDiscussion: ->
      @discussion.update(forkedEventIds: [])
      @discussion.update(isForking: false)

    startNewThread: ->
      @selectedDiscussion = Records.discussions.build(groupId: @groupId)
      @setIsForking()
      @resetSourceDiscussion()
      EventBus.$emit('openModal',
                      component: 'DiscussionForm',
                      props: {
                        discussion: @selectedDiscussion
                      })

    submit: ->
      @loading = true
      @selectedDiscussion.moveComments()
      .then =>
        @loading = false
        @resetSourceDiscussion()
        @selectedDiscussion.update(forkedEventIds: [])
        @selectedDiscussion.update(isForking: false)
        @close()
        Flash.success("discussion_fork_actions.moved")
      .catch onError(@selectedDiscussion)

    setIsForking: ->
      @selectedDiscussion.update(isForking: true)
      @selectedDiscussion.update(forkedEventIds: @discussion.forkedEventIds)

    fetch: debounce ->
      return unless @searchFragment
      @loading = true
      Records.discussions.search(@groupId, @searchFragment).then (data) =>
        @loading = false
        @searchResults = Records.discussions.collection.chain()
          .find(groupId: @groupId)
          .find({ id: { $ne: @discussion.id } })
          .find(title: { $regex: [@searchFragment, 'i'] })
          .where((d) -> !!AbilityService.canAddComment(d))
          .simplesort('title')
          .data()
    , 500

  watch:
    selectedDiscussion: 'setIsForking'
    searchFragment: 'fetch'

</script>
<template lang="pug">
v-card
  submit-overlay(:value='selectedDiscussion && selectedDiscussion.processing')
  v-card-title
    h1.headline(v-t="'action_dock.move_items'")
    v-spacer
    dismiss-modal-button(aria-hidden='true', :close='close')
  v-card-text
    v-select(v-model="groupId" :items="groups" item-text="name" item-value="id")
    v-autocomplete(hide-no-data return-object v-model="selectedDiscussion" :search-input.sync="searchFragment" :items="searchResults" item-text="title" :placeholder="$t('discussion_fork_actions.search_placeholder')" :label="$t('discussion_fork_actions.move_to_existing_thread')" :loading="loading")
  v-card-actions
    v-spacer
    v-btn(color="accent" @click="startNewThread()" v-t="'discussion_fork_actions.start_new_thread'")
    v-btn(color="primary" @click="submit()" v-t="'common.action.save'" :disabled="!selectedDiscussion")
</template>

<script lang="coffee">
import Records from '@/shared/services/records'
import Session from '@/shared/services/session'
import AbilityService from '@/shared/services/ability_service'
import Flash   from '@/shared/services/flash'
import EventBus          from '@/shared/services/event_bus'
import { onError } from '@/shared/helpers/form'
import { orderBy, debounce } from 'lodash-es'

export default
  data: ->
    selectedDiscussion: null
    searchFragment: ''
    searchResults: []
    groupId: @discussion.groupId
    groups: Session.user().groups()
    loading: false

  props:
    discussion: Object
    close: Function

  methods:
    resetSourceDiscussion: ->
      @discussion.update(forkedEventIds: [])
      @discussion.update(isForking: false)

    startNewThread: ->
      @selectedDiscussion = Records.discussions.build(groupId: @groupId)
      @setIsForking()
      @resetSourceDiscussion()
      EventBus.$emit('openModal',
                      component: 'DiscussionForm',
                      props: {
                        discussion: @selectedDiscussion
                      })

    submit: ->
      @loading = true
      @selectedDiscussion.moveComments()
      .then =>
        @loading = false
        @resetSourceDiscussion()
        @selectedDiscussion.update(forkedEventIds: [])
        @selectedDiscussion.update(isForking: false)
        @close()
        Flash.success("discussion_fork_actions.moved")
      .catch onError(@selectedDiscussion)

    setIsForking: ->
      @selectedDiscussion.update(isForking: true)
      @selectedDiscussion.update(forkedEventIds: @discussion.forkedEventIds)

    fetch: debounce ->
      return unless @searchFragment
      @loading = true
      Records.discussions.search(@groupId, @searchFragment).then (data) =>
        @loading = false
        @searchResults = Records.discussions.collection.chain()
          .find(groupId: @groupId)
          .find({ id: { $ne: @discussion.id } })
          .find(title: { $regex: [@searchFragment, 'i'] })
          .where((d) -> !!AbilityService.canAddComment(d))
          .simplesort('title')
          .data()
    , 500

  watch:
    selectedDiscussion: 'setIsForking'
    searchFragment: 'fetch'

</script>
<template lang="pug">
v-card
  submit-overlay(:value='loading')
  v-card-title
    h1.headline(v-t="'action_dock.move_items'")
    v-spacer
    dismiss-modal-button(aria-hidden='true', :close='close')
  v-card-text
    v-select(v-model="groupId" :items="groups" item-text="name" item-value="id")
    v-autocomplete(hide-no-data return-object v-model="selectedDiscussion" :search-input.sync="searchFragment" :items="searchResults" item-text="title" :placeholder="$t('discussion_fork_actions.search_placeholder')" :label="$t('discussion_fork_actions.move_to_existing_thread')")
  v-card-actions
    v-spacer
    v-btn(color="accent" @click="startNewThread()" v-t="'discussion_fork_actions.start_new_thread'")
    v-btn(color="primary" @click="submit()" v-t="'common.action.save'" :disabled="!selectedDiscussion")
</template>


Change background of audio thumbnails

Change background of audio thumbnails

<template>

  <div>
    <Alert
      v-model="showErrorAlert"
      :header="$tr('thumbnailGenerationFailedHeader')"
      :text="$tr('thumbnailGenerationFailedText')"
    />
    <slot :generate="generate"></slot>
  </div>

</template>

<script>

  import { mapActions } from 'vuex';
  import chunk from 'lodash/chunk';
  import min from 'lodash/min';
  import map from 'lodash/map';
  import max from 'lodash/max';
  import pdfJSLib from 'pdfjs-dist';
  import epubJS from 'epubjs';
  import Alert from 'shared/views/Alert';
  import PDFJSWorker from '!!file-loader!pdfjs-dist/build/pdf.worker.min.js';

  pdfJSLib.GlobalWorkerOptions.workerSrc = PDFJSWorker;
  const MAX_AUDIO_SAMPLE_SIZE = 64000;

  export default {
    name: 'ThumbnailGenerator',
    components: {
      Alert,
    },
    props: {
      width: {
        type: Number,
        default: 320,
      },
      filePath: {
        type: String,
        required: false,
      },
      // Method to call to handle generated files
      handleFiles: {
        type: Function,
        required: true,
      },
    },
    data() {
      return {
        showErrorAlert: false,
      };
    },
    computed: {
      height() {
        return (this.width * 9) / 16;
      },
      isVideo() {
        return this.filePath.endsWith('mp4');
      },
      isAudio() {
        return this.filePath.endsWith('mp3');
      },
      isHTML() {
        return this.filePath.endsWith('zip');
      },
      isPDF() {
        return this.filePath.endsWith('pdf');
      },
      isEPub() {
        return this.filePath.endsWith('epub');
      },
    },
    methods: {
      ...mapActions('task', ['startTask', 'clearCurrentTask']),
      ...mapActions('file', ['getAudioData', 'generateThumbnail']),
      handleError(error) {
        this.$emit('error', error);
        this.showErrorAlert = true;
      },
      generateVideoThumbnail() {
        let video = document.createElement('video');
        video.width = this.width;
        video.height = this.height;
        video.crossOrigin = 'anonymous';
        video.preload = 'metadata';
        video.src = this.filePath;
        video.onloadedmetadata = () => {
          video.currentTime = (video.duration / 4) * 3;
        };
        video.ontimeupdate = () => {
          let canvas = document.createElement('canvas');
          canvas.getContext('2d').drawImage(video, 0, 0, this.width, this.height);
          this.handleGenerated(canvas.toDataURL('image/png'));
        };
      },
      generateAudioThumbnail() {
        let canvas = document.createElement('canvas');
        let context = canvas.getContext('2d');
        // Add light background
        context.fillStyle = 'black';
        context.fillRect(0, 0, this.width, this.height);
        context.fillStyle = this.$vuetify.theme.primary;
        this.getAudioData(this.filePath)
          .then(data => {
            let sampleStart = Math.max(0, (data.length - MAX_AUDIO_SAMPLE_SIZE) / 2);
            let sampleEnd = Math.min(data.length, (data.length + MAX_AUDIO_SAMPLE_SIZE) / 2);
            data = data.slice(sampleStart, sampleEnd);
            let amp = this.height / 2;
            let peaks = chunk(data, Math.ceil(data.length / this.width));

            // Go through set of peaks and draw the max range (highest peak to lowest peak)
            peaks.forEach((peakArray, x) => {
              let minPeak = min(peakArray);
              let maxPeak = max(peakArray);
              context.fillRect(x, (1 + minPeak) * amp, 1, Math.max(1, (maxPeak - minPeak) * amp));
            });
            this.handleGenerated(canvas.toDataURL('image/png'));
          })
          .catch(this.handleError);
      },
      generatePDFThumbnail() {
        let canvas = document.createElement('canvas');
        pdfJSLib
          .getDocument(this.filePath)
          .promise.then(pdf => {
            pdf
              .getPage(1)
              .then(page => {
                let viewport = page.getViewport({ scale: 1 });
                page
                  .render({
                    canvasContext: canvas.getContext('2d'),
                    viewport: page.getViewport({
                      scale: this.width / viewport.width,
                    }),
                  })
                  .promise.then(() => {
                    this.handleGenerated(canvas.toDataURL('image/png'));
                  })
                  .catch(this.handleError);
              })
              .catch(this.handleError);
          })
          .catch(this.handleError);
      },
      generateEPubThumbnail() {
        const book = epubJS(this.filePath);
        return book.ready.then(() => {
          return book.loaded.cover.then(() => {
            if (!book.cover) {
              this.handleError();
            }
            return book.archive.createUrl(book.cover, { base64: true }).then(this.handleGenerated);
          });
        });
      },
      generateThumbnailOnServer() {
        const filename = this.filePath.split('/');
        this.generateThumbnail(filename[filename.length - 1])
          .then(response => {
            const payload = {
              task: response.data,
              resolveCallback: result => {
                this.clearCurrentTask();
                this.handleGenerated(result);
              },
              rejectCallback: error => {
                this.clearCurrentTask();
                this.handleError(error);
              },
            };
            this.startTask(payload);
          })
          .catch(this.handleError);
      },
      handleGenerated(encoding) {
        // If there isn't an encoding, throw an error
        if (!encoding) {
          this.handleError();
          return;
        }
        const chunks = chunk(atob(encoding.split(',')[1]), 512);
        const byteArrays = map(
          chunks,
          chunk => new Uint8Array(map(chunk.join('').toString(), s => s.charCodeAt(0)))
        );
        const filename = `${this.$tr('generatedDefaultFilename')}.png`;
        const file = new File(byteArrays, filename, { type: 'image/png' });
        this.handleFiles([file]);
      },
      generate() {
        this.$emit('generating');
        if (this.isVideo) {
          this.generateVideoThumbnail();
        } else if (this.isAudio) {
          this.generateAudioThumbnail();
        } else if (this.isPDF) {
          this.generatePDFThumbnail();
        } else if (this.isEPub) {
          this.generateEPubThumbnail();
        } else if (this.isHTML) {
          this.generateThumbnailOnServer();
        } else {
          this.handleError();
        }
      },
    },
    $trs: {
      thumbnailGenerationFailedHeader: 'Unable to generate thumbnail',
      thumbnailGenerationFailedText: 'There was a problem generating a thumbnail for this item',
      generatedDefaultFilename: 'Generated thumbnail',
    },
  };

</script>

<template>

  <div>
    <Alert
      v-model="showErrorAlert"
      :header="$tr('thumbnailGenerationFailedHeader')"
      :text="$tr('thumbnailGenerationFailedText')"
    />
    <slot :generate="generate"></slot>
  </div>

</template>

<script>

  import { mapActions } from 'vuex';
  import chunk from 'lodash/chunk';
  import min from 'lodash/min';
  import map from 'lodash/map';
  import max from 'lodash/max';
  import pdfJSLib from 'pdfjs-dist';
  import epubJS from 'epubjs';
  import Alert from 'shared/views/Alert';
  import PDFJSWorker from '!!file-loader!pdfjs-dist/build/pdf.worker.min.js';

  pdfJSLib.GlobalWorkerOptions.workerSrc = PDFJSWorker;
  const MAX_AUDIO_SAMPLE_SIZE = 64000;

  export default {
    name: 'ThumbnailGenerator',
    components: {
      Alert,
    },
    props: {
      width: {
        type: Number,
        default: 320,
      },
      filePath: {
        type: String,
        required: false,
      },
      // Method to call to handle generated files
      handleFiles: {
        type: Function,
        required: true,
      },
    },
    data() {
      return {
        showErrorAlert: false,
      };
    },
    computed: {
      height() {
        return (this.width * 9) / 16;
      },
      isVideo() {
        return this.filePath.endsWith('mp4');
      },
      isAudio() {
        return this.filePath.endsWith('mp3');
      },
      isHTML() {
        return this.filePath.endsWith('zip');
      },
      isPDF() {
        return this.filePath.endsWith('pdf');
      },
      isEPub() {
        return this.filePath.endsWith('epub');
      },
    },
    methods: {
      ...mapActions('task', ['startTask', 'clearCurrentTask']),
      ...mapActions('file', ['getAudioData', 'generateThumbnail']),
      handleError(error) {
        this.$emit('error', error);
        this.showErrorAlert = true;
      },
      generateVideoThumbnail() {
        let video = document.createElement('video');
        video.width = this.width;
        video.height = this.height;
        video.crossOrigin = 'anonymous';
        video.preload = 'metadata';
        video.src = this.filePath;
        video.onloadedmetadata = () => {
          video.currentTime = (video.duration / 4) * 3;
        };
        video.ontimeupdate = () => {
          let canvas = document.createElement('canvas');
          canvas.getContext('2d').drawImage(video, 0, 0, this.width, this.height);
          this.handleGenerated(canvas.toDataURL('image/png'));
        };
      },
      generateAudioThumbnail() {
        let canvas = document.createElement('canvas');
        let context = canvas.getContext('2d');
        // Add light background
        context.fillStyle = this.$vuetify.theme.primaryBackground;
        context.fillRect(0, 0, this.width, this.height);
        context.fillStyle = this.$vuetify.theme.primary;
        this.getAudioData(this.filePath)
          .then(data => {
            let sampleStart = Math.max(0, (data.length - MAX_AUDIO_SAMPLE_SIZE) / 2);
            let sampleEnd = Math.min(data.length, (data.length + MAX_AUDIO_SAMPLE_SIZE) / 2);
            data = data.slice(sampleStart, sampleEnd);
            let amp = this.height / 2;
            let peaks = chunk(data, Math.ceil(data.length / this.width));

            // Go through set of peaks and draw the max range (highest peak to lowest peak)
            peaks.forEach((peakArray, x) => {
              let minPeak = min(peakArray);
              let maxPeak = max(peakArray);
              context.fillRect(x, (1 + minPeak) * amp, 1, Math.max(1, (maxPeak - minPeak) * amp));
            });
            this.handleGenerated(canvas.toDataURL('image/png'));
          })
          .catch(this.handleError);
      },
      generatePDFThumbnail() {
        let canvas = document.createElement('canvas');
        pdfJSLib
          .getDocument(this.filePath)
          .promise.then(pdf => {
            pdf
              .getPage(1)
              .then(page => {
                let viewport = page.getViewport({ scale: 1 });
                page
                  .render({
                    canvasContext: canvas.getContext('2d'),
                    viewport: page.getViewport({
                      scale: this.width / viewport.width,
                    }),
                  })
                  .promise.then(() => {
                    this.handleGenerated(canvas.toDataURL('image/png'));
                  })
                  .catch(this.handleError);
              })
              .catch(this.handleError);
          })
          .catch(this.handleError);
      },
      generateEPubThumbnail() {
        const book = epubJS(this.filePath);
        return book.ready.then(() => {
          return book.loaded.cover.then(() => {
            if (!book.cover) {
              this.handleError();
            }
            return book.archive.createUrl(book.cover, { base64: true }).then(this.handleGenerated);
          });
        });
      },
      generateThumbnailOnServer() {
        const filename = this.filePath.split('/');
        this.generateThumbnail(filename[filename.length - 1])
          .then(response => {
            const payload = {
              task: response.data,
              resolveCallback: result => {
                this.clearCurrentTask();
                this.handleGenerated(result);
              },
              rejectCallback: error => {
                this.clearCurrentTask();
                this.handleError(error);
              },
            };
            this.startTask(payload);
          })
          .catch(this.handleError);
      },
      handleGenerated(encoding) {
        // If there isn't an encoding, throw an error
        if (!encoding) {
          this.handleError();
          return;
        }
        const chunks = chunk(atob(encoding.split(',')[1]), 512);
        const byteArrays = map(
          chunks,
          chunk => new Uint8Array(map(chunk.join('').toString(), s => s.charCodeAt(0)))
        );
        const filename = `${this.$tr('generatedDefaultFilename')}.png`;
        const file = new File(byteArrays, filename, { type: 'image/png' });
        this.handleFiles([file]);
      },
      generate() {
        this.$emit('generating');
        if (this.isVideo) {
          this.generateVideoThumbnail();
        } else if (this.isAudio) {
          this.generateAudioThumbnail();
        } else if (this.isPDF) {
          this.generatePDFThumbnail();
        } else if (this.isEPub) {
          this.generateEPubThumbnail();
        } else if (this.isHTML) {
          this.generateThumbnailOnServer();
        } else {
          this.handleError();
        }
      },
    },
    $trs: {
      thumbnailGenerationFailedHeader: 'Unable to generate thumbnail',
      thumbnailGenerationFailedText: 'There was a problem generating a thumbnail for this item',
      generatedDefaultFilename: 'Generated thumbnail',
    },
  };

</script>


[前端] 增加阅读更多链接

[前端] 增加阅读更多链接

<template>

{{ article.createdAt }}

 <router-link :to="{ name:'post', params:{ pathName: article.pathName } }" >{{ article.title }}</router-link>

      <router-link :to="{ name:'post', params:{ pathName: article.pathName } }" >阅读更多 »</router-link>

</template>

<template>

{{ article.createdAt }}

 <router-link :to="{ name:'post', params:{ pathName: article.pathName } }" >{{ article.title }}</router-link>

</template>


Update index to $ convention

Update index to $ convention

<template>

    <GeneratorMenu/>

Provide a URL

              Provide your URL and we'll help fill in the gaps if there are any.

            <form @submit.prevent="checkUrlAndGenerate" @keydown.enter.prevent="checkUrlAndGenerate">

                URL
                <input
                  class="l-generator-input"
                  placeholder="Enter a URL"
                  name="siteUrl" id="siteUrl"
                  type="text"
                  ref="url"
                  v-model="url$">

                    <button
                      type="submit"
                      class="get-started pwa-button isEnabled next-step"
                      data-flare="{'category': 'Build', 'action': 'Step 2', 'label': 'Scan for Manifest', 'value': { 'page': '/build/manifest-scan' }}" >
                      {{ $t('generator.start') }} <Loading :active="inProgress" class="u-display-inline_block u-margin-left-sm" />
                    </button>

{{error}}

                  <button
                    @click="skipCheckUrl"
                    class="pwa-button pwa-button--simple"
                    data-flare="{'category': 'Skip', 'action': 'Manifest', 'label': 'Skip to Service Worker', 'value': { 'page': '/skip/service-worker' }}">
                    {{ $t('generator.skip') }}
                  </button>

If you already have the Manifest and want to download the service worker source code you can skip the first step.

            </form>

    <TwoWays/>

</template>

<template>

    <GeneratorMenu/>

Provide a URL

              Provide your URL and we'll help fill in the gaps if there are any.

            <form @submit.prevent="checkUrlAndGenerate" @keydown.enter.prevent="checkUrlAndGenerate">

                URL
                <input
                  class="l-generator-input"
                  placeholder="Enter a URL"
                  name="siteUrl" id="siteUrl"
                  type="text"
                  ref="url"
                  v-model="siteUrl">

                    <button
                      type="submit"
                      class="get-started pwa-button isEnabled next-step"
                      data-flare="{'category': 'Build', 'action': 'Step 2', 'label': 'Scan for Manifest', 'value': { 'page': '/build/manifest-scan' }}" >
                      {{ $t('generator.start') }} <Loading :active="inProgress" class="u-display-inline_block u-margin-left-sm" />
                    </button>

{{error}}

                  <button
                    @click="skipCheckUrl"
                    class="pwa-button pwa-button--simple"
                    data-flare="{'category': 'Skip', 'action': 'Manifest', 'label': 'Skip to Service Worker', 'value': { 'page': '/skip/service-worker' }}">
                    {{ $t('generator.skip') }}
                  </button>

If you already have the Manifest and want to download the service worker source code you can skip the first step.

            </form>

    <TwoWays/>

</template>


fix disabled state of references tab

fix disabled state of references tab

<template>

            <context-tree
                class="col px-0 scroll-y-auto"
                :on-entity-add="requestAddNewEntity"
                :on-context-menu-duplicate="duplicateEntity"
                :on-context-menu-delete="requestDeleteEntity"
                :roots="roots"
                :selection-callback="onSetSelectedElement">
            </context-tree>

            <router-view class="h-100"
                :bibliography="bibliography">
            </router-view>

                    <router-link class="nav-link" :class="{active: tab == plugin.key}" :to="{ query: { tab: plugin.key }}" append>
                         {{ plugin.label }}
                    </router-link>

                         References

                <keep-alive>
                    <component
                        :context="selectedContext"
                        :context-data-loaded="dataLoaded"
                        :is="activePlugin">
                    </component>
                </keep-alive>

                        No references found.

                            {{ $translateConcept(key) }}

                                        {{ reference.description }}

                                        {{ reference.bibliography.author }} in
                                            {{ reference.bibliography.title }} ,{{ reference.bibliography.year }}

        <modal name="add-entity-modal" height="auto" :scrollable="true" classes="of-visible">

Add new Entity

                    <button type="button" class="close" aria-label="Close" @click="hideNewEntityModal">
                        ×
                    </button>

                    <form id="newEntityForm" name="newEntityForm" role="form" v-on:submit.prevent="addNewEntity(newEntity)">

                                Name:

                                <input type="text" id="name" class="form-control" required v-model="newEntity.name" />

                                Type:

                            <multiselect class="col-md-9" style="box-sizing: border-box;"
                                :customLabel="translateLabel"
                                label="thesaurus_url"
                                track-by="id"
                                v-model="newEntity.type"
                                :allowEmpty="false"
                                :closeOnSelect="true"
                                :hideSelected="true"
                                :multiple="false"
                                :options="newEntity.selection">
                            </multiselect>

                    </form>

                    <button type="submit" class="btn btn-success" :disabled="addEntityDisabled(newEntity)" form="newEntityForm">
                         Add
                    </button>
                    <button type="button" class="btn btn-secondary" @click="hideNewEntityModal">
                         Cancel
                    </button>

        </modal>

        <modal name="delete-entity-modal" height="auto" :scrollable="true">

Delete {{ toDeleteEntity.name }}

                    <button type="button" class="close" aria-label="Close" @click="hideDeleteEntityModal">
                        ×
                    </button>

                        Do you really want to delete Entity {{ toDeleteEntity.name }}?

                    <button type="button" class="btn btn-danger" @click="deleteEntity(toDeleteEntity)">
                         Delete
                    </button>
                    <button type="button" class="btn btn-secondary" @click="hideDeleteEntityModal">
                         Cancel
                    </button>

        </modal>

        <modal name="discard-changes-modal" height="auto" :scrollable="true">

Unsaved Changes

                    <button type="button" class="close" aria-label="Close" @click="hideDiscardModal">
                        ×
                    </button>

                        There are unsaved changes in {{ selectedContext.name }}. Do you really want to continue and discard these changes?

                    <button type="button" class="btn btn-warning" @click="discardCallback">
                         Yes, Discard Changes
                    </button>
                    <button type="button" class="btn btn-success" @click="saveAndContinueCallback">
                         No, Save and continue
                    </button>
                    <button type="button" class="btn btn-secondary" @click="hideDiscardModal">
                         Cancel
                    </button>

        </modal>

</template>

<template>

            <context-tree
                class="col px-0 scroll-y-auto"
                :on-entity-add="requestAddNewEntity"
                :on-context-menu-duplicate="duplicateEntity"
                :on-context-menu-delete="requestDeleteEntity"
                :roots="roots"
                :selection-callback="onSetSelectedElement">
            </context-tree>

            <router-view class="h-100"
                :bibliography="bibliography">
            </router-view>

                    <router-link class="nav-link" :class="{active: tab == plugin.key}" :to="{ query: { tab: plugin.key }}" append>
                         {{ plugin.label }}
                    </router-link>

                    <router-link class="nav-link" :class="{active: tab == 'references', disabled: !selectedContext.id}" :to="{ query: { tab: 'references' }}" append>
                         References
                    </router-link>

                <keep-alive>
                    <component
                        :context="selectedContext"
                        :context-data-loaded="dataLoaded"
                        :is="activePlugin">
                    </component>
                </keep-alive>

                        No references found.

                            {{ $translateConcept(key) }}

                                        {{ reference.description }}

                                        {{ reference.bibliography.author }} in
                                            {{ reference.bibliography.title }} ,{{ reference.bibliography.year }}

        <modal name="add-entity-modal" height="auto" :scrollable="true" classes="of-visible">

Add new Entity

                    <button type="button" class="close" aria-label="Close" @click="hideNewEntityModal">
                        ×
                    </button>

                    <form id="newEntityForm" name="newEntityForm" role="form" v-on:submit.prevent="addNewEntity(newEntity)">

                                Name:

                                <input type="text" id="name" class="form-control" required v-model="newEntity.name" />

                                Type:

                            <multiselect class="col-md-9" style="box-sizing: border-box;"
                                :customLabel="translateLabel"
                                label="thesaurus_url"
                                track-by="id"
                                v-model="newEntity.type"
                                :allowEmpty="false"
                                :closeOnSelect="true"
                                :hideSelected="true"
                                :multiple="false"
                                :options="newEntity.selection">
                            </multiselect>

                    </form>

                    <button type="submit" class="btn btn-success" :disabled="addEntityDisabled(newEntity)" form="newEntityForm">
                         Add
                    </button>
                    <button type="button" class="btn btn-secondary" @click="hideNewEntityModal">
                         Cancel
                    </button>

        </modal>

        <modal name="delete-entity-modal" height="auto" :scrollable="true">

Delete {{ toDeleteEntity.name }}

                    <button type="button" class="close" aria-label="Close" @click="hideDeleteEntityModal">
                        ×
                    </button>

                        Do you really want to delete Entity {{ toDeleteEntity.name }}?

                    <button type="button" class="btn btn-danger" @click="deleteEntity(toDeleteEntity)">
                         Delete
                    </button>
                    <button type="button" class="btn btn-secondary" @click="hideDeleteEntityModal">
                         Cancel
                    </button>

        </modal>

        <modal name="discard-changes-modal" height="auto" :scrollable="true">

Unsaved Changes

                    <button type="button" class="close" aria-label="Close" @click="hideDiscardModal">
                        ×
                    </button>

                        There are unsaved changes in {{ selectedContext.name }}. Do you really want to continue and discard these changes?

                    <button type="button" class="btn btn-warning" @click="discardCallback">
                         Yes, Discard Changes
                    </button>
                    <button type="button" class="btn btn-success" @click="saveAndContinueCallback">
                         No, Save and continue
                    </button>
                    <button type="button" class="btn btn-secondary" @click="hideDiscardModal">
                         Cancel
                    </button>

        </modal>

</template>


"sign up" -> account creation

"sign up" -> account creation

<template>

    <form
      ref="form"
      class="signup-form"
      @submit.prevent="signUp"
    >

{{ $tr('createAccount') }}

      <KTextbox
        id="name"
        ref="name"
        v-model="name"
        type="text"
        autocomplete="name"
        :label="$tr('name')"
        :maxlength="120"
        :autofocus="true"
        :invalid="nameIsInvalid"
        :invalidText="nameIsInvalidText"
        @blur="nameBlurred = true"
      />

      <KTextbox
        id="username"
        ref="username"
        v-model="username"
        type="text"
        autocomplete="username"
        :label="$tr('username')"
        :maxlength="30"
        :invalid="usernameIsInvalid"
        :invalidText="usernameIsInvalidText"
        @blur="usernameBlurred = true"
        @input="resetSignUpState"
      />

      <KTextbox
        id="password"
        ref="password"
        v-model="password"
        type="password"
        autocomplete="new-password"
        :label="$tr('password')"
        :invalid="passwordIsInvalid"
        :invalidText="passwordIsInvalidText"
        @blur="passwordBlurred = true"
      />

      <KTextbox
        id="confirmed-password"
        ref="confirmedPassword"
        v-model="confirmedPassword"
        type="password"
        autocomplete="new-password"
        :label="$tr('reEnterPassword')"
        :invalid="confirmedPasswordIsInvalid"
        :invalidText="confirmedPasswordIsInvalidText"
        @blur="confirmedPasswordBlurred = true"
      />

      <KSelect
        v-model="selectedFacility"
        :label="$tr('facility')"
        :options="facilityList"
        :invalid="facilityIsInvalid"
        :invalidText="facilityIsInvalidText"
        :disabled="facilityList.length === 1"
        @blur="facilityBlurred = true"
      />

        <KButton
          :text="$tr('privacyLink')"
          appearance="basic-link"
          @click="privacyModalVisible = true"
        />

        <KButton
          :disabled="busy"
          :primary="true"
          :text="$tr('finish')"
          type="submit"
          class="submit"
        />

    </form>

      <LanguageSwitcherFooter />

    <PrivacyInfoModal
      v-if="privacyModalVisible"
      hideOwnersSection
      @cancel="privacyModalVisible = false"
    />

</template>


Fix error when no auto complete item available

<template>

            <template v-if="item.type === 'user'">
                {{item.text}}
                {{$t('send_message')}}
            </template>
            <template v-else-if="item.type === 'command'">
                {{item.text}}
                {{item.description}}
            </template>
            <template v-else>
                {{item.text}}
            </template>

</template>


Search: Add event:on-submit. close #218

Search: Add event:on-submit. close #218

<template>

      <form class="weui_search_outer" @submit.prevent="@emit('on-submit', value)">

          <input type="text" class="weui_search_input" id="search_input" placeholder="{{placeholder}}" autocomplete="off" required v-model="value" v-el:input/>

          {{placeholder}}

      </form>
      {{cancelText}}

{{item.title}}

</template>

<template>

      <form class="weui_search_outer">

          <input type="text" class="weui_search_input" id="search_input" placeholder="{{placeholder}}" autocomplete="off" required v-model="value" v-el:input/>

          {{placeholder}}

      </form>
      {{cancelText}}

{{item.title}}

</template>


add styling for sample pucks columns

add styling for sample pucks columns

<template>

      <button
        class="button"
        @click="$emit('clone-container', 0)">
        Clone all from first row
      </button>

      <a
        v-for="(tabName, tabNameIndex) in tabNames" :key="tabNameIndex"
        :class="{
          'tw-border-t': true,
          'tw-border-l': true,
          'tw-cursor-pointer': true,
          'tw-border-r': tabNameIndex === tabNames.length - 1,
          'tw-bg-table-header-background': currentTab === tabName.key,
          'tw-text-table-header-color': currentTab === tabName.key,
          'tw-p-2': currentTab !== tabName.key,
          'tw-p-3': currentTab === tabName.key,
        }"
        @click="switchTabColumn(tabName.key)">
        {{ tabName.name }}

        {{ column.title }}

          {{ column.title }}

    <sample-table-row
      v-for="(sample, sampleIndex) in samples"
      :key="sampleIndex"
      :basic-columns="basicColumns"
      :extra-fields-columns="extraFieldsColumns"
      :required-columns="requiredColumns"
      :current-tab="currentTab"
      :sample="sample"
      :sample-index="sampleIndex"
      :udc-columns="udcColumns"
      :proteins="proteins"
      :samplesLength="samples.length"
      :containerId="containerId"
      v-on="$listeners"
    />
    <portal to="dialog">
      <custom-dialog-box
        v-if="displaySampleGroupModal"
        size="small"
        :hide-ok-button="true"
        @perform-modal-action="performModalAction"
        @close-modal-action="closeModalAction">
        <template>

Sample Groups

            <button
              class="tw-flex tw-items-center tw-border tw-rounded-sm tw-border-content-border tw-bg-white tw-text-content-page-color tw-p-1"
              @click="closeModalAction">

            </button>

            <base-input-select
              class="tw-py-5"
              option-text-key="text"
              option-value-key="value"
              :options="shipments"
              inputClass="tw-w-full tw-h-6"
              :value="containerSamplesGroupData['shipmentId']"
              @input="onSampleGroupDataChange($event, 'shipmentId')"
              label="Shipment:"
              outerClass="tw-w-full tw-flex"
              labelClass="tw-w-3/5 tw-font-bold"
            />

            <base-input-select
              class="tw-py-5"
              option-text-key="text"
              option-value-key="value"
              :options="dewars"
              inputClass="tw-w-full tw-h-6"
              :value="containerSamplesGroupData['dewarId']"
              @input="onSampleGroupDataChange($event, 'dewarId')"
              label="Dewars:"
              outerClass="tw-w-full tw-flex"
              labelClass="tw-w-3/5 tw-font-bold"
            />

            <base-input-select
              class="tw-py-5"
              option-text-key="text"
              option-value-key="value"
              :options="containers"
              inputClass="tw-w-full tw-h-6"
              :value="containerSamplesGroupData['containerId']"
              @input="onSampleGroupDataChange($event, 'containerId')"
              label="Containers:"
              outerClass="tw-w-full tw-flex"
              labelClass="tw-w-3/5 tw-font-bold"
            />

              <button class="button" @click="createNewSampleGroup">
                   Create Group
              </button>

        </template>
      </custom-dialog-box>
    </portal>

</template>


Add new tooltipPosition prop (extension, non-breaking) to KIconButton used in overflow menu, to prevent overlay. Requires corresponding KDS update

<template>

  <Navbar>

      <NavbarLink
        :vIf="link.condition"
        :title="link.title"
        :link="link.link"
      >
        <KIcon
          :icon="link.icon"
          :color="link.color"
        />
      </NavbarLink>

     0">
      <KIconButton
        class="menu-icon"
        :tooltip="coreString('moreOptions')"
        tooltipPosition="top"
        :ariaLabel="coreString('moreOptions')"
        icon="optionsHorizontal"
        appearance="flat-button"
        :color="color"
        :primary="false"
      >
        <template #menu>
          <KDropdownMenu
            :primary="false"
            :disabled="false"
            :hasIcons="true"
            :options="overflowMenuLinks"
            @select="handleSelect"
          />
        </template>
      </KIconButton>

  </Navbar>

</template>

<template>

  <Navbar>

      <NavbarLink
        :vIf="link.condition"
        :title="link.title"
        :link="link.link"
      >
        <KIcon
          :icon="link.icon"
          :color="link.color"
        />
      </NavbarLink>

     0">
      <KIconButton
        class="menu-icon"
        :tooltip="coreString('moreOptions')"
        :ariaLabel="coreString('moreOptions')"
        icon="optionsHorizontal"
        appearance="flat-button"
        :color="color"
        :primary="false"
      >
        <template #menu>
          <KDropdownMenu
            :primary="false"
            :disabled="false"
            :hasIcons="true"
            :options="overflowMenuLinks"
            @select="handleSelect"
          />
        </template>
      </KIconButton>

  </Navbar>

</template>


handle col fill

handle col fill

<template>

                <template v-for="column in columns">

                        <slot name="cell" :row="row" :column="column">
                            <template v-if="column.html">

                            </template>
                            <template v-else>
                                {{ row[column.key] }}
                            </template>
                        </slot>

                </template>

            <template v-if="hasLink">

            </template>

        <template v-if="$scopedSlots.append">

                <slot name="append" v-bind="this" />

        </template>
        <template v-else>

        </template>

</template>


Add v-once to navbar component for caching performance

<template>

      <button
        @click.stop="showNav = !showNav"
        type="button"
        class="dn-l btn-clear mr3"
      >
        <svg class="nav-icon link">
          <use v-if="showNav" xlink:href="~@wearegenki/icons/src/x.svg"/>
          <use v-else xlink:href="~@wearegenki/icons/src/menu.svg"/>
        </svg>
      </button>

      <router-link v-once to="/" class="nav-logo ml-1-l">
        <svg class="logo"><use xlink:href="~@/assets/logo.svg"/></svg>
      </router-link>

        <router-link
          v-once
          v-for="item in items"
          :key="item.url"
          :to="item.url"
          class="nav-link"
        >
          {{ item.name }}
        </router-link>

</template>

<template>

      <button @click.stop="showNav = !showNav" class="dn-l btn-clear mr3" type="button">
        <svg class="nav-icon link">
          <use v-if="showNav" xlink:href="~@wearegenki/icons/src/x.svg"/>
          <use v-else xlink:href="~@wearegenki/icons/src/menu.svg"/>
        </svg>
      </button>

      <router-link to="/" class="nav-logo ml-1-l">
        <svg class="logo"><use xlink:href="~@/assets/logo.svg"/></svg>
      </router-link>

        <router-link v-for="item in items" :key="item.url" :to="item.url" class="nav-link">
          {{ item.name }}
        </router-link>

</template>


fix path

fix path

<template>
  <uw-card v-if="alum" :loaded="true">
    <template #card-heading>

        Alumni and Alumni Association

    </template>
    <template #card-body>

        Sharing a connection across three campuses and extending to places
        all over the world, UW alumni are united in the belief that together
        we will make a difference.

          Alumni News, Events and Services

          Columns Magazine

        University of Washington Alumni Association (UWAA)

        With 56,000 members, UWAA helps Huskies of all ages stay in touch
        and in the know.

            Learn about the UWAA

    </template>
  </uw-card>
</template>


Improve example text

Improve example text

<template>

      Below the line , you see the some content.

      It was created in the component on the right, and transfered here with VuePortal.

      Click the "x" to remove links - all vue directives continue to work in their original context.

      Hint: When the source component is removed, then the target's content below disappears as well.

    <portal-target name="toggle-destination">

    </portal-target>

</template>

<template>

      Below this paragraph, you see the some content.

      It was created in the component on the right, and transfered here with VuePortal.

      Click the "x" to remove links - all vue directives continue to work in their original context.

      Hint: When the source component is removed, then the target's content below disappears as well.

    <portal-target name="toggle-destination">

    </portal-target>

</template>


Add icon

Add icon


Tuned up detection params

Tuned up detection params

<template>
	<div>

		<video id="userWebcam" width="400" height="300" preload autoplay loop muted></video>
		<canvas id="userCanvasId" width="400" height="300" ref="userCanvas"></canvas>
	</div>
</template>
<script>
	import * as tracking from 'exports-loader?tracking!tracking';
	import {face} from "tracking/build/data/face";

	const WARNING_THRESHOLD_MS = 1500;
	const FACE_DETECTION_TIMEOUT_MS = 1000;

	export default {

		mounted() {
			var self = this;
			var canvas = self.$refs["userCanvas"];
			var context = canvas.getContext("2d");

			var tracker = new tracking.ObjectTracker("face");
			tracker.setInitialScale(2);
      tracker.setStepSize(1);
      tracker.setEdgesDensity(0.1);

			tracking.track("#userWebcam", tracker, {camera: true});

			tracker.on('track', function(event) {
				context.clearRect(0, 0, canvas.width, canvas.height);
				if (event.data.length === 0 || event.data.length > 1) {
			    // No objects were detected in this frame.

					if (self.lastTimeSeen + WARNING_THRESHOLD_MS < new Date().getTime()) {

						if (FACE_DETECTION_TIMEOUT_MS + WARNING_THRESHOLD_MS - (new Date().getTime() - self.lastTimeSeen) <= 0) {
							var potentialFraudData = {};
							potentialFraudData.numberOfFacesFound = event.data.length;
							potentialFraudData.eventTime = new Date().toLocaleString();

							self.$parent.$emit('potentialFraudFound', potentialFraudData);
							self.lastTimeSeen = new Date().getTime();
						}
					}
			  }
				else {
					self.lastTimeSeen = new Date().getTime();

			    /*event.data.forEach(function(rect) {
			      // rect.x, rect.y, rect.height, rect.width
						context.strokeStyle = '#a64ceb';
	          context.strokeRect(rect.x, rect.y, rect.width, rect.height);
	          context.font = '11px Helvetica';
	          context.fillStyle = "#fff";
	          context.fillText('x: ' + rect.x + 'px', rect.x + rect.width + 5, rect.y + 11);
	          context.fillText('y: ' + rect.y + 'px', rect.x + rect.width + 5, rect.y + 22);
		    	});*/
			  }
			});
		},
		props: ["supcio"],
		data() {
			return {
				now: null,
				lastTimeSeen: new Date().getTime()
			};
		},
		computed: {
			seconds() {
				return (this.now !== 0) ? this.now % 60 : 0;
			},
		}
	};
</script>

<style lang="scss">
	.main-content {
		padding: 16px;
	}

	.face-detection-warning {
		color: red;
		font-size: 50px;
	}

	#noticeTimer {
		color: red;
		font-size: 50px;
	}

	video {
    //position: absolute;
  }

	canvas {
    position: relative;
		left: -400px;
  }
</style>

<template>
	<div>

		<video id="userWebcam" width="400" height="300" preload autoplay loop muted></video>
		<canvas id="userCanvasId" width="400" height="300" ref="userCanvas"></canvas>
	</div>
</template>
<script>
	import * as tracking from 'exports-loader?tracking!tracking';
	import {face} from "tracking/build/data/face";

	const WARNING_THRESHOLD_MS = 1500;
	const FACE_DETECTION_TIMEOUT_MS = 2500;

	export default {

		mounted() {
			var self = this;
			var canvas = self.$refs["userCanvas"];
			var context = canvas.getContext("2d");

			var tracker = new tracking.ObjectTracker("face");
			tracker.setInitialScale(4);
      tracker.setStepSize(2);
      tracker.setEdgesDensity(0.1);

			tracking.track("#userWebcam", tracker, {camera: true});

			tracker.on('track', function(event) {
				context.clearRect(0, 0, canvas.width, canvas.height);
				if (event.data.length === 0 || event.data.length > 1) {
			    // No objects were detected in this frame.

					if (self.lastTimeSeen + WARNING_THRESHOLD_MS < new Date().getTime()) {

						if (FACE_DETECTION_TIMEOUT_MS + WARNING_THRESHOLD_MS - (new Date().getTime() - self.lastTimeSeen) <= 0) {
							var potentialFraudData = {};
							potentialFraudData.numberOfFacesFound = event.data.length;
							potentialFraudData.eventTime = new Date().toLocaleString();

							self.$parent.$emit('potentialFraudFound', potentialFraudData);
							self.lastTimeSeen = new Date().getTime();
						}
					}
			  }
				else {
					self.lastTimeSeen = new Date().getTime();

			    /*event.data.forEach(function(rect) {
			      // rect.x, rect.y, rect.height, rect.width
						context.strokeStyle = '#a64ceb';
	          context.strokeRect(rect.x, rect.y, rect.width, rect.height);
	          context.font = '11px Helvetica';
	          context.fillStyle = "#fff";
	          context.fillText('x: ' + rect.x + 'px', rect.x + rect.width + 5, rect.y + 11);
	          context.fillText('y: ' + rect.y + 'px', rect.x + rect.width + 5, rect.y + 22);
		    	});*/
			  }
			});
		},
		props: ["supcio"],
		data() {
			return {
				now: null,
				lastTimeSeen: new Date().getTime()
			};
		},
		computed: {
			seconds() {
				return (this.now !== 0) ? this.now % 60 : 0;
			},
		}
	};
</script>

<style lang="scss">
	.main-content {
		padding: 16px;
	}

	.face-detection-warning {
		color: red;
		font-size: 50px;
	}

	#noticeTimer {
		color: red;
		font-size: 50px;
	}

	video {
    //position: absolute;
  }

	canvas {
    position: relative;
		left: -400px;
  }
</style>


Increase drop shadow radius on navbar

Increase drop shadow radius on navbar

<template>

        <router-link class="navbar-item" @click="menu = false" to="/">Pixelect</router-link>

        <div :class="`navbar-burger${ menu ? ' is-active' : '' }`" @click="menu = !menu">

      <div :class="`navbar-menu${ menu ? ' is-active' : '' }`">

                <button :class="button.class" @click="bus.$emit(button.action)">

                  {{ button.text }}
                </button>

                Welcome Back, {{userName}}! 🎉

                Logout

</template>


chore: 👷 added comment for tokens

chore: 👷 added comment for tokens

<template>
  <mew-module
    class="d-flex flex-grow-1 pt-6"
    title="Send"
    :has-elevation="true"
    :has-indicator="true"
  >
    <template #moduleBody>

      <v-row class="mt-5">
        <v-col cols="12" sm="6" class="pr-sm-1 pt-0 pb-0 pb-sm-4">

            <app-button-balance
              :balance="selectedBalance"
              :loading="!showSelectedBalance"
              class="d-sm-none"
            />
            <mew-select
              ref="mewSelect"
              label="Token"
              :items="tokens"
              :is-custom="true"
              :value="selectedCurrency"
              @input="setCurrency"
            />

        </v-col>
        <v-col cols="12" sm="6" class="pl-sm-1 pt-0 pb-2 pb-sm-4">

            <app-button-balance
              :balance="selectedBalance"
              :loading="!showSelectedBalance"
              class="d-none d-sm-block"
            />
            <mew-input
              label="Amount"
              placeholder="0"
              :value="amount"
              type="number"
              :persistent-hint="true"
              :error-messages="amountErrorMessage"
              :max-btn-obj="{
                title: 'Max',
                disabled: disableSwapBtn,
                method: setEntireBal
              }"
              :buy-more-str="buyMore"
              @input="setAmount"
            />

        </v-col>

        <v-col v-if="showBalanceNotice" cols="12" class="pt-0 pb-4">
          <send-low-balance-notice
            :address="address"
            :currency-name="currencyName"
            class="pa-3"
          />
        </v-col>

        <v-col cols="12" class="pt-4 pb-2">
          <module-address-book ref="addressInput" @setAddress="setAddress" />
        </v-col>

        <v-col cols="12" class="py-0 mb-8">
          <app-transaction-fee
            :show-fee="showSelectedBalance"
            :getting-fee="!txFeeIsReady"
            :error="feeError"
            :total-cost="totalCost"
            :tx-fee="txFee"
            :total-gas-limit="gasLimit"
            :message="feeError"
            :not-enough-eth="!hasEnoughEth"
            :from-eth="isFromNetworkCurrency"
            @onLocalGasPrice="handleLocalGasPrice"
          />
        </v-col>

        <v-col cols="12" class="py-4">
          <mew-expand-panel
            ref="expandPanel"
            :panel-items="expandPanel"
            :idx-to-expand="[]"
            @toggled="closeToggle"
          >
            <template #panelBody1>

                    <v-icon class="textBlack2--text mew-body mr-1">
                      mdi-alert-outline</v-icon
                    >For advanced users only

                    Please don’t edit these fields unless you are an expert user
                    & know what you’re doing. Entering the wrong information
                    could result in your transaction failing or getting stuck.

                    Reset to default: {{ formattedDefaultGasLimit }}

                <mew-input
                  :value="gasLimit"
                  :label="$t('common.gas.limit')"
                  placeholder=""
                  :error-messages="gasLimitError"
                  type="number"
                  @input="setGasLimit"
                />

                <mew-input
                  v-show="!isToken"
                  v-model="data"
                  :label="$t('sendTx.add-data')"
                  placeholder="0x..."
                  :rules="dataRules"
                  class="mb-8"
                />

            </template>
          </mew-expand-panel>
        </v-col>
      </v-row>

          <mew-button
            title="Next"
            :has-full-width="false"
            btn-size="xlarge"
            :disabled="isDisabledNextBtn"
            @click.native="send()"
          />

          <mew-button
            :title="$t('common.clear-all')"
            :has-full-width="false"
            btn-size="small"
            btn-style="transparent"
            @click.native="clear()"
          />

    </template>
  </mew-module>
</template>


When <el-form> missing `model`, validate will return false, then throw a warning to facilitate the developer troubleshooting

<template>
  <form class="el-form" :class="[
    labelPosition ? 'el-form--label-' + labelPosition : '',
    { 'el-form--inline': inline }
  ]">
    <slot></slot>
  </form>
</template>
<script>
  export default {
    name: 'ElForm',

    componentName: 'ElForm',

    props: {
      model: Object,
      rules: Object,
      labelPosition: String,
      labelWidth: String,
      labelSuffix: {
        type: String,
        default: ''
      },
      inline: Boolean,
      showMessage: {
        type: Boolean,
        default: true
      }
    },
    watch: {
      rules() {
        this.validate();
      }
    },
    data() {
      return {
        fields: []
      };
    },
    created() {
      this.$on('el.form.addField', (field) => {
        if (field) {
          this.fields.push(field);
        }
      });
      /* istanbul ignore next */
      this.$on('el.form.removeField', (field) => {
        if (field.prop) {
          this.fields.splice(this.fields.indexOf(field), 1);
        }
      });
    },
    methods: {
      resetFields() {
        if (!this.model) {
          process.env.NODE_ENV !== 'production' &&
          console.warn('[Element Warn][Form]model is required for resetFields to work.');
          return;
        }
        this.fields.forEach(field => {
          field.resetField();
        });
      },
      validate(callback) {
        if (!this.model) {
          console.warn('[Element Warn][Form]model is required for validate to work!');
          return;
        };
        let valid = true;
        let count = 0;
        // 如果需要验证的fields为空，调用验证时立刻返回callback
        if (this.fields.length === 0 && callback) {
          callback(true);
        }
        this.fields.forEach((field, index) => {
          field.validate('', errors => {
            if (errors) {
              valid = false;
            }
            if (typeof callback === 'function' && ++count === this.fields.length) {
              callback(valid);
            }
          });
        });
      },
      validateField(prop, cb) {
        var field = this.fields.filter(field => field.prop === prop)[0];
        if (!field) { throw new Error('must call validateField with valid prop string!'); }

        field.validate('', cb);
      }
    }
  };
</script>

<template>
  <form class="el-form" :class="[
    labelPosition ? 'el-form--label-' + labelPosition : '',
    { 'el-form--inline': inline }
  ]">
    <slot></slot>
  </form>
</template>
<script>
  export default {
    name: 'ElForm',

    componentName: 'ElForm',

    props: {
      model: Object,
      rules: Object,
      labelPosition: String,
      labelWidth: String,
      labelSuffix: {
        type: String,
        default: ''
      },
      inline: Boolean,
      showMessage: {
        type: Boolean,
        default: true
      }
    },
    watch: {
      rules() {
        this.validate();
      }
    },
    data() {
      return {
        fields: []
      };
    },
    created() {
      this.$on('el.form.addField', (field) => {
        if (field) {
          this.fields.push(field);
        }
      });
      /* istanbul ignore next */
      this.$on('el.form.removeField', (field) => {
        if (field.prop) {
          this.fields.splice(this.fields.indexOf(field), 1);
        }
      });
    },
    methods: {
      resetFields() {
        if (!this.model) {
          process.env.NODE_ENV !== 'production' &&
          console.warn('[Element Warn][Form]model is required for resetFields to work.');
          return;
        }
        this.fields.forEach(field => {
          field.resetField();
        });
      },
      validate(callback) {
        let valid = true;
        let count = 0;
        // 如果需要验证的fields为空，调用验证时立刻返回callback
        if (this.fields.length === 0 && callback) {
          callback(true);
        }
        this.fields.forEach((field, index) => {
          field.validate('', errors => {
            if (errors) {
              valid = false;
            }
            if (typeof callback === 'function' && ++count === this.fields.length) {
              callback(valid);
            }
          });
        });
      },
      validateField(prop, cb) {
        var field = this.fields.filter(field => field.prop === prop)[0];
        if (!field) { throw new Error('must call validateField with valid prop string!'); }

        field.validate('', cb);
      }
    }
  };
</script>


解决vue指令中没有this

解决vue指令中没有this

<template>

file1

file2

file3

file4

file5

file6

file7

file8

file9

file10

file11

file12

file13

file14

file15

file16

file17

</template>


focus right place on thread load

focus right place on thread load

<script lang="coffee">
import Vue from 'vue'
import AppConfig                from '@/shared/services/app_config'
import EventBus                 from '@/shared/services/event_bus'
import RecordLoader             from '@/shared/services/record_loader'
import AbilityService           from '@/shared/services/ability_service'
import Session from '@/shared/services/session'
import Records from '@/shared/services/records'
import Flash   from '@/shared/services/flash'
import { print } from '@/shared/helpers/window'
import ThreadService  from '@/shared/services/thread_service'

import { pickBy, identity, camelCase, first, last, isNumber } from 'lodash'

export default
  components:
    ThreadRenderer: -> import('@/components/thread/renderer.vue')

  props:
    discussion: Object
    viewportIsBelow: Boolean
    viewportIsAbove: Boolean

  data: ->
    parentEvent: @discussion.createdEvent()
    focalEvent: null
    loader: null
    initialSlots: []
    isReturning: @discussion.lastReadAt?

  mounted: ->
    @loader = new RecordLoader
      collection: 'events'

    @watchRecords
      key: @discussion.id
      collections: ['groups', 'memberships']
      query: =>
        @canAddComment = AbilityService.canAddComment(@discussion)

    @respondToRoute()

  methods:
    respondToRoute: ->
      return if @discussion.key != @$route.params.key
      return if @parentEvent.childCount == 0

      args = if parseInt(@$route.params.comment_id)
        {column: 'commentId', id: parseInt(@$route.params.comment_id), scrollTo: true}
      else if parseInt(@$route.query.p)
        {column: 'position', id: parseInt(@$route.query.p), scrollTo: true}
      else if parseInt(@$route.params.sequence_id)
        {column: 'sequenceId', id: parseInt(@$route.params.sequence_id), scrollTo: true}
      else
        if @discussion.readItemsCount() > 0 && @discussion.unreadItemsCount() > 0
          {column: 'sequenceId', id: @discussion.firstUnreadSequenceId(), scrollTo: true}
        else
          if (@discussion.newestFirst && !@viewportIsBelow) || (!@discussion.newestFirst &&  @viewportIsBelow)
            {column: 'position', id: @parentEvent.childCount}
          else
            {column: 'position', id: 1}

      @fetchEvent(args.column, args.id).then (event) =>
        if event
          @focalEvent = event
          @scrollTo "#sequence-#{@focalEvent.sequenceId}" if args.scrollTo
        else
          Flash.error('thread_context.item_maybe_deleted')

    fetchEvent: (idType, id) ->
      if event = @findEvent(idType, id)
        Promise.resolve(event)
      else
        param = switch idType
          when 'sequenceId' then 'from'
          when 'commentId' then 'comment_id'
          when 'position' then 'from_sequence_id_of_position'

        @loader.fetchRecords(
          discussion_id: @discussion.id
          order: 'sequence_id'
          per: 5
          "#{param}": id
        ).then =>
          # console.log "fetched: #{idType}, #{id}"
          Promise.resolve(@findEvent(idType, id))

    findEvent: (column, id) ->
      return false unless isNumber(id)
      records = Records
      if id == 0
        @discussion.createdEvent()
      else
        args = switch camelCase(column)
          when 'position'
            discussionId: @discussion.id
            position: id
            depth: 1
          when 'sequenceId'
            discussionId: @discussion.id
            sequenceId: id
          when 'commentId'
            kind: 'new_comment'
            eventableId: id
        # console.log "finding: ", args
        Records.events.find(args)[0]

    refocus: ->
      if @focalEvent
        @scrollTo("#sequence-#{@focalEvent.sequenceId}").then =>
          @focalEvent = null

    fetch: (slots, padding) ->
      return unless slots.length
      @loader.fetchRecords(
        comment_id: null
        from: null
        from_unread: null
        discussion_id: @discussion.id
        order: 'sequence_id'
        from_sequence_id_of_position: first(slots)
        until_sequence_id_of_position: last(slots)
        per: padding * 4).then => @refocus()

    openArrangementForm: ->
      ThreadService.actions(@discussion, @)['edit_arrangement'].perform()

  watch:
    '$route.params.sequence_id': 'respondToRoute'
    '$route.params.comment_id': 'respondToRoute'
    '$route.query.p': 'respondToRoute'
    'parentEvent.childCount': (newVal, oldVal) ->
      @respondToRoute() if oldVal == 0 and newVal != 0

  computed:
    canStartPoll: ->
      AbilityService.canStartPoll(@discussion)

    canEditThread: ->
      AbilityService.canEditThread(@discussion)

</script>

<template lang="pug">
.activity-panel
  .text-center.py-2
    v-btn.action-button.grey--text(text small @click="openArrangementForm()" v-if="canEditThread")
      span(v-t="{path: 'activity_card.count_responses', args: {count: parentEvent.childCount}}")
      space
      span(v-if="discussion.newestFirst" v-t="'poll_common_votes_panel.newest_first'")
      span(v-if="!discussion.newestFirst" v-t="'poll_common_votes_panel.oldest_first'")
  thread-renderer(v-if="parentEvent" :newest-first="discussion.newestFirst" :parent-event="parentEvent" :fetch="fetch" :focal-event="focalEvent" :is-returning="isReturning")
</template>

<script lang="coffee">
import Vue from 'vue'
import AppConfig                from '@/shared/services/app_config'
import EventBus                 from '@/shared/services/event_bus'
import RecordLoader             from '@/shared/services/record_loader'
import AbilityService           from '@/shared/services/ability_service'
import Session from '@/shared/services/session'
import Records from '@/shared/services/records'
import Flash   from '@/shared/services/flash'
import { print } from '@/shared/helpers/window'
import ThreadService  from '@/shared/services/thread_service'

import { pickBy, identity, camelCase, first, last, isNumber } from 'lodash'

export default
  components:
    ThreadRenderer: -> import('@/components/thread/renderer.vue')

  props:
    discussion: Object
    viewportIsBelow: Boolean
    viewportIsAbove: Boolean

  data: ->
    parentEvent: @discussion.createdEvent()
    focalEvent: null
    loader: null
    initialSlots: []
    isReturning: @discussion.lastReadAt?

  mounted: ->
    @loader = new RecordLoader
      collection: 'events'

    @watchRecords
      key: @discussion.id
      collections: ['groups', 'memberships']
      query: =>
        @canAddComment = AbilityService.canAddComment(@discussion)

    @respondToRoute()

  methods:
    respondToRoute: ->
      return if @discussion.key != @$route.params.key
      return if @parentEvent.childCount == 0

      args = if parseInt(@$route.params.comment_id)
        {column: 'commentId', id: parseInt(@$route.params.comment_id), scrollTo: true}
      else if parseInt(@$route.query.p)
        {column: 'position', id: parseInt(@$route.query.p), scrollTo: true}
      else if parseInt(@$route.params.sequence_id)
        {column: 'sequenceId', id: parseInt(@$route.params.sequence_id), scrollTo: true}
      else
        if @discussion.readItemsCount() > 0 && @discussion.unreadItemsCount() > 0
          {column: 'sequenceId', id: @discussion.firstUnreadSequenceId(), scrollTo: true}
        else
          if (@discussion.newestFirst && !@viewportIsBelow) || (!@discussion.newestFirst &&  @viewportIsBelow)
            {column: 'position', id: @parentEvent.childCount}
          else
            {column: 'position', id: 1}

      @fetchEvent(args.column, args.id).then (event) =>
        if event
          @focalEvent = event
          if args.scrollTo
            @scrollTo "#sequence-#{event.sequenceId}", =>
              setTimeout =>
                @focalEvent = null
              , 1000
          else
            setTimeout =>
              @focalEvent = null
            , 1000
        else
          Flash.error('thread_context.item_maybe_deleted')

    fetchEvent: (idType, id) ->
      if event = @findEvent(idType, id)
        Promise.resolve(event)
      else
        param = switch idType
          when 'sequenceId' then 'from'
          when 'commentId' then 'comment_id'
          when 'position' then 'from_sequence_id_of_position'

        @loader.fetchRecords(
          discussion_id: @discussion.id
          order: 'sequence_id'
          per: 5
          "#{param}": id
        ).then =>
          # console.log "fetched: #{idType}, #{id}"
          Promise.resolve(@findEvent(idType, id))

    findEvent: (column, id) ->
      return false unless isNumber(id)
      records = Records
      if id == 0
        @discussion.createdEvent()
      else
        args = switch camelCase(column)
          when 'position'
            discussionId: @discussion.id
            position: id
            depth: 1
          when 'sequenceId'
            discussionId: @discussion.id
            sequenceId: id
          when 'commentId'
            kind: 'new_comment'
            eventableId: id
        # console.log "finding: ", args
        Records.events.find(args)[0]

    fetch: (slots, padding) ->
      return unless slots.length
      @loader.fetchRecords
        comment_id: null
        from: null
        from_unread: null
        discussion_id: @discussion.id
        order: 'sequence_id'
        from_sequence_id_of_position: first(slots)
        until_sequence_id_of_position: last(slots)
        per: padding * 4

    openArrangementForm: ->
      ThreadService.actions(@discussion, @)['edit_arrangement'].perform()

  watch:
    '$route.params.sequence_id': 'respondToRoute'
    '$route.params.comment_id': 'respondToRoute'
    '$route.query.p': 'respondToRoute'
    'parentEvent.childCount': (newVal, oldVal) ->
      @respondToRoute() if oldVal == 0 and newVal != 0

  computed:
    canStartPoll: ->
      AbilityService.canStartPoll(@discussion)

    canEditThread: ->
      AbilityService.canEditThread(@discussion)

</script>

<template lang="pug">
.activity-panel
  .text-center.py-2
    v-btn.action-button.grey--text(text small @click="openArrangementForm()" v-if="canEditThread")
      span(v-t="{path: 'activity_card.count_responses', args: {count: parentEvent.childCount}}")
      space
      span(v-if="discussion.newestFirst" v-t="'poll_common_votes_panel.newest_first'")
      span(v-if="!discussion.newestFirst" v-t="'poll_common_votes_panel.oldest_first'")
  thread-renderer(v-if="parentEvent" :newest-first="discussion.newestFirst" :parent-event="parentEvent" :fetch="fetch" :focal-event="focalEvent" :is-returning="isReturning")
</template>


edit text content

edit text content

<template>

 						Get the eBook Sample

 						Read a few chapters for free from the book.
Or Purchase the eBook on Amazon.

 					Get free sample

 					Purchase eBook

        </template>

<template>

 						Get the eBook Sample

 						Read a few chapters for free from the book. Or Purchase the eBook on Amazon Kindle.

 					Get free sample

 					Purchase eBook

        </template>


Update call modal copy

Update call modal copy

<template>

      Calling you now...

Introduce yourself, be polite, and say:

{{ callScript }}

      If lines are busy, we may call you in a few minutes.

Done calling? Do these things, too!

        Join an event near you

        <facebook-button @clicked="$trackEvent('call_script_facebook_button', 'click')">Share on Facebook</facebook-button>
        <twitter-button @clicked="$trackEvent('call_script_twitter_button', 'click')">Share on Twitter</twitter-button>
        <donate-button @clicked="$trackEvent('call_script_donate_button', 'click')">Donate</donate-button>

</template>


Fix searchbar sizing

Fix searchbar sizing

<template>

  <div class="searchbar" role="search">
    <mat-svg class="icon" category="action" name="search" aria-hidden="true"/>
    <mat-svg
      class="clearicon"
      category="content"
      name="clear"
      aria-hidden="true"
      @click="clearSearchTerm()"
    />
    <input
      :aria-label="$tr('searchLabel')"
      :placeholder="$tr('searchPlaceholder')"
      :value="value"
      @input="updateSearchTerm($event.target.value)"
      type="search"
    >
  </div>

</template>

<script>

  export default {
    name: 'searchBar',
    props: {
      value: {
        type: String,
        required: true,
      },
    },
    data() {
      return {
        searchTerm: '',
      };
    },
    methods: {
      updateSearchTerm(text) {
        this.searchTerm = text;
        this.$emit('input', text);
      },
      clearSearchTerm() {
        this.updateSearchTerm('');
      },
    },
    $trs: {
      searchLabel: 'Search for a user',
      searchPlaceholder: 'Search for a user...',
    },
  };

</script>

<style lang="stylus" scoped>

  @require '~kolibri.styles.definitions'

  // copy-pasted from user-page
  input[type='search']
    display: inline-block
    box-sizing: border-box
    position: relative
    top: 0
    left: 10px
    height: 100%
    width: calc(100% - 50px)
    border-color: transparent
    background-color: transparent
    clear: both

  .searchbar .icon
    display: inline-block
    float: left
    position: relative
    fill: $core-text-annotation
    left: 5px
    top: 5px

  .clearicon
    display: inline-block
    float: right
    position: relative
    fill: $core-text-annotation
    top: 5px
    right: 5px
    cursor: pointer

  .searchbar
    border-radius: 5px
    padding: inherit
    border: 1px solid #c0c0c0
    margin-left: 5px
    width: 100%
    height: 2.5em

  @media screen and (min-width: $portrait-breakpoint + 1)
    .searchbar
      font-size: 0.9em

  @media screen and (max-width: 840px)
    .searchbar
      font-size: 0.9em
      width: 100%
      margin-top: 5px

</style>

<template>

  <div class="searchbar" role="search">
    <mat-svg class="icon" category="action" name="search" aria-hidden="true"/>
    <mat-svg
      class="clearicon"
      category="content"
      name="clear"
      aria-hidden="true"
      @click="clearSearchTerm()"
    />
    <input
      :aria-label="$tr('searchLabel')"
      :placeholder="$tr('searchPlaceholder')"
      :value="value"
      @input="updateSearchTerm($event.target.value)"
      type="search"
    >
  </div>

</template>

<script>

  export default {
    name: 'searchBar',
    props: {
      value: {
        type: String,
        required: true,
      },
    },
    data() {
      return {
        searchTerm: '',
      };
    },
    methods: {
      updateSearchTerm(text) {
        this.searchTerm = text;
        this.$emit('input', text);
      },
      clearSearchTerm() {
        this.updateSearchTerm('');
      },
    },
    $trs: {
      searchLabel: 'Search for a user',
      searchPlaceholder: 'Search for a user...',
    },
  };

</script>

<style lang="stylus" scoped>

  @require '~kolibri.styles.definitions'

  // copy-pasted from user-page
  input[type='search']
    display: inline-block
    box-sizing: border-box
    position: relative
    top: 0
    left: 10px
    height: 100%
    width: 85%
    border-color: transparent
    background-color: transparent
    clear: both

  .searchbar .icon
    display: inline-block
    float: left
    position: relative
    fill: $core-text-annotation
    left: 5px
    top: 5px

  .clearicon
    display: inline-block
    float: right
    position: relative
    fill: $core-text-annotation
    top: 5px
    right: 5px
    cursor: pointer

  .searchbar
    border-radius: 5px
    padding: inherit
    border: 1px solid #c0c0c0
    width: 450px
    height: 2.5em
    margin-left: 5px

  @media screen and (min-width: $portrait-breakpoint + 1)
    .searchbar
      font-size: 0.9em
      min-width: 170px
    #search-field
      width: 80%

  @media screen and (max-width: 840px)
    .searchbar
      font-size: 0.9em
      width: 100%
      margin-top: 5px

</style>


Changes in the AllHomeworks component

Changes in the AllHomeworks component

<template>

 0">

              {{homework.subject.title}}

              Deadline

                {{relativeDate(homework.deadline)}}

              Details:

{{homework.details}}

              No details provided.

 Posted by
            <router-link :to="'/profile/' + homework.author.user.id">

                  {{homework.author.user.username}}

            </router-link>

              <button id="info-button" class="btn btn-primary" v-on:click="$router.push(`/homeworks/${homework.id}`)">
                <router-link class="heading-anchor" :to="'/homeworks/' + homework.id">Info</router-link>
              </button>

There are no assigned homeworks so far.

    <button v-if="hasTeacherRights()" v-on:click="$router.push('/homeworks/add')" type="button" class="btn btn-primary btn-circle btn-lg" id="add-homework">

    </button>

</template>

<template>

              {{homework.subject.title}}

              Deadline

                {{homework.deadline}}

              Details:

{{homework.details}}

              No details provided.

 Posted by
            <router-link :to="'#/profile/' + homework.author.user.id">

                  {{homework.author.user.username}}

            </router-link>

              <button id="info-button" class="btn btn-primary" v-on:click="$router.push(`/homeworks/${homework.id}`)">
                <router-link class="heading-anchor" :to="'/homeworks/' + homework.id">Info</router-link>
              </button>

There are no assigned homeworks so far.

    <button v-if="hasTeacherRights()" v-on:click="$router.push('/homeworks/add')" type="button" class="btn btn-primary btn-circle btn-lg" id="add-homework">

    </button>

</template>


Prevent Kee Vault being accidentally closed

When using the popup, users can accidentally sign
 out of their password database if the Kee Vault tab
 is currently active. This commit prevents that from happening.

<template>
  <v-hover>
    <v-card
      :tabindex="`${tabindex}`"
      slot-scope="{ hover }"
      :elevation="`${hover ? 12 : 3}`"
      class="my-2"
      @focusin="focusin"
      @focusout="focusout"
      ref="card"
      @keyup.context-menu.stop.prevent="showFullDetails"
      @keyup.arrow-down.stop.prevent="nextInList"
      @keyup.arrow-up.stop.prevent="prevInList"
      @keyup.arrow-right.stop.prevent="showFullDetails"
      @keyup.arrow-left.stop.prevent="hideFullDetails"
      @keyup.escape.stop.prevent="exitList"
      @keyup.enter.self.stop.prevent="primaryClickAction"
      @keyup.enter.ctrl.self.stop.prevent="loadInNewTab"
    >
      <v-layout
        row
        justify-center
        align-start
        :style="`${hover ? 'cursor: pointer' : ''}`"
        @click.left.exact="primaryClickAction"
        @click.middle.exact="loadInNewTab"
        @click.left.ctrl="loadInNewTab"
        @click.middle.ctrl="primaryClickAction"
        @click.right.stop.prevent="showFullDetails"
      >
        <v-flex class="text-truncate">
          <v-hover>
            <v-card-title :class="`pl-5 ml-2 py-0 mt-3 ${expanded ? 'mb-0' : 'mb-3'} subheading`" :style="titleStyle">
              <v-layout column>
                <v-flex class="text-truncate">{{entry.title}}</v-flex>
                <v-flex
                    :style="`${expanded ? 'visibility:hidden' : ''}`"
                    class="text-truncate caption pr-2 pt-1 py-1"
                >{{usernameDisplayValue}}</v-flex>
              </v-layout>
            </v-card-title>
          </v-hover>
        </v-flex>
        <v-flex shrink class="my-0" v-if="hover || focussed">
          <v-btn icon @click.stop.prevent="toggleFullDetails">
            <v-icon>{{ expanded ? 'keyboard_arrow_down' : 'keyboard_arrow_up' }}</v-icon>
          </v-btn>
        </v-flex>
      </v-layout>
      <v-card-text class="py-0 text-truncate">
        <v-slide-y-transition>

            <Field v-for="f of allFields" :key="f.value" :field="f"/>

            <v-layout row justify-space-between align-center>
              <v-flex class="text-truncate">
                <v-tooltip top :disabled="!entryPathIsLong">
                  <v-layout row justify-left align-center slot="activator">
                    <v-icon small class="py-1 pl-0 pr-2">folder</v-icon>
                    {{entryPath}}
                  </v-layout>
                  {{fullEntryPath}}
                </v-tooltip>
                <v-tooltip top>
                  <v-layout row justify-left align-center slot="activator">
                    <v-icon small class="py-1 pl-0 pr-2">cloud</v-icon>
                    {{entryDomain}}
                  </v-layout>
                  {{entry.url}}
                </v-tooltip>
              </v-flex>

              <v-flex shrink class="ma-2">
                <v-btn fab small left @click="editEntry">
                  <v-icon>edit</v-icon>
                </v-btn>
              </v-flex>
            </v-layout>

        </v-slide-y-transition>
      </v-card-text>
    </v-card>
  </v-hover>
</template>


Signed-off-by: DawnCHL <chen_huanlu@163.com>

<template>

{{item.apiName}}

{{url}}

{{item.respond}}

</template>

<template>

{{item.apiName}}

</template>