diff --git a/.travis.yml b/.travis.yml
index 8518313c..e2d5bc54 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,10 @@
-language: python
+language: minimal
services:
- docker
-python:
- - "3.6"
-
-cache: pip
-
-install:
- - pip install -r requirements.txt
-
script:
- - tools/ci.sh
+ - docker build .
deploy:
- provider: script
diff --git a/Dockerfile b/Dockerfile
index 046a684e..35fef53f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -17,11 +17,15 @@ RUN pip install -r /requirements.txt \
COPY app/server/static /doccano/app/server/static/
COPY app/server/webpack.config.js /doccano/app/server/
RUN cd /doccano/app/server \
- && DEBUG=False npm run build \
- && rm -rf node_modules/
+ && DEBUG=False npm run build
COPY . /doccano
+RUN cd /doccano \
+ && tools/ci.sh
+
+RUN rm -rf /doccano/app/server/node_modules/
+
FROM python:${PYTHON_VERSION}-slim AS runtime
COPY --from=builder /deps /deps
diff --git a/app/server/package.json b/app/server/package.json
index c63facf0..21dda24d 100644
--- a/app/server/package.json
+++ b/app/server/package.json
@@ -5,6 +5,7 @@
"scripts": {
"start": "cross-env HOT_RELOAD=1 DEBUG=1 webpack-dev-server",
"build": "webpack",
+ "lint": "eslint --max-warnings=0 static/js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
diff --git a/app/server/static/js/.eslintrc.js b/app/server/static/js/.eslintrc.js
index 060800bd..5456e427 100644
--- a/app/server/static/js/.eslintrc.js
+++ b/app/server/static/js/.eslintrc.js
@@ -1,4 +1,21 @@
module.exports = {
- "extends": "airbnb-base"
+ env: {
+ browser: true,
+ es6: true,
+ node: true,
+ },
+ parserOptions: {
+ parser: "babel-eslint",
+ },
+ extends: [
+ "airbnb-base",
+ ],
+ rules: {
+ "no-param-reassign": "off",
+ "no-plusplus": "off",
+ "object-shorthand": "off",
+ "prefer-destructuring": "off",
+ "prefer-template": "off",
+ },
};
// https://travishorn.com/setting-up-eslint-on-vs-code-with-airbnb-javascript-style-guide-6eb78a535ba6
\ No newline at end of file
diff --git a/app/server/static/js/dataset.js b/app/server/static/js/dataset.js
index ff3a3a76..9ef1a65a 100644
--- a/app/server/static/js/dataset.js
+++ b/app/server/static/js/dataset.js
@@ -1,9 +1,9 @@
import Vue from 'vue';
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
delimiters: ['[[', ']]'],
data: {
- messages: []
+ messages: [],
},
});
diff --git a/app/server/static/js/demo/demo_mixin.js b/app/server/static/js/demo/demo_mixin.js
index 52de65c7..d232548f 100644
--- a/app/server/static/js/demo/demo_mixin.js
+++ b/app/server/static/js/demo/demo_mixin.js
@@ -1,3 +1,5 @@
+/* global marked:readonly */
+
const annotationMixin = {
data() {
return {
@@ -113,7 +115,7 @@ const annotationMixin = {
},
id2label() {
- let id2label = {};
+ const id2label = {};
for (let i = 0; i < this.labels.length; i++) {
const label = this.labels[i];
id2label[label.id] = label;
diff --git a/app/server/static/js/demo/demo_named_entity.js b/app/server/static/js/demo/demo_named_entity.js
index 0664b36b..e60c5fef 100644
--- a/app/server/static/js/demo/demo_named_entity.js
+++ b/app/server/static/js/demo/demo_named_entity.js
@@ -6,14 +6,14 @@ Vue.use(require('vue-shortkey'), {
});
Vue.component('annotator', {
- template: '
\
- {{ text.slice(r.start_offset, r.end_offset) }}\
-
',
+ template: ''
+ + ' {{ text.slice(r.start_offset, r.end_offset) }}'
+ + '
',
props: {
labels: Array, // [{id: Integer, color: String, text: String}]
text: String,
@@ -27,7 +27,7 @@ Vue.component('annotator', {
},
methods: {
- setSelectedRange(e) {
+ setSelectedRange() {
let start;
let end;
if (window.getSelection) {
@@ -47,7 +47,7 @@ Vue.component('annotator', {
}
this.startOffset = start;
this.endOffset = end;
- console.log(start, end);
+ console.log(start, end); // eslint-disable-line no-console
},
validRange() {
@@ -132,7 +132,7 @@ Vue.component('annotator', {
},
id2label() {
- let id2label = {};
+ const id2label = {};
// default value;
id2label[-1] = {
text_color: '',
@@ -147,7 +147,7 @@ Vue.component('annotator', {
},
});
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
delimiters: ['[[', ']]'],
mixins: [annotationMixin],
diff --git a/app/server/static/js/demo/demo_text_classification.js b/app/server/static/js/demo/demo_text_classification.js
index 3b6c93b6..226aca7e 100644
--- a/app/server/static/js/demo/demo_text_classification.js
+++ b/app/server/static/js/demo/demo_text_classification.js
@@ -6,7 +6,7 @@ Vue.use(require('vue-shortkey'), {
});
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
delimiters: ['[[', ']]'],
mixins: [annotationMixin],
@@ -88,7 +88,7 @@ const vm = new Vue({
label: label.id,
};
this.annotations[this.pageNumber].push(annotation);
- console.log(this.annotations);
+ console.log(this.annotations); // eslint-disable-line no-console
}
},
},
diff --git a/app/server/static/js/demo/demo_translation.js b/app/server/static/js/demo/demo_translation.js
index 9c5eafd6..c603c8d5 100644
--- a/app/server/static/js/demo/demo_translation.js
+++ b/app/server/static/js/demo/demo_translation.js
@@ -4,7 +4,7 @@ import annotationMixin from './demo_mixin';
Vue.use(require('vue-shortkey'));
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
delimiters: ['[[', ']]'],
data: {
@@ -55,7 +55,7 @@ const vm = new Vue({
},
mixins: [annotationMixin],
directives: {
- 'todo-focus': function(el, binding) {
+ 'todo-focus': (el, binding) => {
if (binding.value) {
el.focus();
}
diff --git a/app/server/static/js/document_classification.js b/app/server/static/js/document_classification.js
index a6054a49..6a599f13 100644
--- a/app/server/static/js/document_classification.js
+++ b/app/server/static/js/document_classification.js
@@ -10,7 +10,7 @@ Vue.use(require('vue-shortkey'), {
Vue.filter('simpleShortcut', simpleShortcut);
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
delimiters: ['[[', ']]'],
mixins: [annotationMixin],
diff --git a/app/server/static/js/filter.js b/app/server/static/js/filter.js
index ce94eb09..d250ac6e 100644
--- a/app/server/static/js/filter.js
+++ b/app/server/static/js/filter.js
@@ -1,10 +1,7 @@
export default function simpleShortcut(shortcut) {
- if (shortcut === null) {
- shortcut = '';
- } else {
- shortcut = shortcut.replace('ctrl', 'C');
- shortcut = shortcut.replace('shift', 'S');
- shortcut = shortcut.split(' ').join('-');
- }
- return shortcut;
+ let simplified = shortcut === null ? '' : shortcut;
+ simplified = simplified.replace('ctrl', 'C');
+ simplified = simplified.replace('shift', 'S');
+ simplified = simplified.split(' ').join('-');
+ return simplified;
}
diff --git a/app/server/static/js/guideline.js b/app/server/static/js/guideline.js
index 8966f98e..3f5819c4 100644
--- a/app/server/static/js/guideline.js
+++ b/app/server/static/js/guideline.js
@@ -1,7 +1,10 @@
+/* global _:readonly */
+/* global marked:readonly */
+
import Vue from 'vue';
import HTTP from './http';
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
data: {
input: '',
@@ -25,10 +28,10 @@ const vm = new Vue({
},
methods: {
- update: _.debounce(function (e) {
+ update: _.debounce((e) => {
this.input = e.target.value;
const payload = {
- 'guideline': this.input
+ guideline: this.input,
};
HTTP.patch('', payload).then((response) => {
this.project = response.data;
diff --git a/app/server/static/js/label.js b/app/server/static/js/label.js
index 95083fb2..2571940f 100644
--- a/app/server/static/js/label.js
+++ b/app/server/static/js/label.js
@@ -5,7 +5,7 @@ import simpleShortcut from './filter';
Vue.filter('simpleShortcut', simpleShortcut);
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
delimiters: ['[[', ']]'],
data: {
@@ -17,8 +17,8 @@ const vm = new Vue({
methods: {
generateColor() {
- const color = (Math.random() * 0xFFFFFF | 0).toString(16);
- const randomColor = "#" + ("000000" + color).slice(-6);
+ const color = (Math.random() * 0xFFFFFF | 0).toString(16); // eslint-disable-line no-bitwise
+ const randomColor = '#' + ('000000' + color).slice(-6);
return randomColor;
},
@@ -57,14 +57,14 @@ const vm = new Vue({
this.messages = [];
})
.catch((error) => {
- console.log(error);
+ console.log(error); // eslint-disable-line no-console
this.messages.push('You cannot use same label name or shortcut key.');
});
},
removeLabel(label) {
const labelId = label.id;
- HTTP.delete(`labels/${labelId}`).then((response) => {
+ HTTP.delete(`labels/${labelId}`).then(() => {
const index = this.labels.indexOf(label);
this.labels.splice(index, 1);
});
@@ -99,12 +99,12 @@ const vm = new Vue({
this.removeLabel(label);
}
HTTP.patch(`labels/${label.id}`, label)
- .then((response) => {
+ .then(() => {
this.sortLabels();
this.messages = [];
})
.catch((error) => {
- console.log(error);
+ console.log(error); // eslint-disable-line no-console
this.messages.push('You cannot use same label name or shortcut key.');
});
},
diff --git a/app/server/static/js/mixin.js b/app/server/static/js/mixin.js
index 739527e6..888ed170 100644
--- a/app/server/static/js/mixin.js
+++ b/app/server/static/js/mixin.js
@@ -1,6 +1,8 @@
+/* global marked:readonly */
+
import HTTP from './http';
-const getOffsetFromUrl = function (url) {
+const getOffsetFromUrl = (url) => {
const offsetMatch = url.match(/[?#].*offset=(\d+)/);
if (offsetMatch == null) {
return 0;
@@ -9,7 +11,7 @@ const getOffsetFromUrl = function (url) {
return parseInt(offsetMatch[1], 10);
};
-const storeOffsetInUrl = function (offset) {
+const storeOffsetInUrl = (offset) => {
let href = window.location.href;
const fragmentStart = href.indexOf('#') + 1;
@@ -19,7 +21,7 @@ const storeOffsetInUrl = function (offset) {
const prefix = href.substring(0, fragmentStart);
const fragment = href.substring(fragmentStart);
- const newFragment = fragment.split('&').map(function (fragmentPart) {
+ const newFragment = fragment.split('&').map((fragmentPart) => {
const keyValue = fragmentPart.split('=');
return keyValue[0] === 'offset'
? 'offset=' + offset
@@ -112,7 +114,7 @@ const annotationMixin = {
removeLabel(annotation) {
const docId = this.docs[this.pageNumber].id;
- HTTP.delete(`docs/${docId}/annotations/${annotation.id}`).then((response) => {
+ HTTP.delete(`docs/${docId}/annotations/${annotation.id}`).then(() => {
const index = this.annotations[this.pageNumber].indexOf(annotation);
this.annotations[this.pageNumber].splice(index, 1);
});
@@ -177,7 +179,7 @@ const annotationMixin = {
},
id2label() {
- let id2label = {};
+ const id2label = {};
for (let i = 0; i < this.labels.length; i++) {
const label = this.labels[i];
id2label[label.id] = label;
diff --git a/app/server/static/js/projects.js b/app/server/static/js/projects.js
index 098b5ee6..13bba464 100644
--- a/app/server/static/js/projects.js
+++ b/app/server/static/js/projects.js
@@ -6,7 +6,7 @@ axios.defaults.xsrfHeaderName = 'X-CSRFToken';
const baseUrl = window.location.href.split('/').slice(0, 3).join('/');
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#projects_root',
delimiters: ['[[', ']]'],
data: {
@@ -26,7 +26,7 @@ const vm = new Vue({
methods: {
deleteProject() {
- axios.delete(`${baseUrl}/v1/projects/${this.project.id}`).then((response) => {
+ axios.delete(`${baseUrl}/v1/projects/${this.project.id}`).then(() => {
this.isDelete = false;
const index = this.items.indexOf(this.project);
this.items.splice(index, 1);
@@ -108,13 +108,7 @@ const vm = new Vue({
computed: {
selectedProjects() {
- const projects = [];
- for (let item of this.items) {
- if ((this.selected === 'All Project') || this.matchType(item.project_type)) {
- projects.push(item);
- }
- }
- return projects;
+ return this.items.filter(item => this.selected === 'All Project' || this.matchType(item.project_type));
},
},
diff --git a/app/server/static/js/seq2seq.js b/app/server/static/js/seq2seq.js
index d7d6eb3e..a00abfc2 100644
--- a/app/server/static/js/seq2seq.js
+++ b/app/server/static/js/seq2seq.js
@@ -5,7 +5,7 @@ import HTTP from './http';
Vue.use(require('vue-shortkey'));
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
delimiters: ['[[', ']]'],
data: {
@@ -14,7 +14,7 @@ const vm = new Vue({
},
mixins: [annotationMixin],
directives: {
- 'todo-focus': function (el, binding) {
+ 'todo-focus': (el, binding) => {
if (binding.value) {
el.focus();
}
@@ -41,7 +41,7 @@ const vm = new Vue({
removeTodo(todo) {
const docId = this.docs[this.pageNumber].id;
- HTTP.delete(`docs/${docId}/annotations/${todo.id}`).then((response) => {
+ HTTP.delete(`docs/${docId}/annotations/${todo.id}`).then(() => {
const index = this.annotations[this.pageNumber].indexOf(todo);
this.annotations[this.pageNumber].splice(index, 1);
});
@@ -63,7 +63,7 @@ const vm = new Vue({
}
const docId = this.docs[this.pageNumber].id;
HTTP.put(`docs/${docId}/annotations/${todo.id}`, todo).then((response) => {
- console.log(response);
+ console.log(response); // eslint-disable-line no-console
});
},
diff --git a/app/server/static/js/sequence_labeling.js b/app/server/static/js/sequence_labeling.js
index 904a8cea..54b231ae 100644
--- a/app/server/static/js/sequence_labeling.js
+++ b/app/server/static/js/sequence_labeling.js
@@ -10,16 +10,16 @@ Vue.use(require('vue-shortkey'), {
Vue.filter('simpleShortcut', simpleShortcut);
Vue.component('annotator', {
- template: '\
- {{ [...text].slice(r.start_offset, r.end_offset).join(\'\') }}\
-
',
+ template: ''
+ + ' {{ [...text].slice(r.start_offset, r.end_offset).join(\'\') }}'
+ + '
',
props: {
labels: Array, // [{id: Integer, color: String, text: String}]
text: String,
@@ -33,7 +33,7 @@ Vue.component('annotator', {
},
methods: {
- setSelectedRange(e) {
+ setSelectedRange() {
let start;
let end;
if (window.getSelection) {
@@ -53,7 +53,7 @@ Vue.component('annotator', {
}
this.startOffset = start;
this.endOffset = end;
- console.log(start, end);
+ console.log(start, end); // eslint-disable-line no-console
},
validRange() {
@@ -144,7 +144,7 @@ Vue.component('annotator', {
},
id2label() {
- let id2label = {};
+ const id2label = {};
// default value;
id2label[-1] = {
text_color: '',
@@ -159,7 +159,7 @@ Vue.component('annotator', {
},
});
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
delimiters: ['[[', ']]'],
mixins: [annotationMixin],
diff --git a/app/server/static/js/stats.js b/app/server/static/js/stats.js
index b4ddd91e..914e0427 100644
--- a/app/server/static/js/stats.js
+++ b/app/server/static/js/stats.js
@@ -2,7 +2,7 @@ import { HorizontalBar, mixins, Doughnut } from 'vue-chartjs';
import Vue from 'vue';
import HTTP from './http';
-const { reactiveProp, reactiveData } = mixins;
+const { reactiveProp } = mixins;
Vue.component('line-chart', {
extends: HorizontalBar,
@@ -50,7 +50,7 @@ Vue.component('doughnut-chart', {
},
});
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
delimiters: ['[[', ']]'],
data: {
@@ -94,6 +94,5 @@ const vm = new Vue({
],
};
});
-
},
});
diff --git a/app/server/static/js/upload.js b/app/server/static/js/upload.js
index 508801e6..fdd4e42a 100644
--- a/app/server/static/js/upload.js
+++ b/app/server/static/js/upload.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import HTTP from './http';
-const vm = new Vue({
+const vm = new Vue({ // eslint-disable-line no-unused-vars
el: '#mail-app',
delimiters: ['[[', ']]'],
data: {
@@ -16,7 +16,7 @@ const vm = new Vue({
upload() {
this.isLoading = true;
this.file = this.$refs.file.files[0];
- let formData = new FormData();
+ const formData = new FormData();
formData.append('file', this.file);
formData.append('format', this.format);
HTTP.post('docs/upload',
@@ -27,7 +27,7 @@ const vm = new Vue({
},
})
.then((response) => {
- console.log(response);
+ console.log(response); // eslint-disable-line no-console
this.messages = [];
window.location = window.location.pathname.split('/').slice(0, -1).join('/');
})
@@ -42,7 +42,7 @@ const vm = new Vue({
},
download() {
- let headers = {};
+ const headers = {};
if (this.format === 'csv') {
headers.Accept = 'text/csv; charset=utf-8';
headers['Content-Type'] = 'text/csv; charset=utf-8';
@@ -62,7 +62,7 @@ const vm = new Vue({
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
- link.setAttribute('download', 'file.' + this.format); //or any other extension
+ link.setAttribute('download', 'file.' + this.format); // or any other extension
document.body.appendChild(link);
link.click();
}).catch((error) => {
diff --git a/tools/ci.sh b/tools/ci.sh
index 8342620e..3e4f9592 100755
--- a/tools/ci.sh
+++ b/tools/ci.sh
@@ -6,3 +6,5 @@ flake8
python app/manage.py migrate
python app/manage.py collectstatic
python app/manage.py test server.tests
+
+(cd app/server && npm run lint)