]> source.dussan.org Git - gitea.git/commitdiff
Add Vue linting (#13447)
authorsilverwind <me@silverwind.io>
Sat, 7 Nov 2020 15:11:09 +0000 (16:11 +0100)
committerGitHub <noreply@github.com>
Sat, 7 Nov 2020 15:11:09 +0000 (23:11 +0800)
* Add Vue linting

Turns out the .vue files were not linted at all, so I added that as well
as re-indented the file to 2-space and fixed all reasonable issues that
cam up except one case of a unintended side effect for which I have no
idea how to fix it, so the rule was disabled.

* misc tweaks

* update lockfile

* use overrides to include .vue files

* treat warnings as errors on lint-frontend

* also treat stylelint warnings as errors

* use equal sign syntax

Co-authored-by: Lauris BH <lauris@nix.lv>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
.eslintrc
Makefile
package-lock.json
package.json
web_src/js/components/ActivityHeatmap.vue
web_src/js/components/ActivityTopAuthors.vue
web_src/js/index.js

index 7b8376e8ea5959b5ab286cff3b9b574d61960425..398d1a610f3adc0798c1ccb80833c44c9a31cee6 100644 (file)
--- a/.eslintrc
+++ b/.eslintrc
@@ -11,6 +11,10 @@ parserOptions:
 plugins:
   - eslint-plugin-unicorn
   - eslint-plugin-import
+  - eslint-plugin-vue
+
+extends:
+  - plugin:vue/recommended
 
 env:
   es2021: true
@@ -24,7 +28,7 @@ globals:
   u2fApi: false
 
 overrides:
-  - files: ["web_src/**/*.js"]
+  - files: ["web_src/**/*.js", "web_src/**/*.vue"]
     env:
       browser: true
       jquery: true
@@ -387,6 +391,11 @@ rules:
   use-isnan: [2]
   valid-typeof: [2, {requireStringLiterals: true}]
   vars-on-top: [0]
+  vue/attributes-order: [0]
+  vue/component-definition-name-casing: [0]
+  vue/html-closing-bracket-spacing: [0]
+  vue/max-attributes-per-line: [0]
+  vue/one-component-per-file: [0]
   wrap-iife: [2, inside]
   wrap-regex: [0]
   yield-star-spacing: [2, after]
index 301a38f50730dcf514f1c6ed72b8c4943061b51f..a4b7c736383c004cbe9cda3a1ea3b2ed888d8af2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -312,8 +312,8 @@ lint: lint-frontend lint-backend
 
 .PHONY: lint-frontend
 lint-frontend: node_modules
-       npx eslint web_src/js build webpack.config.js
-       npx stylelint web_src/less
+       npx eslint --max-warnings=0 web_src/js build webpack.config.js
+       npx stylelint --max-warnings=0 web_src/less
 
 .PHONY: lint-backend
 lint-backend: golangci-lint revive vet
index 05f47a153d1673dafa764fd86e27e73d238a91f0..0433c68fe44f3e47fc8971627a7c3c951c2d8362 100644 (file)
         }
       }
     },
+    "eslint-plugin-vue": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.1.0.tgz",
+      "integrity": "sha512-9dW7kj8/d2IkDdgNpvIhJdJ3XzU3x4PThXYMzWt49taktYnGyrTY6/bXCYZ/VtQKU9kXPntPrZ41+8Pw0Nxblg==",
+      "dev": true,
+      "requires": {
+        "eslint-utils": "^2.1.0",
+        "natural-compare": "^1.4.0",
+        "semver": "^7.3.2",
+        "vue-eslint-parser": "^7.1.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "7.3.2",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+          "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
+          "dev": true
+        }
+      }
+    },
     "eslint-scope": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
         "v-tooltip": "^2.0.0-rc.32"
       }
     },
+    "vue-eslint-parser": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.1.tgz",
+      "integrity": "sha512-8FdXi0gieEwh1IprIBafpiJWcApwrU+l2FEj8c1HtHFdNXMd0+2jUSjBVmcQYohf/E72irwAXEXLga6TQcB3FA==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.1",
+        "eslint-scope": "^5.0.0",
+        "eslint-visitor-keys": "^1.1.0",
+        "espree": "^6.2.1",
+        "esquery": "^1.0.1",
+        "lodash": "^4.17.15"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "7.4.1",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+          "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+          "dev": true
+        },
+        "eslint-scope": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+          "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+          "dev": true,
+          "requires": {
+            "esrecurse": "^4.3.0",
+            "estraverse": "^4.1.1"
+          }
+        },
+        "espree": {
+          "version": "6.2.1",
+          "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
+          "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
+          "dev": true,
+          "requires": {
+            "acorn": "^7.1.1",
+            "acorn-jsx": "^5.2.0",
+            "eslint-visitor-keys": "^1.1.0"
+          }
+        }
+      }
+    },
     "vue-hot-reload-api": {
       "version": "2.3.4",
       "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
index 4d0a055297115308e68505fb0f397cee72d83e77..3c6013daf6579a8db131038aa41a891f680fbd4c 100644 (file)
@@ -54,6 +54,7 @@
     "eslint": "7.11.0",
     "eslint-plugin-import": "2.22.1",
     "eslint-plugin-unicorn": "23.0.0",
+    "eslint-plugin-vue": "7.1.0",
     "stylelint": "13.7.2",
     "stylelint-config-standard": "20.0.0",
     "svgo": "1.3.2",
index ec241b64fae8c7a2391275650c36628228e644b8..cfa28252442e99fd23fee552e09151724b6c4d9c 100644 (file)
@@ -1,79 +1,71 @@
 <template>
-    <div>
-        <div v-show="isLoading">
-            <slot name="loading"></slot>
-        </div>
-        <h4 class="total-contributions" v-if="!isLoading">
-            {{ totalContributions }} total contributions in the last 12 months
-        </h4>
-        <calendar-heatmap v-show="!isLoading" :locale="locale" :no-data-text="locale.no_contributions" :tooltip-unit="locale.contributions" :end-date="endDate" :values="values" :range-color="colorRange"/>
+  <div>
+    <div v-show="isLoading">
+      <slot name="loading"/>
     </div>
+    <h4 v-if="!isLoading" class="total-contributions">
+      {{ values.length }} total contributions in the last 12 months
+    </h4>
+    <calendar-heatmap
+      v-show="!isLoading"
+      :locale="locale"
+      :no-data-text="locale.no_contributions"
+      :tooltip-unit="locale.contributions"
+      :end-date="endDate"
+      :values="values"
+      :range-color="colorRange"
+    />
+  </div>
 </template>
-
 <script>
 import {CalendarHeatmap} from 'vue-calendar-heatmap';
 const {AppSubUrl, heatmapUser} = window.config;
 
 export default {
-    name: "ActivityHeatmap",
-    components: {
-        CalendarHeatmap
-    },
-    data() {
-        return {
-            isLoading: true,
-            colorRange: [],
-            endDate: null,
-            values: [],
-            totalContributions: 0,
-            suburl: AppSubUrl,
-            user: heatmapUser,
-            locale: {
-                contributions: 'contributions',
-                no_contributions: 'No contributions',
-            },
-        };
+  name: 'ActivityHeatmap',
+  components: {CalendarHeatmap},
+  data: () => ({
+    isLoading: true,
+    colorRange: [],
+    endDate: null,
+    values: [],
+    suburl: AppSubUrl,
+    user: heatmapUser,
+    locale: {
+      contributions: 'contributions',
+      no_contributions: 'No contributions',
     },
-    mounted() {
-        this.colorRange = [
-            this.getColor(0),
-            this.getColor(1),
-            this.getColor(2),
-            this.getColor(3),
-            this.getColor(4),
-            this.getColor(5)
-        ];
-        this.endDate = new Date();
-        this.loadHeatmap(this.user);
-    },
-    methods: {
-        loadHeatmap(userName) {
-            const self = this;
-            $.get(`${this.suburl}/api/v1/users/${userName}/heatmap`, (chartRawData) => {
-                const chartData = [];
-                for (let i = 0; i < chartRawData.length; i++) {
-                    self.totalContributions += chartRawData[i].contributions;
-                    chartData[i] = {date: new Date(chartRawData[i].timestamp * 1000), count: chartRawData[i].contributions};
-                }
-                self.values = chartData;
-                self.isLoading = false;
-            });
-        },
-        getColor(idx) {
-            const el = document.createElement('div');
-            el.className = `heatmap-color-${idx}`;
-            document.body.appendChild(el);
-
-            const color = getComputedStyle(el).backgroundColor;
-
-            document.body.removeChild(el);
-
-            return color;
-        }
+  }),
+  mounted() {
+    this.colorRange = [
+      this.getColor(0),
+      this.getColor(1),
+      this.getColor(2),
+      this.getColor(3),
+      this.getColor(4),
+      this.getColor(5)
+    ];
+    this.endDate = new Date();
+    this.loadHeatmap(this.user);
+  },
+  methods: {
+    async loadHeatmap(userName) {
+      const res = await fetch(`${this.suburl}/api/v1/users/${userName}/heatmap`);
+      const data = await res.json();
+      this.values = data.map(({contributions, timestamp}) => {
+        return {date: new Date(timestamp * 1000), count: contributions};
+      });
+      this.isLoading = false;
     },
-}
+    getColor(idx) {
+      const el = document.createElement('div');
+      el.className = `heatmap-color-${idx}`;
+      document.body.appendChild(el);
+      const color = getComputedStyle(el).backgroundColor;
+      document.body.removeChild(el);
+      return color;
+    }
+  },
+};
 </script>
-
-<style scoped>
-
-</style>
+<style scoped/>
index a5c6e062b249aee231a1976b398310f707a576a9..a9ee0e56d599373a72e9c974627ec3646758b1f7 100644 (file)
 <template>
-    <div>
-        <div class="activity-bar-graph" ref="style" style="width:0px;height:0px"></div>
-        <div class="activity-bar-graph-alt" ref="altStyle" style="width:0px;height:0px"></div>
-        <vue-bar-graph
-            :points="graphData"
-            :show-x-axis="true"
-            :show-y-axis="false"
-            :show-values="true"
-            :width="graphWidth"
-            :bar-color="colors.barColor"
-            :text-color="colors.textColor"
-            :text-alt-color="colors.textAltColor"
-            :height="100"
-            :label-height="20"
-        >
-            <template v-slot:label="opt">
-                <g v-for="(author, idx) in authors" :key="author.position">
-                    <a
-                        v-if="opt.bar.index === idx && author.home_link !== ''"
-                        :href="author.home_link"
-                    >
-                        <image
-                            :x="`${opt.bar.midPoint - 10}px`"
-                            :y="`${opt.bar.yLabel}px`"
-                            height="20"
-                            width="20"
-                            :href="author.avatar_link"
-                        />
-                    </a>
-                    <image
-                        v-else-if="opt.bar.index === idx"
-                        :x="`${opt.bar.midPoint - 10}px`"
-                        :y="`${opt.bar.yLabel}px`"
-                        height="20"
-                        width="20"
-                        :href="author.avatar_link"
-                    />
-                </g>
-            </template>
-            <template v-slot:title="opt">
-                <tspan v-for="(author, idx) in authors" :key="author.position"><tspan v-if="opt.bar.index === idx">{{ author.name }}</tspan></tspan>
-            </template>
-        </vue-bar-graph>
-    </div>
+  <div>
+    <div class="activity-bar-graph" ref="style" style="width:0px;height:0px"/>
+    <div class="activity-bar-graph-alt" ref="altStyle" style="width:0px;height:0px"/>
+    <vue-bar-graph
+      :points="graphData"
+      :show-x-axis="true"
+      :show-y-axis="false"
+      :show-values="true"
+      :width="graphWidth"
+      :bar-color="colors.barColor"
+      :text-color="colors.textColor"
+      :text-alt-color="colors.textAltColor"
+      :height="100"
+      :label-height="20"
+    >
+      <template #label="opt">
+        <g v-for="(author, idx) in authors" :key="author.position">
+          <a
+            v-if="opt.bar.index === idx && author.home_link !== ''"
+            :href="author.home_link"
+          >
+            <image
+              :x="`${opt.bar.midPoint - 10}px`"
+              :y="`${opt.bar.yLabel}px`"
+              height="20"
+              width="20"
+              :href="author.avatar_link"
+            />
+          </a>
+          <image
+            v-else-if="opt.bar.index === idx"
+            :x="`${opt.bar.midPoint - 10}px`"
+            :y="`${opt.bar.yLabel}px`"
+            height="20"
+            width="20"
+            :href="author.avatar_link"
+          />
+        </g>
+      </template>
+      <template #title="opt">
+        <tspan v-for="(author, idx) in authors" :key="author.position">
+          <tspan v-if="opt.bar.index === idx">
+            {{ author.name }}
+          </tspan>
+        </tspan>
+      </template>
+    </vue-bar-graph>
+  </div>
 </template>
-
 <script>
 import VueBarGraph from 'vue-bar-graph';
 
 export default {
-    components: {
-        VueBarGraph,
-    },
-    props: {
-        data: { type: Array, default: () => [] },
+  components: {VueBarGraph},
+  props: {
+    data: {type: Array, default: () => []},
+  },
+  data: () => ({
+    colors: {
+      barColor: 'green',
+      textColor: 'black',
+      textAltColor: 'white',
     },
-    mounted() {
-        const st = window.getComputedStyle(this.$refs.style);
-        const stalt = window.getComputedStyle(this.$refs.altStyle);
-
-        this.colors.barColor = st.backgroundColor;
-        this.colors.textColor = st.color;
-        this.colors.textAltColor = stalt.color;
+  }),
+  computed: {
+    graphData() {
+      return this.data.map((item) => {
+        return {
+          value: item.commits,
+          label: item.name,
+        };
+      });
     },
-    data() {
+    authors() {
+      return this.data.map((item, idx) => {
         return {
-            colors: {
-                barColor: 'green',
-                textColor: 'black',
-                textAltColor: 'white',
-            },
+          position: idx + 1,
+          ...item,
         };
+      });
+    },
+    graphWidth() {
+      return this.data.length * 40;
     },
-    computed: {
-        graphData() {
-            return this.data.map((item) => {
-                return {
-                    value: item.commits,
-                    label: item.name,
-                };
-            });
-        },
-        authors() {
-            return this.data.map((item, idx) => {
-                return {
-                    position: idx+1,
-                    ...item,
-                }
-            });
-        },
-        graphWidth() {
-            return this.data.length * 40;
-        },
+  },
+  mounted() {
+    const st = window.getComputedStyle(this.$refs.style);
+    const stalt = window.getComputedStyle(this.$refs.altStyle);
+
+    this.colors.barColor = st.backgroundColor;
+    this.colors.textColor = st.color;
+    this.colors.textAltColor = stalt.color;
+  },
+  methods: {
+    hasHomeLink(i) {
+      return this.graphData[i].homeLink !== '' && this.graphData[i].homeLink !== null;
     },
-    methods: {
-        hasHomeLink(i) {
-            return this.graphData[i].homeLink !== '' && this.graphData[i].homeLink !== null;
-        },
-    }
-}
-</script>
\ No newline at end of file
+  }
+};
+</script>
index ef6d72a4f7decf2433edf4747aa7152f409759e6..9921075f23af2651f3f49e124a68eb1c52b080d7 100644 (file)
@@ -2687,7 +2687,15 @@ function initVueComponents() {
       .replace(/height="[0-9]+"/, 'v-bind:height="size"')
       .replace(/width="[0-9]+"/, 'v-bind:width="size"');
 
-    Vue.component(name, {props: ['size'], template});
+    Vue.component(name, {
+      props: {
+        size: {
+          type: String,
+          default: '16',
+        },
+      },
+      template,
+    });
   }
 
   const vueDelimeters = ['${', '}'];
@@ -2710,7 +2718,7 @@ function initVueComponents() {
       },
       organizations: {
         type: Array,
-        default: []
+        default: () => [],
       },
       isOrganization: {
         type: Boolean,
@@ -3050,17 +3058,19 @@ function initVueApp() {
   initVueComponents();
 
   new Vue({
-    delimiters: ['${', '}'],
     el,
-    data: {
-      searchLimit: Number((document.querySelector('meta[name=_search_limit]') || {}).content),
-      suburl: AppSubUrl,
-      uid: Number((document.querySelector('meta[name=_context_uid]') || {}).content),
-      activityTopAuthors: window.ActivityTopAuthors || [],
-    },
+    delimiters: ['${', '}'],
     components: {
       ActivityTopAuthors,
     },
+    data: () => {
+      return {
+        searchLimit: Number((document.querySelector('meta[name=_search_limit]') || {}).content),
+        suburl: AppSubUrl,
+        uid: Number((document.querySelector('meta[name=_context_uid]') || {}).content),
+        activityTopAuthors: window.ActivityTopAuthors || [],
+      };
+    },
   });
 }
 
@@ -3105,60 +3115,52 @@ function initFilterBranchTagDropdown(selector) {
     });
     $data.remove();
     new Vue({
-      delimiters: ['${', '}'],
       el: this,
+      delimiters: ['${', '}'],
       data,
-
-      beforeMount() {
-        const vm = this;
-
-        this.noResults = vm.$el.getAttribute('data-no-results');
-        this.canCreateBranch = vm.$el.getAttribute('data-can-create-branch') === 'true';
-
-        document.body.addEventListener('click', (event) => {
-          if (vm.$el.contains(event.target)) {
-            return;
-          }
-          if (vm.menuVisible) {
-            Vue.set(vm, 'menuVisible', false);
-          }
-        });
-      },
-
-      watch: {
-        menuVisible(visible) {
-          if (visible) {
-            this.focusSearchField();
-          }
-        }
-      },
-
       computed: {
         filteredItems() {
-          const vm = this;
-
-          const items = vm.items.filter((item) => {
-            return ((vm.mode === 'branches' && item.branch) || (vm.mode === 'tags' && item.tag)) &&
-              (!vm.searchTerm || item.name.toLowerCase().includes(vm.searchTerm.toLowerCase()));
+          const items = this.items.filter((item) => {
+            return ((this.mode === 'branches' && item.branch) || (this.mode === 'tags' && item.tag)) &&
+              (!this.searchTerm || item.name.toLowerCase().includes(this.searchTerm.toLowerCase()));
           });
 
-          vm.active = (items.length === 0 && vm.showCreateNewBranch ? 0 : -1);
-
+          // no idea how to fix this so linting rule is disabled instead
+          this.active = (items.length === 0 && this.showCreateNewBranch ? 0 : -1); // eslint-disable-line vue/no-side-effects-in-computed-properties
           return items;
         },
         showNoResults() {
           return this.filteredItems.length === 0 && !this.showCreateNewBranch;
         },
         showCreateNewBranch() {
-          const vm = this;
-          if (!this.canCreateBranch || !vm.searchTerm || vm.mode === 'tags') {
+          if (!this.canCreateBranch || !this.searchTerm || this.mode === 'tags') {
             return false;
           }
 
-          return vm.items.filter((item) => item.name.toLowerCase() === vm.searchTerm.toLowerCase()).length === 0;
+          return this.items.filter((item) => item.name.toLowerCase() === this.searchTerm.toLowerCase()).length === 0;
+        }
+      },
+
+      watch: {
+        menuVisible(visible) {
+          if (visible) {
+            this.focusSearchField();
+          }
         }
       },
 
+      beforeMount() {
+        this.noResults = this.$el.getAttribute('data-no-results');
+        this.canCreateBranch = this.$el.getAttribute('data-can-create-branch') === 'true';
+
+        document.body.addEventListener('click', (event) => {
+          if (this.$el.contains(event.target)) return;
+          if (this.menuVisible) {
+            Vue.set(this, 'menuVisible', false);
+          }
+        });
+      },
+
       methods: {
         selectItem(item) {
           const prev = this.getSelected();
@@ -3169,15 +3171,12 @@ function initFilterBranchTagDropdown(selector) {
           window.location.href = item.url;
         },
         createNewBranch() {
-          if (!this.showCreateNewBranch) {
-            return;
-          }
+          if (!this.showCreateNewBranch) return;
           $(this.$refs.newBranchForm).trigger('submit');
         },
         focusSearchField() {
-          const vm = this;
           Vue.nextTick(() => {
-            vm.$refs.searchField.focus();
+            this.$refs.searchField.focus();
           });
         },
         getSelected() {
@@ -3194,15 +3193,12 @@ function initFilterBranchTagDropdown(selector) {
         },
         scrollToActive() {
           let el = this.$refs[`listItem${this.active}`];
-          if (!el || el.length === 0) {
-            return;
-          }
+          if (!el || !el.length) return;
           if (Array.isArray(el)) {
             el = el[0];
           }
 
           const cont = this.$refs.scrollContainer;
-
           if (el.offsetTop < cont.scrollTop) {
             cont.scrollTop = el.offsetTop;
           } else if (el.offsetTop + el.clientHeight > cont.scrollTop + cont.clientHeight) {
@@ -3210,49 +3206,41 @@ function initFilterBranchTagDropdown(selector) {
           }
         },
         keydown(event) {
-          const vm = this;
-          if (event.keyCode === 40) {
-            // arrow down
+          if (event.keyCode === 40) { // arrow down
             event.preventDefault();
 
-            if (vm.active === -1) {
-              vm.active = vm.getSelectedIndexInFiltered();
+            if (this.active === -1) {
+              this.active = this.getSelectedIndexInFiltered();
             }
 
-            if (vm.active + (vm.showCreateNewBranch ? 0 : 1) >= vm.filteredItems.length) {
+            if (this.active + (this.showCreateNewBranch ? 0 : 1) >= this.filteredItems.length) {
               return;
             }
-            vm.active++;
-            vm.scrollToActive();
-          }
-          if (event.keyCode === 38) {
-            // arrow up
+            this.active++;
+            this.scrollToActive();
+          } else if (event.keyCode === 38) { // arrow up
             event.preventDefault();
 
-            if (vm.active === -1) {
-              vm.active = vm.getSelectedIndexInFiltered();
+            if (this.active === -1) {
+              this.active = this.getSelectedIndexInFiltered();
             }
 
-            if (vm.active <= 0) {
+            if (this.active <= 0) {
               return;
             }
-            vm.active--;
-            vm.scrollToActive();
-          }
-          if (event.keyCode === 13) {
-            // enter
+            this.active--;
+            this.scrollToActive();
+          } else if (event.keyCode === 13) { // enter
             event.preventDefault();
 
-            if (vm.active >= vm.filteredItems.length) {
-              vm.createNewBranch();
-            } else if (vm.active >= 0) {
-              vm.selectItem(vm.filteredItems[vm.active]);
+            if (this.active >= this.filteredItems.length) {
+              this.createNewBranch();
+            } else if (this.active >= 0) {
+              this.selectItem(this.filteredItems[this.active]);
             }
-          }
-          if (event.keyCode === 27) {
-            // escape
+          } else if (event.keyCode === 27) { // escape
             event.preventDefault();
-            vm.menuVisible = false;
+            this.menuVisible = false;
           }
         }
       }