Comment out wip methods

Comment out wip methods

<template>

      <BackLink
        :to="classRoute('ReportsLessonListPage')"
        :text="$tr('back')"
      />

    <KGrid>
      <KGridItem sizes="100, 50, 50" percentage>

          <KLabeledIcon>
            <KIcon slot="icon" lesson />
            {{ lesson.title }}
          </KLabeledIcon>

      </KGridItem>
      <KGridItem sizes="100, 50, 50" percentage alignment="right">

      </KGridItem>
    </KGrid>

    <HeaderTable>
      <HeaderTableRow>
        <template slot="key">
          {{ coachStrings.$tr('statusLabel') }}
        </template>
        <template slot="value">
          <LessonActive :active="lesson.active" />
        </template>
      </HeaderTableRow>
      <HeaderTableRow>
        <template slot="key">
          {{ coachStrings.$tr('recipientsLabel') }}
        </template>
        <template slot="value">
          <Recipients :groupNames="getGroupNames(lesson.groups)" />
        </template>
      </HeaderTableRow>

    </HeaderTable>

    <HeaderTabs>

      <HeaderTab
        :text="coachStrings.$tr('reportLabel')"
        :to="classRoute('ReportsLessonReportPage', {})"
      />
      <HeaderTab
        :text="coachStrings.$tr('learnersLabel')"
        :to="classRoute('ReportsLessonLearnerListPage', {})"
      />
    </HeaderTabs>

</template>


code style

code style

<template>

    <content-icon
      v-if="!thumbnail"
      :kind="kind"
      class="thumbnail-icon"
    />

    <progress-icon
      v-if="progress > 0"
      class="progress-icon"
      :progress="progress"
    />

      <svg
        height="64"
        width="64"
        viewBox="0 0 64 64"
        class="content-icon-bg"
        :style="contentIconBgColor"
      >
        <polygon stroke-width="0" points="0,0 60,0 0,60" />
      </svg>
      <content-icon :kind="kind" class="content-icon"/>

      <div
        class="progress-bar"
        :style="{ width: `${progress * 100}%` }"
        :class="{ 'progress-bar-mastered': isMastered, 'progress-bar-progress': isInProgress }">

</template>

<template>

    <content-icon
      v-if="!thumbnail"
      :kind="kind"
      class="thumbnail-icon"
    />

    <progress-icon
      v-if="progress > 0"
      class="progress-icon"
      :progress="progress"
    />

      <svg
        height="64"
        width="64"
        viewBox="0 0 64 64"
        class="content-icon-bg"
        :style="contentIconBgColor"
      >
        <polygon stroke-width="0" points="0,0 60,0 0,60"/>
      </svg>
      <content-icon :kind="kind" class="content-icon"/>

      <div
        class="progress-bar"
        :style="{ width: `${progress * 100}%` }"
        :class="{ 'progress-bar-mastered': isMastered, 'progress-bar-progress': isInProgress }">

</template>


the menu bar starts collapsed on mobile/tablet

<template>

		<nprogress></nprogress>
		<navbar class="animated slideInDown"
	        :env-is-local="meta.env === 'local'">
	    </navbar>
	    <sidebar class="animated"
	        :class="{ 'slideInLeft': navbar.isVisible, 'slideOutLeft': !navbar.isVisible }">
	    </sidebar>

	        	<page-header :title="__($route.meta.title)"></page-header>
		        <router></router>

	    <settings class="animated"
	    	:class="{ 'slideInRight': settingsBar.isVisible, 'slideOutRight': !settingsBar.isVisible }">
	    </settings>
	    <app-footer class="animated slideInUp">
	    </app-footer>

</template>


Show recenter button only if not centered

<template>

    <canvas id="deck-canvas"></canvas>

      <BasicLegend
        v-if="isGeography"
        :title="variableName"
      />
      <ColorBinsLegend
        v-else-if="variableMax"
        :title="variableName"
        :min="variableMin"
        :max="variableMax"
        :avg="variableAvg"
        :bins="variableBins"
      />
      <ColorCategoriesLegend
        v-else-if="variableCategories"
        :title="variableName"
        :categories="variableCategories"
      />

{{ variableDescription }}

Recenter

</template>


no message

no message

<template>

          㒄

  			购物车

  			<search-nav></search-nav>

      <router-link to="/myself" class="login">登陆后可同步电脑与手机购物车中的商品</router-link>

      <router-link to="/myself" class="r iconfont"></router-link>

购物车快饿扁了／(ToT)／～～

主人快点给我挑点宝贝吧

      <router-link to="/shopList">去逛逛～走你</router-link>

            <input type="checkbox" checked ref="checkshop" :value="item.shopcarid" class="checkshop"/>

{{item.shopname}}

闪

              ￥{{item.price}}

                      -
                      {{item.quantity}}
                      +

      <input type="checkbox" checked ref="checkall" @click="checkall"/>全选

        合计：

        <button class="r" @touchend="apply">去结算</button>

        ￥{{totle}}

    <bottom-nav></bottom-nav>
    <router-link to="/shopList" class="l" tag="p">请选购</router-link>

</template>

<template>

          㒄

  			购物车

  			<search-nav></search-nav>

      <router-link to="/myself" class="login">登陆后可同步电脑与手机购物车中的商品</router-link>

      <router-link to="/myself" class="r iconfont"></router-link>

购物车快饿扁了／(ToT)／～～

主人快点给我挑点宝贝吧

      <router-link to="/shopList">去逛逛～走你</router-link>

            <input type="checkbox" checked v-model="$store.state.isChecked" ref="checkshop" :value="item.shopcarid" class="checkshop"/>

{{item.shopname}}

闪

              ￥{{item.price}}

                      -
                      {{item.quantity}}
                      +

      <input type="checkbox" checked v-model="$store.state.isChecked" ref="checkall" />全选

        合计：

        <button class="r" @touchend="apply">去结算</button>

        ￥{{totle}}

    <bottom-nav></bottom-nav>
    <router-link to="/shopList" class="l" tag="p">请选购</router-link>

</template>


change to pug

change to pug

<template lang="pug">
  v-app(light)
    v-toolbar(dark)
      v-toolbar-title cinderella
      v-spacer
      v-toolbar-items(v-for="item in items" v-bind:key="item.text")
        v-btn(:to="'/' + item.text" flat) {{ item.text }}
    main
    v-container(fluid)
      div#app
        router-view
    v-footer
</template>

<script>

  export default {
    name: 'app',
    data: () => ({
      items: [
        { text: 'top' },
        { text: 'songs' },
        { text: 'idol' },
      ],
    }),
  };
</script>

<style>
  /* material-iconsがうまくあたらないのでワークアラウンド*/
  /* @see: https://github.com/FountainJS/generator-fountain-webapp/issues/109*/
  /* fallback */
  @font-face {
    font-family: 'Material Icons';
    font-style: normal;
    font-weight: 400;
    src: local('Material Icons'), local('MaterialIcons-Regular'), url(https://fonts.gstatic.com/s/materialicons/v17/2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2) format('woff2');
  }

  .material-icons {
    font-family: 'Material Icons';
    font-weight: normal;
    font-style: normal;
    font-size: 24px;
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    display: inline-block;
    white-space: nowrap;
    word-wrap: normal;
    direction: ltr;
    -webkit-font-feature-settings: 'liga';
    -webkit-font-smoothing: antialiased;
  }
</style>

<template>
  <v-app light>
    <v-toolbar dark>
      <v-toolbar-title>cinderella</v-toolbar-title>
      <v-spacer></v-spacer>
      <v-toolbar-items v-for="item in items" :key="item.text">
        <v-btn :to="'/' + item.text" flat>{{ item.text }}</v-btn>
      </v-toolbar-items>
    </v-toolbar>
    <main>
      <v-container fluid>
        <div id="app">
          <router-view></router-view>
        </div>
      </v-container>
    </main>
    <v-footer></v-footer>
  </v-app>
</template>

<script>

  export default {
    name: 'app',
    data: () => ({
      items: [
        { text: 'top' },
        { text: 'songs' },
        { text: 'idol' },
      ],
    }),
  };
</script>

<style>
  /* material-iconsがうまくあたらないのでワークアラウンド*/
  /* @see: https://github.com/FountainJS/generator-fountain-webapp/issues/109*/
  /* fallback */
  @font-face {
    font-family: 'Material Icons';
    font-style: normal;
    font-weight: 400;
    src: local('Material Icons'), local('MaterialIcons-Regular'), url(https://fonts.gstatic.com/s/materialicons/v17/2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2) format('woff2');
  }

  .material-icons {
    font-family: 'Material Icons';
    font-weight: normal;
    font-style: normal;
    font-size: 24px;
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    display: inline-block;
    white-space: nowrap;
    word-wrap: normal;
    direction: ltr;
    -webkit-font-feature-settings: 'liga';
    -webkit-font-smoothing: antialiased;
  }
</style>


Fix vertical alignment of icon.

Fix vertical alignment of icon.

<template>

  <CoreFullscreen
    ref="html5Renderer"
    class="html5-renderer"
    @changeFullscreen="isInFullscreen = $event"
  >
    <KButton
      :primary="false"
      class="fullscreen-btn"
      @click="$refs.html5Renderer.toggleFullscreen()"
    >
      <mat-svg v-if="isInFullscreen" name="fullscreen_exit" category="navigation" />
      <mat-svg v-else name="fullscreen" category="navigation" />
      {{ fullscreenText }}
    </KButton>
    <div class="iframe-container">
      <iframe
        ref="iframe"
        class="iframe"
        :style="{ backgroundColor: $themePalette.grey.v_100 }"
        :sandbox="sandbox"
        frameBorder="0"
        :name="name"
        :src="rooturl"
      >
      </iframe>
    </div>
  </CoreFullscreen>

</template>

<script>

  import { now } from 'kolibri.utils.serverClock';
  import CoreFullscreen from 'kolibri.coreVue.components.CoreFullscreen';
  import Hashi from 'hashi';
  import { nameSpace } from 'hashi/src/hashiBase';
  import plugin_data from 'plugin_data';

  export default {
    name: 'Html5AppRendererIndex',
    components: {
      CoreFullscreen,
    },
    data() {
      return {
        isInFullscreen: false,
      };
    },
    computed: {
      name() {
        return nameSpace;
      },
      rooturl() {
        return this.defaultFile.storage_url;
      },
      sandbox() {
        return plugin_data.html5_sandbox_tokens;
      },
      fullscreenText() {
        return this.isInFullscreen ? this.$tr('exitFullscreen') : this.$tr('enterFullscreen');
      },
    },
    mounted() {
      this.hashi = new Hashi({ iframe: this.$refs.iframe, now });
      this.hashi.onStateUpdate(data => {
        this.$emit('updateContentState', data);
      });
      this.hashi.initialize((this.extraFields && this.extraFields.contentState) || {});
      this.$emit('startTracking');
      this.startTime = now();
      this.pollProgress();
    },
    beforeDestroy() {
      if (this.timeout) {
        clearTimeout(this.timeout);
      }
      this.$emit('stopTracking');
    },
    methods: {
      recordProgress() {
        const totalTime = now() - this.startTime;
        this.$emit('updateProgress', Math.max(0, totalTime / 300000));
        this.pollProgress();
      },
      pollProgress() {
        this.timeout = setTimeout(() => {
          this.recordProgress();
        }, 15000);
      },
    },
    $trs: {
      exitFullscreen: 'Exit Fullscreen',
      enterFullscreen: 'View Fullscreen',
    },
  };

</script>

<style lang="scss" scoped>

  @import '~kolibri.styles.definitions';

  .fullscreen-btn {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    width: 100%;
    margin: 0;
    text-align: right;
    box-shadow: none;
  }

  .html5-renderer {
    position: relative;
    height: 500px;
    text-align: center;
  }

  .iframe {
    width: 100%;
    height: 100%;
  }

  .iframe-container {
    @extend %momentum-scroll;

    top: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
  }

</style>

<template>

  <CoreFullscreen
    ref="html5Renderer"
    class="html5-renderer"
    @changeFullscreen="isInFullscreen = $event"
  >
    <KButton
      :primary="false"
      class="fullscreen-btn"
      @click="$refs.html5Renderer.toggleFullscreen()"
    >
      <!--
        FIXME: Restore these once I find the right way to vertially center the SVG icon
        <mat-svg v-if="isInFullscreen" name="fullscreen_exit" category="navigation" />
        <mat-svg v-else name="fullscreen" category="navigation" />
      -->
      {{ fullscreenText }}
    </KButton>
    <div class="iframe-container">
      <iframe
        ref="iframe"
        class="iframe"
        :style="{ backgroundColor: $themePalette.grey.v_100 }"
        :sandbox="sandbox"
        frameBorder="0"
        :name="name"
        :src="rooturl"
      >
      </iframe>
    </div>
  </CoreFullscreen>

</template>

<script>

  import { now } from 'kolibri.utils.serverClock';
  import CoreFullscreen from 'kolibri.coreVue.components.CoreFullscreen';
  import Hashi from 'hashi';
  import { nameSpace } from 'hashi/src/hashiBase';
  import plugin_data from 'plugin_data';

  export default {
    name: 'Html5AppRendererIndex',
    components: {
      CoreFullscreen,
    },
    data() {
      return {
        isInFullscreen: false,
      };
    },
    computed: {
      name() {
        return nameSpace;
      },
      rooturl() {
        return this.defaultFile.storage_url;
      },
      sandbox() {
        return plugin_data.html5_sandbox_tokens;
      },
      fullscreenText() {
        return this.isInFullscreen ? this.$tr('exitFullscreen') : this.$tr('enterFullscreen');
      },
    },
    mounted() {
      this.hashi = new Hashi({ iframe: this.$refs.iframe, now });
      this.hashi.onStateUpdate(data => {
        this.$emit('updateContentState', data);
      });
      this.hashi.initialize((this.extraFields && this.extraFields.contentState) || {});
      this.$emit('startTracking');
      this.startTime = now();
      this.pollProgress();
    },
    beforeDestroy() {
      if (this.timeout) {
        clearTimeout(this.timeout);
      }
      this.$emit('stopTracking');
    },
    methods: {
      recordProgress() {
        const totalTime = now() - this.startTime;
        this.$emit('updateProgress', Math.max(0, totalTime / 300000));
        this.pollProgress();
      },
      pollProgress() {
        this.timeout = setTimeout(() => {
          this.recordProgress();
        }, 15000);
      },
    },
    $trs: {
      exitFullscreen: 'Exit Fullscreen',
      enterFullscreen: 'View Fullscreen',
    },
  };

</script>

<style lang="scss" scoped>

  @import '~kolibri.styles.definitions';

  .fullscreen-btn {
    width: 100%;
    margin: 0;
    text-align: right;
    box-shadow: none;
  }

  .html5-renderer {
    position: relative;
    height: 500px;
    text-align: center;
  }

  .iframe {
    width: 100%;
    height: 100%;
  }

  .iframe-container {
    @extend %momentum-scroll;

    top: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    overflow: visible;
  }

</style>


docs(Migration): fix label and select item

<template>
  <v-layout wrap mb-12>
    <doc-subheading>releaseHeader</doc-subheading>
    <v-flex
      xs12
      mb-12
    >
      <v-autocomplete
        v-model="releaseNotes"
        :items="releases"
        label="Select Release Version"
        item-text="name"
        solo
        prepend-inner-icon="mdi-database-search"
        clearable
        chips
        return-object
      >
        <template v-slot:selection="props">
          <v-chip
            :value="props.selected"
            color="primary"
            class="white--text"
            label
          >
            <v-icon left>
              mdi-tag
            </v-icon>
            <span v-text="props.item.name" />
          </v-chip>
        </template>
        <template v-slot:item="props">
          <v-list-item-action>
            <v-icon>mdi-tag</v-icon>
          </v-list-item-action>
          <v-list-item-content>
            <v-list-item-title
              :id="props.attrs['aria-labelledby']"
              v-text="props.item.name"
            />
            <v-list-item-subtitle v-text="`Published: ${new Date(props.item.published_at).toDateString()}`" />
          </v-list-item-content>
        </template>
      </v-autocomplete>
      <doc-markdown :code="releaseNotes ? releaseNotes.body : ' '" />
    </v-flex>

    <v-flex>
      <doc-subheading>migrationHeader</doc-subheading>
      <doc-markdown class="migration-markdown" :code="migration || ' '" />
    </v-flex>
  </v-layout>

</template>

<script>
  import { getBranch } from '@/util/helpers'
  // Utilities
  import { mapState } from 'vuex'

  export default {
    data: () => ({
      migration: undefined,
      branch: undefined,
      githubReleases: [],
      releaseNotes: undefined,
    }),

    computed: {
      ...mapState('app', ['currentVersion']),
      releases () {
        const v1 = this.githubReleases.filter(release => release.name && release.name.substring(0, 3) === 'v1.')
        const v2 = this.githubReleases.filter(release => release.name && release.name.substring(0, 3) === 'v2.')
        if (v1.length > 0) {
          v1.unshift({ header: 'v1.x' })
        }
        if (v2.length > 0) {
          v2.unshift({ header: 'v2.x' })
        }
        return v2.concat(v1) || []
      },
    },

    mounted () {
      this.branch = getBranch()
      fetch(`https://api.github.com/repos/vuetifyjs/vuetify/contents/MIGRATION.md?ref=${this.branch}`, {
        method: 'get',
        headers: { 'Content-Type': 'application/json' },
      })
        .then(res => res.json())
        .then(res => {
          this.migration = res.content ? Buffer.from(res.content, 'base64').toString() : ' '
        })
        .catch(err => console.log(err))

      fetch(`https://api.github.com/repos/vuetifyjs/vuetify/releases?per_page=100`, {
        method: 'get',
        headers: { 'Content-Type': 'application/json' },
      })
        .then(res => res.json())
        .then(res => {
          this.githubReleases = res
          this.releaseNotes = res.find(release => release.name === `v${this.currentVersion}`)
        })
        .catch(err => console.log(err))
    },
  }
</script>
<style lang="sass" scoped>
  ::v-deep pre
    background: #2d2d2d !important
    padding: 8px
    margin: 8px

    code
      box-shadow: none !important
      background-color: unset !important
      color: #ccc !important

  ::v-deep .migration-markdown
    h4, h3, p, pre, ul
      margin-bottom: 16px !important
</style>

<template>
  <v-layout wrap mb-12>
    <doc-subheading>releaseHeader</doc-subheading>
    <v-flex
      xs12
      mb-12
    >
      <v-autocomplete
        v-model="releaseNotes"
        :items="releases"
        label="Select Label"
        item-text="name"
        solo
        prepend-inner-icon="mdi-database-search"
        clearable
        chips
        return-object
      >
        <template v-slot:selection="props">
          <v-chip
            :value="props.selected"
            color="primary"
            class="white--text"
            label
          >
            <v-icon left>
              mdi-tag
            </v-icon>
            <span v-text="props.item.name" />
          </v-chip>
        </template>
        <template v-slot:item="props">
          <v-list-item-action>
            <v-icon>mdi-tag</v-icon>
          </v-list-item-action>
          <v-list-item-content>
            <v-list-item-title
              :id="props.attrs['aria-labelledby']"
              v-text="props.item.name"
            />
            <v-list-item-subtitle v-text="`published: ${new Date(props.item.published_at).toDateString()}`" />
          </v-list-item-content>
        </template>
      </v-autocomplete>
      <doc-markdown :code="releaseNotes ? releaseNotes.body : ' '" />
    </v-flex>

    <v-flex>
      <doc-subheading>migrationHeader</doc-subheading>
      <doc-markdown class="migration-markdown" :code="migration || ' '" />
    </v-flex>
  </v-layout>

</template>

<script>
  import { getBranch } from '@/util/helpers'
  // Utilities
  import { mapState } from 'vuex'

  export default {
    data: () => ({
      migration: undefined,
      branch: undefined,
      githubReleases: [],
      releaseNotes: undefined,
    }),

    computed: {
      ...mapState('app', ['currentVersion']),
      releases () {
        const v1 = this.githubReleases.filter(release => release.name && release.name.substring(0, 3) === 'v1.')
        const v2 = this.githubReleases.filter(release => release.name && release.name.substring(0, 3) === 'v2.')
        if (v1.length > 0) {
          v1.unshift({ header: 'v1.x' })
        }
        if (v2.length > 0) {
          v2.unshift({ header: 'v2.x' })
        }
        return v2.concat(v1) || []
      },
    },

    mounted () {
      this.branch = getBranch()
      fetch(`https://api.github.com/repos/vuetifyjs/vuetify/contents/MIGRATION.md?ref=${this.branch}`, {
        method: 'get',
        headers: { 'Content-Type': 'application/json' },
      })
        .then(res => res.json())
        .then(res => {
          this.migration = res.content ? Buffer.from(res.content, 'base64').toString() : ' '
        })
        .catch(err => console.log(err))

      fetch(`https://api.github.com/repos/vuetifyjs/vuetify/releases?per_page=100`, {
        method: 'get',
        headers: { 'Content-Type': 'application/json' },
      })
        .then(res => res.json())
        .then(res => {
          this.githubReleases = res
          this.releaseNotes = res.find(release => release.name === `v${this.currentVersion}`)
        })
        .catch(err => console.log(err))
    },
  }
</script>
<style lang="sass" scoped>
  ::v-deep pre
    background: #2d2d2d !important
    padding: 8px
    margin: 8px

    code
      box-shadow: none !important
      background-color: unset !important
      color: #ccc !important

  ::v-deep .migration-markdown
    h4, h3, p, pre, ul
      margin-bottom: 16px !important
</style>


Text size scaling more generic on cinematic chalkboard (#628)

Bold and uncolored text now scales with the colored text.

<template>

    <div
      v-if="displayed"
      id="chalkboard-modal"
      :style="{
        width: `${chalkboardWidth}%`,
        height: `${chalkboardHeight}%`,
        transform: `translateX(${xOffset}%) translateY(${yOffset}%)`,
        transition: `transform ${transitionTime}s`
      }"
    >

</template>


Add dismiss button

Add dismiss button

Signed-off-by: Marco Ambrosini <3397c7e210a9d1588196cfebb12b2c6b133f6934@icloud.com>

<template>

				<PollIcon :size="20" />

					{{ pollName }}

				{{ t('spreed', 'Poll ・ Click to vote') }}

		<NcModal v-if="vote !== undefined && showModal"
			size="small"
			@close="showModal = false">

				<template v-if="modalPage === 'voting'">

						<PollIcon :size="20" />

							{{ pollName }}

						<template v-if="checkboxRadioSwitchType === 'radio'">
							<NcCheckboxRadioSwitch v-for="(option, index) in options"
								:key="'radio' + index"
								:checked.sync="vote"
								class="poll__option"
								:value="index.toString()"
								:type="checkboxRadioSwitchType"
								name="answerType">
								{{ option }}
							</NcCheckboxRadioSwitch>
						</template>
						<template v-else>
							<NcCheckboxRadioSwitch v-for="(option, index) in options"
								:key="'checkbox' + index"
								:checked.sync="vote"
								:value="index.toString()"
								:type="checkboxRadioSwitchType"
								name="answerType">
								{{ option }}
							</NcCheckboxRadioSwitch>
						</template>

						<NcButton type="tertiary" @click="dismissModal">
							{{ t('spreed', 'Dismiss') }}
						</NcButton>

						<NcButton type="primary" :disabled="!canSubmitVote" @click="submitVote">
							{{ t('spreed', 'Submit') }}
						</NcButton>

				</template>

				<template v-if="modalPage === 'results'">

						<PollIcon :size="20" />

							{{ pollName }}

						{{ n('spreed', 'Poll results • %n vote', 'Poll results • %n votes', votersNumber) }}

									{{ option }}

									{{ getVotePercentage(index) + '%' }}

							<NcProgressBar :value="getVotePercentage(index)" size="medium" />

						<NcButton type="secondary"
							@click="dismissModal">
							{{ t('spreed', 'Dismiss') }}
						</NcButton>
							@click="modalPage = 'voting'">
							{{ t('spreed', 'Back') }}
						</NcButton>

						<NcButton v-if="canClosePoll"
							type="error"
							@click="closePoll">
							{{ t('spreed', 'Close poll') }}
						</NcButton>

				</template>

		</NcModal>

</template>

<template>

				<PollIcon :size="20" />

					{{ pollName }}

				{{ t('spreed', 'Poll ・ Click to vote') }}

		<NcModal v-if="vote !== undefined && showModal"
			size="small"
			@close="showModal = false">

				<template v-if="modalPage === 'voting'">

						<PollIcon :size="20" />

							{{ pollName }}

						<template v-if="checkboxRadioSwitchType === 'radio'">
							<NcCheckboxRadioSwitch v-for="(option, index) in options"
								:key="'radio' + index"
								:checked.sync="vote"
								class="poll__option"
								:value="index.toString()"
								:type="checkboxRadioSwitchType"
								name="answerType">
								{{ option }}
							</NcCheckboxRadioSwitch>
						</template>
						<template v-else>
							<NcCheckboxRadioSwitch v-for="(option, index) in options"
								:key="'checkbox' + index"
								:checked.sync="vote"
								:value="index.toString()"
								:type="checkboxRadioSwitchType"
								name="answerType">
								{{ option }}
							</NcCheckboxRadioSwitch>
						</template>

						<NcButton type="tertiary" @click="dismissModal">
							{{ t('spreed', 'Dismiss') }}
						</NcButton>

						<NcButton type="primary" :disabled="!canSubmitVote" @click="submitVote">
							{{ t('spreed', 'Submit') }}
						</NcButton>

				</template>

				<template v-if="modalPage === 'results'">

						<PollIcon :size="20" />

							{{ pollName }}

						{{ n('spreed', 'Poll results • %n vote', 'Poll results • %n votes', votersNumber) }}

									{{ option }}

									{{ getVotePercentage(index) + '%' }}

							<NcProgressBar :value="getVotePercentage(index)" size="medium" />

						<NcButton type="tertiary"
							@click="modalPage = 'voting'">
							{{ t('spreed', 'Back') }}
						</NcButton>

						<NcButton v-if="canClosePoll"
							type="error"
							@click="closePoll">
							{{ t('spreed', 'Close poll') }}
						</NcButton>

				</template>

		</NcModal>

</template>


Make chart full width

Make chart full width

<template>

Uitgaves

{{title}}

                <stats-chart v-bind:stats="stats"></stats-chart>

                <expenditures-table v-bind:expenditures="expenditures"
                                    v-on:expenditure-deleted="expenditureDeleted" />

</template>

<template>

Uitgaves

{{title}}

                <expenditures-table v-bind:expenditures="expenditures"
                                    v-on:expenditure-deleted="expenditureDeleted" />

                <stats-chart v-bind:stats="stats"></stats-chart>

</template>


debounce move comments search

debounce move comments search

<script lang="coffee">
import Records from '@/shared/services/records'
import Session from '@/shared/services/session'
import AbilityService from '@/shared/services/ability_service'
import { submitDiscussion } from '@/shared/helpers/form'
import { orderBy, debounce } from 'lodash'

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

  props:
    discussion: Object
    close: Function

  methods:
    setSubmit: ->
      @selectedDiscussion.update(isForking: true)
      @selectedDiscussion.update(forkedEventIds: @discussion.forkedEventIds)
      @submit = submitDiscussion @, @selectedDiscussion,
        successCallback: (data) =>
          @discussion.update(forkedEventIds: [])
          @discussion.update(isForking: false)
          @selectedDiscussion.update(forkedEventIds: [])
          @selectedDiscussion.update(isForking: false)
          @close()
          @$router.push @urlFor(@selectedDiscussion)
    fetch: debounce ->
      return unless @searchFragment.length
      Records.discussions.search(@groupId, @searchFragment).then (data) =>
        @searchResults = Records.discussions.collection.chain()
          .find(groupId: @groupId)
          .find({ id: { $ne: @discussion.id } })
          .find(title: { $regex: [@searchFragment, 'i'] })
          .where((d) -> AbilityService.canMoveThread(d))
          .data()
    , 500

  watch:
    selectedDiscussion: 'setSubmit'
    searchFragment: 'fetch'

</script>
<template lang="pug">
v-card
  submit-overlay(:value='discussion.processing')
  v-card-title
    h1.headline(v-t="'action_dock.move_comments'")
    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')")
  v-card-actions
    v-spacer
    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 { submitDiscussion } from '@/shared/helpers/form'
import { orderBy } from 'lodash'

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

  props:
    discussion: Object
    close: Function

  methods:
    setSubmit: ->
      @selectedDiscussion.update(isForking: true)
      @selectedDiscussion.update(forkedEventIds: @discussion.forkedEventIds)
      @submit = submitDiscussion @, @selectedDiscussion,
        successCallback: (data) =>
          @discussion.update(forkedEventIds: [])
          @discussion.update(isForking: false)
          @selectedDiscussion.update(forkedEventIds: [])
          @selectedDiscussion.update(isForking: false)
          @close()
          @$router.push @urlFor(@selectedDiscussion)

  watch:
    selectedDiscussion: 'setSubmit'
    searchFragment: (fragment) ->
      return unless fragment.length
      Records.discussions.search(@groupId, fragment).then (data) =>
        @searchResults = Records.discussions.collection.chain()
          .find(groupId: @groupId)
          .find({ id: { $ne: @discussion.id } })
          .find(title: { $regex: [fragment, 'i'] })
          .where((d) -> AbilityService.canMoveThread(d))
          .data()

</script>
<template lang="pug">
v-card
  submit-overlay(:value='discussion.processing')
  v-card-title
    h1.headline(v-t="'action_dock.move_comments'")
    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')")
  v-card-actions
    v-spacer
    v-btn(color="primary" @click="submit()" v-t="'common.action.save'" :disabled="!selectedDiscussion")
</template>


Make details better resemble the Custom Fields section

<template>

      Details

          Status:

          <status-pill :status="ticket.status" />

          Reporter:

          <user-stub :username="ticket.reporter" />

          Assignee:

          <user-stub :username="ticket.assignee" />

          Created:

          {{ dateFormat(ticket.createdDate) }}

          Updated:

          {{ dateFormat(ticket.updatedDate) }}

          Type:

          {{ ticket.type }}

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

            Labels:

              <router-link :to="{ path: '/queries',
                                query: { q: labelQuery(label)} }">
                {{ label }}
              </router-link>

        </template>

</template>

<template>

      Details

          Status:

        <status-pill :status="ticket.status" />

          Reporter:
          <user-stub :username="ticket.reporter" />

          Assignee:
          <user-stub :username="ticket.assignee" />

          Created:
          {{ dateFormat(ticket.createdDate) }}

          Updated:
          {{ dateFormat(ticket.updatedDate) }}

          Type:
          {{ ticket.type }}

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

            Labels:

            {{ label }}

        </template>

</template>


Fix calc bug

Fix calc bug

<template lang="html">
  <drag-helper target-selector=".range-slider-controller" @drag="drag" @dragend="dragEnd">

      <input class="range-slider-hidden" type="text" :name="name" :value="actualValue">

  </drag-helper>
</template>


Fixed the condition to display images in the Album cards.

Refs #78.

<template>

		<v-card v-if="loading" outlined class="c-AlbumCard--loading">
			<v-card-title />
			<v-card-text>

			</v-card-text>
		</v-card>
		<v-card v-else outlined class="c-AlbumCard">
			<router-link :to="`/albums/${album.id}/view`" class="c-AlbumCard__albumLink">
				<v-img
					v-if="album.cover.id"
					:src="`${baseImageUrl}?w=400`"
					:srcset="`
							${baseImageUrl}?w=400 400w,
							${baseImageUrl}?w=700 700w`"
					:eager="eagerCovers"
					aspect-ratio="3"
					gradient="to top, rgba(0, 0, 0, 0.8) 0%, transparent 72px"
				>
					<v-row align="end" class="fill-height px-4	">
						<v-col class="title">
							{{ album.identifyingName }}
						</v-col>
					</v-row>
				</v-img>
				<v-card-title v-else>
					{{ album.identifyingName }}
				</v-card-title>
			</router-link>
			<v-card-text>
				<v-alert
					v-if="album.wishlist" color="primary" border="left"
					icon="mdi-gift" text dense
				>
					{{ $t('field.Album.wishlist.value.true') }}
				</v-alert>
				<template v-if="album.firstEditionDate && album.firstEditionDate === album.currentEditionDate">
					<FieldValue :label="$t('field.Album.firstEditionDate')">
						{{ $d(new Date(album.firstEditionDate), 'long') }}
						<template v-if="album.markedAsFirstEdition">
							{{ $t('field.Album.markedAsFirstEdition.view') }}
						</template>
						<template v-else>
							{{ $t('field.Album.isFirstEdition') }}
						</template>
					</FieldValue>
				</template>
				<template v-else>
					<FieldValue v-if="album.firstEditionDate" :label="$t('field.Album.firstEditionDate')">
						{{ $d(new Date(album.firstEditionDate), 'long') }}
					</FieldValue>
					<FieldValue v-if="album.currentEditionDate" :label="$t('field.Album.currentEditionDate')">
						{{ $d(new Date(album.currentEditionDate), 'long') }}
					</FieldValue>
				</template>
				<FieldValue
					v-if="album.writers"
					:label="$tc('field.Album.writers', album.writers ? album.writers.length : 0)"
				>
					<ModelLink :model="album.writers" view="AuthorView" />
				</FieldValue>
				<FieldValue
					v-if="album.artists"
					:label="$tc('field.Album.artists', album.artists ? album.artists.length : 0)"
				>
					<ModelLink :model="album.artists" view="AuthorView" />
				</FieldValue>
				<v-fade-transition>

						<FieldValue
							v-if="album.colorists"
							:label="$tc('field.Album.colorists', album.colorists ? album.colorists.length : 0)"
						>
							<ModelLink :model="album.colorists" view="AuthorView" />
						</FieldValue>
						<FieldValue
							v-if="album.inkers"
							:label="$tc('field.Album.inkers', album.inkers ? album.inkers.length : 0)"
						>
							<ModelLink :model="album.inkers" view="AuthorView" />
						</FieldValue>
						<FieldValue
							v-if="album.translators"
							:label="$tc('field.Album.translators', album.translators ? album.translators.length : 0)"
						>
							<ModelLink :model="album.translators" view="AuthorView" />
						</FieldValue>
						<FieldValue v-if="album.publisher" :label="$t('field.Album.publisher')">
							<ModelLink :model="album.publisher" view="PublisherView" />
						</FieldValue>
						<FieldValue v-if="album.collection" :label="$t('field.Album.collection')">
							<ModelLink :model="album.collection" view="CollectionView" />
						</FieldValue>
						<FieldValue v-if="album.tags" :label="$t('field.Album.tags')">
							<TagLink :model="album.tags" />
						</FieldValue>
						<FieldValue v-if="album.binding" :label="$t('field.Album.binding')">
							<ModelLink :model="album.binding" view="BindingView" />
						</FieldValue>
						<FieldValue v-if="album.acquisitionDate" :label="$t('field.Album.acquisitionDate')">
							{{ $d(new Date(album.acquisitionDate), 'long') }}
						</FieldValue>

				</v-fade-transition>
			</v-card-text>
			<v-card-actions>
				<v-btn v-if="!expanded" text color="accent" @click="expanded = true">
					{{ $t('page.Series.albums.viewMore') }}
				</v-btn>
				<v-btn v-if="expanded" text color="accent" @click="expanded = false">
					{{ $t('page.Series.albums.viewLess') }}
				</v-btn>
				<v-spacer />
				<v-btn v-if="isInReadingList" :loading="readingListLoading" icon @click="markAsRead">
					<v-icon>mdi-library</v-icon>
				</v-btn>
				<FavouriteButton :model-id="album.id" type="Album" />

				<v-btn :to="`/albums/${album.id}/edit`" icon>
					<v-icon>mdi-pencil</v-icon>
				</v-btn>
			</v-card-actions>
		</v-card>

</template>

<template>

		<v-card v-if="loading" outlined class="c-AlbumCard--loading">
			<v-card-title />
			<v-card-text>

			</v-card-text>
		</v-card>
		<v-card v-else outlined class="c-AlbumCard">
			<router-link :to="`/albums/${album.id}/view`" class="c-AlbumCard__albumLink">
				<v-img
					v-if="album.cover"
					:src="`${baseImageUrl}?w=400`"
					:srcset="`
							${baseImageUrl}?w=400 400w,
							${baseImageUrl}?w=700 700w`"
					:eager="eagerCovers"
					aspect-ratio="3"
					gradient="to top, rgba(0, 0, 0, 0.8) 0%, transparent 72px"
				>
					<v-row align="end" class="fill-height px-4	">
						<v-col class="title">
							{{ album.identifyingName }}
						</v-col>
					</v-row>
				</v-img>
				<v-card-title v-else>
					{{ album.identifyingName }}
				</v-card-title>
			</router-link>
			<v-card-text>
				<v-alert
					v-if="album.wishlist" color="primary" border="left"
					icon="mdi-gift" text dense
				>
					{{ $t('field.Album.wishlist.value.true') }}
				</v-alert>
				<template v-if="album.firstEditionDate && album.firstEditionDate === album.currentEditionDate">
					<FieldValue :label="$t('field.Album.firstEditionDate')">
						{{ $d(new Date(album.firstEditionDate), 'long') }}
						<template v-if="album.markedAsFirstEdition">
							{{ $t('field.Album.markedAsFirstEdition.view') }}
						</template>
						<template v-else>
							{{ $t('field.Album.isFirstEdition') }}
						</template>
					</FieldValue>
				</template>
				<template v-else>
					<FieldValue v-if="album.firstEditionDate" :label="$t('field.Album.firstEditionDate')">
						{{ $d(new Date(album.firstEditionDate), 'long') }}
					</FieldValue>
					<FieldValue v-if="album.currentEditionDate" :label="$t('field.Album.currentEditionDate')">
						{{ $d(new Date(album.currentEditionDate), 'long') }}
					</FieldValue>
				</template>
				<FieldValue
					v-if="album.writers"
					:label="$tc('field.Album.writers', album.writers ? album.writers.length : 0)"
				>
					<ModelLink :model="album.writers" view="AuthorView" />
				</FieldValue>
				<FieldValue
					v-if="album.artists"
					:label="$tc('field.Album.artists', album.artists ? album.artists.length : 0)"
				>
					<ModelLink :model="album.artists" view="AuthorView" />
				</FieldValue>
				<v-fade-transition>

						<FieldValue
							v-if="album.colorists"
							:label="$tc('field.Album.colorists', album.colorists ? album.colorists.length : 0)"
						>
							<ModelLink :model="album.colorists" view="AuthorView" />
						</FieldValue>
						<FieldValue
							v-if="album.inkers"
							:label="$tc('field.Album.inkers', album.inkers ? album.inkers.length : 0)"
						>
							<ModelLink :model="album.inkers" view="AuthorView" />
						</FieldValue>
						<FieldValue
							v-if="album.translators"
							:label="$tc('field.Album.translators', album.translators ? album.translators.length : 0)"
						>
							<ModelLink :model="album.translators" view="AuthorView" />
						</FieldValue>
						<FieldValue v-if="album.publisher" :label="$t('field.Album.publisher')">
							<ModelLink :model="album.publisher" view="PublisherView" />
						</FieldValue>
						<FieldValue v-if="album.collection" :label="$t('field.Album.collection')">
							<ModelLink :model="album.collection" view="CollectionView" />
						</FieldValue>
						<FieldValue v-if="album.tags" :label="$t('field.Album.tags')">
							<TagLink :model="album.tags" />
						</FieldValue>
						<FieldValue v-if="album.binding" :label="$t('field.Album.binding')">
							<ModelLink :model="album.binding" view="BindingView" />
						</FieldValue>
						<FieldValue v-if="album.acquisitionDate" :label="$t('field.Album.acquisitionDate')">
							{{ $d(new Date(album.acquisitionDate), 'long') }}
						</FieldValue>

				</v-fade-transition>
			</v-card-text>
			<v-card-actions>
				<v-btn v-if="!expanded" text color="accent" @click="expanded = true">
					{{ $t('page.Series.albums.viewMore') }}
				</v-btn>
				<v-btn v-if="expanded" text color="accent" @click="expanded = false">
					{{ $t('page.Series.albums.viewLess') }}
				</v-btn>
				<v-spacer />
				<v-btn v-if="isInReadingList" :loading="readingListLoading" icon @click="markAsRead">
					<v-icon>mdi-library</v-icon>
				</v-btn>
				<FavouriteButton :model-id="album.id" type="Album" />

				<v-btn :to="`/albums/${album.id}/edit`" icon>
					<v-icon>mdi-pencil</v-icon>
				</v-btn>
			</v-card-actions>
		</v-card>

</template>


Specify position relative to container for loading component in docs

<template>

Loading

A simple loading overlay

        <Example :component="ExSimple" :code="ExSimpleCode">

The Loading will be closed after about 10 seconds, by pressing escape or by clicking outside.

                Use :is-full-page="false" to limit loader to its container.

                In this case, the container element should be positioned as position: relative.

        </Example>

        <Example :component="ExProgrammatically" :code="ExProgrammaticallyCode" title="Programmatically opening">

When you want to close the Loading, call the close() method from the component.

        </Example>

        <ApiView :data="api"/>

</template>

<template>

Loading

A simple loading overlay

        <Example :component="ExSimple" :code="ExSimpleCode">

The Loading will be closed after about 10 seconds, by pressing escape or by clicking outside.

Use :is-full-page="false" to limit loader to its container.

        </Example>

        <Example :component="ExProgrammatically" :code="ExProgrammaticallyCode" title="Programmatically opening">

When you want to close the Loading, call the close() method from the component.

        </Example>

        <ApiView :data="api"/>

</template>


Improve Bulma link text (#1158)

Improve Bulma link text (#1158)

<template>

1

Install Buefy via npm

                <CodeView code="npm install buefy" lang="bash" expanded/>

Or clone the repository: https://github.com/buefy/buefy

2

Set your variables with Sass

Tip: You can see all of Bulma's variables.

There's also two other variables created by Buefy:

$speed-slow: 150ms !default

$speed-slower: 250ms !default

                    You might also have to install node-sass and sass-loader depending on your environment.

                    It can be done in your App.vue within the <style lang="scss"> tag, or in a separate .scss file.

                    <CodeView :code="sass | pre" lang="sass" expanded/>

3

Import and use Buefy

                    Please note that import 'buefy/dist/buefy.css' from the <router-link to="/documentation/start">start section</router-link> has been omitted.

                <CodeView :code="importing | pre" lang="javascript" expanded/>

</template>

<template>

1

Install Buefy via npm

                <CodeView code="npm install buefy" lang="bash" expanded/>

Or clone the repository: https://github.com/buefy/buefy

2

Set your variables with Sass

Tip: You can see all Bulma's variables here.

There's also two other variables created by Buefy:

$speed-slow: 150ms !default

$speed-slower: 250ms !default

                    You might also have to install node-sass and sass-loader depending on your environment.

                    It can be done in your App.vue within the <style lang="scss"> tag, or in a separate .scss file.

                    <CodeView :code="sass | pre" lang="sass" expanded/>

3

Import and use Buefy

                    Please note that import 'buefy/dist/buefy.css' from the <router-link to="/documentation/start">start section</router-link> has been omitted.

                <CodeView :code="importing | pre" lang="javascript" expanded/>

</template>


Fix failing build due to Stylus formatting

<template lang="pug">
#contacts.section.container
	div
		.row
			h4.center-align.section-header My Work
			.center-align
				ContactBubble(url="https://github.com/salvadornico", icon="github", name="GitHub")
				ContactBubble(url="https://codepen.io/salvadornico", icon="codepen", name="CodePen")
				ContactBubble(url="https://vimeo.com/roller4life", icon="vimeo", name="Vimeo")

		.row
			h4.center-align Get In Touch
			.center-align
				ContactBubble(url="mailto:salvador.nico@gmail.com", icon="email", name="Email")
				ContactBubble(url="https://www.linkedin.com/in/salvadornico", icon="linkedin", name="LinkedIn")
				ContactBubble(url="https://plus.google.com/u/0/+NicoSalvador", icon="google", name="Google+")
				ContactBubble(url="https://www.instagram.com/nicolrnzo", icon="instagram", name="Instagram")
				ContactBubble(url="https://www.facebook.com/salvador.nico", icon="fb", name="Facebook")
				ContactBubble(url="https://twitter.com/nicoLorenzooo", icon="twitter", name="Twitter")
				ContactBubble(url="https://www.pinterest.com/roller4life/", icon="pinterest", name="Pinterest")

		.row
			a#resumeLink.waves-effect.waves-light.btn-large.red.darken-1.hoverable(href="../static/files/Nico-Salvador-CV.pdf")
				MaterialIcon(icon="library_books").left
				span Resumé
</template>

<script lang="ts">
import MaterialIcon from "@/components/shared/MaterialIcon.vue"
import Vue from "vue"
import { Component } from "vue-property-decorator"
import ContactBubble from "./ContactBubble.vue"

@Component({
	components: {
		ContactBubble,
		MaterialIcon,
	},
})
export default class Contacts extends Vue {}
</script>

<style lang="stylus" scoped>
#contacts
	height 100vh
	min-height 40rem
	color white

	& > div
		width 100%
		center Y absolute

#resumeLink
	center X relative
	margin 2rem auto
	width 12rem
</style>

<template lang="pug">
#contacts.section.container
	div
		.row
			h4.center-align.section-header My Work
			.center-align
				ContactBubble(url="https://github.com/salvadornico", icon="github", name="GitHub")
				ContactBubble(url="https://codepen.io/salvadornico", icon="codepen", name="CodePen")
				ContactBubble(url="https://vimeo.com/roller4life", icon="vimeo", name="Vimeo")

		.row
			h4.center-align Get In Touch
			.center-align
				ContactBubble(url="mailto:salvador.nico@gmail.com", icon="email", name="Email")
				ContactBubble(url="https://www.linkedin.com/in/salvadornico", icon="linkedin", name="LinkedIn")
				ContactBubble(url="https://plus.google.com/u/0/+NicoSalvador", icon="google", name="Google+")
				ContactBubble(url="https://www.instagram.com/nicolrnzo", icon="instagram", name="Instagram")
				ContactBubble(url="https://www.facebook.com/salvador.nico", icon="fb", name="Facebook")
				ContactBubble(url="https://twitter.com/nicoLorenzooo", icon="twitter", name="Twitter")
				ContactBubble(url="https://www.pinterest.com/roller4life/", icon="pinterest", name="Pinterest")

		.row
			a#resumeLink.waves-effect.waves-light.btn-large.red.darken-1.hoverable(href="../static/files/Nico-Salvador-CV.pdf")
				MaterialIcon(icon="library_books").left
				span Resumé
</template>

<script lang="ts">
import MaterialIcon from "@/components/shared/MaterialIcon.vue"
import Vue from "vue"
import { Component } from "vue-property-decorator"
import ContactBubble from "./ContactBubble.vue"

@Component({
	components: {
		ContactBubble,
		MaterialIcon,
	},
})
export default class Contacts extends Vue {}
</script>

<style lang="stylus" scoped>
#contacts
  height 100vh
  min-height 40rem
  color white

  & > div
    width 100%
    center Y absolute

#resumeLink
  margin 2rem auto
  center X relative
  width 12rem
</style>


Tweak max on logos for larger screens.

Tweak max on logos for larger screens.

<template>
  <SectionSlice :class="$options.className">
    <ul :class="`${$options.className}__list`">
      <li
        v-for="(client, index) in slice.fields"
        :key="index"
        :class="`${$options.className}__item`">
        <SvgIcon
          :class="`${$options.className}__logo`"
          :name="`logo-${client.logo}`"
        />
        <div :class="`${$options.className}__info`">
          <h4
            v-if="client.name"
            :class="`${$options.className}__name`">
            {{ client.name }}
          </h4>
          <p
            v-if="client.role"
            :class="`${$options.className}__role`">
            {{ client.role }}
          </p>
        </div>
      </li>
    </ul>
  </SectionSlice>
</template>

<script>
import '@/assets/icons/logo-allofus'
import '@/assets/icons/logo-foolproof'
import '@/assets/icons/logo-parallel'
import '@/assets/icons/logo-raggededge'
import '@/assets/icons/logo-rotate'
import '@/assets/icons/logo-someone'
import '@/assets/icons/logo-somo'
import '@/assets/icons/logo-thehoxton'
import SectionSlice from '@/components/SectionSlice/SectionSlice'

export default {
  name: 'SliceClients',
  className: 'SliceClients',
  components: {
    SectionSlice
  },
  props: {
    slice: {
      type: Object,
      default: () => ({})
    }
  }
}
</script>

<style lang="scss" scoped>
.SliceClients {
  $root: &;

  background-color: color("light");

  &__list {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    width: 100%;
    min-height: 100vh;
    margin: 0;
    grid-gap: 1px;
  }

  &__item {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: color("grey");

    &:hover {
      #{$root}__info {
        @supports (clip-path: circle(0% at 0 0)) {
          clip-path: circle(100% at 50% 50%);
        }
      }

      #{$root}__name,
      #{$root}__role {
        opacity: 1;
        visibility: visible;
        transform: translate3d(0, 0, 0);
        transition-delay: $trans-speed;
      }
    }
  }

  &__logo {
    max-width: 80%;
    height: 16px;
    color: color("dark");

    @include media("sm") {
      max-width: 200px;
      height: 23px;
    }
  }

  &__info {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    overflow: hidden;
    padding: var(--spacing-unit);
    opacity: 0;
    visibility: hidden;
    background-color: color("light");
    pointer-events: none;
    transition:
      opacity $trans-speed $trans-ease,
      visibility $trans-speed $trans-ease;

    @supports (clip-path: circle(0% at 0 0)) {
      opacity: 1;
      visibility: visible;
      clip-path: circle(0% at 50% 50%);
      transition: clip-path ($trans-speed * 2) $trans-ease;
    }

    @include media("lg") {
      padding: calc(var(--spacing-unit) / 2);
    }
  }

  &__name,
  &__role {
    width: 100%;
    max-width: 320px;
    opacity: 0;
    visibility: hidden;
    transform: translate3d(0, 10px, 0);
    transition:
      opacity $trans-speed $trans-ease,
      transform $trans-speed $trans-ease,
      visibility $trans-speed $trans-ease;
  }

  &__role {
    margin-bottom: 0;
  }
}
</style>

<template>
  <SectionSlice :class="$options.className">
    <ul :class="`${$options.className}__list`">
      <li
        v-for="(client, index) in slice.fields"
        :key="index"
        :class="`${$options.className}__item`">
        <SvgIcon
          :class="`${$options.className}__logo`"
          :name="`logo-${client.logo}`"
        />
        <div :class="`${$options.className}__info`">
          <h4
            v-if="client.name"
            :class="`${$options.className}__name`">
            {{ client.name }}
          </h4>
          <p
            v-if="client.role"
            :class="`${$options.className}__role`">
            {{ client.role }}
          </p>
        </div>
      </li>
    </ul>
  </SectionSlice>
</template>

<script>
import '@/assets/icons/logo-allofus'
import '@/assets/icons/logo-foolproof'
import '@/assets/icons/logo-parallel'
import '@/assets/icons/logo-raggededge'
import '@/assets/icons/logo-rotate'
import '@/assets/icons/logo-someone'
import '@/assets/icons/logo-somo'
import '@/assets/icons/logo-thehoxton'
import SectionSlice from '@/components/SectionSlice/SectionSlice'

export default {
  name: 'SliceClients',
  className: 'SliceClients',
  components: {
    SectionSlice
  },
  props: {
    slice: {
      type: Object,
      default: () => ({})
    }
  }
}
</script>

<style lang="scss" scoped>
.SliceClients {
  $root: &;

  background-color: color("light");

  &__list {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    width: 100%;
    min-height: 100vh;
    margin: 0;
    grid-gap: 1px;
  }

  &__item {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: color("grey");

    &:hover {
      #{$root}__info {
        @supports (clip-path: circle(0% at 0 0)) {
          clip-path: circle(100% at 50% 50%);
        }
      }

      #{$root}__name,
      #{$root}__role {
        opacity: 1;
        visibility: visible;
        transform: translate3d(0, 0, 0);
        transition-delay: $trans-speed;
      }
    }
  }

  &__logo {
    max-width: 80%;
    height: 16px;
    color: color("dark");

    @include media("sm") {
      height: 23px;
    }
  }

  &__info {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    overflow: hidden;
    padding: var(--spacing-unit);
    opacity: 0;
    visibility: hidden;
    background-color: color("light");
    pointer-events: none;
    transition:
      opacity $trans-speed $trans-ease,
      visibility $trans-speed $trans-ease;

    @supports (clip-path: circle(0% at 0 0)) {
      opacity: 1;
      visibility: visible;
      clip-path: circle(0% at 50% 50%);
      transition: clip-path ($trans-speed * 2) $trans-ease;
    }

    @include media("lg") {
      padding: calc(var(--spacing-unit) / 2);
    }
  }

  &__name,
  &__role {
    width: 100%;
    max-width: 320px;
    opacity: 0;
    visibility: hidden;
    transform: translate3d(0, 10px, 0);
    transition:
      opacity $trans-speed $trans-ease,
      transform $trans-speed $trans-ease,
      visibility $trans-speed $trans-ease;
  }

  &__role {
    margin-bottom: 0;
  }
}
</style>


Fix utm_source for dapps that already have a query string


Sidebar hover color

Sidebar hover color

<template>

          {{ totalBalance.toFixed(2) }}

      <accounts-section class="accounts" />

          <fa-icon :icon="['fal', lockIcon]" />

          <fa-icon :icon="['fal', 'gem']" />

          <fa-icon :icon="['fal', 'user-circle']" />

      <portal-target
        ref="headerSlot"
        name="header-slot"
        class="header"
        @change="headerSlotChanged"
      ></portal-target>

        <router-view />

      <portal-target
        ref="footerSlot"
        name="footer-slot"
        class="footer"
        @change="footerSlotChanged"
      ></portal-target>

          <portal-target name="sidebar-right-title" />

          <fa-icon :icon="['fal', 'times']" />

      <portal-target
        class="component"
        ref="sidebarRight"
        name="sidebar-right"
        @change="sidebarRightSlotChanged"
      />

</template>


use classic link for logo

use classic link for logo

<template>
    <RouterLink :to="url" custom v-slot="{ href }">

                    <Logo class="logo" width="120" />

                        Sharp
                        <template v-if="versionTitle">
                            {{ versionTitle }}
                        </template>

    </RouterLink>
</template>

<template>
    <RouterLink :to="url" style="color: inherit">

                <Logo class="logo" width="120" />

                    Sharp
                    <template v-if="versionTitle">
                        {{ versionTitle }}
                    </template>

    </RouterLink>
</template>


More margin

More margin

<template>

      <uw-card
        v-for="(subcatRes, i) in Object.values(resource.subcategories)"
        :id="subcatRes.subcat_id"
        :key="i"
        loaded
        class="resource-card"
      >
        <template #card-heading>

              {{subcatRes.subcat_name}}

            <b-button
              v-if="!subcatRes.is_pinned"
              variant="link"
              class="myuw-text-sm text-muted"
              :title="`Add ${subcatRes.subcat_name} resources to home page`"
              @click="pinWrapper(subcatRes)"
            >
              Pin to Home

            <b-button
              v-else
              variant="link"
              class="myuw-text-sm text-muted"
              :title="`Remove ${subcatRes.subcat_name} resources from home page`"
              @click="unpinWrapper(subcatRes)"
            >
              Unpin

        </template>
        <template #card-body>

              {{link.title}}

        </template>
      </uw-card>

</template>


Fix Vega messages appearing atop signup modal


docs(VParallax): update simple example (#9404)

* Update parallax simple example

Proper grid row>col

* Update content.vue

<template>
  <v-parallax
    dark
    src="https://cdn.vuetifyjs.com/images/backgrounds/vbanner.jpg"
  >
    <v-row
      align="center"
      justify="center"
    >
      <v-col class="text-center" cols="12">

Vuetify.js

Build your application today!

      </v-col>
    </v-row>
  </v-parallax>
</template>

<template>
  <v-parallax
    dark
    src="https://cdn.vuetifyjs.com/images/backgrounds/vbanner.jpg"
  >
    <v-row
      align="center"
      justify="center"
    >

Vuetify.js

Build your application today!

    </v-row>
  </v-parallax>
</template>


put tag population behind an option

put tag population behind an option

<!--
  AposMediaManager will be in charge of all media-related state logic
  this includes doing the selecting and deselecting of items, deciding the editor/selection view,
  emitting batch action events, etc. All sub views will recieve `media` as a prop
-->

<template>
  <AposModal
    :modal="modal" :modal-title="modalTitle"
    class="apos-media-manager"
    @inactive="modal.active = false" @show-modal="modal.showModal = true"
    @esc="confirmAndCancel" @no-modal="$emit('safe-close')"
  >
    <template v-if="relationshipField" #secondaryControls>
      <AposButton
        type="default" label="Cancel"
        @click="confirmAndCancel"
      />
    </template>
    <template v-else #secondaryControls>
      <AposButton
        type="default" label="Exit"
        @click="confirmAndCancel"
      />
    </template>
    <template v-if="relationshipField" #primaryControls>
      <AposButton
        type="primary"
        :label="`Select ${moduleLabels.pluralLabel || ''}`"
        :disabled="relationshipErrors"
        @click="saveRelationship"
      />
    </template>
    <template #leftRail>
      <AposModalRail>
        <AposTagList
          title="Filter by Tag"
          :tags="tagList"
          @update="filter('_tags', $event)"
        />
      </AposModalRail>
    </template>
    <template #main>
      <AposModalBody>
        <template #bodyHeader>
          <AposDocsManagerToolbar
            :selected-state="selectAllState"
            :total-pages="totalPages" :current-page="currentPage"
            :filters="toolbarFilters" :labels="moduleLabels"
            :disable="relationshipErrors === 'min'"
            :options="{ hideSelectAll: !relationshipField }"
            @page-change="updatePage"
            @select-click="selectClick"
            @trash-click="trashClick"
            @search="search"
            @filter="filter"
          />
        </template>
        <template #bodyMain>
          <AposMediaManagerDisplay
            ref="display"
            :accept="accept"
            :items="items"
            :module-options="options"
            @edit="updateEditing"
            v-model="checked"
            @select="select"
            @select-series="selectSeries"
            @select-another="selectAnother"
            @upload-started="uploading = true"
            @upload-complete="completeUploading"
            @create-placeholder="createPlaceholder"
            :max-reached="maxReached()"
            :options="{
              disableUnchecked: maxReached(),
              hideCheckboxes: !relationshipField
            }"
          />
        </template>
      </AposModalBody>
    </template>
    <template #rightRail>
      <AposModalRail type="right">
        <div
          class="apos-media-manager__sidebar"
          :class="{ 'apos-media-manager__sidebar--empty' : !checked.length }"
        >
          <AposMediaManagerEditor
            v-show="editing"
            :media="editing" :selected="selected"
            :module-labels="moduleLabels"
            @back="updateEditing(null)" @saved="updateMedia"
            @modified="editorModified"
          />
          <AposMediaManagerSelections
            :items="selected"
            @clear="clearSelected" @edit="updateEditing"
            v-show="!editing"
          />
        </div>
      </AposModalRail>
    </template>
  </AposModal>
</template>

<script>
import AposModalModifiedMixin from 'Modules/@apostrophecms/modal/mixins/AposModalModifiedMixin';
import AposDocsManagerMixin from 'Modules/@apostrophecms/modal/mixins/AposDocsManagerMixin';
import cuid from 'cuid';

export default {
  mixins: [ AposModalModifiedMixin, AposDocsManagerMixin ],
  props: {
    moduleName: {
      type: String,
      required: true
    }
  },
  emits: [ 'safe-close', 'trash', 'save', 'search' ],
  data() {
    return {
      items: [],
      totalPages: 1,
      currentPage: 1,
      tagList: [],
      filterValues: {},
      modal: {
        active: false,
        type: 'overlay',
        showModal: false
      },
      editing: undefined,
      modified: false,
      uploading: false,
      lastSelected: null,
      emptyDisplay: {
        title: 'No Media Found',
        message: 'Uploaded media will appear here',
        emoji: '🖼'
      },
      cancelDescription: 'Do you want to discard changes to the active image?'
    };
  },
  computed: {
    modalTitle () {
      const verb = this.relationshipField ? 'Choose' : 'Manage';
      return `${verb} ${this.moduleLabels.pluralLabel}`;
    },
    options() {
      return window.apos.modules[this.moduleName];
    },
    toolbarFilters() {
      if (!this.options || !this.options.filters) {
        return null;
      }

      return this.options.filters.filter(filter => {
        // Removes _tags since that will be in the left sidebar.
        return filter.name !== '_tags';
      });
    },
    moduleLabels() {
      if (!this.options) {
        return null;
      }
      return {
        label: this.options.label,
        pluralLabel: this.options.pluralLabel
      };
    },
    selected() {
      return this.items.filter(item => this.checked.includes(item._id));
    },
    accept() {
      return this.options.schema.find(field => field.name === 'attachment').accept;
    }
  },
  watch: {
    async checked (newVal, oldVal) {
      if (newVal.length > 1 || newVal.length === 0) {
        if (!await this.updateEditing(null)) {
          this.checked = oldVal;
        }
      }
    }
  },
  async mounted() {
    this.modal.active = true;
    await this.getMedia({ tags: true });
  },
  methods: {
    // Whether a cancellation requires confirmation or not
    isModified () {
      return (this.editing && this.modified) || this.relationshipIsModified();
    },
    // Update our current idea of whether the doc in the right hand rail
    // has been modified (via event from the editor)
    editorModified (val) {
      this.modified = val;
    },
    async getMedia (options) {
      const qs = {
        ...this.filterValues,
        page: this.currentPage
      };

      if (this.options && Array.isArray(this.options.filters)) {
        this.options.filters.forEach(filter => {
          if (!filter.choices && qs.choices) {
            qs.choices += `,${filter.name}`;
          } else if (!filter.choices) {
            qs.choices = filter.name;
          }
        });
      }

      // Avoid undefined properties.
      for (const prop in qs) {
        if (qs[prop] === undefined) {
          delete qs[prop];
        };
      }
      const apiResponse = (await apos.http.get(
        this.options.action, {
          busy: true,
          qs
        }
      ));

      if (options && options.tags) {
        this.tagList = apiResponse.choices ? apiResponse.choices._tags : [];
      }

      this.currentPage = apiResponse.currentPage;
      this.totalPages = apiResponse.pages;
      this.items = apiResponse.results;
    },
    async updateMedia () {
      this.updateEditing(null);
      await this.getMedia();
    },
    async filter(name, value) {
      this.filterValues[name] = value;
      this.currentPage = 1;

      await this.getMedia();
    },
    createPlaceholder(dimensions) {
      this.items.unshift({
        _id: cuid(),
        title: 'placeholder image',
        dimensions
      });
    },
    async completeUploading (imgIds) {
      this.uploading = false;
      await this.getMedia();

      if (Array.isArray(imgIds) && imgIds.length) {
        this.checked = this.checked.concat(imgIds);

        // If we're currently editing one, don't interrupt that by replacing it.
        if (!this.editing && imgIds.length === 1) {
          this.updateEditing(imgIds[0]);
        }
      }
    },
    clearSelected() {
      this.checked = [];
      this.editing = undefined;
    },
    async updateEditing(id) {
      // We only care about the current doc for this prompt,
      // we are not in danger of discarding a selection when
      // we switch images
      if (this.editing && this.modified) {
        const discard = await apos.confirm({
          heading: this.cancelHeading,
          description: this.cancelDescription,
          negativeLabel: this.cancelNegativeLabel,
          affirmativeLabel: this.cancelAffirmativeLabel
        });
        if (!discard) {
          return false;
        }
      }
      this.editing = this.items.find(item => item._id === id);
      return true;
    },
    // select setters
    select(id) {
      if (this.checked.includes(id)) {
        this.checked = [];
      } else {
        this.checked = [ id ];
      }
      this.updateEditing(id);
      this.lastSelected = id;
    },
    selectAnother(id) {
      if (this.checked.includes(id)) {
        this.checked = this.checked.filter(checkedId => checkedId !== id);
      } else {
        this.checked.push(id);
      }

      this.lastSelected = id;
      this.editing = undefined;
    },

    selectSeries(id) {
      if (!this.lastSelected) {
        this.select(id);
        return;
      }

      let beginIndex = this.items.findIndex(item => item._id === this.lastSelected);
      let endIndex = this.items.findIndex(item => item._id === id);
      const direction = beginIndex > endIndex ? -1 : 1;

      if (direction < 0) {
        [ beginIndex, endIndex ] = [ endIndex, beginIndex ];
      } else {
        endIndex++;
      }

      const sliced = this.items.slice(beginIndex, endIndex);
      // always want to check, never toggle
      sliced.forEach(item => {
        if (!this.checked.includes(item._id)) {
          this.checked.push(item._id);
        }
      });

      this.lastSelected = sliced[sliced.length - 1]._id;
      this.editing = undefined;
    },

    // Toolbar handlers
    selectClick() {
      this.selectAll();
      this.editing = undefined;
    },
    async updatePage(num) {
      if (num) {
        this.currentPage = num;
        await this.getMedia();
      }
    },
    // TODO stub
    trashClick() {
      this.$emit('trash', this.checked);
    },

    search(query) {
      // TODO stub
      this.$emit('search', query);
    }
  }
};
</script>

<style lang="scss" scoped>
.apos-media-manager /deep/ .apos-media-manager-toolbar {
  z-index: $z-index-manager-toolbar;
  position: relative;
}

.apos-media-manager__empty {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  margin-top: 130px;
}

.apos-media-manager__sidebar {
  position: relative;
  width: 100%;
}

.apos-media-manager__sidebar--empty {
  height: 100%;
}

</style>

<!--
  AposMediaManager will be in charge of all media-related state logic
  this includes doing the selecting and deselecting of items, deciding the editor/selection view,
  emitting batch action events, etc. All sub views will recieve `media` as a prop
-->

<template>
  <AposModal
    :modal="modal" :modal-title="modalTitle"
    class="apos-media-manager"
    @inactive="modal.active = false" @show-modal="modal.showModal = true"
    @esc="confirmAndCancel" @no-modal="$emit('safe-close')"
  >
    <template v-if="relationshipField" #secondaryControls>
      <AposButton
        type="default" label="Cancel"
        @click="confirmAndCancel"
      />
    </template>
    <template v-else #secondaryControls>
      <AposButton
        type="default" label="Exit"
        @click="confirmAndCancel"
      />
    </template>
    <template v-if="relationshipField" #primaryControls>
      <AposButton
        type="primary"
        :label="`Select ${moduleLabels.pluralLabel || ''}`"
        :disabled="relationshipErrors"
        @click="saveRelationship"
      />
    </template>
    <template #leftRail>
      <AposModalRail>
        <AposTagList
          title="Filter by Tag"
          :tags="tagList"
          @update="filter('_tags', $event)"
        />
      </AposModalRail>
    </template>
    <template #main>
      <AposModalBody>
        <template #bodyHeader>
          <AposDocsManagerToolbar
            :selected-state="selectAllState"
            :total-pages="totalPages" :current-page="currentPage"
            :filters="toolbarFilters" :labels="moduleLabels"
            :disable="relationshipErrors === 'min'"
            :options="{ hideSelectAll: !relationshipField }"
            @page-change="updatePage"
            @select-click="selectClick"
            @trash-click="trashClick"
            @search="search"
            @filter="filter"
          />
        </template>
        <template #bodyMain>
          <AposMediaManagerDisplay
            ref="display"
            :accept="accept"
            :items="items"
            :module-options="options"
            @edit="updateEditing"
            v-model="checked"
            @select="select"
            @select-series="selectSeries"
            @select-another="selectAnother"
            @upload-started="uploading = true"
            @upload-complete="completeUploading"
            @create-placeholder="createPlaceholder"
            :max-reached="maxReached()"
            :options="{
              disableUnchecked: maxReached(),
              hideCheckboxes: !relationshipField
            }"
          />
        </template>
      </AposModalBody>
    </template>
    <template #rightRail>
      <AposModalRail type="right">
        <div
          class="apos-media-manager__sidebar"
          :class="{ 'apos-media-manager__sidebar--empty' : !checked.length }"
        >
          <AposMediaManagerEditor
            v-show="editing"
            :media="editing" :selected="selected"
            :module-labels="moduleLabels"
            @back="updateEditing(null)" @saved="updateMedia"
            @modified="editorModified"
          />
          <AposMediaManagerSelections
            :items="selected"
            @clear="clearSelected" @edit="updateEditing"
            v-show="!editing"
          />
        </div>
      </AposModalRail>
    </template>
  </AposModal>
</template>

<script>
import AposModalModifiedMixin from 'Modules/@apostrophecms/modal/mixins/AposModalModifiedMixin';
import AposDocsManagerMixin from 'Modules/@apostrophecms/modal/mixins/AposDocsManagerMixin';
import cuid from 'cuid';

export default {
  mixins: [ AposModalModifiedMixin, AposDocsManagerMixin ],
  props: {
    moduleName: {
      type: String,
      required: true
    }
  },
  emits: [ 'safe-close', 'trash', 'save', 'search' ],
  data() {
    return {
      items: [],
      totalPages: 1,
      currentPage: 1,
      tagList: [],
      filterValues: {},
      modal: {
        active: false,
        type: 'overlay',
        showModal: false
      },
      editing: undefined,
      modified: false,
      uploading: false,
      lastSelected: null,
      emptyDisplay: {
        title: 'No Media Found',
        message: 'Uploaded media will appear here',
        emoji: '🖼'
      },
      cancelDescription: 'Do you want to discard changes to the active image?'
    };
  },
  computed: {
    modalTitle () {
      const verb = this.relationshipField ? 'Choose' : 'Manage';
      return `${verb} ${this.moduleLabels.pluralLabel}`;
    },
    options() {
      return window.apos.modules[this.moduleName];
    },
    toolbarFilters() {
      if (!this.options || !this.options.filters) {
        return null;
      }

      return this.options.filters.filter(filter => {
        // Removes _tags since that will be in the left sidebar.
        return filter.name !== '_tags';
      });
    },
    moduleLabels() {
      if (!this.options) {
        return null;
      }
      return {
        label: this.options.label,
        pluralLabel: this.options.pluralLabel
      };
    },
    selected() {
      return this.items.filter(item => this.checked.includes(item._id));
    },
    accept() {
      return this.options.schema.find(field => field.name === 'attachment').accept;
    }
  },
  watch: {
    async checked (newVal, oldVal) {
      if (newVal.length > 1 || newVal.length === 0) {
        if (!await this.updateEditing(null)) {
          this.checked = oldVal;
        }
      }
    }
  },
  async mounted() {
    this.modal.active = true;
    await this.getMedia();
  },
  methods: {
    // Whether a cancellation requires confirmation or not
    isModified () {
      return (this.editing && this.modified) || this.relationshipIsModified();
    },
    // Update our current idea of whether the doc in the right hand rail
    // has been modified (via event from the editor)
    editorModified (val) {
      this.modified = val;
    },
    async getMedia () {
      const qs = {
        ...this.filterValues,
        page: this.currentPage
      };

      if (this.options && Array.isArray(this.options.filters)) {
        this.options.filters.forEach(filter => {
          if (!filter.choices && qs.choices) {
            qs.choices += `,${filter.name}`;
          } else if (!filter.choices) {
            qs.choices = filter.name;
          }
        });
      }

      // Avoid undefined properties.
      for (const prop in qs) {
        if (qs[prop] === undefined) {
          delete qs[prop];
        };
      }
      const apiResponse = (await apos.http.get(
        this.options.action, {
          busy: true,
          qs
        }
      ));

      this.tagList = apiResponse.choices ? apiResponse.choices._tags : [];
      this.currentPage = apiResponse.currentPage;
      this.totalPages = apiResponse.pages;
      this.items = apiResponse.results;
    },
    async updateMedia () {
      this.updateEditing(null);
      await this.getMedia();
    },
    async filter(name, value) {
      this.filterValues[name] = value;
      this.currentPage = 1;

      await this.getMedia();
    },
    createPlaceholder(dimensions) {
      this.items.unshift({
        _id: cuid(),
        title: 'placeholder image',
        dimensions
      });
    },
    async completeUploading (imgIds) {
      this.uploading = false;
      await this.getMedia();

      if (Array.isArray(imgIds) && imgIds.length) {
        this.checked = this.checked.concat(imgIds);

        // If we're currently editing one, don't interrupt that by replacing it.
        if (!this.editing && imgIds.length === 1) {
          this.updateEditing(imgIds[0]);
        }
      }
    },
    clearSelected() {
      this.checked = [];
      this.editing = undefined;
    },
    async updateEditing(id) {
      // We only care about the current doc for this prompt,
      // we are not in danger of discarding a selection when
      // we switch images
      if (this.editing && this.modified) {
        const discard = await apos.confirm({
          heading: this.cancelHeading,
          description: this.cancelDescription,
          negativeLabel: this.cancelNegativeLabel,
          affirmativeLabel: this.cancelAffirmativeLabel
        });
        if (!discard) {
          return false;
        }
      }
      this.editing = this.items.find(item => item._id === id);
      return true;
    },
    // select setters
    select(id) {
      if (this.checked.includes(id)) {
        this.checked = [];
      } else {
        this.checked = [ id ];
      }
      this.updateEditing(id);
      this.lastSelected = id;
    },
    selectAnother(id) {
      if (this.checked.includes(id)) {
        this.checked = this.checked.filter(checkedId => checkedId !== id);
      } else {
        this.checked.push(id);
      }

      this.lastSelected = id;
      this.editing = undefined;
    },

    selectSeries(id) {
      if (!this.lastSelected) {
        this.select(id);
        return;
      }

      let beginIndex = this.items.findIndex(item => item._id === this.lastSelected);
      let endIndex = this.items.findIndex(item => item._id === id);
      const direction = beginIndex > endIndex ? -1 : 1;

      if (direction < 0) {
        [ beginIndex, endIndex ] = [ endIndex, beginIndex ];
      } else {
        endIndex++;
      }

      const sliced = this.items.slice(beginIndex, endIndex);
      // always want to check, never toggle
      sliced.forEach(item => {
        if (!this.checked.includes(item._id)) {
          this.checked.push(item._id);
        }
      });

      this.lastSelected = sliced[sliced.length - 1]._id;
      this.editing = undefined;
    },

    // Toolbar handlers
    selectClick() {
      this.selectAll();
      this.editing = undefined;
    },
    async updatePage(num) {
      if (num) {
        this.currentPage = num;
        await this.getMedia();
      }
    },
    // TODO stub
    trashClick() {
      this.$emit('trash', this.checked);
    },

    search(query) {
      // TODO stub
      this.$emit('search', query);
    }
  }
};
</script>

<style lang="scss" scoped>
.apos-media-manager /deep/ .apos-media-manager-toolbar {
  z-index: $z-index-manager-toolbar;
  position: relative;
}

.apos-media-manager__empty {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  margin-top: 130px;
}

.apos-media-manager__sidebar {
  position: relative;
  width: 100%;
}

.apos-media-manager__sidebar--empty {
  height: 100%;
}

</style>