Reduce the size of the deploy flag when there's no tag available

<script>
  import { dateFormatWithName, timeFormat } from '../../utils/date_time_formatters';
  import Icon from '../../../vue_shared/components/icon.vue';

  export default {
    props: {
      showDeployInfo: {
        type: Boolean,
        required: true,
      },
      deploymentData: {
        type: Array,
        required: true,
      },
      graphHeight: {
        type: Number,
        required: true,
      },
      graphHeightOffset: {
        type: Number,
        required: true,
      },
      graphWidth: {
        type: Number,
        required: true,
      },
    },

    components: {
      Icon,
    },

    computed: {
      calculatedHeight() {
        return this.graphHeight - this.graphHeightOffset;
      },
    },

    methods: {
      refText(d) {
        return d.tag ? d.ref : d.sha.slice(0, 8);
      },

      formatTime(deploymentTime) {
        return timeFormat(deploymentTime);
      },

      formatDate(deploymentTime) {
        return dateFormatWithName(deploymentTime);
      },

      nameDeploymentClass(deployment) {
        return `deploy-info-${deployment.id}`;
      },

      transformDeploymentGroup(deployment) {
        return `translate(${Math.floor(deployment.xPos) + 1}, 20)`;
      },

      positionFlag(deployment) {
        let xPosition = 3;
        if (deployment.xPos > (this.graphWidth - 225)) {
          xPosition = -142;
        }
        return xPosition;
      },

      svgContainerHeight(tag) {
        let svgHeight = 80;
        if (!tag) {
          svgHeight -= 20;
        }
        return svgHeight;
      },
    },
  };
</script>
<template>
  <g
    class="deploy-info"
    v-if="showDeployInfo">
    <g
      v-for="(deployment, index) in deploymentData"
      :key="index"
      :class="nameDeploymentClass(deployment)"
      :transform="transformDeploymentGroup(deployment)">
      <rect
        x="0"
        y="0"
        :height="calculatedHeight"
        width="3"
        fill="url(#shadow-gradient)">
      </rect>
      <line
        class="deployment-line"
        x1="0"
        y1="0"
        x2="0"
        :y2="calculatedHeight"
        stroke="#000">
      </line>
      <svg
        v-if="deployment.showDeploymentFlag"
        class="js-deploy-info-box"
        :x="positionFlag(deployment)"
        y="0"
        width="134"
        :height="svgContainerHeight(deployment.tag)">
        <rect
          class="rect-text-metric deploy-info-rect rect-metric"
          x="1"
          y="1"
          rx="2"
          width="132"
          :height="svgContainerHeight(deployment.tag) - 2">
        </rect>
        <text
          class="deploy-info-text text-metric-bold"
          transform="translate(5, 2)">
          Deployed
        </text>
        <!--The date info-->
        <g transform="translate(5, 20)">
          <text class="deploy-info-text">
            {{formatDate(deployment.time)}}
          </text>
          <text
            class="deploy-info-text text-metric-bold"
            x="62">
            {{formatTime(deployment.time)}}
          </text>
        </g>
        <line
          class="divider-line"
          x1="0"
          y1="38"
          x2="132"
          :y2="38"
          stroke="#000">
        </line>
        <!--Commit information-->
        <g transform="translate(5, 40)">
          <icon
            name="commit"
            :width="12"
            :height="12"
            :y="3">
          </icon>
          <a :xlink:href="deployment.commitUrl">
            <text
              class="deploy-info-text deploy-info-text-link"
              transform="translate(20, 2)">
              {{refText(deployment)}}
            </text>
          </a>
        </g>
        <!--Tag information-->
        <g
          transform="translate(5, 55)"
          v-if="deployment.tag">
          <icon
            name="label"
            :width="12"
            :height="12"
            :y="5">
          </icon>
          <a :xlink:href="deployment.tagUrl">
            <text
              class="deploy-info-text deploy-info-text-link"
              transform="translate(20, 2)"
              y="2">
              {{deployment.tag}}
            </text>
          </a>
        </g>
      </svg>
    </g>
    <svg
      height="0"
      width="0">
      <defs>
        <linearGradient
          id="shadow-gradient">
          <stop
            offset="0%"
            stop-color="#000"
            stop-opacity="0.4">
          </stop>
          <stop
            offset="100%"
            stop-color="#000"
            stop-opacity="0">
          </stop>
        </linearGradient>
      </defs>
    </svg>
  </g>
</template>

<script>
  import { dateFormatWithName, timeFormat } from '../../utils/date_time_formatters';
  import Icon from '../../../vue_shared/components/icon.vue';

  export default {
    props: {
      showDeployInfo: {
        type: Boolean,
        required: true,
      },
      deploymentData: {
        type: Array,
        required: true,
      },
      graphHeight: {
        type: Number,
        required: true,
      },
      graphHeightOffset: {
        type: Number,
        required: true,
      },
      graphWidth: {
        type: Number,
        required: true,
      },
    },

    components: {
      Icon,
    },

    computed: {
      calculatedHeight() {
        return this.graphHeight - this.graphHeightOffset;
      },
    },

    methods: {
      refText(d) {
        return d.tag ? d.ref : d.sha.slice(0, 8);
      },

      formatTime(deploymentTime) {
        return timeFormat(deploymentTime);
      },

      formatDate(deploymentTime) {
        return dateFormatWithName(deploymentTime);
      },

      nameDeploymentClass(deployment) {
        return `deploy-info-${deployment.id}`;
      },

      transformDeploymentGroup(deployment) {
        return `translate(${Math.floor(deployment.xPos) + 1}, 20)`;
      },

      positionFlag(deployment) {
        let xPosition = 3;
        if (deployment.xPos > (this.graphWidth - 225)) {
          xPosition = -142;
        }
        return xPosition;
      },
    },
  };
</script>
<template>
  <g
    class="deploy-info"
    v-if="showDeployInfo">
    <g
      v-for="(deployment, index) in deploymentData"
      :key="index"
      :class="nameDeploymentClass(deployment)"
      :transform="transformDeploymentGroup(deployment)">
      <rect
        x="0"
        y="0"
        :height="calculatedHeight"
        width="3"
        fill="url(#shadow-gradient)">
      </rect>
      <line
        class="deployment-line"
        x1="0"
        y1="0"
        x2="0"
        :y2="calculatedHeight"
        stroke="#000">
      </line>
      <svg
        v-if="deployment.showDeploymentFlag"
        class="js-deploy-info-box"
        :x="positionFlag(deployment)"
        y="0"
        width="134"
        height="80">
        <rect
          class="rect-text-metric deploy-info-rect rect-metric"
          x="1"
          y="1"
          rx="2"
          width="132"
          height="78">
        </rect>
        <text
          class="deploy-info-text text-metric-bold"
          transform="translate(5, 2)">
          Deployed
        </text>
        <!--The date info-->
        <g transform="translate(5, 20)">
          <text class="deploy-info-text">
            {{formatDate(deployment.time)}}
          </text>
          <text
            class="deploy-info-text text-metric-bold"
            x="62">
            {{formatTime(deployment.time)}}
          </text>
        </g>
        <line
          class="divider-line"
          x1="0"
          y1="38"
          x2="132"
          :y2="38"
          stroke="#000">
        </line>
        <!--Commit information-->
        <g transform="translate(5, 40)">
          <icon
            name="commit"
            :width="12"
            :height="12"
            :y="5">
          </icon>
          <a :xlink:href="deployment.commitUrl">
            <text
              class="deploy-info-text deploy-info-text-link"
              transform="translate(20, 2)">
              {{refText(deployment)}}
            </text>
          </a>
        </g>
        <!--Tag information-->
        <g
          transform="translate(5, 55)"
          v-if="deployment.tag">
          <icon
            name="label"
            :width="12"
            :height="12"
            :y="5">
          </icon>
          <a :xlink:href="deployment.tagUrl">
            <text
              class="deploy-info-text deploy-info-text-link"
              transform="translate(20, 2)"
              y="2">
              {{deployment.tag}}
            </text>
          </a>
        </g>
      </svg>
    </g>
    <svg
      height="0"
      width="0">
      <defs>
        <linearGradient
          id="shadow-gradient">
          <stop
            offset="0%"
            stop-color="#000"
            stop-opacity="0.4">
          </stop>
          <stop
            offset="100%"
            stop-color="#000"
            stop-opacity="0">
          </stop>
        </linearGradient>
      </defs>
    </svg>
  </g>
</template>


more polygons

more polygons

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/devKate/geometry-editor.git/'


Make copyright unselectable

Make copyright unselectable

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/barprive/barprive.com.git/'


Removes return button

Removes return button

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/NathanielDavid/Sumie.git/'


Fix issue with missing objects

Fix issue with missing objects

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/JonathanTroyer/OC-Summer-2017-Table-Web.git/'


header

header

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/vexojs/vexo.git/'


Update map when potential match is rejected.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/ux4c/ux4c-admin.git/'


reformatting edit issues panel to no longer use modal, #33

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/almibe/library-weasel-ghissue-view.git/'


Close #3

Close #3


Added smart tables

Added smart tables

<template>
  <modal name="dialog"
         classes="v--modal vue-dialog"
         :width="400"
         :height="180"
         :pivot-y="0.3"
         :adaptive="true"
         transition="fade"
         @before-open="beforeOpened"
         @before-close="beforeClosed">
    <flex-box direction="column" class="dialog-flex">

 0">
        <button v-for="(button, i) in buttons"
                :style="buttonStyle"
                :key="i"
                @click.stop="click(i, $event)">
          {{button.title}}
        </button>

    </flex-box>
  </modal>
</template>

error: pathspec 'src/Dialog.vue' did not match any file(s) known to git


Fix typo

Fix typo

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/stefanjudis/stefan-judis-website.git/'


[collapse] Support nested collapses + improvements fixes #236

<template>
    <transition
            @enter="enter"
            @after-enter="clearHeight"
            @leave="leave"
            @after-leave="clearHeight"
            name="collapse"
    >
        <div :class="classObject" v-show="show">
            <slot></slot>
        </div>
    </transition>
</template>

<style scoped>
    .collapse-enter-active, .collapse-leave-active {
        transition: all .35s ease;
        overflow: hidden;
    }

    .collapse-enter, .collapse-leave-to {
        /*height: 0;*/
    }
</style>

<script>
    export default {

        data() {
            return {
                show: false
            };
        },

        computed: {
            classObject() {
                return {
                    'navbar-collapse': this.isNav,
                    show: this.show
                };
            }
        },

        props: {
            isNav: {
                type: Boolean,
                default: false
            },
            id: {
                type: String,
                required: true
            }
        },

        methods: {
            toggle() {
                this.show = !this.show;
            },
            enter(el) {
                el.style.height = 'auto';
                const realHeight = getComputedStyle(el).height;
                el.style.height = '0px';
                el.offsetHeight; // force repaint
                el.style.height = realHeight;
            },
            leave(el) {
                el.style.height = 'auto';
                const realHeight = getComputedStyle(el).height;
                el.style.height = realHeight;
                el.offsetHeight; // force repaint
                el.style.height = '0px';
            },
            clearHeight(el) {
                el.style.height = null;
            },
        },

        created() {
            this.$root.$on('collapse::toggle', target => {
                if (target !== this.id) {
                    return;
                }
                this.toggle();
            });
        }
    };

</script>

<template>
    <transition @enter="enter" @leave="leave" name="collapse">
        <div :class="classObject" v-show="show">
            <slot></slot>
        </div>
    </transition>
</template>

<style scoped>
    .collapse-enter-active, .collapse-leave-active {
        transition: all .35s ease;
        overflow: hidden;
    }

    .collapse-enter, .collapse-leave-to {
        height: 0;
    }
</style>

<script>
    export default {

        data() {
            return {
                show: false
            };
        },

        computed: {
            classObject() {
                return {
                    'navbar-collapse': this.isNav,
                    show: this.show
                };
            }
        },

        props: {
            isNav: {
                type: Boolean,
                default: false
            },
            id: {
                type: String,
                required: true
            }
        },

        methods: {
            toggle() {
                this.show = !this.show;
            },
            enter(el) {
                let height = 0;
                Array.prototype.forEach.call(el.children, c => {
                    height += parseInt(getComputedStyle(c).height, 10);
                });
                el.style.height = `${height}px`;
            },
            leave(el) {
                el.style.height = null;
            }
        },

        created() {
            this.$root.$on('collapse::toggle', target => {
                if (target !== this.id) {
                    return;
                }
                this.toggle();
            });
        }
    };

</script>


Add DTM filter

Add DTM filter

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/PayBreak/basket.git/'


fix: 🐛 currency select disabled when amount invalid

<template>

Select currency

      <mew-select
        label="Currency"
        :items="currencyItems"
        :value="selectedCurrency"
        :disabled="disableCurrencySelect"
        :error-messages="currencyErrorMessages"
        is-custom
        @input="setCurrency"
      />

        How much do you want to spend?

        <mew-input
          v-model="amount"
          type="number"
          :error-messages="amountErrorMessages"
          class="mr-2"
        />
        <mew-select
          v-model="selectedFiat"
          :items="fiatCurrencyItems"
          is-custom
          class="selectedFiat"
        />

You will get

            {{ cryptoToFiat }}
            {{ selectedCryptoName }}

 ≈ {{ plusFeeF }}

            <mew-tooltip style="height: 23px">
              <template #contentSlot>

                  {{ includesFeeText }}

                  {{ networkFeeText }}

                  {{ dailyLimit }}

                  {{ monthlyLimit }}

              </template>
            </mew-tooltip>

        <v-skeleton-loader max-width="200px" type="heading" />

          Where should we send your crypto?

        <module-address-book
          ref="addressInput"
          label="Enter Crypto Address"
          :currency="selectedCryptoName"
          :enable-save-address="false"
          :is-home-page="true"
          @setAddress="setAddress"
        />

      <mew-button
        btn-size="xlarge"
        has-full-width
        :disabled="disableBuy"
        :title="buyBtnTitle"
        :is-valid-address-func="isValidToAddress"
        @click.native="buy"
      />

</template>

<template>

Select currency

      <mew-select
        label="Currency"
        :items="currencyItems"
        :value="selectedCurrency"
        :disabled="loading"
        :error-messages="currencyErrorMessages"
        is-custom
        @input="setCurrency"
      />

        How much do you want to spend?

        <mew-input
          v-model="amount"
          type="number"
          :error-messages="amountErrorMessages"
          class="mr-2"
        />
        <mew-select
          v-model="selectedFiat"
          :items="fiatCurrencyItems"
          is-custom
          class="selectedFiat"
        />

You will get

            {{ cryptoToFiat }}
            {{ selectedCryptoName }}

 ≈ {{ plusFeeF }}

            <mew-tooltip style="height: 23px">
              <template #contentSlot>

                  {{ includesFeeText }}

                  {{ networkFeeText }}

                  {{ dailyLimit }}

                  {{ monthlyLimit }}

              </template>
            </mew-tooltip>

        <v-skeleton-loader max-width="200px" type="heading" />

          Where should we send your crypto?

        <module-address-book
          ref="addressInput"
          label="Enter Crypto Address"
          :currency="selectedCryptoName"
          :enable-save-address="false"
          :is-home-page="true"
          @setAddress="setAddress"
        />

      <mew-button
        btn-size="xlarge"
        has-full-width
        :disabled="disableBuy"
        :title="buyBtnTitle"
        :is-valid-address-func="isValidToAddress"
        @click.native="buy"
      />

</template>


Force remove of all tooltips

Force remove of all tooltips

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/praneetloke/vue-material.git/'


fix version toggle

fix version toggle

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/jaceks-iRonin/vue-material-test.git/'


Enleve css inutile pour barre de menu (oubli)


Made sure VNavigationDrawer calls genOverlay on mount if active

<script>
  import Overlayable from '../../mixins/overlayable'
  import Themeable from '../../mixins/themeable'

  import ClickOutside from '../../directives/click-outside'
  import Resize from '../../directives/resize'
  import Touch from '../../directives/touch'

  export default {
    name: 'v-navigation-drawer',

    mixins: [Overlayable, Themeable],

    directives: {
      ClickOutside,
      Resize,
      Touch
    },

    data () {
      return {
        isActive: this.value,
        isBooted: false,
        isMobile: false,
        touchArea: {
          left: 0,
          right: 0
        }
      }
    },

    props: {
      absolute: Boolean,
      clipped: Boolean,
      disableRouteWatcher: Boolean,
      enableResizeWatcher: Boolean,
      height: String,
      floating: Boolean,
      miniVariant: Boolean,
      mobileBreakPoint: {
        type: Number,
        default: 1024
      },
      permanent: Boolean,
      persistent: Boolean,
      right: Boolean,
      temporary: Boolean,
      touchless: Boolean,
      value: { required: false }
    },

    computed: {
      calculatedHeight () {
        return this.height || '100%'
      },
      classes () {
        return {
          'navigation-drawer': true,
          'navigation-drawer--absolute': this.absolute,
          'navigation-drawer--clipped': this.clipped,
          'navigation-drawer--close': !this.isActive,
          'navigation-drawer--floating': this.floating,
          'navigation-drawer--is-booted': this.isBooted,
          'navigation-drawer--is-mobile': this.isMobile,
          'navigation-drawer--mini-variant': this.miniVariant,
          'navigation-drawer--open': this.isActive,
          'navigation-drawer--permanent': this.permanent,
          'navigation-drawer--persistent': this.persistent,
          'navigation-drawer--right': this.right,
          'navigation-drawer--temporary': this.temporary,
          'theme--dark': this.dark,
          'theme--light': this.light
        }
      },
      showOverlay () {
        return !this.permanent &&
          this.isActive &&
          (this.temporary || this.isMobile)
      }
    },

    watch: {
      $route () {
        if (!this.disableRouteWatcher) {
          this.isActive = !this.closeConditional()
        }
      },
      isActive (val) {
        this.$emit('input', val)
        this.showOverlay && val && this.genOverlay() || this.removeOverlay()
        this.$el.scrollTop = 0
      },
      isMobile (val) {
        !val && this.removeOverlay()
      },
      permanent (val) {
        this.$emit('input', val)
      },
      value (val) {
        if (this.permanent) return
        if (val !== this.isActive) this.isActive = val
      }
    },

    mounted () {
      this.$vuetify.load(this.init)
    },

    methods: {
      init () {
        this.checkIfMobile()
        setTimeout(() => (this.isBooted = true), 0)

        if (this.permanent) this.isActive = true
        else if (this.isMobile) this.isActive = false
        else if (!this.value &&
          (this.persistent || this.temporary)
        ) this.isActive = false

        this.isActive && this.genOverlay()
      },
      checkIfMobile () {
        this.isMobile = window.innerWidth < parseInt(this.mobileBreakPoint)
      },
      closeConditional () {
        return !this.permanent && (this.temporary || this.isMobile)
      },
      onResize () {
        clearTimeout(this.resizeTimeout)

        if (!this.enableResizeWatcher ||
          this.permanent ||
          this.temporary
        ) return

        this.resizeTimeout = setTimeout(() => {
          this.checkIfMobile()
          this.isActive = !this.isMobile
        }, 200)
      },
      swipeRight (e) {
        if (this.isActive && !this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (!this.right &&
          e.touchstartX <= this.touchArea.left
        ) this.isActive = true
        else if (this.right && this.isActive) this.isActive = false
      },
      swipeLeft (e) {
        if (this.isActive && this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (this.right &&
          e.touchstartX >= this.touchArea.right
        ) this.isActive = true
        else if (!this.right && this.isActive) this.isActive = false
      },
      calculateTouchArea () {
        if (!this.$el.parentNode) return
        const parentRect = this.$el.parentNode.getBoundingClientRect()

        this.touchArea = {
          left: parentRect.left + 50,
          right: parentRect.right - 50
        }
      },
      genDirectives () {
        const directives = [
          {
            name: 'click-outside',
            value: this.closeConditional
          },
          {
            name: 'resize',
            value: this.onResize
          }
        ]

        !this.touchless && directives.push({
          name: 'touch',
          value: {
            parent: true,
            left: this.swipeLeft,
            right: this.swipeRight
          }
        })

        return directives
      }
    },

    render (h) {
      const data = {
        'class': this.classes,
        style: { height: this.calculatedHeight },
        directives: this.genDirectives(),
        on: Object.assign({}, {
          click: () => this.$emit('update:miniVariant', false)
        }, this.$listeners)
      }

      return h('aside', data, [
        this.$slots.default,
        h('div', { 'class': 'navigation-drawer__border' })
      ])
    }
  }
</script>

<style lang="stylus" src="../../stylus/components/_navigation-drawer.styl"></style>

<script>
  import Overlayable from '../../mixins/overlayable'
  import Themeable from '../../mixins/themeable'

  import ClickOutside from '../../directives/click-outside'
  import Resize from '../../directives/resize'
  import Touch from '../../directives/touch'

  export default {
    name: 'v-navigation-drawer',

    mixins: [Overlayable, Themeable],

    directives: {
      ClickOutside,
      Resize,
      Touch
    },

    data () {
      return {
        isActive: this.value,
        isBooted: false,
        isMobile: false,
        touchArea: {
          left: 0,
          right: 0
        }
      }
    },

    props: {
      absolute: Boolean,
      clipped: Boolean,
      disableRouteWatcher: Boolean,
      enableResizeWatcher: Boolean,
      height: String,
      floating: Boolean,
      miniVariant: Boolean,
      mobileBreakPoint: {
        type: Number,
        default: 1024
      },
      permanent: Boolean,
      persistent: Boolean,
      right: Boolean,
      temporary: Boolean,
      touchless: Boolean,
      value: { required: false }
    },

    computed: {
      calculatedHeight () {
        return this.height || '100%'
      },
      classes () {
        return {
          'navigation-drawer': true,
          'navigation-drawer--absolute': this.absolute,
          'navigation-drawer--clipped': this.clipped,
          'navigation-drawer--close': !this.isActive,
          'navigation-drawer--floating': this.floating,
          'navigation-drawer--is-booted': this.isBooted,
          'navigation-drawer--is-mobile': this.isMobile,
          'navigation-drawer--mini-variant': this.miniVariant,
          'navigation-drawer--open': this.isActive,
          'navigation-drawer--permanent': this.permanent,
          'navigation-drawer--persistent': this.persistent,
          'navigation-drawer--right': this.right,
          'navigation-drawer--temporary': this.temporary,
          'theme--dark': this.dark,
          'theme--light': this.light
        }
      },
      showOverlay () {
        return !this.permanent &&
          this.isActive &&
          (this.temporary || this.isMobile)
      }
    },

    watch: {
      $route () {
        if (!this.disableRouteWatcher) {
          this.isActive = !this.closeConditional()
        }
      },
      isActive (val) {
        this.$emit('input', val)
        this.showOverlay && val && this.genOverlay() || this.removeOverlay()
        this.$el.scrollTop = 0
      },
      isMobile (val) {
        !val && this.removeOverlay()
      },
      permanent (val) {
        this.$emit('input', val)
      },
      value (val) {
        if (this.permanent) return
        if (val !== this.isActive) this.isActive = val
      }
    },

    mounted () {
      this.$vuetify.load(this.init)
    },

    methods: {
      init () {
        this.checkIfMobile()
        setTimeout(() => (this.isBooted = true), 0)

        if (this.permanent) this.isActive = true
        else if (this.isMobile) this.isActive = false
        else if (!this.value &&
          (this.persistent || this.temporary)
        ) this.isActive = false
      },
      checkIfMobile () {
        this.isMobile = window.innerWidth < parseInt(this.mobileBreakPoint)
      },
      closeConditional () {
        return !this.permanent && (this.temporary || this.isMobile)
      },
      onResize () {
        clearTimeout(this.resizeTimeout)

        if (!this.enableResizeWatcher ||
          this.permanent ||
          this.temporary
        ) return

        this.resizeTimeout = setTimeout(() => {
          this.checkIfMobile()
          this.isActive = !this.isMobile
        }, 200)
      },
      swipeRight (e) {
        if (this.isActive && !this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (!this.right &&
          e.touchstartX <= this.touchArea.left
        ) this.isActive = true
        else if (this.right && this.isActive) this.isActive = false
      },
      swipeLeft (e) {
        if (this.isActive && this.right) return
        this.calculateTouchArea()

        if (Math.abs(e.touchendX - e.touchstartX) < 100) return
        else if (this.right &&
          e.touchstartX >= this.touchArea.right
        ) this.isActive = true
        else if (!this.right && this.isActive) this.isActive = false
      },
      calculateTouchArea () {
        if (!this.$el.parentNode) return
        const parentRect = this.$el.parentNode.getBoundingClientRect()

        this.touchArea = {
          left: parentRect.left + 50,
          right: parentRect.right - 50
        }
      },
      genDirectives () {
        const directives = [
          {
            name: 'click-outside',
            value: this.closeConditional
          },
          {
            name: 'resize',
            value: this.onResize
          }
        ]

        !this.touchless && directives.push({
          name: 'touch',
          value: {
            parent: true,
            left: this.swipeLeft,
            right: this.swipeRight
          }
        })

        return directives
      }
    },

    render (h) {
      const data = {
        'class': this.classes,
        style: { height: this.calculatedHeight },
        directives: this.genDirectives(),
        on: Object.assign({}, {
          click: () => this.$emit('update:miniVariant', false)
        }, this.$listeners)
      }

      return h('aside', data, [
        this.$slots.default,
        h('div', { 'class': 'navigation-drawer__border' })
      ])
    }
  }
</script>

<style lang="stylus" src="../../stylus/components/_navigation-drawer.styl"></style>


添加加载动画

添加加载动画

error: pathspec 'source/component/imageUpload.vue' did not match any file(s) known to git


[improved] responsiveness

[improved] responsiveness

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/mtlong29/JBShop_Site.git/'


Wrong reference

Wrong reference

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/MrTyx/Habitica-MagicWand.git/'


fixing alignment issue on 500 error as it has a very long status code description.

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/codeweavers/Consign.git/'


Remove unused function

Remove unused function


Pixel design

Pixel design


Change def page description to something awesome


Add ItemTalk

Add ItemTalk

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/mplusmuseum/mplusmuseum-stories.git/'


Add social media GTM event tracker

Add social media GTM event tracker


IssueNotesRefactor: Fix GFM rendering after edits come in.

<script>
  import issueNoteEditedText from './issue_note_edited_text.vue';
  import issueNoteAwardsList from './issue_note_awards_list.vue';
  import issueNoteAttachment from './issue_note_attachment.vue';
  import issueNoteForm from './issue_note_form.vue';
  import TaskList from '../../task_list';
  import autosave from '../mixins/autosave';

  export default {
    props: {
      note: {
        type: Object,
        required: true,
      },
      canEdit: {
        type: Boolean,
        required: true,
      },
      isEditing: {
        type: Boolean,
        required: false,
        default: false,
      },
    },
    mixins: [
      autosave,
    ],
    components: {
      issueNoteEditedText,
      issueNoteAwardsList,
      issueNoteAttachment,
      issueNoteForm,
    },
    computed: {
      noteBody() {
        return this.note.note;
      },
    },
    methods: {
      renderGFM() {
        $(this.$refs['note-body']).renderGFM();
      },
      initTaskList() {
        if (this.canEdit) {
          this.taskList = new TaskList({
            dataType: 'note',
            fieldName: 'note',
            selector: '.notes',
          });
        }
      },
      handleFormUpdate(note, parentElement, callback) {
        this.$emit('handleFormUpdate', note, parentElement, callback);
      },
      formCancelHandler(shouldConfirm, isDirty) {
        this.$emit('cancelFormEdition', shouldConfirm, isDirty);
      },
    },
    mounted() {
      this.renderGFM();
      this.initTaskList();

      if (this.isEditing) {
        this.initAutoSave();
      }
    },
    updated() {
      this.initTaskList();
      this.renderGFM();

      if (this.isEditing) {
        if (!this.autosave) {
          this.initAutoSave();
        } else {
          this.setAutoSave();
        }
      }
    },
  };
</script>

<template>
  <div
    :class="{ 'js-task-list-container': canEdit }"
    ref="note-body"
    class="note-body">
    <div
      v-html="note.note_html"
      class="note-text md"></div>
    <issue-note-form
      v-if="isEditing"
      ref="noteForm"
      @handleFormUpdate="handleFormUpdate"
      @cancelFormEdition="formCancelHandler"
      :is-editing="isEditing"
      :note-body="noteBody"
      :note-id="note.id"
      />
    <textarea
      v-if="canEdit"
      v-model="note.note"
      :data-update-url="note.path"
      class="hidden js-task-list-field"></textarea>
    <issue-note-edited-text
      v-if="note.last_edited_at"
      :edited-at="note.last_edited_at"
      :edited-by="note.last_edited_by"
      action-text="Edited"
      />
    <issue-note-awards-list
      v-if="note.award_emoji.length"
      :note-id="note.id"
      :note-author-id="note.author.id"
      :awards="note.award_emoji"
      :toggle-award-path="note.toggle_award_path"
      />
    <issue-note-attachment
      v-if="note.attachment"
      :attachment="note.attachment"
      />
  </div>
</template>

<script>
  import issueNoteEditedText from './issue_note_edited_text.vue';
  import issueNoteAwardsList from './issue_note_awards_list.vue';
  import issueNoteAttachment from './issue_note_attachment.vue';
  import issueNoteForm from './issue_note_form.vue';
  import TaskList from '../../task_list';
  import autosave from '../mixins/autosave';

  export default {
    props: {
      note: {
        type: Object,
        required: true,
      },
      canEdit: {
        type: Boolean,
        required: true,
      },
      isEditing: {
        type: Boolean,
        required: false,
        default: false,
      },
    },
    mixins: [
      autosave,
    ],
    components: {
      issueNoteEditedText,
      issueNoteAwardsList,
      issueNoteAttachment,
      issueNoteForm,
    },
    computed: {
      noteBody() {
        return this.note.note;
      },
    },
    methods: {
      renderGFM() {
        $(this.$refs['note-body']).renderGFM();
      },
      initTaskList() {
        if (this.canEdit) {
          this.taskList = new TaskList({
            dataType: 'note',
            fieldName: 'note',
            selector: '.notes',
          });
        }
      },
      handleFormUpdate(note, parentElement, callback) {
        this.$emit('handleFormUpdate', note, parentElement, callback);
      },
      formCancelHandler(shouldConfirm, isDirty) {
        this.$emit('cancelFormEdition', shouldConfirm, isDirty);
      },
    },
    mounted() {
      this.renderGFM();
      this.initTaskList();
      if (this.isEditing) {
        this.initAutoSave();
      }
    },
    updated() {
      this.initTaskList();
      if (this.isEditing) {
        if (!this.autosave) {
          this.initAutoSave();
        } else {
          this.setAutoSave();
        }
      }
    },
  };
</script>

<template>
  <div
    :class="{ 'js-task-list-container': canEdit }"
    ref="note-body"
    class="note-body">
    <div
      v-html="note.note_html"
      class="note-text md"></div>
    <issue-note-form
      v-if="isEditing"
      ref="noteForm"
      @handleFormUpdate="handleFormUpdate"
      @cancelFormEdition="formCancelHandler"
      :is-editing="isEditing"
      :note-body="noteBody"
      :note-id="note.id"
      />
    <textarea
      v-if="canEdit"
      v-model="note.note"
      :data-update-url="note.path"
      class="hidden js-task-list-field"></textarea>
    <issue-note-edited-text
      v-if="note.last_edited_at"
      :edited-at="note.last_edited_at"
      :edited-by="note.last_edited_by"
      action-text="Edited"
      />
    <issue-note-awards-list
      v-if="note.award_emoji.length"
      :note-id="note.id"
      :note-author-id="note.author.id"
      :awards="note.award_emoji"
      :toggle-award-path="note.toggle_award_path"
      />
    <issue-note-attachment
      v-if="note.attachment"
      :attachment="note.attachment"
      />
  </div>
</template>


Minor update

Minor update

<template>
  <div id="Category">
    Core Category
  </div>
</template>

<script>
import builder from 'bodybuilder'

import { breadCrumbRoutes } from '@vue-storefront/store/helpers'
import config from 'config'
import Sidebar from 'core/components/blocks/Category/Sidebar.vue'
import ProductListing from 'core/components/ProductListing.vue'
import Breadcrumbs from 'core/components/Breadcrumbs.vue'
import { optionLabel } from '@vue-storefront/store/modules/attribute/helpers'
import EventBus from 'core/plugins/event-bus'
import Composite from 'core/mixins/composite'
import _ from 'lodash'
import i18n from 'core/lib/i18n'

function filterChanged (filterOption) { // slection of product variant on product page
  if (this.filters.chosen[filterOption.attribute_code] && ((_.toString(filterOption.id) === _.toString(this.filters.chosen[filterOption.attribute_code].id)) || filterOption.id === this.filters.chosen[filterOption.attribute_code].id)) { // for price filter it's a string
    delete this.filters.chosen[filterOption.attribute_code]
  } else {
    this.filters.chosen[filterOption.attribute_code] = filterOption
  }

  let filterQr = baseFilterQuery(config.products.defaultFilters, this.$store.state.category.current)

  let attrFilterBuilder = (filterQr, attrPostfix = '') => {
    for (let code of Object.keys(this.filters.chosen)) {
      const filter = this.filters.chosen[code]

      if (filter.attribute_code !== 'price') {
        filterQr = filterQr.andFilter('match', filter.attribute_code + attrPostfix, filter.id)
      } else { // multi should be possible filter here?
        const rangeqr = {}
        if (filter.from) {
          rangeqr['gte'] = filter.from
        }
        if (filter.to) {
          rangeqr['lte'] = filter.to
        }
        filterQr = filterQr.andFilter('range', filter.attribute_code, rangeqr)
      }
    }
    return filterQr
  }
  filterQr = filterQr.orFilter('bool', (b) => attrFilterBuilder(b).filter('match', 'type_id', 'simple'))
    .orFilter('bool', (b) => attrFilterBuilder(b, '_options').filter('match', 'type_id', 'configurable'))

  const fsC = Object.assign({}, this.filters.chosen) // create a copy because it will be used asynchronously (take a look below)
  filterData({ populateAggregations: false, searchProductQuery: filterQr, store: this.$store, route: this.$route, current: this.pagination.current, perPage: this.pagination.perPage, filters: config.products.defaultFilters }).then((res) => {
    EventBus.$emit('product-after-configured', { configuration: fsC })
  }) // because already aggregated
}

function baseFilterQuery (filters, parentCategory) { // TODO add aggregation of color_options and size_options fields
  let searchProductQuery = builder().query('range', 'price', { 'gt': 0 }).andFilter('range', 'visibility', { 'gte': 3, 'lte': 4 }/** Magento visibility in search & categories */)

  // add filters to query
  for (let attrToFilter of filters) {
    if (attrToFilter !== 'price') {
      searchProductQuery = searchProductQuery.aggregation('terms', attrToFilter)
      searchProductQuery = searchProductQuery.aggregation('terms', attrToFilter + '_options')
    } else {
      searchProductQuery = searchProductQuery.aggregation('terms', attrToFilter)
      searchProductQuery.aggregation('range', 'price', {
        ranges: [
          { from: 0, to: 50 },
          { from: 50, to: 100 },
          { from: 100, to: 150 },
          { from: 150 }
        ]
      })
    }
  }

  let childCats = [parentCategory.id]
  if (parentCategory.children_data) {
    let recurCatFinderBuilder = (category) => {
      if (!category) {
        return
      }

      if (!category.children_data) {
        return
      }

      for (let sc of category.children_data) {
        if (sc && sc.id) {
          childCats.push(sc.id)
        }
        recurCatFinderBuilder(sc)
      }
    }
    recurCatFinderBuilder(parentCategory)
  }
  searchProductQuery = searchProductQuery.filter('terms', 'category.category_id', childCats)
  return searchProductQuery
}

// TODO: Refactor - move this function to the Vuex store
function filterData ({ populateAggregations = false, filters = [], searchProductQuery, store, route, current = 0, perPage = 50, includeFields = null, excludeFields = null }) {
  if (config.entities.twoStageCaching && config.entities.optimize && !global.$VS.isSSR && !global.$VS.twoStageCachingDisabled) { // only client side, only when two stage caching enabled
    includeFields = config.entities.productListWithChildren.includeFields // we need configurable_children for filters to work
    excludeFields = config.entities.productListWithChildren.excludeFields
    console.log('Using two stage caching for performance optimization - executing first stage product pre-fetching')
  } else {
    if (global.$VS.twoStageCachingDisabled) {
      console.log('Two stage caching is disabled runtime because of no performance gain')
    } else {
      console.log('Two stage caching is disabled by the config')
    }
  }
  let t0 = new Date().getTime()
  let precachedQuery = searchProductQuery.build()
  let productPromise = store.dispatch('product/list', {
    query: precachedQuery,
    start: current,
    size: perPage,
    excludeFields: excludeFields,
    includeFields: includeFields
  }).then(function (res) {
    let t1 = new Date().getTime()
    global.$VS.twoStageCachingDelta1 = t1 - t0

    let subloaders = []
    if (!res || (res.noresults)) {
      EventBus.$emit('notification', {
        type: 'warning',
        message: i18n.t('No products synchronized for this category. Please come back while online!'),
        action1: { label: 'OK', action: 'close' }
      })
      store.dispatch('product/reset')
      store.state.product.list = { items: [] } // no products to show TODO: refactor to store.state.category.reset() and store.state.product.reset()
      // store.state.category.filters = { color: [], size: [], price: [] }
    } else {
      if (populateAggregations === true) { // populate filter aggregates
        for (let attrToFilter of filters) { // fill out the filter options
          store.state.category.filters.available[attrToFilter] = []

          let uniqueFilterValues = new Set()
          if (attrToFilter !== 'price') {
            if (res.aggregations['agg_terms_' + attrToFilter]) {
              let buckets = res.aggregations['agg_terms_' + attrToFilter].buckets
              if (res.aggregations['agg_terms_' + attrToFilter + '_options']) {
                buckets = buckets.concat(res.aggregations['agg_terms_' + attrToFilter + '_options'].buckets)
              }

              for (let option of buckets) {
                uniqueFilterValues.add(_.toString(option.key))
              }
            }

            for (let key of uniqueFilterValues.values()) {
              const label = optionLabel(store.state.attribute, { attributeKey: attrToFilter, optionId: key })
              if (_.trim(label) !== '') { // is there any situation when label could be empty and we should still support it?
                store.state.category.filters.available[attrToFilter].push({
                  id: key,
                  label: label
                })
              }
            }
          } else { // special case is range filter for prices
            if (res.aggregations['agg_range_' + attrToFilter]) {
              let index = 0
              let count = res.aggregations['agg_range_' + attrToFilter].buckets.length
              for (let option of res.aggregations['agg_range_' + attrToFilter].buckets) {
                store.state.category.filters.available[attrToFilter].push({
                  id: option.key,
                  from: option.from,
                  to: option.to,
                  label: (index === 0 || (index === count - 1)) ? (option.to ? '< $' + option.to : '> $' + option.from) : '$' + option.from + (option.to ? ' - ' + option.to : '')// TODO: add better way for formatting, extract currency sign
                })
                index++
              }
            }
          }
        }
      }
    }
    return subloaders
  }).catch((err) => {
    console.info(err)
    EventBus.$emit('notification', {
      type: 'warning',
      message: i18n.t('No products synchronized for this category. Please come back while online!'),
      action1: { label: 'OK', action: 'close' }
    })
  })

  if (config.entities.twoStageCaching && config.entities.optimize && !global.$VS.isSSR && !global.$VS.twoStageCachingDisabled) { // second stage - request for caching entities
    console.log('Using two stage caching for performance optimization - executing second stage product caching') // TODO: in this case we can pre-fetch products in advance getting more products than set by pageSize
    store.dispatch('product/list', {
      query: precachedQuery,
      start: current,
      size: perPage,
      excludeFields: null,
      includeFields: null
    }).catch((err) => {
      console.info("Problem with second stage caching - couldn't store the data")
      console.info(err)
    }).then((res) => {
      let t2 = new Date().getTime()
      global.$VS.twoStageCachingDelta2 = t2 - t0
      console.log('Using two stage caching for performance optimization - Time comparison stage1 vs stage2', global.$VS.twoStageCachingDelta1, global.$VS.twoStageCachingDelta2)
      if (global.$VS.twoStageCachingDelta1 > global.$VS.twoStageCachingDelta2) { // two stage caching is not making any good
        global.$VS.twoStageCachingDisabled = true
        console.log('Disabling two stage caching')
      }
    })
  }
  return productPromise
}

export default {
  name: 'Category',
  metaInfo () {
    return {
      title: this.$route.meta.title || this.categoryName,
      meta: this.$route.meta.description ? [{vmid: 'description', description: this.$route.meta.description}] : []
    }
  },
  mixins: [Composite],
  methods: {
    onFilterChanged (filterData) {
      (filterChanged.bind(this))(filterData)
    },
    fetchData ({ store, route }) {
      let self = this
      let searchProductQuery = baseFilterQuery(config.products.defaultFilters, store.state.category.current)

      if (self.category) { // fill breadcrumb data - TODO: extract it to a helper to be used on product page
        this.$bus.$emit('current-category-changed', store.state.category.current_path)
        store.dispatch('attribute/list', { // load filter attributes for this specific category
          filterValues: config.products.defaultFilters// TODO: assign specific filters/ attribute codes dynamicaly to specific categories
        })
      }
      return filterData({ searchProductQuery: searchProductQuery, populateAggregations: true, store: store, route: route, current: self.pagination.current, perPage: self.pagination.perPage, filters: config.products.defaultFilters })
    },
    validateRoute ({store, route}) {
      let self = this
      if (store == null) {
        store = self.$store
      }
      if (route == null) {
        route = self.$route
      }
      let slug = route.params.slug
      this.filters.chosen = {} // reset selected filters
      this.$bus.$emit('filter-reset')

      store.dispatch('category/single', { key: 'slug', value: slug }).then((category) => {
        store.state.category.breadcrumbs.routes = breadCrumbRoutes(store.state.category.current_path)

        if (!self.category) {
          self.$router.push('/')
        } else {
          self.fetchData({store: store, route: route})
          EventBus.$emitFilter('category-after-load', { store: store, route: route })
        }
      })
    }
  },
  watch: {
    '$route': 'validateRoute'
  },
  asyncData ({ store, route }) { // this is for SSR purposes to prefetch data
    return new Promise((resolve, reject) => {
      console.log('Entering asyncData for Category root ' + new Date())
      const defaultFilters = config.products.defaultFilters
      store.dispatch('category/list', { includeFields: config.entities.optimize && global.$VS.isSSR ? config.entities.category.includeFields : null }).then((categories) => {
        store.dispatch('attribute/list', { // load filter attributes for this specific category
          filterValues: defaultFilters, // TODO: assign specific filters/ attribute codes dynamicaly to specific categories
          includeFields: config.entities.optimize && global.$VS.isSSR ? config.entities.attribute.includeFields : null
        }).then((attrs) => {
          store.dispatch('category/single', { key: 'slug', value: route.params.slug }).then((parentCategory) => {
            filterData({ searchProductQuery: baseFilterQuery(defaultFilters, parentCategory), populateAggregations: true, store: store, route: route, current: 0, perPage: 50, filters: defaultFilters, includeFields: config.entities.optimize && global.$VS.isSSR ? config.entities.productList.includeFields : null, excludeFields: config.entities.optimize && global.$VS.isSSR ? config.entities.productList.excludeFields : null }).then((subloaders) => {
              Promise.all(subloaders).then((results) => {
                store.state.category.breadcrumbs.routes = breadCrumbRoutes(store.state.category.current_path)

                EventBus.$emitFilter('category-after-load', { store: store, route: route }).then((results) => {
                  return resolve()
                }).catch((err) => {
                  console.error(err)
                  return resolve()
                })
              })
            })
          }).catch(err => {
            console.error(err)
            return reject(Error(err))
          })
        })
      })
    })
  },
  beforeMount () {
    this.$bus.$on('filter-changed-category', this.onFilterChanged)
  },
  beforeDestroy () {
    this.$bus.$off('filter-changed-category', this.onFilterChanged)
  },
  computed: {
    products () {
      return this.$store.state.product.list.items
    },
    productsCounter () {
      return this.$store.state.product.list.items.length
    },
    isCategoryEmpty () {
      return (!(this.$store.state.product.list.items) || this.$store.state.product.list.items.length === 0)
    },
    category () {
      return this.$store.state.category.current
    },
    categoryName () {
      return this.$store.state.category.current ? this.$store.state.category.current.name : ''
    },
    categoryId () {
      return this.$store.state.category.current ? this.$store.state.category.current.id : ''
    },
    filters () {
      return this.$store.state.category.filters
    },
    breadcrumbs () {
      return this.$store.state.category.breadcrumbs
    }

  },
  data () {
    return {
      pagination: {
        perPage: 50,
        current: 0,
        enabled: false
      }
    }
  },
  components: {
    ProductListing,
    Breadcrumbs,
    Sidebar
  }
}
</script>

<template>
  <div id="Category">
    Core Category
  </div>
</template>

<script>
import builder from 'bodybuilder'

import { breadCrumbRoutes } from '@vue-storefront/store/helpers'
import config from 'config'
import Sidebar from 'core/components/blocks/Category/Sidebar.vue'
import ProductListing from 'core/components/ProductListing.vue'
import Breadcrumbs from 'core/components/Breadcrumbs.vue'
import { optionLabel } from '@vue-storefront/store/modules/attribute/helpers'
import EventBus from 'core/plugins/event-bus'
import Composite from 'core/mixins/composite'
import _ from 'lodash'
import i18n from 'core/lib/i18n'

function filterChanged (filterOption) { // slection of product variant on product page
  if (this.filters.chosen[filterOption.attribute_code] && ((_.toString(filterOption.id) === _.toString(this.filters.chosen[filterOption.attribute_code].id)) || filterOption.id === this.filters.chosen[filterOption.attribute_code].id)) { // for price filter it's a string
    delete this.filters.chosen[filterOption.attribute_code]
  } else {
    this.filters.chosen[filterOption.attribute_code] = filterOption
  }

  let filterQr = baseFilterQuery(config.products.defaultFilters, this.$store.state.category.current)

  let attrFilterBuilder = (filterQr, attrPostfix = '') => {
    for (let code of Object.keys(this.filters.chosen)) {
      const filter = this.filters.chosen[code]

      if (filter.attribute_code !== 'price') {
        filterQr = filterQr.andFilter('match', filter.attribute_code + attrPostfix, filter.id)
      } else { // multi should be possible filter here?
        const rangeqr = {}
        if (filter.from) {
          rangeqr['gte'] = filter.from
        }
        if (filter.to) {
          rangeqr['lte'] = filter.to
        }
        filterQr = filterQr.andFilter('range', filter.attribute_code, rangeqr)
      }
    }
    return filterQr
  }
  filterQr = filterQr.orFilter('bool', (b) => attrFilterBuilder(b).filter('match', 'type_id', 'simple'))
    .orFilter('bool', (b) => attrFilterBuilder(b, '_options').filter('match', 'type_id', 'configurable'))

  const fsC = Object.assign({}, this.filters.chosen) // create a copy because it will be used asynchronously (take a look below)
  filterData({ populateAggregations: false, searchProductQuery: filterQr, store: this.$store, route: this.$route, current: this.pagination.current, perPage: this.pagination.perPage, filters: config.products.defaultFilters }).then((res) => {
    EventBus.$emit('product-after-configured', { configuration: fsC })
  }) // because already aggregated
}

function baseFilterQuery (filters, parentCategory) { // TODO add aggregation of color_options and size_options fields
  let searchProductQuery = builder().query('range', 'price', { 'gt': 0 }).andFilter('range', 'visibility', { 'gte': 3, 'lte': 4 }/** Magento visibility in search & categories */)

  // add filters to query
  for (let attrToFilter of filters) {
    if (attrToFilter !== 'price') {
      searchProductQuery = searchProductQuery.aggregation('terms', attrToFilter)
      searchProductQuery = searchProductQuery.aggregation('terms', attrToFilter + '_options')
    } else {
      searchProductQuery = searchProductQuery.aggregation('terms', attrToFilter)
      searchProductQuery.aggregation('range', 'price', {
        ranges: [
          { from: 0, to: 50 },
          { from: 50, to: 100 },
          { from: 100, to: 150 },
          { from: 150 }
        ]
      })
    }
  }

  let childCats = [parentCategory.id]
  if (parentCategory.children_data) {
    let recurCatFinderBuilder = (category) => {
      if (!category) {
        return
      }

      if (!category.children_data) {
        return
      }

      for (let sc of category.children_data) {
        if (sc && sc.id) {
          childCats.push(sc.id)
        }
        recurCatFinderBuilder(sc)
      }
    }
    recurCatFinderBuilder(parentCategory)
  }
  searchProductQuery = searchProductQuery.filter('terms', 'category.category_id', childCats)
  return searchProductQuery
}

// TODO: Refactor - move this function to the Vuex store
function filterData ({ populateAggregations = false, filters = [], searchProductQuery, store, route, current = 0, perPage = 50, includeFields = null, excludeFields = null }) {
  if (config.entities.twoStageCaching && config.entities.optimize && !global.$VS.isSSR && !global.$VS.twoStageCachingDisabled) { // only client side, only when two stage caching enabled
    includeFields = config.entities.productListWithChildren.includeFields // we need configurable_children for filters to work
    excludeFields = config.entities.productList.excludeFields
    console.log('Using two stage caching for performance optimization - executing first stage product pre-fetching')
  } else {
    if (global.$VS.twoStageCachingDisabled) {
      console.log('Two stage caching is disabled runtime because of no performance gain')
    } else {
      console.log('Two stage caching is disabled by the config')
    }
  }
  let t0 = new Date().getTime()
  let precachedQuery = searchProductQuery.build()
  let productPromise = store.dispatch('product/list', {
    query: precachedQuery,
    start: current,
    size: perPage,
    excludeFields: excludeFields,
    includeFields: includeFields
  }).then(function (res) {
    let t1 = new Date().getTime()
    global.$VS.twoStageCachingDelta1 = t1 - t0

    let subloaders = []
    if (!res || (res.noresults)) {
      EventBus.$emit('notification', {
        type: 'warning',
        message: i18n.t('No products synchronized for this category. Please come back while online!'),
        action1: { label: 'OK', action: 'close' }
      })
      store.dispatch('product/reset')
      store.state.product.list = { items: [] } // no products to show TODO: refactor to store.state.category.reset() and store.state.product.reset()
      // store.state.category.filters = { color: [], size: [], price: [] }
    } else {
      if (populateAggregations === true) { // populate filter aggregates
        for (let attrToFilter of filters) { // fill out the filter options
          store.state.category.filters.available[attrToFilter] = []

          let uniqueFilterValues = new Set()
          if (attrToFilter !== 'price') {
            if (res.aggregations['agg_terms_' + attrToFilter]) {
              let buckets = res.aggregations['agg_terms_' + attrToFilter].buckets
              if (res.aggregations['agg_terms_' + attrToFilter + '_options']) {
                buckets = buckets.concat(res.aggregations['agg_terms_' + attrToFilter + '_options'].buckets)
              }

              for (let option of buckets) {
                uniqueFilterValues.add(_.toString(option.key))
              }
            }

            for (let key of uniqueFilterValues.values()) {
              const label = optionLabel(store.state.attribute, { attributeKey: attrToFilter, optionId: key })
              if (_.trim(label) !== '') { // is there any situation when label could be empty and we should still support it?
                store.state.category.filters.available[attrToFilter].push({
                  id: key,
                  label: label
                })
              }
            }
          } else { // special case is range filter for prices
            if (res.aggregations['agg_range_' + attrToFilter]) {
              let index = 0
              let count = res.aggregations['agg_range_' + attrToFilter].buckets.length
              for (let option of res.aggregations['agg_range_' + attrToFilter].buckets) {
                store.state.category.filters.available[attrToFilter].push({
                  id: option.key,
                  from: option.from,
                  to: option.to,
                  label: (index === 0 || (index === count - 1)) ? (option.to ? '< $' + option.to : '> $' + option.from) : '$' + option.from + (option.to ? ' - ' + option.to : '')// TODO: add better way for formatting, extract currency sign
                })
                index++
              }
            }
          }
        }
      }
    }
    return subloaders
  }).catch((err) => {
    console.info(err)
    EventBus.$emit('notification', {
      type: 'warning',
      message: i18n.t('No products synchronized for this category. Please come back while online!'),
      action1: { label: 'OK', action: 'close' }
    })
  })

  if (config.entities.twoStageCaching && config.entities.optimize && !global.$VS.isSSR && !global.$VS.twoStageCachingDisabled) { // second stage - request for caching entities
    console.log('Using two stage caching for performance optimization - executing second stage product caching') // TODO: in this case we can pre-fetch products in advance getting more products than set by pageSize
    store.dispatch('product/list', {
      query: precachedQuery,
      start: current,
      size: perPage,
      excludeFields: null,
      includeFields: null
    }).catch((err) => {
      console.info("Problem with second stage caching - couldn't store the data")
      console.info(err)
    }).then((res) => {
      let t2 = new Date().getTime()
      global.$VS.twoStageCachingDelta2 = t2 - t0
      console.log('Using two stage caching for performance optimization - Time comparison stage1 vs stage2', global.$VS.twoStageCachingDelta1, global.$VS.twoStageCachingDelta2)
      if (global.$VS.twoStageCachingDelta1 > global.$VS.twoStageCachingDelta2) { // two stage caching is not making any good
        global.$VS.twoStageCachingDisabled = true
        console.log('Disabling two stage caching')
      }
    })
  }
  return productPromise
}

export default {
  name: 'Category',
  metaInfo () {
    return {
      title: this.$route.meta.title || this.categoryName,
      meta: this.$route.meta.description ? [{vmid: 'description', description: this.$route.meta.description}] : []
    }
  },
  mixins: [Composite],
  methods: {
    onFilterChanged (filterData) {
      (filterChanged.bind(this))(filterData)
    },
    fetchData ({ store, route }) {
      let self = this
      let searchProductQuery = baseFilterQuery(config.products.defaultFilters, store.state.category.current)

      if (self.category) { // fill breadcrumb data - TODO: extract it to a helper to be used on product page
        this.$bus.$emit('current-category-changed', store.state.category.current_path)
        store.dispatch('attribute/list', { // load filter attributes for this specific category
          filterValues: config.products.defaultFilters// TODO: assign specific filters/ attribute codes dynamicaly to specific categories
        })
      }
      return filterData({ searchProductQuery: searchProductQuery, populateAggregations: true, store: store, route: route, current: self.pagination.current, perPage: self.pagination.perPage, filters: config.products.defaultFilters })
    },
    validateRoute ({store, route}) {
      let self = this
      if (store == null) {
        store = self.$store
      }
      if (route == null) {
        route = self.$route
      }
      let slug = route.params.slug
      this.filters.chosen = {} // reset selected filters
      this.$bus.$emit('filter-reset')

      store.dispatch('category/single', { key: 'slug', value: slug }).then((category) => {
        store.state.category.breadcrumbs.routes = breadCrumbRoutes(store.state.category.current_path)

        if (!self.category) {
          self.$router.push('/')
        } else {
          self.fetchData({store: store, route: route})
          EventBus.$emitFilter('category-after-load', { store: store, route: route })
        }
      })
    }
  },
  watch: {
    '$route': 'validateRoute'
  },
  asyncData ({ store, route }) { // this is for SSR purposes to prefetch data
    return new Promise((resolve, reject) => {
      console.log('Entering asyncData for Category root ' + new Date())
      const defaultFilters = config.products.defaultFilters
      store.dispatch('category/list', { includeFields: config.entities.optimize && global.$VS.isSSR ? config.entities.category.includeFields : null }).then((categories) => {
        store.dispatch('attribute/list', { // load filter attributes for this specific category
          filterValues: defaultFilters, // TODO: assign specific filters/ attribute codes dynamicaly to specific categories
          includeFields: config.entities.optimize && global.$VS.isSSR ? config.entities.attribute.includeFields : null
        }).then((attrs) => {
          store.dispatch('category/single', { key: 'slug', value: route.params.slug }).then((parentCategory) => {
            filterData({ searchProductQuery: baseFilterQuery(defaultFilters, parentCategory), populateAggregations: true, store: store, route: route, current: 0, perPage: 50, filters: defaultFilters, includeFields: config.entities.optimize && global.$VS.isSSR ? config.entities.productList.includeFields : null, excludeFields: config.entities.optimize && global.$VS.isSSR ? config.entities.productList.excludeFields : null }).then((subloaders) => {
              Promise.all(subloaders).then((results) => {
                store.state.category.breadcrumbs.routes = breadCrumbRoutes(store.state.category.current_path)

                EventBus.$emitFilter('category-after-load', { store: store, route: route }).then((results) => {
                  return resolve()
                }).catch((err) => {
                  console.error(err)
                  return resolve()
                })
              })
            })
          }).catch(err => {
            console.error(err)
            return reject(Error(err))
          })
        })
      })
    })
  },
  beforeMount () {
    this.$bus.$on('filter-changed-category', this.onFilterChanged)
  },
  beforeDestroy () {
    this.$bus.$off('filter-changed-category', this.onFilterChanged)
  },
  computed: {
    products () {
      return this.$store.state.product.list.items
    },
    productsCounter () {
      return this.$store.state.product.list.items.length
    },
    isCategoryEmpty () {
      return (!(this.$store.state.product.list.items) || this.$store.state.product.list.items.length === 0)
    },
    category () {
      return this.$store.state.category.current
    },
    categoryName () {
      return this.$store.state.category.current ? this.$store.state.category.current.name : ''
    },
    categoryId () {
      return this.$store.state.category.current ? this.$store.state.category.current.id : ''
    },
    filters () {
      return this.$store.state.category.filters
    },
    breadcrumbs () {
      return this.$store.state.category.breadcrumbs
    }

  },
  data () {
    return {
      pagination: {
        perPage: 50,
        current: 0,
        enabled: false
      }
    }
  },
  components: {
    ProductListing,
    Breadcrumbs,
    Sidebar
  }
}
</script>


Initial snackbar component

Initial snackbar component


EBEAST: vc/icon-button.vue: add <vc-icon-button/>, suports icon prop

Signed-off-by: Tim Janik <6ec6ff229c4e1c5bbd5933ee92e6c5832ff023bb@gnu.org>

<!-- GNU LGPL v2.1+: http://www.gnu.org/licenses/lgpl.html -->

<docs>
  ## vc-icon-button - A Button containing an icon
  ### Props:
  - **icon** - Name of the icon to be displayed by this button
  - **hotkey** - Keyboard accelerator to trigger a ''click'' event.
  ### Events:
  - **click** - A ''click'' event is emitted for activation through mouse buttons, Space or Enter keys.
  ### Slots:
  - **slot** - All contents passed into this element will be rendered as contents besides the icon.
</docs>

<style lang="less">
  @import 'styles.less';
  .vc-icon-button			{ margin: 0 0; padding: 7px; }
  .vc-icon-button .vc-icon-button-icon	{ font-size: 1.3em /*1.33333333em*/; }
</style>

<template>
  <vc-button
      class="vc-icon-button"
      :hotkey="hotkey"
      @click="emit ('click', $event)"
  ><span
       :class="iconclasses"
   ></span><slot
	       class="vc-slot"
	   ></slot></vc-button>
</template>

<script>
module.exports = {
  name: 'vc-icon-button',
  props: [ 'icon', 'hotkey' ],
  computed: {
    iconclasses() { return "vc-icon-button-icon fa fa-lg fa-fw " + this.icon; },
  },
  methods: {
    emit (what, ev) {
      this.$emit (what, ev);
    },
  },
};
</script>

error: pathspec 'ebeast/vc/icon-button.vue' did not match any file(s) known to git


modify

modify

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/KyLeoHC/webpack-vue.git/'


updated time picker docs (allowedHours)

updated time picker docs (allowedHours)

<template>
  <v-layout row wrap>
    <v-flex xs12>
      <v-btn
        :color="allowedTimes === everyOtherValue ? 'primary' : ''"
        :class="{ 'white--text': allowedTimes === everyOtherValue }"
        @click.native="allowedTimes = everyOtherValue"
      >Function</v-btn>
      <v-btn
        :color="allowedTimes === randomValues ? 'primary' : ''"
        :class="{ 'white--text': allowedTimes === randomValues }"
        @click.native="allowedTimes = randomValues"
      >Array</v-btn>
      <v-btn
        :color="allowedTimes === minMaxValues ? 'primary' : ''"
        :class="{ 'white--text': allowedTimes === minMaxValues }"
        @click.native="allowedTimes = minMaxValues"
      >Object</v-btn>
      <v-time-picker
        class="mt-3"
        scrollable
        v-model="date"
        :allowed-hours="allowedTimes.hours"
        :allowed-minutes="allowedTimes.minutes"
      ></v-time-picker>
    </v-flex>
  </v-layout>
</template>

<script>
  export default {
    data () {
      return {
        date: null,
        allowedTimes: {
          hours: null,
          minutes: null
        },
        everyOtherValue: {
          hours: function (value) {
            return value % 2 === 0
          },
          minutes: function (value) {
            return value % 15 === 0
          }
        },
        randomValues: {
          hours: [],
          minute: []
        },
        minMaxValues: {
          hours: {
            min: 9,
            max: 15
          },
          minutes: {
            min: 30,
            max: 60
          }
        }
      }
    },
    mounted () {
      this.randomValues.hours = [...Array(8)].map(() => {
        return Math.floor(Math.random() * 24)
      })
      this.randomValues.minutes = [...Array(6)].map(() => {
        return Math.floor(Math.random() * 12) * 5
      })

      this.allowedTimes = this.everyOtherValue
    }
  }
</script>

<template>
  <v-layout row wrap>
    <v-flex xs12>
      <v-btn
        :color="allowedTimes === everyOtherValue ? 'primary' : ''"
        :class="{ 'white--text': allowedTimes === everyOtherValue }"
        @click.native="allowedTimes = everyOtherValue"
      >Function</v-btn>
      <v-btn
        :color="allowedTimes === randomValues ? 'primary' : ''"
        :class="{ 'white--text': allowedTimes === randomValues }"
        @click.native="allowedTimes = randomValues"
      >Array</v-btn>
      <v-btn
        :color="allowedTimes === minMaxValues ? 'primary' : ''"
        :class="{ 'white--text': allowedTimes === minMaxValues }"
        @click.native="allowedTimes = minMaxValues"
      >Object</v-btn>
      <v-time-picker
        class="mt-3"
        scrollable
        v-model="date"
        :allowed-hours="allowedTimes.hours"
        :allowed-minutes="allowedTimes.minutes"
      ></v-time-picker>
    </v-flex>
  </v-layout>
</template>

<script>
  export default {
    data () {
      return {
        date: null,
        allowedTimes: {
          hours: null,
          minutes: null
        },
        everyOtherValue: {
          hours: function (value) {
            return value % 2 === 0
          },
          minutes: function (value) {
            return value % 15 === 0
          }
        },
        randomValues: {
          hours: [],
          minute: []
        },
        minMaxValues: {
          hours: {
            min: '9AM',
            max: '3PM'
          },
          minutes: {
            min: 30,
            max: 60
          }
        }
      }
    },
    mounted () {
      this.randomValues.hours = [...Array(6)].map(() => {
        return Math.floor(Math.random() * 12)
      })
      this.randomValues.minutes = [...Array(6)].map(() => {
        return Math.floor(Math.random() * 12) * 5
      })

      this.allowedTimes = this.everyOtherValue
    }
  }
</script>


Implement panel group

Implement panel group


feat: added Youtube Video component

feat: added Youtube Video component

error: pathspec 'erpnext/public/js/education/lms/components/YoutubePlayer.vue' did not match any file(s) known to git


Update ProxiesTcp.vue

Update ProxiesTcp.vue

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/KunTengRom/xfrps.git/'


add example for get network data by github

<template>

Latest Vue.js Commits

    <template v-for="branch in branches">
      <input type="radio"
             :id="branch"
             :value="branch"
             name="branch"
             v-model="currentBranch">
      {{ branch }}
    </template>

vuejs/vue@{{ currentBranch }}

        {{ record.sha.slice(0, 7) }}
        - {{ record.commit.message | truncate }}

        by {{ record.commit.author.name }}
        at {{ record.commit.author.date | formatDate }}

</template>

error: pathspec 'VueApp/src/pages/Github.vue' did not match any file(s) known to git


Add CPV component

Add CPV component

<template>

    CPV
    <input type="text" name="cpv" class="form-control">

</template>

error: pathspec 'n3-composer/components/specific-decision-fields/common-properties/Cpv.vue' did not match any file(s) known to git


Fix style

Fix style

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/sosahcarlos/te-site.git/'


set empty file when click on cancel button in md-file (#291)

#290


Refactor huge method

Refactor huge method


Fix unreactive sticky properies

Fix unreactive sticky properies


add

add

popout

<template>

{{ successToastContent }}

{{ loadingToastContent }}

{{ alertDialogContent }}

{{ option }}

{{ confirmDialogTitle }}

{{ confirmDialogContent }}

{{ option }}

{{ option }}

{{ actionSheetCancelText }}

</template>

error: pathspec 'src/package/1.0.0/components/popout.vue' did not match any file(s) known to git


Small fix in layout of DetailRow

Small fix in layout of DetailRow

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/modum-io/web.git/'


Remote table-section from div holding cluster error messages

<template>

        {{ title }}

        {{ title }}

        <slot name="description"></slot>

            {{ manageButtonLabel }}

          <loading-button
            class="js-cluster-application-install-button"
            :loading="installButtonLoading"
            :disabled="installButtonDisabled"
            :label="installButtonLabel"
            @click="installClicked"
          />

            {{ generalErrorDescription }}

              {{ statusReason }}

              {{ requestReason }}

</template>


Removed quagga stop

Removed quagga stop

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/NathanielDavid/Sumie.git/'


Add example os basic and opening from and to

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/praneetloke/vue-material.git/'


Add Award component

Add Award component

<template>

     <expense-award></expense-award>

</template>

error: pathspec 'n3-composer/components/specific-decision-fields/Award.vue' did not match any file(s) known to git


fix(introduction): style "malfunction"

fix(introduction): style "malfunction"

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/manolof/personal-site.git/'


Learn Vuex Dome Done

Learn Vuex Dome Done

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/rookielzy/Learn-Vue.js.git/'


Remove debugging

Remove debugging

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/JonathanTroyer/OC-Summer-2017-Table-Web.git/'


Added sorting capability to secrets page. Closed #200

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/04n0/goldfish.git/'


Remove deprecated tooltip

Remove deprecated tooltip


Adds 'days'

Adds 'days'


Removed quotes around page title

Removed quotes around page title

<script>
import _ from 'underscore';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';

export default {
  components: {
    GlModal,
  },
  props: {
    deleteWikiUrl: {
      type: String,
      required: false,
      default: '',
    },
    pageTitle: {
      type: String,
      required: false,
      default: '',
    },
    csrfToken: {
      type: String,
      required: false,
      default: '',
    },
  },
  computed: {
    message() {
      return s__('WikiPageConfirmDelete|Are you sure you want to delete this page?');
    },
    title() {
      return sprintf(
        s__('WikiPageConfirmDelete|Delete %{pageTitle}?'),
        {
          pageTitle: _.escape(this.pageTitle),
        },
        false,
      );
    },
  },
  methods: {
    onSubmit() {
      this.$refs.form.submit();
    },
  },
};
</script>

<template>
  <gl-modal
    id="delete-wiki-modal"
    :header-title-text="title"
    footer-primary-button-variant="danger"
    :footer-primary-button-text="s__('WikiPageConfirmDelete|Delete Page')"
    @submit="onSubmit"
  >
    {{ message }}
    <form
      ref="form"
      :action="deleteWikiUrl"
      method="post"
      class="form-horizontal js-requires-input"
    >
      <input
        ref="method"
        type="hidden"
        name="_method"
        value="delete"
      />
      <input
        type="hidden"
        name="authenticity_token"
        :value="csrfToken"
      />
    </form>
  </gl-modal>
</template>

<script>
import _ from 'underscore';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';

export default {
  components: {
    GlModal,
  },
  props: {
    deleteWikiUrl: {
      type: String,
      required: false,
      default: '',
    },
    pageTitle: {
      type: String,
      required: false,
      default: '',
    },
    csrfToken: {
      type: String,
      required: false,
      default: '',
    },
  },
  computed: {
    message() {
      return s__('WikiPageConfirmDelete|Are you sure you want to delete this page?');
    },
    title() {
      return sprintf(
        s__('WikiPageConfirmDelete|Delete Page %{pageTitle}?'),
        {
          pageTitle: `'${_.escape(this.pageTitle)}'`,
        },
        false,
      );
    },
  },
  methods: {
    onSubmit() {
      this.$refs.form.submit();
    },
  },
};
</script>

<template>
  <gl-modal
    id="delete-wiki-modal"
    :header-title-text="title"
    footer-primary-button-variant="danger"
    :footer-primary-button-text="s__('WikiPageConfirmDelete|Delete Page')"
    @submit="onSubmit"
  >
    {{ message }}
    <form
      ref="form"
      :action="deleteWikiUrl"
      method="post"
      class="form-horizontal js-requires-input"
    >
      <input
        ref="method"
        type="hidden"
        name="_method"
        value="delete"
      />
      <input
        type="hidden"
        name="authenticity_token"
        :value="csrfToken"
      />
    </form>
  </gl-modal>
</template>


Changed copy of delete button to sentence case

<script>
import _ from 'underscore';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';

export default {
  components: {
    GlModal,
  },
  props: {
    deleteWikiUrl: {
      type: String,
      required: true,
      default: '',
    },
    pageTitle: {
      type: String,
      required: true,
      default: '',
    },
    csrfToken: {
      type: String,
      required: true,
      default: '',
    },
  },
  computed: {
    message() {
      return s__('WikiPageConfirmDelete|Are you sure you want to delete this page?');
    },
    title() {
      return sprintf(
        s__('WikiPageConfirmDelete|Delete page %{pageTitle}?'),
        {
          pageTitle: _.escape(this.pageTitle),
        },
        false,
      );
    },
  },
  methods: {
    onSubmit() {
      this.$refs.form.submit();
    },
  },
};
</script>

<template>
  <gl-modal
    id="delete-wiki-modal"
    :header-title-text="title"
    footer-primary-button-variant="danger"
    :footer-primary-button-text="s__('WikiPageConfirmDelete|Delete page')"
    @submit="onSubmit"
  >
    {{ message }}
    <form
      ref="form"
      :action="deleteWikiUrl"
      method="post"
      class="form-horizontal js-requires-input"
    >
      <input
        ref="method"
        type="hidden"
        name="_method"
        value="delete"
      />
      <input
        type="hidden"
        name="authenticity_token"
        :value="csrfToken"
      />
    </form>
  </gl-modal>
</template>

<script>
import _ from 'underscore';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';

export default {
  components: {
    GlModal,
  },
  props: {
    deleteWikiUrl: {
      type: String,
      required: true,
      default: '',
    },
    pageTitle: {
      type: String,
      required: true,
      default: '',
    },
    csrfToken: {
      type: String,
      required: true,
      default: '',
    },
  },
  computed: {
    message() {
      return s__('WikiPageConfirmDelete|Are you sure you want to delete this page?');
    },
    title() {
      return sprintf(
        s__('WikiPageConfirmDelete|Delete %{pageTitle}?'),
        {
          pageTitle: _.escape(this.pageTitle),
        },
        false,
      );
    },
  },
  methods: {
    onSubmit() {
      this.$refs.form.submit();
    },
  },
};
</script>

<template>
  <gl-modal
    id="delete-wiki-modal"
    :header-title-text="title"
    footer-primary-button-variant="danger"
    :footer-primary-button-text="s__('WikiPageConfirmDelete|Delete Page')"
    @submit="onSubmit"
  >
    {{ message }}
    <form
      ref="form"
      :action="deleteWikiUrl"
      method="post"
      class="form-horizontal js-requires-input"
    >
      <input
        ref="method"
        type="hidden"
        name="_method"
        value="delete"
      />
      <input
        type="hidden"
        name="authenticity_token"
        :value="csrfToken"
      />
    </form>
  </gl-modal>
</template>


Projects Dropdown Searched Projects List Component

<script>
import { s__ } from '../../locale';
import projectsListItem from './projects_list_item.vue';

export default {
  components: {
    projectsListItem,
  },
  props: {
    matcher: {
      type: String,
      required: true,
    },
    projects: {
      type: Array,
      required: true,
    },
    searchFailed: {
      type: Boolean,
      required: true,
    },
  },
  computed: {
    isListEmpty() {
      return this.projects.length === 0;
    },
    listEmptyMessage() {
      return this.searchFailed ?
        s__('ProjectsDropdown|Something went wrong on our end.') :
        s__('ProjectsDropdown|No projects matched your query');
    },
  },
};
</script>

<template>
  <div
    class="projects-list-search-container"
  >
    <ul
      class="list-unstyled"
    >
      <li
        v-if="isListEmpty"
        :class="{ 'section-failure': searchFailed }"
        class="section-empty"
      >
        {{ listEmptyMessage }}
      </li>
      <projects-list-item
        v-else
        v-for="(project, index) in projects"
        :key="index"
        :project-id="project.id"
        :project-name="project.name"
        :namespace="project.namespace"
        :web-url="project.webUrl"
        :avatar-url="project.avatarUrl"
        :matcher="matcher"
      />
    </ul>
  </div>
</template>

error: pathspec 'app/assets/javascripts/projects_dropdown/components/projects_list_search.vue' did not match any file(s) known to git


editor image/video picker updated

editor image/video picker updated

<template>

{{ 'Video' | trans }}

{{ 'Select image' | trans }}

                    <video-view src="{{ video.src }}"></video-view>

                    <input type="text" class="uk-width-1-1" placeholder="{{ 'URL' | trans }}" v-model="video.src" lazy>

                    <button class="uk-button uk-button-link uk-modal-close" type="button">{{ 'Cancel' | trans }}</button>
                    <button class="uk-button uk-button-link uk-button-primary uk-modal-close" type="button" v-on="click: update">{{ 'Update' | trans }}</button>

{{ 'Select Video' | trans }}

                <v-finder root="{{ finder.root }}" v-ref="finder"></v-finder>

                    <button class="uk-button uk-button-primary" type="button" v-attr="disabled: !finder.select" v-on="click: closeFinder(finder.select)">{{ 'Select' | trans }}</button>
                    <button class="uk-button" type="button" v-on="click: closeFinder(false)">{{ 'Cancel' | trans }}</button>

</template>

<template>

{{ 'Video' | trans }}

                        <video-view src="{{ video.src }}"></video-view>

                            <input type="text" class="uk-width-4-5" placeholder="{{ 'URL' | trans }}" v-model="video.src" lazy>
                            <button type="button" class="uk-button uk-float-right uk-width-1-6" v-on="click: openFinder">{{ 'Select video' | trans }}</button>

                    <button class="uk-button uk-button-primary uk-modal-close" type="button" v-on="click: update">{{ 'Update' | trans }}</button>
                    <button class="uk-button uk-modal-close" type="button">{{ 'Cancel' | trans }}</button>

{{ 'Select Video' | trans }}

                <v-finder root="{{ finder.root }}" v-ref="finder"></v-finder>

                    <button class="uk-button uk-button-primary" type="button" v-attr="disabled: !finder.select" v-on="click: closeFinder(finder.select)">{{ 'Select' | trans }}</button>
                    <button class="uk-button" type="button" v-on="click: closeFinder(false)">{{ 'Cancel' | trans }}</button>

</template>


Adds Pager component

Adds Pager component

<template>
    <div class="pager">
        <button class="btn" :class="{disabled: initialPageReached}" :disabled="initialPageReached"
                @click.prevent="goBackward()">
            Previous
        </button>
        {{currentPage}}
        <span> of </span>
        {{totalPages}}
        <button class="btn" :class="{disabled: totalPagesReached}" :disabled="totalPagesReached"
                @click.prevent="goForward()">
            Next
        </button>
    </div>
</template>
<script>
    export default {
        props: {
            itemsPerPage: {type: Number, default: 5},
            items: Array
        },
        computed: {
            totalPages() {
                if (this.$props.items && this.$props.items.length) {
                    return Math.ceil(this.$props.items.length / this.$props.itemsPerPage);
                }

                return 1;
            },
            totalPagesReached() {
                return this.currentPage === this.totalPages;
            },
            initialPageReached() {
                return this.currentPage === 1;
            }
        },
        data() {
            return {
                currentPage: 1,
                totalItemsDisplayed: this.$props.items.length > this.itemsPerPage ? this.itemsPerPage : this.$props.items.length
            };
        },
        methods: {
            goBackward() {
                if (!this.initialPageReached) {
                    this.currentPage--;
                    const itemsToEmit = this.$props.items.slice(
                        this.totalItemsDisplayed - (this.$props.itemsPerPage * 2),
                        this.totalItemsDisplayed - this.$props.itemsPerPage
                    );
                    this.totalItemsDisplayed -= this.$props.itemsPerPage;
                    this.refreshItems(itemsToEmit);
                }
            },
            goForward() {
                if (!this.totalPagesReached) {
                    this.currentPage++;
                    const itemsToEmit = this.$props.items.slice(
                        this.totalItemsDisplayed,
                        this.$props.itemsPerPage + this.totalItemsDisplayed
                    );
                    this.totalItemsDisplayed += this.$props.itemsPerPage;
                    this.refreshItems(itemsToEmit);
                }
            },
            refreshItems(items) {
                this.$emit('pager-update', items);
            }
        }
    };
</script>
<style scoped lang="scss">
    .btn {
        &.disabled:hover {
            color: initial !important;
        }
    }
</style>

error: pathspec 'resources/assets/js/components/UIComponents/Pager.vue' did not match any file(s) known to git


Add vertical spacing for task_text and task_number (fix #40)

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/tum-rt/web-quiz.git/'


Google button improvement

Google button improvement

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/mejiamanuel57/RAMdomizer.git/'


Improve complete example on list page

Improve complete example on list page


Change ‘members’ -> ‘learners’ column header on coach

<template>

{{ $tr('classPageHeader') }}

{{ $tr('classPageSubheader') }}

      <core-table>

{{ $tr('tableCaption') }}

            {{ $tr('classroomName') }}
            {{ $tr('learnerColumnHeader') }}

              <content-icon :kind="CLASSROOM" />

              <k-router-link
                :text="cl.name"
                :to="learnerPageLink(cl.id)"
              />

            {{ cl.memberCount }}

      </core-table>

{{ $tr('noClassesExist') }}

</template>

<template>

{{ $tr('classPageHeader') }}

{{ $tr('pageDescription') }}

      <core-table>

{{ $tr('tableCaption') }}

            {{ $tr('className') }}
            {{ $tr('members') }}

              <content-icon :kind="CLASSROOM" />

              <k-router-link
                :text="cl.name"
                :to="learnerPageLink(cl.id)"
              />

            {{ cl.memberCount }}

      </core-table>

{{ $tr('noClassesExist') }}

</template>


Fix for issue #41 (md-button ignoring type prop)


Closes #17

Closes #17

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/soanvig/hptcg-deck-builder.git/'


Add support for QP to Library defaults

Add support for QP to Library defaults


Fix markdown-preview events for new isPreview var

<template>

          Write

          Preview

          <toolbar-button
            tag="**"
            button-title="Add bold text"
            icon="bold" />
          <toolbar-button
            tag="*"
            button-title="Add italic text"
            icon="italic" />
          <toolbar-button
            tag="> "
            :prepend="true"
            button-title="Insert a quote"
            icon="quote-right" />
          <toolbar-button
            tag="`"
            tag-block="```"
            button-title="Insert code"
            icon="code" />
          <toolbar-button
            tag="* "
            :prepend="true"
            button-title="Add a bullet list"
            icon="list-ul" />
          <toolbar-button
            tag="1. "
            :prepend="true"
            button-title="Add a numbered list"
            icon="list-ol" />
          <toolbar-button
            tag="* [ ] "
            :prepend="true"
            button-title="Add a task list"
            icon="check-square-o" />

          <button
            v-tooltip
            aria-label="Go full screen"
            class="toolbar-btn js-zen-enter"
            data-container="body"
            tabindex="-1"
            title="Go full screen"
            type="button">

          </button>

</template>


Version bump

Version bump


Fix minor regression in titles of zoom graphs

<template lang='html'>

    <el-dialog
      :visible.sync='showZoomedGraph'
      :title='dialogTitle'
      :show-close='true'
      width='80%'
      top='10vh'
      @close='onCloseDialog'
      @open='onOpenDialog'
    >

      <data-view-statistics :initial-statistics='availableStatistics' :zoom='true'></data-view-statistics>

    <data-view-graph
      :elementId='elementId'
      :theme='theme'
      :indicator='indicator'
      :zoom='true'
      :timestamp='timestamp'
    ></data-view-graph>
  </el-dialog>

</template>


update placer

update placer

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/lijianqiang/open-plan-vue.git/'


add avatar component

add avatar component

<template>

            {{ entity.initials }}

</template>

error: pathspec 'res/js/core/components/avatar.vue' did not match any file(s) known to git