diff options
author | silverwind <me@silverwind.io> | 2020-11-07 16:11:09 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-07 23:11:09 +0800 |
commit | 7c47e240936b8706a888a46149ca399219caef03 (patch) | |
tree | 94afb02f748aae6d704d5c6c3f86a27f744705af | |
parent | ed47da2e29aa832bd892f03d14e08e926d80bf24 (diff) | |
download | gitea-7c47e240936b8706a888a46149ca399219caef03.tar.gz gitea-7c47e240936b8706a888a46149ca399219caef03.zip |
Add Vue linting (#13447)
* 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>
-rw-r--r-- | .eslintrc | 11 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | package-lock.json | 63 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | web_src/js/components/ActivityHeatmap.vue | 128 | ||||
-rw-r--r-- | web_src/js/components/ActivityTopAuthors.vue | 179 | ||||
-rw-r--r-- | web_src/js/index.js | 150 |
7 files changed, 294 insertions, 242 deletions
@@ -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] @@ -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 diff --git a/package-lock.json b/package-lock.json index 05f47a153d..0433c68fe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5195,6 +5195,26 @@ } } }, + "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", @@ -14005,6 +14025,49 @@ "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", diff --git a/package.json b/package.json index 4d0a055297..3c6013daf6 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/web_src/js/components/ActivityHeatmap.vue b/web_src/js/components/ActivityHeatmap.vue index ec241b64fa..cfa2825244 100644 --- a/web_src/js/components/ActivityHeatmap.vue +++ b/web_src/js/components/ActivityHeatmap.vue @@ -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/> diff --git a/web_src/js/components/ActivityTopAuthors.vue b/web_src/js/components/ActivityTopAuthors.vue index a5c6e062b2..a9ee0e56d5 100644 --- a/web_src/js/components/ActivityTopAuthors.vue +++ b/web_src/js/components/ActivityTopAuthors.vue @@ -1,102 +1,101 @@ <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> diff --git a/web_src/js/index.js b/web_src/js/index.js index ef6d72a4f7..9921075f23 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -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; } } } |