Compare commits

..

36 Commits

Author SHA1 Message Date
0af54a0d52 Merge pull request 'Prise en compte de la pagination sur la récupération des projets' (#44) from feature/fix-projects-fetch into develop
Reviewed-on: #44
2021-04-02 10:57:23 +02:00
d8dcf636ea Prise en compte de la pagination sur la récupération des projets 2021-04-02 10:54:49 +02:00
6bb8afd914 Merge branch 'issue-35' of wpetit/gengitkan into develop 2020-06-17 11:36:21 +02:00
3eb96fc75e Amelioration affichage colonnes fermees 2020-06-17 11:33:09 +02:00
d6597270dd Fixe erreur lors du deplacement d'un ticket d'une colonne a une autre 2020-06-17 11:15:55 +02:00
fbad143bed Factorisation de l'ouverture/fermeture d'une colonne 2020-06-16 11:31:11 +02:00
e3459d136e Ajout mode compacté 2020-06-16 11:23:57 +02:00
fe0e2667a0 Correction affichage issue ajouter depuis l'UI 2020-06-16 10:54:11 +02:00
0e93e7c52f Permettre la réduction des colonnes d'un tableau 2020-06-05 17:14:55 +02:00
832cca1c66 Refonte de l'UI des tableaux 2020-06-05 16:18:53 +02:00
94bfb77d87 Merge branch 'issue-31' of wpetit/gengitkan into develop 2020-06-05 10:34:47 +02:00
5a677d2491 Ajout commande d'installation des dépendances client #31 2020-06-05 10:33:36 +02:00
fbb2b3f8da Ajout d'une redirection automatique sur la page "referer" en cas de
perte de session
2020-05-19 22:22:37 +02:00
44182fd1cd Modification nom cookie 2020-05-19 22:22:24 +02:00
4e9298f5b6 Filtrage des PRs de la liste des issues 2020-05-19 17:21:46 +02:00
9dce43fd58 Intégration de github.com/zalmoxisus/redux-devtools-extension 2020-05-19 17:20:31 +02:00
3fa2b5905a Correction auto-logout 2020-05-06 11:30:26 +02:00
b456fe9f65 Correction algorithme de détection des tickets non collectés
Voir #22
2020-04-30 17:27:58 +02:00
7309a2157b Correction création de ticket sur la première colonne
Voir #26
2020-04-30 17:08:48 +02:00
79a12e89f7 Ajout d'un mode "compact"
Ce mode compact est activé par défaut

Voir #24
2020-04-30 17:08:06 +02:00
b4ce7c3777 Possibilité de créer une voie de type "Backlog"
Une voie peut désormais "récolter" toutes les issues qui ne sont pas
déjà sélectionnées par d'autres voies i.e. matérialiser un "backlog".

Voir #22
2020-04-30 15:43:40 +02:00
647c5c0806 Migration du client sur Typescript 2020-04-30 13:02:56 +02:00
676df834f7 Rename project/module to gengitkan 2020-04-30 10:32:12 +02:00
7df6e9ee01 Ajout procédure de démarrage avec configuration OAuth2
fix #23
2019-12-27 16:35:47 +01:00
38815389bc Fix automatic logout on failure 2019-12-13 15:48:58 +01:00
0074f318fd Use HTMLWebpackPlugin 2019-12-13 14:21:20 +01:00
a7f0eabb97 Show empty boards to all users 2019-12-13 13:31:50 +01:00
477f221b24 Add project and issue links in card 2019-12-13 13:31:27 +01:00
b087e50292 Fix error when no lane is defined in board 2019-12-13 13:30:53 +01:00
33a0c7850a Logout via AJAX 2019-12-13 13:30:33 +01:00
860ee438fc Allow board delete via UI
ref #16
2019-12-13 13:28:59 +01:00
1dedda7d50 Add margin before board edition form 2019-12-13 12:00:49 +01:00
e20bf045cf Fix logo display in Firefox 2019-12-13 12:00:25 +01:00
6580d01370 Fix lane height display 2019-12-13 12:00:07 +01:00
da6a408634 Fix session timeout detection 2019-12-06 17:15:18 +01:00
10151db229 Fix card move in Firefox 2019-12-06 17:14:40 +01:00
77 changed files with 1745 additions and 640 deletions

View File

@ -1,4 +1,4 @@
# Gitea Kan # GenGitKan
## Démarrer avec les sources ## Démarrer avec les sources
@ -9,9 +9,41 @@
### Procédure ### Procédure
```bash ```bash
cd client && npm install # Installation des dépendances client
make watch # Surveiller les modifications sur le sources et compiler/démarrer le serveur make watch # Surveiller les modifications sur le sources et compiler/démarrer le serveur
``` ```
Au premier lancement, le fichier de configuration doit être complété afin de d'utiliser l'instance Gitea ciblée comme fournisseur OAuth2.
Modifier le fichier `data/server.conf` de la manière suivante (la forge Cadoles est utilisée dans cet exemple):
```ini
Debug = false
[HTTP]
Address = :3000
PublicDir = ${GENGITKAN_HTTP_PUBDIR}
[Gitea]
BaseURL = https://forge.cadoles.com
ClientID = <ClientID>
ClientSecret = <ClientSecret>
RedirectURL = http://localhost:3000/callback
AuthURL = https://forge.cadoles.com/login/oauth/authorize
LogoutURL = https://forge.cadoles.com/user/logout
TokenURL = https://forge.cadoles.com/login/oauth/access_token
APIBaseURL = https://forge.cadoles.com/api
Scopes = api
[Data]
DBPath = ${GENGITKAN_DATA_DBPATH}
```
Les valeurs pour `<ClientID>` et `<ClientSecret>` sont à récupérer sur la page https://forge.cadoles.com/user/settings/applications, dans la section `Gérer les applications OAuth2`.
Vous devrez créer une application et configurer l'URL de redirection pour qu'elle corresponde à la valeur `RedirectURL` de votre fichier de configuration afin de pouvoir utiliser votre instance locale de GenGitKan.
## Licence ## Licence
[AGPL-3.0](./LICENSE) [AGPL-3.0](./LICENSE)

View File

@ -1,10 +0,0 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
[ "@babel/transform-runtime" ],
[ "@babel/plugin-proposal-class-properties" ]
]
}

819
client/package-lock.json generated
View File

@ -1162,22 +1162,6 @@
} }
} }
}, },
"@babel/runtime-corejs2": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.7.4.tgz",
"integrity": "sha512-hKNcmHQbBSJFnZ82ewYtWDZ3fXkP/l1XcfRtm7c8gHPM/DMecJtFFBEp7KMLZTuHwwb7RfemHdsEnd7L916Z6A==",
"requires": {
"core-js": "^2.6.5",
"regenerator-runtime": "^0.13.2"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz",
"integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw=="
}
}
},
"@babel/template": { "@babel/template": {
"version": "7.7.0", "version": "7.7.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz",
@ -1259,11 +1243,11 @@
"dev": true "dev": true
}, },
"@lourenci/react-kanban": { "@lourenci/react-kanban": {
"version": "0.15.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@lourenci/react-kanban/-/react-kanban-0.15.0.tgz", "resolved": "https://registry.npmjs.org/@lourenci/react-kanban/-/react-kanban-2.0.0.tgz",
"integrity": "sha512-/2XjB26iXcvpwDwlT3sz8/ptQ7QyTpMGlrPf1f02+V1Z4jdbVMo6Luz1sGlHe/TP68N8yz69/YT9qwqHZ6YYmQ==", "integrity": "sha512-ieNi7d/01wgT9t8kN7Z/RBubyZq9VvKXyU/5sj+UlrY8h4GPRNrN11jLV/ykg55lhyXGKgXDk3ObYurOfrmu3w==",
"requires": { "requires": {
"react-beautiful-dnd": "^11.0.0" "react-beautiful-dnd": "^13.0.0"
} }
}, },
"@redux-saga/core": { "@redux-saga/core": {
@ -1313,6 +1297,92 @@
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.1.0.tgz",
"integrity": "sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==" "integrity": "sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg=="
}, },
"@types/history": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz",
"integrity": "sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw==",
"dev": true
},
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dev": true,
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/node": {
"version": "13.13.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz",
"integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==",
"dev": true
},
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
"dev": true
},
"@types/react": {
"version": "16.9.34",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.34.tgz",
"integrity": "sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-dom": {
"version": "16.9.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.7.tgz",
"integrity": "sha512-GHTYhM8/OwUCf254WO5xqR/aqD3gC9kSTLpopWGpQLpnw23jk44RvMHsyUSEplvRJZdHxhJGMMLF0kCPYHPhQA==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/react-redux": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.7.tgz",
"integrity": "sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg==",
"dev": true,
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/react-router": {
"version": "5.1.7",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.7.tgz",
"integrity": "sha512-2ouP76VQafKjtuc0ShpwUebhHwJo0G6rhahW9Pb8au3tQTjYXd2jta4wv6U2tGLR/I42yuG00+UXjNYY0dTzbg==",
"dev": true,
"requires": {
"@types/history": "*",
"@types/react": "*"
}
},
"@types/react-router-dom": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz",
"integrity": "sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==",
"dev": true,
"requires": {
"@types/history": "*",
"@types/react": "*",
"@types/react-router": "*"
}
},
"@types/uuid": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-7.0.3.tgz",
"integrity": "sha512-PUdqTZVrNYTNcIhLHkiaYzoOIaUi5LFg/XLerAdgvwQrUCx+oSbtoBze1AMyvYbcwzUSNC+Isl58SM4Sm/6COw==",
"dev": true
},
"@webassemblyjs/ast": { "@webassemblyjs/ast": {
"version": "1.8.5", "version": "1.8.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
@ -1508,9 +1578,9 @@
"dev": true "dev": true
}, },
"acorn": { "acorn": {
"version": "6.3.0", "version": "6.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
"integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
"dev": true "dev": true
}, },
"adjust-sourcemap-loader": { "adjust-sourcemap-loader": {
@ -1727,9 +1797,9 @@
"dev": true "dev": true
}, },
"aws4": { "aws4": {
"version": "1.8.0", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
"dev": true "dev": true
}, },
"babel-code-frame": { "babel-code-frame": {
@ -1909,6 +1979,12 @@
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"dev": true "dev": true
}, },
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -2076,6 +2152,11 @@
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.5.tgz", "resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.5.tgz",
"integrity": "sha512-cX98TIn0I6sKba/DhW0FBjtaDpxTelU166pf7ICXpCCuplHWyu6C9LYZmL5PEsnePIeJaiorsTEzzNk3Tsm1hw==" "integrity": "sha512-cX98TIn0I6sKba/DhW0FBjtaDpxTelU166pf7ICXpCCuplHWyu6C9LYZmL5PEsnePIeJaiorsTEzzNk3Tsm1hw=="
}, },
"bulma-switch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/bulma-switch/-/bulma-switch-2.0.0.tgz",
"integrity": "sha512-myD38zeUfjmdduq+pXabhJEe3x2hQP48l/OI+Y0fO3HdDynZUY/VJygucvEAJKRjr4HxD5DnEm4yx+oDOBXpAA=="
},
"cacache": { "cacache": {
"version": "12.0.3", "version": "12.0.3",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz",
@ -2433,7 +2514,8 @@
"core-js": { "core-js": {
"version": "2.6.10", "version": "2.6.10",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz",
"integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==",
"dev": true
}, },
"core-js-compat": { "core-js-compat": {
"version": "3.4.1", "version": "3.4.1",
@ -2538,9 +2620,9 @@
} }
}, },
"css-box-model": { "css-box-model": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.0.tgz", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-lri0br+jSNV0kkkiGEp9y9y3Njq2PmpqbeGWRFQJuZteZzY9iC9GZhQ8Y4WpPwM/2YocjHePxy14igJY7YKzkA==", "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"requires": { "requires": {
"tiny-invariant": "^1.0.6" "tiny-invariant": "^1.0.6"
} }
@ -2570,6 +2652,18 @@
"source-list-map": "^2.0.0" "source-list-map": "^2.0.0"
} }
}, },
"css-select": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
"dev": true,
"requires": {
"boolbase": "~1.0.0",
"css-what": "2.1",
"domutils": "1.5.1",
"nth-check": "~1.0.1"
}
},
"css-selector-tokenizer": { "css-selector-tokenizer": {
"version": "0.7.1", "version": "0.7.1",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz",
@ -2591,12 +2685,24 @@
"postcss-value-parser": "^3.3.0" "postcss-value-parser": "^3.3.0"
} }
}, },
"css-what": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
"dev": true
},
"cssesc": { "cssesc": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
"integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
"dev": true "dev": true
}, },
"csstype": {
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
"integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==",
"dev": true
},
"currently-unhandled": { "currently-unhandled": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@ -2747,12 +2853,64 @@
"randombytes": "^2.0.0" "randombytes": "^2.0.0"
} }
}, },
"dom-converter": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
"integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==",
"dev": true,
"requires": {
"utila": "~0.4"
}
},
"dom-serializer": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
"integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
"dev": true,
"requires": {
"domelementtype": "^2.0.1",
"entities": "^2.0.0"
},
"dependencies": {
"domelementtype": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
"dev": true
}
}
},
"domain-browser": { "domain-browser": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
"integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
"dev": true "dev": true
}, },
"domelementtype": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
"dev": true
},
"domhandler": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
"dev": true,
"requires": {
"domelementtype": "1"
}
},
"domutils": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
"dev": true,
"requires": {
"dom-serializer": "0",
"domelementtype": "1"
}
},
"duplexify": { "duplexify": {
"version": "3.7.1", "version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
@ -2828,6 +2986,12 @@
"tapable": "^1.0.0" "tapable": "^1.0.0"
} }
}, },
"entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==",
"dev": true
},
"errno": { "errno": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
@ -2846,6 +3010,36 @@
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
}, },
"es-abstract": {
"version": "1.17.0-next.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.0-next.1.tgz",
"integrity": "sha512-7MmGr03N7Rnuid6+wyhD9sHNE2n4tFSwExnU2lQl3lIo2ShXWGePY80zYaoMOmILWv57H0amMjZGHNzzGG70Rw==",
"dev": true,
"requires": {
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.1.4",
"is-regex": "^1.0.4",
"object-inspect": "^1.7.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.0",
"string.prototype.trimleft": "^2.1.0",
"string.prototype.trimright": "^2.1.0"
}
},
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
"dev": true,
"requires": {
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
"is-symbol": "^1.0.2"
}
},
"es5-ext": { "es5-ext": {
"version": "0.10.52", "version": "0.10.52",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.52.tgz", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.52.tgz",
@ -3326,7 +3520,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -3347,12 +3542,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -3367,17 +3564,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -3494,7 +3694,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -3506,6 +3707,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -3520,6 +3722,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -3527,12 +3730,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -3551,6 +3756,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -3631,7 +3837,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -3643,6 +3850,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -3728,7 +3936,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -3764,6 +3973,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -3783,6 +3993,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -3826,12 +4037,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@ -3991,13 +4204,13 @@
"dev": true "dev": true
}, },
"globule": { "globule": {
"version": "1.2.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz",
"integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==",
"dev": true, "dev": true,
"requires": { "requires": {
"glob": "~7.1.1", "glob": "~7.1.1",
"lodash": "~4.17.10", "lodash": "~4.17.12",
"minimatch": "~3.0.2" "minimatch": "~3.0.2"
} }
}, },
@ -4028,6 +4241,15 @@
"har-schema": "^2.0.0" "har-schema": "^2.0.0"
} }
}, },
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
},
"has-ansi": { "has-ansi": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
@ -4154,9 +4376,9 @@
} }
}, },
"hosted-git-info": { "hosted-git-info": {
"version": "2.8.5", "version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"dev": true "dev": true
}, },
"html-loader": { "html-loader": {
@ -4187,6 +4409,80 @@
"uglify-js": "3.4.x" "uglify-js": "3.4.x"
} }
}, },
"html-webpack-plugin": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
"integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=",
"dev": true,
"requires": {
"html-minifier": "^3.2.3",
"loader-utils": "^0.2.16",
"lodash": "^4.17.3",
"pretty-error": "^2.0.2",
"tapable": "^1.0.0",
"toposort": "^1.0.0",
"util.promisify": "1.0.0"
},
"dependencies": {
"big.js": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz",
"integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==",
"dev": true
},
"json5": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
"dev": true
},
"loader-utils": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
"integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
"dev": true,
"requires": {
"big.js": "^3.1.3",
"emojis-list": "^2.0.0",
"json5": "^0.5.0",
"object-assign": "^4.0.1"
}
}
}
},
"htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
"dev": true,
"requires": {
"domelementtype": "^1.3.1",
"domhandler": "^2.3.0",
"domutils": "^1.5.1",
"entities": "^1.1.1",
"inherits": "^2.0.1",
"readable-stream": "^3.1.1"
},
"dependencies": {
"entities": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==",
"dev": true
},
"readable-stream": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"http-signature": { "http-signature": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@ -4248,9 +4544,9 @@
"dev": true "dev": true
}, },
"in-publish": { "in-publish": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz",
"integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==",
"dev": true "dev": true
}, },
"indent-string": { "indent-string": {
@ -4351,6 +4647,12 @@
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true "dev": true
}, },
"is-callable": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
"dev": true
},
"is-data-descriptor": { "is-data-descriptor": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
@ -4371,6 +4673,12 @@
} }
} }
}, },
"is-date-object": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
"dev": true
},
"is-descriptor": { "is-descriptor": {
"version": "0.1.6", "version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
@ -4403,13 +4711,10 @@
"dev": true "dev": true
}, },
"is-finite": { "is-finite": {
"version": "1.0.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz",
"integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==",
"dev": true, "dev": true
"requires": {
"number-is-nan": "^1.0.0"
}
}, },
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "1.0.0", "version": "1.0.0",
@ -4458,12 +4763,30 @@
"isobject": "^3.0.1" "isobject": "^3.0.1"
} }
}, },
"is-regex": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
"integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
"dev": true,
"requires": {
"has": "^1.0.1"
}
},
"is-stream": { "is-stream": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"dev": true "dev": true
}, },
"is-symbol": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
"dev": true,
"requires": {
"has-symbols": "^1.0.1"
}
},
"is-typedarray": { "is-typedarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@ -4518,9 +4841,9 @@
"dev": true "dev": true
}, },
"js-base64": { "js-base64": {
"version": "2.5.1", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
"integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==",
"dev": true "dev": true
}, },
"js-levenshtein": { "js-levenshtein": {
@ -4592,9 +4915,9 @@
} }
}, },
"kind-of": { "kind-of": {
"version": "6.0.2", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true "dev": true
}, },
"lcid": { "lcid": {
@ -4659,6 +4982,12 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
}, },
"lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
"integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=",
"dev": true
},
"loose-envify": { "loose-envify": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -4842,18 +5171,18 @@
} }
}, },
"mime-db": { "mime-db": {
"version": "1.42.0", "version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
"integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==", "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
"dev": true "dev": true
}, },
"mime-types": { "mime-types": {
"version": "2.1.25", "version": "2.1.27",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
"integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
"dev": true, "dev": true,
"requires": { "requires": {
"mime-db": "1.42.0" "mime-db": "1.44.0"
} }
}, },
"mimic-fn": { "mimic-fn": {
@ -4905,9 +5234,9 @@
} }
}, },
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true "dev": true
}, },
"mississippi": { "mississippi": {
@ -4950,20 +5279,12 @@
} }
}, },
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "^1.2.5"
},
"dependencies": {
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
}
} }
}, },
"move-concurrently": { "move-concurrently": {
@ -5138,9 +5459,9 @@
} }
}, },
"node-sass": { "node-sass": {
"version": "4.13.0", "version": "4.14.0",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.0.tgz", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.0.tgz",
"integrity": "sha512-W1XBrvoJ1dy7VsvTAS5q1V45lREbTlZQqFbiHb3R3OTTCma0XBtuG6xZ6Z4506nR4lmHPTqVRwxT6KgtWC97CA==", "integrity": "sha512-AxqU+DFpk0lEz95sI6jO0hU0Rwyw7BXVEv6o9OItoXLyeygPeaSpiV4rwQb10JiTghHaa0gZeD21sz+OsQluaw==",
"dev": true, "dev": true,
"requires": { "requires": {
"async-foreach": "^0.1.3", "async-foreach": "^0.1.3",
@ -5210,6 +5531,15 @@
"set-blocking": "~2.0.0" "set-blocking": "~2.0.0"
} }
}, },
"nth-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
"integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
"dev": true,
"requires": {
"boolbase": "~1.0.0"
}
},
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
@ -5258,6 +5588,12 @@
} }
} }
}, },
"object-inspect": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
"integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==",
"dev": true
},
"object-keys": { "object-keys": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@ -5291,6 +5627,16 @@
"object-keys": "^1.0.11" "object-keys": "^1.0.11"
} }
}, },
"object.getownpropertydescriptors": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz",
"integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.0-next.1"
}
},
"object.pick": { "object.pick": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
@ -5533,6 +5879,12 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true "dev": true
}, },
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
"dev": true
},
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@ -5666,6 +6018,16 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
}, },
"pretty-error": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz",
"integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=",
"dev": true,
"requires": {
"renderkid": "^2.0.1",
"utila": "~0.4"
}
},
"private": { "private": {
"version": "0.1.8", "version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
@ -5713,9 +6075,9 @@
"dev": true "dev": true
}, },
"psl": { "psl": {
"version": "1.4.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
"dev": true "dev": true
}, },
"public-encrypt": { "public-encrypt": {
@ -5824,18 +6186,32 @@
} }
}, },
"react-beautiful-dnd": { "react-beautiful-dnd": {
"version": "11.0.5", "version": "13.0.0",
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-11.0.5.tgz", "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz",
"integrity": "sha512-7llby9U+jIfkINcyxPHVWU0HFYzqxMemUYgGHsFsbx4fZo1n/pW6sYKYzhxGxR3Ap5HxqswcQkKUZX4uEUWhlw==", "integrity": "sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg==",
"requires": { "requires": {
"@babel/runtime-corejs2": "^7.4.5", "@babel/runtime": "^7.8.4",
"css-box-model": "^1.1.2", "css-box-model": "^1.2.0",
"memoize-one": "^5.0.4", "memoize-one": "^5.1.1",
"raf-schd": "^4.0.0", "raf-schd": "^4.0.2",
"react-redux": "^7.0.3", "react-redux": "^7.1.1",
"redux": "^4.0.1", "redux": "^4.0.4",
"tiny-invariant": "^1.0.4", "use-memo-one": "^1.1.1"
"use-memo-one": "^1.1.0" },
"dependencies": {
"@babel/runtime": {
"version": "7.10.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.2.tgz",
"integrity": "sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"regenerator-runtime": {
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
} }
}, },
"react-dom": { "react-dom": {
@ -5965,6 +6341,12 @@
} }
} }
}, },
"recursive-readdir-sync": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/recursive-readdir-sync/-/recursive-readdir-sync-1.0.6.tgz",
"integrity": "sha1-Hb9tMvPFu4083pemxYjVR6nhPVY=",
"dev": true
},
"redent": { "redent": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
@ -6085,6 +6467,19 @@
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
"dev": true "dev": true
}, },
"renderkid": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz",
"integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==",
"dev": true,
"requires": {
"css-select": "^1.1.0",
"dom-converter": "^0.2",
"htmlparser2": "^3.3.0",
"strip-ansi": "^3.0.0",
"utila": "^0.4.0"
}
},
"repeat-element": { "repeat-element": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
@ -6107,9 +6502,9 @@
} }
}, },
"request": { "request": {
"version": "2.88.0", "version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
"dev": true, "dev": true,
"requires": { "requires": {
"aws-sign2": "~0.7.0", "aws-sign2": "~0.7.0",
@ -6119,7 +6514,7 @@
"extend": "~3.0.2", "extend": "~3.0.2",
"forever-agent": "~0.6.1", "forever-agent": "~0.6.1",
"form-data": "~2.3.2", "form-data": "~2.3.2",
"har-validator": "~5.1.0", "har-validator": "~5.1.3",
"http-signature": "~1.2.0", "http-signature": "~1.2.0",
"is-typedarray": "~1.0.0", "is-typedarray": "~1.0.0",
"isstream": "~0.1.2", "isstream": "~0.1.2",
@ -6129,7 +6524,7 @@
"performance-now": "^2.1.0", "performance-now": "^2.1.0",
"qs": "~6.5.2", "qs": "~6.5.2",
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"tough-cookie": "~2.4.3", "tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0", "tunnel-agent": "^0.6.0",
"uuid": "^3.3.2" "uuid": "^3.3.2"
} }
@ -6446,12 +6841,6 @@
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true "dev": true
}, },
"serialize-javascript": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz",
"integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
"dev": true
},
"set-blocking": { "set-blocking": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@ -6692,9 +7081,9 @@
} }
}, },
"spdx-exceptions": { "spdx-exceptions": {
"version": "2.2.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
"integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
"dev": true "dev": true
}, },
"spdx-expression-parse": { "spdx-expression-parse": {
@ -6828,6 +7217,26 @@
"strip-ansi": "^3.0.0" "strip-ansi": "^3.0.0"
} }
}, },
"string.prototype.trimleft": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
"integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"function-bind": "^1.1.1"
}
},
"string.prototype.trimright": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
"integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
"dev": true,
"requires": {
"define-properties": "^1.1.3",
"function-bind": "^1.1.1"
}
},
"string_decoder": { "string_decoder": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@ -6968,20 +7377,28 @@
} }
}, },
"terser-webpack-plugin": { "terser-webpack-plugin": {
"version": "1.4.1", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
"integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
"dev": true, "dev": true,
"requires": { "requires": {
"cacache": "^12.0.2", "cacache": "^12.0.2",
"find-cache-dir": "^2.1.0", "find-cache-dir": "^2.1.0",
"is-wsl": "^1.1.0", "is-wsl": "^1.1.0",
"schema-utils": "^1.0.0", "schema-utils": "^1.0.0",
"serialize-javascript": "^1.7.0", "serialize-javascript": "^2.1.2",
"source-map": "^0.6.1", "source-map": "^0.6.1",
"terser": "^4.1.2", "terser": "^4.1.2",
"webpack-sources": "^1.4.0", "webpack-sources": "^1.4.0",
"worker-farm": "^1.7.0" "worker-farm": "^1.7.0"
},
"dependencies": {
"serialize-javascript": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
"dev": true
}
} }
}, },
"through": { "through": {
@ -7072,22 +7489,20 @@
"repeat-string": "^1.6.1" "repeat-string": "^1.6.1"
} }
}, },
"toposort": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz",
"integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=",
"dev": true
},
"tough-cookie": { "tough-cookie": {
"version": "2.4.3", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"dev": true, "dev": true,
"requires": { "requires": {
"psl": "^1.1.24", "psl": "^1.1.28",
"punycode": "^1.4.1" "punycode": "^2.1.1"
},
"dependencies": {
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
}
} }
}, },
"trim-newlines": { "trim-newlines": {
@ -7105,6 +7520,99 @@
"glob": "^7.1.2" "glob": "^7.1.2"
} }
}, },
"ts-loader": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.2.tgz",
"integrity": "sha512-DwpZFB67RoILQHx42dMjSgv2STpacsQu5X+GD/H9ocd8IhU0m8p3b/ZrIln2KmcucC6xep2PdEMEblpWT71euA==",
"dev": true,
"requires": {
"chalk": "^2.3.0",
"enhanced-resolve": "^4.0.0",
"loader-utils": "^1.0.2",
"micromatch": "^4.0.0",
"semver": "^6.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"micromatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
"dev": true,
"requires": {
"braces": "^3.0.1",
"picomatch": "^2.0.5"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
}
}
},
"tslib": { "tslib": {
"version": "1.10.0", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
@ -7144,6 +7652,11 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"dev": true "dev": true
}, },
"typescript": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w=="
},
"typescript-compare": { "typescript-compare": {
"version": "0.0.2", "version": "0.0.2",
"resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
@ -7360,6 +7873,22 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true "dev": true
}, },
"util.promisify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
"integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
"dev": true,
"requires": {
"define-properties": "^1.1.2",
"object.getownpropertydescriptors": "^2.0.3"
}
},
"utila": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
"integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=",
"dev": true
},
"uuid": { "uuid": {
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
@ -7457,6 +7986,28 @@
} }
} }
}, },
"webpack-cleanup-plugin": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/webpack-cleanup-plugin/-/webpack-cleanup-plugin-0.5.1.tgz",
"integrity": "sha1-3y1wa9dTZMBuZbBRGGMW1nTrlq8=",
"dev": true,
"requires": {
"lodash.union": "4.6.0",
"minimatch": "3.0.3",
"recursive-readdir-sync": "1.0.6"
},
"dependencies": {
"minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=",
"dev": true,
"requires": {
"brace-expansion": "^1.0.0"
}
}
}
},
"webpack-cli": { "webpack-cli": {
"version": "3.3.10", "version": "3.3.10",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.10.tgz", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.10.tgz",

View File

@ -25,23 +25,32 @@
"@babel/preset-env": "^7.7.1", "@babel/preset-env": "^7.7.1",
"@babel/preset-react": "^7.7.4", "@babel/preset-react": "^7.7.4",
"@fortawesome/fontawesome-free": "^5.11.2", "@fortawesome/fontawesome-free": "^5.11.2",
"@types/node": "^13.13.4",
"@types/react-dom": "^16.9.7",
"@types/react-router-dom": "^5.1.5",
"@types/uuid": "^7.0.3",
"@types/react-redux": "^7.1.7",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"css-loader": "^1.0.1", "css-loader": "^1.0.1",
"extract-loader": "^3.1.0", "extract-loader": "^3.1.0",
"file-loader": "^2.0.0", "file-loader": "^2.0.0",
"html-loader": "^0.5.5", "html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.4.4", "mini-css-extract-plugin": "^0.4.4",
"node-sass": "^4.10.0", "node-sass": "^4.14.0",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"resolve-url-loader": "^3.0.0", "resolve-url-loader": "^3.0.0",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"webpack": "^4.25.0", "webpack": "^4.25.0",
"webpack-cli": "^3.1.2" "webpack-cleanup-plugin": "^0.5.1",
"webpack-cli": "^3.1.2",
"ts-loader": "^7.0.2"
}, },
"dependencies": { "dependencies": {
"@lourenci/react-kanban": "^0.15.0", "@lourenci/react-kanban": "^2.0.0",
"bulma": "^0.7.2", "bulma": "^0.7.2",
"bulma-switch": "^2.0.0",
"react": "^16.12.0", "react": "^16.12.0",
"react-dom": "^16.12.0", "react-dom": "^16.12.0",
"react-redux": "^7.1.3", "react-redux": "^7.1.3",
@ -50,6 +59,7 @@
"redux": "^4.0.4", "redux": "^4.0.4",
"redux-saga": "^1.1.3", "redux-saga": "^1.1.3",
"styled-components": "^4.4.1", "styled-components": "^4.4.1",
"typescript": "^3.8.3",
"uuid": "^3.3.3" "uuid": "^3.3.3"
} }
} }

View File

@ -5,6 +5,7 @@ import { ConnectedBoardPage as BoardPage } from './BoardPage/BoardPage';
import { ConnectedEditBoardPage as EditBoardPage } from './BoardPage/EditBoardPage'; import { ConnectedEditBoardPage as EditBoardPage } from './BoardPage/EditBoardPage';
import { store } from '../store/store'; import { store } from '../store/store';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { logout } from '../store/actions/logout';
export class App extends React.Component { export class App extends React.Component {
render() { render() {
@ -16,10 +17,9 @@ export class App extends React.Component {
<Route path="/boards/new" exact component={EditBoardPage} /> <Route path="/boards/new" exact component={EditBoardPage} />
<Route path="/boards/:id" exact component={BoardPage} /> <Route path="/boards/:id" exact component={BoardPage} />
<Route path="/boards/:id/edit" exact component={EditBoardPage} /> <Route path="/boards/:id/edit" exact component={EditBoardPage} />
<Route path="/boards/:id/delete" exact component={BoardPage} />
<Route path="/logout" exact component={() => { <Route path="/logout" exact component={() => {
window.location = "/logout"; this.logout();
return null; return <Redirect to="/" />;
}} /> }} />
<Route component={() => <Redirect to="/" />} /> <Route component={() => <Redirect to="/" />} />
</Switch> </Switch>
@ -27,4 +27,8 @@ export class App extends React.Component {
</Provider> </Provider>
); );
} }
logout() {
store.dispatch(logout());
}
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React, { Fragment } from 'react';
import { Page } from '../Page'; import { Page } from '../Page';
import { connect } from 'react-redux'; import { connect, DispatchProp } from 'react-redux';
import Board from '@lourenci/react-kanban'; import Board, { addColumn } from '@lourenci/react-kanban';
import { fetchBoards } from '../../store/actions/boards'; import { fetchBoards } from '../../store/actions/boards';
import { createIssue } from '../../store/actions/issues'; import { createIssue } from '../../store/actions/issues';
import { buildKanboard, moveCard } from '../../store/actions/kanboards'; import { buildKanboard, moveCard } from '../../store/actions/kanboards';
@ -9,7 +9,12 @@ import { Loader } from '../Loader';
import { IssueCard } from './IssueCard'; import { IssueCard } from './IssueCard';
import { Modal } from '../Modal'; import { Modal } from '../Modal';
export class BoardPage extends React.Component { export interface BoardPageProps extends DispatchProp {
board: any
kanboard: any
}
export class BoardPage extends React.Component<BoardPageProps> {
state = { state = {
newCardModalActive: false, newCardModalActive: false,
@ -18,10 +23,16 @@ export class BoardPage extends React.Component {
title: "", title: "",
body: "", body: "",
project: "" project: ""
} },
compactMode: true,
hasError: false,
} }
constructor(props) { onNewCardTitleChange: (evt: any) => void;
onNewCardBodyChange: (evt: any) => void;
onNewCardProjectChange: (evt: any) => void;
constructor(props: BoardPageProps) {
super(props); super(props);
this.renderLaneHeader = this.renderLaneHeader.bind(this); this.renderLaneHeader = this.renderLaneHeader.bind(this);
this.onNewCardClick = this.onNewCardClick.bind(this); this.onNewCardClick = this.onNewCardClick.bind(this);
@ -32,41 +43,83 @@ export class BoardPage extends React.Component {
this.onNewCardSaveClick = this.onNewCardSaveClick.bind(this); this.onNewCardSaveClick = this.onNewCardSaveClick.bind(this);
} }
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.error(error, errorInfo);
}
render() { render() {
const { board } = this.props; const { board } = this.props;
return ( return (
<Page title={`${board ? (board.title + ' - ') : ''}GenGitKan`}> <Page title={`${board ? (board.title + ' - ') : ''}GenGitKan`}>
<div className="container is-fluid">
{this.renderBoard()} {this.renderBoard()}
</div>
</Page> </Page>
); );
} }
renderBoard() { renderBoard() {
const { kanboard } = this.props; const { kanboard, board } = this.props;
if (!kanboard) { if (!kanboard) {
return <Loader></Loader> return <Loader></Loader>
} }
return ( return (
<Fragment>
<nav className="navbar is-light">
<div className="container is-fluid">
<div className="navbar-start">
<div className="navbar-item">
{board.title}
</div>
</div>
<div className="navbar-end">
<div className="navbar-item">
<div className="field">
<input id="compactMode"
checked={this.state.compactMode}
onChange={this.onCompactModeChange.bind(this)}
type="checkbox"
className="switch is-outlined is-success"
name="compactMode"
/>
<label htmlFor="compactMode">Mode compact</label>
</div>
</div>
<a href={`#/boards/${board.id}/edit`} className="navbar-item">
<span className="icon">
<i className="fa fa-edit fa-fw"></i>
</span>
<span>Modifier le tableau</span>
</a>
</div>
</div>
</nav>
<div className="container is-fluid">
<div className="kanboard-container is-fullheight"> <div className="kanboard-container is-fullheight">
<Board disableLaneDrag={true} <Board
renderCard={this.renderCard} renderCard={this.renderCard.bind(this)}
renderLaneHeader={this.renderLaneHeader} renderColumnHeader={this.renderLaneHeader.bind(this)}
onCardDragEnd={this.onCardDragEnd.bind(this)}> onCardDragEnd={this.onCardDragEnd.bind(this)}
disableColumnDrag={true}
>
{kanboard} {kanboard}
</Board> </Board>
{ this.renderNewCardModal() } { this.renderNewCardModal() }
</div> </div>
</div>
</Fragment>
); );
} }
renderNewCardModal() { renderNewCardModal() {
const { newCardModalActive, newCardLaneID } = this.state; const { newCardModalActive, newCardLaneID } = this.state;
const { board } = this.props; const { board } = this.props;
if (!board) return null;
if (!board || newCardLaneID === undefined) return null;
return ( return (
<Modal active={newCardModalActive}> <Modal active={newCardModalActive}>
<div className="new-card-modal"> <div className="new-card-modal">
@ -90,18 +143,18 @@ export class BoardPage extends React.Component {
placeholder="Description du nouveau ticket..." placeholder="Description du nouveau ticket..."
value={this.state.newCard.body} value={this.state.newCard.body}
onChange={this.onNewCardBodyChange} onChange={this.onNewCardBodyChange}
rows="10"> rows={10}>
</textarea> </textarea>
</div> </div>
</div> </div>
<div className="field"> <div className="field">
<div className="control is-expanded"> <div className="control is-expanded">
<div className="select is-fullwidth" <div className="select is-fullwidth">
<select
value={this.state.newCard.project} value={this.state.newCard.project}
onChange={this.onNewCardProjectChange}> onChange={this.onNewCardProjectChange}>
<select>
{ {
board.projects.map((p, i) => { board.projects.map((p: any, i: number) => {
return <option key={`new-card-project-${i}`} value={p}>{p}</option> return <option key={`new-card-project-${i}`} value={p}>{p}</option>
}) })
} }
@ -130,37 +183,58 @@ export class BoardPage extends React.Component {
) )
} }
renderCard(card) { renderCard(card: any) {
return <IssueCard card={card} />; return <IssueCard compact={this.state.compactMode} card={card} />;
} }
renderLaneHeader(lane) { renderLaneHeader(lane: any) {
return ( return (
<div className="kanboard-lane"> <div className="kanboard-lane">
<div className="level"> <div className="level">
<div className="level-left"> <div className="level-left">
<h3 className="level-item is-size-3">{lane.title}</h3> <div className="level-item">
<span className="tag is-primary is-light is-normal">{lane.cards.length}</span>
</div> </div>
<div className="level-right"> <button className="button is-light level-item is-small expand"
<button className="button is-light level-item is-medium" onClick={this.onMinimizeColumn}>
<span className="icon">
<i className="fas fa-chevron-right" aria-hidden="true"></i>
</span>
</button>
<h3 className="level-item is-size-5">
{lane.title}
</h3>
</div>
<div className="level-right is-show-expand">
<button className="button is-light level-item is-small"
onClick={this.onNewCardClick.bind(this, lane.id)}> onClick={this.onNewCardClick.bind(this, lane.id)}>
<span className="icon"> <span className="icon">
<i className="fas fa-plus" aria-hidden="true"></i> <i className="fas fa-plus" aria-hidden="true"></i>
</span> </span>
</button> </button>
<button className="button is-light level-item is-small"
onClick={this.onMinimizeColumn}>
<span className="icon">
<i className="fas fa-chevron-left" aria-hidden="true"></i>
</span>
</button>
</div> </div>
</div> </div>
</div> </div>
) )
} }
onCardDragEnd(source, dest) { onMinimizeColumn(e: any) {
e.currentTarget.closest('.react-kanban-column').classList.toggle('minimized');
}
onCardDragEnd(card: any, source: any, dest: any) {
const { board } = this.props; const { board } = this.props;
this.props.dispatch(moveCard( this.props.dispatch(moveCard(
board.id, board.id,
source.fromLaneId, source.fromColumnId,
source.fromPosition, source.fromPosition,
dest.toLaneId, dest.toColumnId,
dest.toPosition dest.toPosition
)); ));
} }
@ -175,7 +249,7 @@ export class BoardPage extends React.Component {
this.requestBuildKanboard(); this.requestBuildKanboard();
} }
onNewCardClick(laneID) { onNewCardClick(laneID: string) {
const { board } = this.props; const { board } = this.props;
this.setState({ this.setState({
newCardModalActive: true, newCardModalActive: true,
@ -192,9 +266,9 @@ export class BoardPage extends React.Component {
this.setState({ newCardModalActive: false }); this.setState({ newCardModalActive: false });
} }
onNewCardAttrChange(attrName, evt) { onNewCardAttrChange(attrName: string, evt: React.ChangeEvent) {
const value = evt.target.value; const value = (evt.target as HTMLInputElement).value;
this.setState(state => { this.setState((state: any) => {
return { return {
...state, ...state,
newCard: { newCard: {
@ -217,7 +291,7 @@ export class BoardPage extends React.Component {
)); ));
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: any) {
if (prevProps.board !== this.props.board) this.requestBuildKanboard(); if (prevProps.board !== this.props.board) this.requestBuildKanboard();
} }
@ -231,9 +305,14 @@ export class BoardPage extends React.Component {
this.props.dispatch(buildKanboard(board)); this.props.dispatch(buildKanboard(board));
} }
onCompactModeChange(evt: React.ChangeEvent) {
const checked = (evt.currentTarget as HTMLInputElement).checked;
this.setState(state => ({...state, compactMode: checked }));
}
} }
export const ConnectedBoardPage = connect(function(state, props) { export const ConnectedBoardPage = connect(function(state: any, props: any) {
const boardID = props.match.params.id; const boardID = props.match.params.id;
return { return {
board: state.boards.byID[boardID], board: state.boards.byID[boardID],

View File

@ -1,17 +1,24 @@
import React from 'react'; import React from 'react';
import { Page } from '../Page'; import { Page } from '../Page';
import { connect } from 'react-redux'; import { connect, DispatchProp } from 'react-redux';
import { selectFlagsIsLoading } from '../../store/selectors/flags'; import { selectFlagsIsLoading } from '../../store/selectors/flags';
import { fetchBoards, saveBoard } from '../../store/actions/boards'; import { fetchBoards, saveBoard, deleteBoard } from '../../store/actions/boards';
import { fetchProjects } from '../../store/actions/projects'; import { fetchProjects } from '../../store/actions/projects';
import uuidv4 from 'uuid/v4'; import uuidv4 from 'uuid/v4';
import { Loader } from '../Loader';
import { RouteComponentProps } from 'react-router';
export class EditBoardPage extends React.Component { export interface EditorBoardPageProps extends DispatchProp, RouteComponentProps {
isLoading: boolean
projects: any
}
export class EditBoardPage extends React.Component<EditorBoardPageProps> {
state = { state = {
edited: false, edited: false,
board: { board: {
id: uuidv4(), id: "",
title: "", title: "",
description: "", description: "",
projects: [], projects: [],
@ -19,7 +26,13 @@ export class EditBoardPage extends React.Component {
}, },
} }
static getDerivedStateFromProps(props, state) { onBoardTitleChange: (evt: any) => void;
onBoardDescriptionChange: (evt: any) => void;
onBoardLaneTitleChange: (laneIndex: any, evt: any) => void;
onBoardLaneIssueLabelChange: (laneIndex: any, evt: any) => void;
onBoardLaneIssueCollectRemainingIssuesChange: (laneIndex: any, evt: any) => void;
static getDerivedStateFromProps(props: any, state: any) {
const { board, isLoading } = props; const { board, isLoading } = props;
if (isLoading || !board || state.edited) return state; if (isLoading || !board || state.edited) return state;
@ -31,17 +44,19 @@ export class EditBoardPage extends React.Component {
title: board.title, title: board.title,
description: board.description, description: board.description,
projects: [ ...board.projects ], projects: [ ...board.projects ],
lanes: [ ...board.lanes.map(l => ({ ...l })) ] lanes: [ ...board.lanes.map((l: any) => ({ ...l })) ]
} }
}; };
} }
constructor(props) { constructor(props: any) {
super(props); super(props);
this.onBoardTitleChange = this.onBoardAttrChange.bind(this, 'title'); this.onBoardTitleChange = this.onBoardAttrChange.bind(this, 'title');
this.onBoardDescriptionChange = this.onBoardAttrChange.bind(this, 'description'); this.onBoardDescriptionChange = this.onBoardAttrChange.bind(this, 'description');
this.onBoardLaneTitleChange = this.onBoardLaneAttrChange.bind(this, 'title'); this.onBoardLaneTitleChange = this.onBoardLaneAttrChange.bind(this, 'title');
this.onBoardLaneIssueLabelChange = this.onBoardLaneAttrChange.bind(this, 'issueLabel'); this.onBoardLaneIssueLabelChange = this.onBoardLaneAttrChange.bind(this, 'issueLabel');
this.onBoardLaneIssueCollectRemainingIssuesChange = this.onBoardLaneAttrChange.bind(this, 'collectRemainingIssues');
this.onDeleteBoardClick = this.onDeleteBoardClick.bind(this);
} }
render() { render() {
@ -50,20 +65,38 @@ export class EditBoardPage extends React.Component {
if (isLoading) { if (isLoading) {
return ( return (
<p>Loading...</p> <Page>
<Loader></Loader>
</Page>
) )
}; };
return ( return (
<Page> <Page>
<div className="container is-fluid"> <div className="container is-fluid has-margin-top-normal">
<div className="columns"> <div className="columns">
<div className="column is-6 is-offset-3"> <div className="column is-6 is-offset-3">
<div className="level is-mobile">
<div className="level-left">
{ {
board.id ? board.id ?
<h3 className="is-size-3">Éditer le tableau</h3> : <h3 className="is-size-3 level-item">Éditer le tableau</h3> :
<h3 className="is-size-3">Nouveau tableau</h3> <h3 className="is-size-3 level-item">Nouveau tableau</h3>
} }
</div>
<div className="level-right">
{
board.id ?
<button onClick={this.onDeleteBoardClick} className="level-item button is-danger">
<span className="icon">
<i className="fas fa-trash"></i>
</span>
<span>Supprimer</span>
</button> :
null
}
</div>
</div>
<div className="field"> <div className="field">
<label className="label">Titre</label> <label className="label">Titre</label>
<div className="control"> <div className="control">
@ -109,15 +142,15 @@ export class EditBoardPage extends React.Component {
const { projects } = this.props; const { projects } = this.props;
const { board } = this.state; const { board } = this.state;
const projectSelectField = (projectIndex, value, withDeleteAddon) => { const projectSelectField = (projectIndex: number, value: any, withDeleteAddon: boolean) => {
return ( return (
<div key={`project-${projectIndex}`} className="field has-addons"> <div key={`project-${projectIndex}`} className="field has-addons">
<div className="control is-expanded"> <div className="control is-expanded">
<div className="select is-fullwidth"> <div className="select is-fullwidth">
<select value={value} onChange={this.onBoardProjectChange.bind(this, projectIndex)}> <select value={value} onChange={this.onBoardProjectChange.bind(this, projectIndex)}>
<option value></option> <option value=""></option>
{ {
projects.map(p => { projects.map((p: any) => {
return <option key={`project-${p}`} value={p}>{p}</option>; return <option key={`project-${p}`} value={p}>{p}</option>;
}) })
} }
@ -159,7 +192,7 @@ export class EditBoardPage extends React.Component {
const { board } = this.state; const { board } = this.state;
const laneSection = (laneIndex, lane) => { const laneSection = (laneIndex: number, lane: any) => {
return ( return (
<React.Fragment key={`board-lane-${laneIndex}`}> <React.Fragment key={`board-lane-${laneIndex}`}>
<div className="columns"> <div className="columns">
@ -192,6 +225,21 @@ export class EditBoardPage extends React.Component {
</div> </div>
</div> </div>
</div> </div>
<div className="field is-horizontal">
<div className="field-label is-normal"></div>
<div className="field-body">
<div className="field">
<div className="control">
<label className="checkbox">
<input type="checkbox"
checked={lane.hasOwnProperty('collectRemainingIssues') ? !!lane.collectRemainingIssues : false}
onChange={this.onBoardLaneIssueCollectRemainingIssuesChange.bind(this, laneIndex)} />
Inclure tous les tickets "restants" (i.e. non sélectionnés par les autres voies)
</label>
</div>
</div>
</div>
</div>
</div> </div>
<div className="column is-2 is-flex"> <div className="column is-2 is-flex">
<div className="buttons"> <div className="buttons">
@ -246,7 +294,7 @@ export class EditBoardPage extends React.Component {
} }
onBoardLaneAdd() { onBoardLaneAdd() {
this.setState(state => { this.setState((state: any) => {
const lanes = [ const lanes = [
...state.board.lanes, ...state.board.lanes,
{ id: uuidv4(), title: "", issueLabel: "" } { id: uuidv4(), title: "", issueLabel: "" }
@ -262,8 +310,8 @@ export class EditBoardPage extends React.Component {
}); });
} }
onBoardProjectDelete(projectIndex) { onBoardProjectDelete(projectIndex: number) {
this.setState(state => { this.setState((state: any) => {
const projects = [ ...state.board.projects ] const projects = [ ...state.board.projects ]
projects.splice(projectIndex, 1); projects.splice(projectIndex, 1);
return { return {
@ -277,8 +325,8 @@ export class EditBoardPage extends React.Component {
}); });
} }
onBoardLaneMove(laneIndex, direction) { onBoardLaneMove(laneIndex: number, direction: number) {
this.setState(state => { this.setState((state: any) => {
const lanes = [ ...state.board.lanes ]; const lanes = [ ...state.board.lanes ];
const nextLaneIndex = laneIndex+direction; const nextLaneIndex = laneIndex+direction;
@ -301,8 +349,8 @@ export class EditBoardPage extends React.Component {
}); });
} }
onBoardLaneDelete(laneIndex) { onBoardLaneDelete(laneIndex: number) {
this.setState(state => { this.setState((state: any) => {
const lanes = [ ...state.board.lanes ] const lanes = [ ...state.board.lanes ]
lanes.splice(laneIndex, 1); lanes.splice(laneIndex, 1);
return { return {
@ -316,9 +364,9 @@ export class EditBoardPage extends React.Component {
}); });
} }
onBoardProjectChange(projectIndex, evt) { onBoardProjectChange(projectIndex: number, evt: React.ChangeEvent) {
const value = evt.target.value; const value = (evt.target as HTMLInputElement).value ;
this.setState(state => { this.setState((state: any) => {
const projects = [ ...state.board.projects ]; const projects = [ ...state.board.projects ];
projects[projectIndex] = value; projects[projectIndex] = value;
return { return {
@ -332,9 +380,9 @@ export class EditBoardPage extends React.Component {
}); });
} }
onBoardAttrChange(attrName, evt) { onBoardAttrChange(attrName: string, evt: React.ChangeEvent) {
const value = evt.target.value; const value = (evt.target as HTMLInputElement).value;
this.setState(state => { this.setState((state: any) => {
return { return {
...state, ...state,
edited: true, edited: true,
@ -346,14 +394,16 @@ export class EditBoardPage extends React.Component {
}); });
} }
onBoardLaneAttrChange(attrName, laneIndex, evt) { onBoardLaneAttrChange(attrName: string, laneIndex: number, evt: React.ChangeEvent) {
const value = evt.target.value; const input = evt.target as HTMLInputElement;
this.setState(state => { const value = input.type === "checkbox" ? input.checked : input.value;
this.setState((state: any) => {
const lanes = [ ...state.board.lanes ]; const lanes = [ ...state.board.lanes ];
lanes[laneIndex] = { lanes[laneIndex] = {
...state.board.lanes[laneIndex], ...state.board.lanes[laneIndex],
[attrName]: value [attrName]: value
}; };
console.log(lanes);
return { return {
...state, ...state,
edited: true, edited: true,
@ -366,8 +416,16 @@ export class EditBoardPage extends React.Component {
} }
onSaveBoardClick() { onSaveBoardClick() {
let { board } = this.state;
board = { ...board };
if (!board.id) board.id = uuidv4();
this.props.dispatch(saveBoard({...board}));
this.props.history.push('/');
}
onDeleteBoardClick() {
const { board } = this.state; const { board } = this.state;
this.props.dispatch(saveBoard(board)); this.props.dispatch(deleteBoard(board.id));
this.props.history.push('/'); this.props.history.push('/');
} }
@ -378,7 +436,7 @@ export class EditBoardPage extends React.Component {
} }
export const ConnectedEditBoardPage = connect(function(state, props) { export const ConnectedEditBoardPage = connect(function(state: any, props: any) {
const boardID = props.match.params.id; const boardID = props.match.params.id;
const board = boardID ? state.boards.byID[boardID] : null; const board = boardID ? state.boards.byID[boardID] : null;

View File

@ -1,47 +0,0 @@
import React from 'react';
export class IssueCard extends React.PureComponent {
render() {
const { card } = this.props;
return (
<div className="kanboard-card">
<div className="box">
<div className="media">
{
card.issue.assignee ?
<div className="media-left">
<figure className="image is-64x64">
<img src={card.issue.assignee.avatar_url} alt="Image" />
</figure>
<small>{`@${card.issue.assignee.login}`}</small>
</div>
: null
}
<div className="media-content">
<div className="content">
<p>
<strong>{`#${card.issue.number}`}</strong>&nbsp;
{ card.issue.milestone ? <small>{`- ${card.issue.milestone.title}`}</small> : null }
<br />
<span className="is-size-6">{card.issue.title}</span>
</p>
</div>
</div>
</div>
<div className="level is-mobile" style={{marginTop:'1rem'}}>
<div className="level-left">
<small className="level-item"><a href="#">{card.project}</a></small>
</div>
<div className="level-right">
<a className="level-item" target="_blank" href={card.issue.url.replace('/api/v1/repos', '')}>
<span className="icon is-small has-text-info">
<i className="fas fa-search" aria-hidden="true"></i>
</span>
</a>
</div>
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,80 @@
import React from 'react';
import { KanboardCard } from '../../types/kanboard';
export interface IssueCardProps {
card: KanboardCard
compact: boolean
}
export class IssueCard extends React.PureComponent<IssueCardProps> {
render() {
const { card, compact } = this.props;
const issueURLInfo = extractInfoFromIssueURL(card.issue.url);
const projectURL = `${issueURLInfo.baseURL}/${issueURLInfo.owner}/${issueURLInfo.projectName}`;
const issueURL = `${projectURL}/issues/${card.issue.number}`;
return (
<div className="kanboard-card">
<div className="box has-padding-small is-radiusless">
<div className="media">
<div className="media-content">
<div className="content">
{ !compact &&
<nav className="level">
<div className="level-left">
<div className="level-item">
<a target="_blank" href={issueURL}><strong>{`#${card.issue.number}`}</strong></a>
</div>
{ !compact &&
<div className="level-item">
<a target="_blank" href={projectURL}>{card.project}</a>
</div>
}
</div>
<div className="level-right">
{
card.issue.assignee && !compact ?
<div className="level-item">
<small>{`@${card.issue.assignee.login}`}</small>
</div>
: null
}
</div>
</nav>
}
{ compact &&
<a target="_blank" className="mr-1" href={issueURL}><strong>{`#${card.issue.number}`}</strong></a>
}
<span>{card.issue.title ? card.issue.title : ''}</span>
</div>
{ !compact &&
<nav className="level">
<div className="level-left"></div>
<div className="level-right">
<div className="level-item is-size-7">
{card.issue.milestone ? card.issue.milestone.title : ''}
</div>
</div>
</nav>
}
</div>
</div>
</div>
</div>
);
}
}
function extractInfoFromIssueURL(issueURL: string): any|void {
const pattern = /^(https?:\/\/[^\/]+)\/api\/v1\/repos\/([^\/]+)\/([^\/]+)\/.*$/;
const matches = pattern.exec(issueURL);
if (!matches) return;
return {
baseURL: matches[1],
owner: matches[2],
projectName: matches[3],
};
}

View File

@ -1,6 +1,10 @@
import React from 'react'; import React from 'react';
export class BoardCard extends React.PureComponent { export interface BoardProps {
board: any
}
export class BoardCard extends React.PureComponent<BoardProps> {
render() { render() {
const { board } = this.props; const { board } = this.props;
return ( return (
@ -26,11 +30,6 @@ export class BoardCard extends React.PureComponent {
<i className="fas fa-edit" aria-hidden="true"></i> <i className="fas fa-edit" aria-hidden="true"></i>
</span> </span>
</a> </a>
<a className="level-item" aria-label="delete" href={`#/boards/${board.id}/delete`}>
<span className="icon is-small has-text-danger">
<i className="fas fa-trash-alt" aria-hidden="true"></i>
</span>
</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,12 +1,17 @@
import React from 'react'; import React from 'react';
import { Page } from '../Page'; import { Page } from '../Page';
import { BoardCard } from './BoardCard'; import { BoardCard } from './BoardCard';
import { connect } from 'react-redux'; import { connect, DispatchProp } from 'react-redux';
import { fetchBoards } from '../../store/actions/boards'; import { fetchBoards } from '../../store/actions/boards';
import { fetchProjects } from '../../store/actions/projects'; import { fetchProjects } from '../../store/actions/projects';
import { selectBoardByUserProjects } from '../../store/selectors/boards'; import { selectBoardByUserProjects } from '../../store/selectors/boards';
export class HomePage extends React.Component { export interface HomePageProps extends DispatchProp {
boards: any[]
}
export class HomePage extends React.Component<HomePageProps> {
render() { render() {
return ( return (
<Page title="GenGitKan - Accueil"> <Page title="GenGitKan - Accueil">
@ -40,8 +45,8 @@ export class HomePage extends React.Component {
boardRows[boardRows.length-1].push(board); boardRows[boardRows.length-1].push(board);
return boardRows; return boardRows;
}, []) }, [])
.map((row, rowIndex) => { .map((row: any, rowIndex: number) => {
const tiles = row.map((board) => { const tiles = row.map((board: any) => {
return ( return (
<div key={`board-${board.id}`} className={`tile is-parent is-4`}> <div key={`board-${board.id}`} className={`tile is-parent is-4`}>
<div className="tile is-child"> <div className="tile is-child">
@ -72,7 +77,7 @@ export class HomePage extends React.Component {
} }
export const ConnectedHomePage = connect(function(state) { export const ConnectedHomePage = connect(function(state: any) {
return { return {
boards: selectBoardByUserProjects(state.boards.byID, state.projects.byName) boards: selectBoardByUserProjects(state.boards.byID, state.projects.byName)
}; };

View File

@ -1,6 +1,12 @@
import React from 'react'; import React, { PropsWithChildren } from 'react';
export class Modal extends React.PureComponent { export interface ModalProps {
active: boolean
showCloseButton?: boolean
onClose?: (evt: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
}
export class Modal extends React.PureComponent<PropsWithChildren<ModalProps>> {
render() { render() {
const { children, active, showCloseButton, onClose } = this.props; const { children, active, showCloseButton, onClose } = this.props;
return ( return (

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import logo from '../resources/logo.svg';
export class Navbar extends React.PureComponent { export class Navbar extends React.PureComponent {
render() { render() {
@ -7,7 +8,7 @@ export class Navbar extends React.PureComponent {
<div className="container is-fluid"> <div className="container is-fluid">
<div className="navbar-brand"> <div className="navbar-brand">
<a className="navbar-item" href="#/"> <a className="navbar-item" href="#/">
<img src="resources/logo.svg" style={{marginRight:'5px'}} /> <img src={logo} style={{marginRight:'5px',width:'28px',height:'28px'}} />
<h1 className="is-size-4">GenGitKan</h1> <h1 className="is-size-4">GenGitKan</h1>
</a> </a>
<a role="button" className="navbar-burger" aria-label="menu" aria-expanded="false"> <a role="button" className="navbar-burger" aria-label="menu" aria-expanded="false">

View File

@ -1,7 +1,11 @@
import React from 'react'; import React from 'react';
import { Navbar } from './Navbar'; import { Navbar } from './Navbar';
export class Page extends React.PureComponent { export interface PageProps {
title?: string
}
export class Page extends React.PureComponent<PageProps> {
render() { render() {
return ( return (
<React.Fragment> <React.Fragment>

5
client/src/custom.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module "*.svg" {
const content: any;
export default content;
}
declare module '@lourenci/react-kanban';

View File

@ -4,11 +4,17 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>GenGitKan</title> <title>GenGitKan</title>
<link rel="stylesheet" href="css/main.css"> <% for (var css in htmlWebpackPlugin.files.css) { %>
<link rel="icon" type="image/png" href="resources/favicon.png"> <link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
<% } %>
<% if (htmlWebpackPlugin.files.favicon) { %>
<link rel="shortcut icon" href="<%= htmlWebpackPlugin.files.favicon%>">
<% } %>
</head> </head>
<body> <body>
<div id="app" class="is-fullheight"></div> <div id="app" class="is-fullheight"></div>
<script type="text/javascript" src="main.js"></script> <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script type="text/javascript" src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
<% } %>
</body> </body>
</html> </html>

View File

@ -1,5 +1,4 @@
import './sass/_all.scss'; import './sass/_all.scss';
import './index.html';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { App } from './components/App'; import { App } from './components/App';
@ -8,7 +7,6 @@ import '@fortawesome/fontawesome-free/js/fontawesome'
import '@fortawesome/fontawesome-free/js/solid' import '@fortawesome/fontawesome-free/js/solid'
import '@fortawesome/fontawesome-free/js/regular' import '@fortawesome/fontawesome-free/js/regular'
import '@fortawesome/fontawesome-free/js/brands' import '@fortawesome/fontawesome-free/js/brands'
import './resources/logo.svg';
import './resources/favicon.png'; import './resources/favicon.png';
ReactDOM.render( ReactDOM.render(

View File

@ -1,4 +1,6 @@
@import 'bulma/bulma.sass'; @import 'bulma/bulma.sass';
@import '../../node_modules/@lourenci/react-kanban/dist/styles.css';
@import 'bulma-switch/dist/css/bulma-switch.sass';
@import '_base.scss'; @import '_base.scss';
@import '_loader.scss'; @import '_loader.scss';
@import '_kanboard.scss'; @import '_kanboard.scss';

View File

@ -11,7 +11,15 @@ html, body {
margin-top: $size-normal; margin-top: $size-normal;
} }
.has-padding-small {
padding: 1rem;
}
#app { #app {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.mr-1 {
margin-right: 5px;
}

View File

@ -8,6 +8,9 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: row;
align-items: stretch;
justify-content: stretch;
// Lanes // Lanes
& > div { & > div {
@ -17,6 +20,11 @@
flex-grow: 1; flex-grow: 1;
flex-basis: 100%; flex-basis: 100%;
background-color: transparent; background-color: transparent;
display: flex;
flex-direction: column;
bottom: 0;
height: 100% !important;
min-height: 100% !important;
// Card container // Card container
& > div > div > div { & > div > div > div {
@ -27,7 +35,7 @@
} }
[data-react-beautiful-dnd-droppable] { [data-react-beautiful-dnd-droppable] {
height: 100%; min-height: calc(100vh) !important;
} }
.kanboard-card { .kanboard-card {
@ -36,6 +44,12 @@
.kanboard-lane { .kanboard-lane {
margin-bottom: $size-small; margin-bottom: $size-small;
background: $white;
top: 0;
.expand {
display: none;
}
} }
} }
@ -44,3 +58,141 @@
align-items: center; align-items: center;
width: 50% !important; width: 50% !important;
} }
.react-kanban-board {
max-height: calc(100vh - calc(52px * 2));
overflow: hidden;
overflow-x: scroll;
scrollbar-color: $grey-lighter, #f1f1f1;
scrollbar-width: 5px;
&::-webkit-scrollbar {
width: 5px;
height: 5px;
}
/* Track */
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: $grey-lighter;
}
/* Handle on hover */
&::-webkit-scrollbar-thumb:hover {
background: $green;
}
}
.react-kanban-column {
transition: width ease .2s;
max-width: 305px;
min-width: 305px;
position: relative;
max-height: 100%;
overflow-x: hidden;
overflow-y: scroll;
scrollbar-color: $grey-lighter, #f1f1f1;
scrollbar-width: 5px;
.kanboard-card {
display: block;
}
&.minimized {
max-width: 70px;
min-width: 70px;
writing-mode: vertical-rl;
text-orientation: sideways-right;
.level-item {
margin-right: 0;
}
.level-left {
margin-right: 0;
}
.level-right.is-show-expand {
display: none;
}
.kanboard-lane {
/*margin-right: -1em;*/
h3 {
margin-top: .5em;
/*margin-right: -.5em;*/
}
.tag {
writing-mode: horizontal-tb;
}
.expand {
display: block !important;
margin-top: .5em;
}
}
.kanboard-card {
display: none;
}
}
&::-webkit-scrollbar {
width: 5px;
}
/* Track */
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: $grey-lighter;
}
/* Handle on hover */
&::-webkit-scrollbar-thumb:hover {
background: $green;
}
}
.react-kanban-card__title {
position: sticky;
position: -webkit-sticky;
}
.react-kanban-column {
background: white !important;
}
.kanboard-card {
overflow-wrap: break-word;
a {
display: inline-block;
}
.level {
margin-bottom: 0;
}
.level-item {
max-width: 100%;
}
}

View File

@ -10,6 +10,14 @@ export const SAVE_BOARD_REQUEST = "SAVE_BOARD_REQUEST";
export const SAVE_BOARD_SUCCESS = "SAVE_BOARD_SUCCESS"; export const SAVE_BOARD_SUCCESS = "SAVE_BOARD_SUCCESS";
export const SAVE_BOARD_FAILURE = "SAVE_BOARD_FAILURE"; export const SAVE_BOARD_FAILURE = "SAVE_BOARD_FAILURE";
export function saveBoard(board) { export function saveBoard(board: any) {
return { type: SAVE_BOARD_REQUEST, board }; return { type: SAVE_BOARD_REQUEST, board };
}; };
export const DELETE_BOARD_REQUEST = "DELETE_BOARD_REQUEST";
export const DELETE_BOARD_SUCCESS = "DELETE_BOARD_SUCCESS";
export const DELETE_BOARD_FAILURE = "DELETE_BOARD_FAILURE";
export function deleteBoard(id: any) {
return { type: DELETE_BOARD_REQUEST, id };
};

View File

@ -2,7 +2,7 @@ export const FETCH_ISSUES_REQUEST = "FETCH_ISSUES_REQUEST";
export const FETCH_ISSUES_SUCCESS = "FETCH_ISSUES_SUCCESS"; export const FETCH_ISSUES_SUCCESS = "FETCH_ISSUES_SUCCESS";
export const FETCH_ISSUES_FAILURE = "FETCH_ISSUES_FAILURE"; export const FETCH_ISSUES_FAILURE = "FETCH_ISSUES_FAILURE";
export function fetchIssues(project) { export function fetchIssues(project: any) {
return { type: FETCH_ISSUES_REQUEST, project }; return { type: FETCH_ISSUES_REQUEST, project };
}; };
@ -10,7 +10,7 @@ export const ADD_LABEL_REQUEST = "ADD_LABEL_REQUEST";
export const ADD_LABEL_SUCCESS = "ADD_LABEL_SUCCESS"; export const ADD_LABEL_SUCCESS = "ADD_LABEL_SUCCESS";
export const ADD_LABEL_FAILURE = "ADD_LABEL_FAILURE"; export const ADD_LABEL_FAILURE = "ADD_LABEL_FAILURE";
export function addLabel(project, issueNumber, label) { export function addLabel(project: any, issueNumber: any, label: string) {
return { type: ADD_LABEL_REQUEST, project, issueNumber, label }; return { type: ADD_LABEL_REQUEST, project, issueNumber, label };
} }
@ -18,7 +18,7 @@ export const REMOVE_LABEL_REQUEST = "REMOVE_LABEL_REQUEST";
export const REMOVE_LABEL_SUCCESS = "REMOVE_LABEL_SUCCESS"; export const REMOVE_LABEL_SUCCESS = "REMOVE_LABEL_SUCCESS";
export const REMOVE_LABEL_FAILURE = "REMOVE_LABEL_FAILURE"; export const REMOVE_LABEL_FAILURE = "REMOVE_LABEL_FAILURE";
export function removeLabel(project, issueNumber, label) { export function removeLabel(project: any, issueNumber: any, label: string) {
return { type: REMOVE_LABEL_REQUEST, project, issueNumber, label }; return { type: REMOVE_LABEL_REQUEST, project, issueNumber, label };
} }
@ -26,6 +26,6 @@ export const CREATE_ISSUE_REQUEST = "CREATE_ISSUE_REQUEST";
export const CREATE_ISSUE_SUCCESS = "CREATE_ISSUE_SUCCESS"; export const CREATE_ISSUE_SUCCESS = "CREATE_ISSUE_SUCCESS";
export const CREATE_ISSUE_FAILURE = "CREATE_ISSUE_FAILURE"; export const CREATE_ISSUE_FAILURE = "CREATE_ISSUE_FAILURE";
export function createIssue(project, title, body, label) { export function createIssue(project: any, title: string, body: any, label: string) {
return { type: CREATE_ISSUE_REQUEST, project, title, body, label }; return { type: CREATE_ISSUE_REQUEST, project, title, body, label };
}; };

View File

@ -2,12 +2,12 @@ export const BUILD_KANBOARD_REQUEST = "BUILD_KANBOARD_REQUEST";
export const BUILD_KANBOARD_SUCCESS = "BUILD_KANBOARD_SUCCESS"; export const BUILD_KANBOARD_SUCCESS = "BUILD_KANBOARD_SUCCESS";
export const BUILD_KANBOARD_FAILURE = "BUILD_KANBOARD_FAILURE"; export const BUILD_KANBOARD_FAILURE = "BUILD_KANBOARD_FAILURE";
export function buildKanboard(board) { export function buildKanboard(board: string) {
return { type: BUILD_KANBOARD_REQUEST, board }; return { type: BUILD_KANBOARD_REQUEST, board };
}; };
export const MOVE_CARD = "MOVE_CARD"; export const MOVE_CARD = "MOVE_CARD";
export function moveCard(boardID, fromLaneID, fromPosition, toLaneID, toPosition) { export function moveCard(boardID: string, fromLaneID: string, fromPosition: any, toLaneID: any, toPosition: any) {
return { type: MOVE_CARD, boardID, fromLaneID, fromPosition, toLaneID, toPosition }; return { type: MOVE_CARD, boardID, fromLaneID, fromPosition, toLaneID, toPosition };
}; };

View File

@ -1,5 +0,0 @@
export const LOGOUT = "LOGOUT";
export function logout() {
return { type: LOGOUT };
};

View File

@ -0,0 +1,7 @@
export const LOGOUT_REQUEST = "LOGOUT_REQUEST";
export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";
export const LOGOUT_FAILURE = "LOGOUT_FAILURE";
export function logout() {
return { type: LOGOUT_REQUEST };
};

View File

@ -4,7 +4,7 @@ export const defaultState = {
byID: {}, byID: {},
}; };
export function boardsReducer(state = defaultState, action) { export function boardsReducer(state = defaultState, action: any) {
switch(action.type) { switch(action.type) {
case SAVE_BOARD_SUCCESS: case SAVE_BOARD_SUCCESS:
return handleSaveBoardSuccess(state, action); return handleSaveBoardSuccess(state, action);
@ -15,20 +15,21 @@ export function boardsReducer(state = defaultState, action) {
} }
} }
function handleSaveBoardSuccess(state, action) { function handleSaveBoardSuccess(state: any, action: any) {
const { board } = action;
return { return {
...state, ...state,
byID: { byID: {
...state.byID, ...state.byID,
[action.board.id.toString()]: { [board.id]: {
...action.board, ...board,
} }
} }
}; };
} }
function handleFetchBoardsSuccess(state, action) { function handleFetchBoardsSuccess(state: any, action: any) {
const boardsByID = action.boards.reduce((byID, board) => { const boardsByID = action.boards.reduce((byID: any, board: any) => {
byID[board.id] = board; byID[board.id] = board;
return byID; return byID;
}, {}); }, {});

View File

@ -2,7 +2,7 @@ const defaultState = {
actions: {} actions: {}
}; };
export function flagsReducer(state = defaultState, action) { export function flagsReducer(state = defaultState, action: any) {
const matches = (/^(.*)_((SUCCESS)|(FAILURE)|(REQUEST))$/).exec(action.type); const matches = (/^(.*)_((SUCCESS)|(FAILURE)|(REQUEST))$/).exec(action.type);
if(!matches) return state; if(!matches) return state;

View File

@ -4,7 +4,7 @@ const defaultState = {
byProject: {} byProject: {}
}; };
export function issuesReducer(state = defaultState, action) { export function issuesReducer(state = defaultState, action: any) {
switch(action.type) { switch(action.type) {
case FETCH_ISSUES_SUCCESS: case FETCH_ISSUES_SUCCESS:
return handleFetchIssuesSuccess(state, action); return handleFetchIssuesSuccess(state, action);
@ -16,7 +16,7 @@ export function issuesReducer(state = defaultState, action) {
} }
function handleFetchIssuesSuccess(state, action) { function handleFetchIssuesSuccess(state: any, action: any) {
return { return {
...state, ...state,
byProject: { byProject: {
@ -28,7 +28,7 @@ function handleFetchIssuesSuccess(state, action) {
} }
} }
function handleCreateIssueSuccess(state, action) { function handleCreateIssueSuccess(state: any, action: any) {
return { return {
...state, ...state,
byProject: { byProject: {

View File

@ -5,7 +5,7 @@ export const defaultState = {
byID: {}, byID: {},
}; };
export function kanboardsReducer(state = defaultState, action) { export function kanboardsReducer(state = defaultState, action: any) {
switch(action.type) { switch(action.type) {
case BUILD_KANBOARD_SUCCESS: case BUILD_KANBOARD_SUCCESS:
return handleBuildKanboardSuccess(state, action); return handleBuildKanboardSuccess(state, action);
@ -16,7 +16,7 @@ export function kanboardsReducer(state = defaultState, action) {
} }
} }
function handleBuildKanboardSuccess(state, action) { function handleBuildKanboardSuccess(state: any, action: any) {
return { return {
...state, ...state,
byID: { byID: {
@ -28,7 +28,7 @@ function handleBuildKanboardSuccess(state, action) {
}; };
} }
function handleMoveCard(state, action) { function handleMoveCard(state: any, action: any) {
const { const {
boardID, fromLaneID, boardID, fromLaneID,
fromPosition, toLaneID, fromPosition, toLaneID,
@ -37,22 +37,22 @@ function handleMoveCard(state, action) {
const kanboard = state.byID[boardID]; const kanboard = state.byID[boardID];
const lanes = [ ...kanboard.lanes ]; const columns = [ ...kanboard.columns ];
const fromLane = lanes[fromLaneID]; const fromLane = columns[fromLaneID];
const toLane = lanes[toLaneID]; const toLane = columns[toLaneID];
const card = fromLane.cards[fromPosition]; const card = fromLane.cards[fromPosition];
const fromCards = [ ...fromLane.cards ]; const fromCards = [ ...fromLane.cards ];
if (fromLaneID !== toLaneID) { if (fromLaneID !== toLaneID) {
fromCards.splice(fromPosition, 1); fromCards.splice(fromPosition, 1);
lanes[fromLaneID] = { columns[fromLaneID] = {
...fromLane, ...fromLane,
cards: fromCards, cards: fromCards,
}; };
const toCards = [ ...toLane.cards ]; const toCards = [ ...toLane.cards ];
toCards.splice(toPosition, 0, card); toCards.splice(toPosition, 0, card);
lanes[toLaneID] = { columns[toLaneID] = {
...toLane, ...toLane,
cards: toCards, cards: toCards,
}; };
@ -67,7 +67,7 @@ function handleMoveCard(state, action) {
...state.byID, ...state.byID,
[boardID]: { [boardID]: {
...state.byID[boardID], ...state.byID[boardID],
lanes, columns,
}, },
} }
}; };

View File

@ -4,7 +4,7 @@ export const defaultState = {
byName: {}, byName: {},
}; };
export function projectsReducer(state = defaultState, action) { export function projectsReducer(state = defaultState, action: any) {
switch(action.type) { switch(action.type) {
case FETCH_PROJECTS_SUCCESS: case FETCH_PROJECTS_SUCCESS:
return handleFetchProjectsSuccess(state, action); return handleFetchProjectsSuccess(state, action);
@ -13,8 +13,8 @@ export function projectsReducer(state = defaultState, action) {
} }
} }
function handleFetchProjectsSuccess(state, action) { function handleFetchProjectsSuccess(state: any, action: any) {
const projectsByName = action.projects.reduce((byName, project) => { const projectsByName = action.projects.reduce((byName: any, project: any) => {
byName[project.full_name] = project; byName[project.full_name] = project;
return byName; return byName;
}, {}); }, {});

View File

@ -1,5 +1,9 @@
import { put, call } from 'redux-saga/effects'; import { put, call } from 'redux-saga/effects';
import { FETCH_BOARDS_SUCCESS, SAVE_BOARD_SUCCESS, SAVE_BOARD_FAILURE, FETCH_BOARDS_FAILURE } from '../actions/boards'; import {
FETCH_BOARDS_SUCCESS, SAVE_BOARD_SUCCESS,
SAVE_BOARD_FAILURE, FETCH_BOARDS_FAILURE,
DELETE_BOARD_FAILURE, DELETE_BOARD_SUCCESS
} from '../actions/boards';
import { api } from '../../util/api'; import { api } from '../../util/api';
export function* fetchBoardsSaga() { export function* fetchBoardsSaga() {
@ -15,7 +19,7 @@ export function* fetchBoardsSaga() {
yield put({ type: FETCH_BOARDS_SUCCESS, boards }); yield put({ type: FETCH_BOARDS_SUCCESS, boards });
} }
export function* saveBoardSaga(action) { export function* saveBoardSaga(action: any) {
let { board } = action; let { board } = action;
try { try {
@ -27,3 +31,17 @@ export function* saveBoardSaga(action) {
yield put({ type: SAVE_BOARD_SUCCESS, board }); yield put({ type: SAVE_BOARD_SUCCESS, board });
} }
export function* deleteBoardSaga(action: any) {
let { id } = action;
try {
yield call(api.deleteBoard, id)
} catch(error) {
yield put({ type: DELETE_BOARD_FAILURE, error });
return
}
yield put({ type: DELETE_BOARD_SUCCESS, id });
}

View File

@ -1,10 +0,0 @@
import { GiteaUnauthorizedError } from "../../util/gitea";
import { LOGOUT } from "../actions/logout";
import { put } from 'redux-saga/effects';
export function* failuresSaga(action) {
const err = action.error;
if (err instanceof GiteaUnauthorizedError) {
yield put({ type: LOGOUT });
}
}

View File

@ -0,0 +1,11 @@
import { GiteaUnauthorizedError } from "../../util/gitea";
import { put } from 'redux-saga/effects';
import { logout } from '../actions/logout';
import { saveReferer } from "../../util/referer";
export function* failuresSaga(action) {
if (action.error instanceof GiteaUnauthorizedError) {
saveReferer();
yield put(logout());
}
}

View File

@ -1,8 +1,13 @@
import { put, call, retry } from 'redux-saga/effects'; import { put, call, retry } from 'redux-saga/effects';
import { FETCH_ISSUES_SUCCESS, FETCH_ISSUES_FAILURE, ADD_LABEL_FAILURE, ADD_LABEL_SUCCESS, REMOVE_LABEL_FAILURE, REMOVE_LABEL_SUCCESS, CREATE_ISSUE_FAILURE, CREATE_ISSUE_SUCCESS } from '../actions/issues'; import {
FETCH_ISSUES_SUCCESS, FETCH_ISSUES_FAILURE,
ADD_LABEL_FAILURE, ADD_LABEL_SUCCESS,
REMOVE_LABEL_FAILURE, REMOVE_LABEL_SUCCESS,
CREATE_ISSUE_FAILURE, CREATE_ISSUE_SUCCESS
} from '../actions/issues';
import { gitea } from '../../util/gitea'; import { gitea } from '../../util/gitea';
export function* fetchIssuesSaga(action) { export function* fetchIssuesSaga(action: any) {
const { project } = action; const { project } = action;
let issues = []; let issues = [];
@ -13,7 +18,7 @@ export function* fetchIssuesSaga(action) {
if (pageIssues.length === 0) { if (pageIssues.length === 0) {
break; break;
} }
issues.push(...pageIssues); issues.push(...pageIssues.filter(issue => issue.pull_request === null));
page++; page++;
} }
@ -25,10 +30,10 @@ export function* fetchIssuesSaga(action) {
yield put({ type: FETCH_ISSUES_SUCCESS, project, issues }); yield put({ type: FETCH_ISSUES_SUCCESS, project, issues });
} }
export function* addLabelSaga(action) { export function* addLabelSaga(action: any) {
const { project, issueNumber, label } = action; const { project, issueNumber, label } = action;
const labels = yield call(gitea.fetchProjectLabels.bind(gitea), project); const labels = yield call(gitea.fetchProjectLabels.bind(gitea), project);
const giteaLabel = labels.find(l => l.name === label) const giteaLabel = labels.find((l: any) => l.name === label)
if (!giteaLabel) { if (!giteaLabel) {
yield put({ type: ADD_LABEL_FAILURE, error: new Error(`Label "${label}" not found !`) }); yield put({ type: ADD_LABEL_FAILURE, error: new Error(`Label "${label}" not found !`) });
@ -45,10 +50,10 @@ export function* addLabelSaga(action) {
yield put({ type: ADD_LABEL_SUCCESS, project, issueNumber, label }); yield put({ type: ADD_LABEL_SUCCESS, project, issueNumber, label });
} }
export function* removeLabelSaga(action) { export function* removeLabelSaga(action: any) {
const { project, issueNumber, label } = action; const { project, issueNumber, label } = action;
const labels = yield call(gitea.fetchProjectLabels.bind(gitea), project); const labels = yield call(gitea.fetchProjectLabels.bind(gitea), project);
const giteaLabel = labels.find(l => l.name === label) const giteaLabel = labels.find((l: any) => l.name === label)
if (!giteaLabel) { if (!giteaLabel) {
yield put({ type: REMOVE_LABEL_FAILURE, error: new Error(`Label "${label}" not found !`) }); yield put({ type: REMOVE_LABEL_FAILURE, error: new Error(`Label "${label}" not found !`) });
@ -66,19 +71,14 @@ export function* removeLabelSaga(action) {
yield put({ type: REMOVE_LABEL_SUCCESS, project, issueNumber, label }); yield put({ type: REMOVE_LABEL_SUCCESS, project, issueNumber, label });
} }
export function* createIssueSaga(action) { export function* createIssueSaga(action: any) {
const { project, title, label, body } = action; const { project, title, label, body } = action;
const labels = yield call(gitea.fetchProjectLabels.bind(gitea), project); const labels = yield call(gitea.fetchProjectLabels.bind(gitea), project);
const giteaLabel = labels.find(l => l.name === label) const giteaLabel = labels.find((l: any) => l.name === label)
if (!giteaLabel) {
yield put({ type: CREATE_ISSUE_FAILURE, error: new Error(`Label "${label}" not found !`) });
return;
}
let issue; let issue;
try { try {
issue = yield call(gitea.createIssue.bind(gitea), project, title, body, giteaLabel.id); issue = yield call(gitea.createIssue.bind(gitea), project, title, body, giteaLabel ? giteaLabel.id : null);
} catch(error) { } catch(error) {
yield put({ type: CREATE_ISSUE_FAILURE, error }); yield put({ type: CREATE_ISSUE_FAILURE, error });
return; return;

View File

@ -1,107 +0,0 @@
import { select, put } from 'redux-saga/effects';
import { fetchIssues, addLabel, removeLabel } from '../actions/issues';
import { fetchIssuesSaga } from './issues';
import { BUILD_KANBOARD_SUCCESS, buildKanboard } from '../actions/kanboards';
export function* moveCardSaga(action) {
const {
boardID, fromLaneID,
fromPosition, toLaneID,
toPosition,
} = action;
if (fromLaneID === toLaneID) return;
const { board, kanboard} = yield select(state => {
return {
kanboard: state.kanboards.byID[boardID],
board: state.boards.byID[boardID]
}
});
const toLane = kanboard.lanes[toLaneID];
const card = toLane.cards[toPosition];
if (!card) return;
yield put(addLabel(card.project, card.issue.number, board.lanes[toLaneID].issueLabel));
yield put(removeLabel(card.project, card.issue.number, board.lanes[fromLaneID].issueLabel));
}
export function* buildKanboardSaga(action) {
const { board } = action;
let kanboard;
try {
for (let p, i = 0; (p = board.projects[i]); i++) {
yield* fetchIssuesSaga(fetchIssues(p));
}
const issues = yield select(state => state.issues);
kanboard = createKanboard(board, issues);
} catch(error) {
yield put({ type: BUILD_KANBOARD_FAILURE, error });
return
}
yield put({ type: BUILD_KANBOARD_SUCCESS, kanboard });
}
export function* refreshKanboardSaga(action) {
const { project } = action;
const boards = yield select(state => state.boards);
const boardValues = Object.values(boards.byID);
for (let b, i = 0; (b = boardValues[i]); i++) {
const hasProject = b.projects.indexOf(project) !== -1;
if (!hasProject) continue;
yield put(buildKanboard(b));
}
}
function createCards(projects, issues, lane) {
return projects.reduce((laneCards, p) => {
const projectIssues = p in issues.byProject ? issues.byProject[p] : [];
return projectIssues.reduce((projectCards, issue) => {
const hasLabel = issue.labels.some(l => l.name === lane.issueLabel);
if (hasLabel) {
projectCards.push({
id: issue.id,
title: issue.title,
project: p,
issue: issue,
});
}
return projectCards;
}, laneCards);
}, []);
}
function createLane(projects, issues, lane, index) {
return {
id: index,
title: lane.title,
cards: createCards(projects, issues, lane)
}
}
function createKanboard(board, issues) {
if (!board) return null;
const kanboard = {
id: board.id,
lanes: board.lanes.map(createLane.bind(null, board.projects, issues)),
};
return kanboard;
}

View File

@ -0,0 +1,152 @@
import { select, put } from 'redux-saga/effects';
import { fetchIssues, addLabel, removeLabel } from '../actions/issues';
import { fetchIssuesSaga } from './issues';
import { BUILD_KANBOARD_SUCCESS, buildKanboard, BUILD_KANBOARD_FAILURE } from '../actions/kanboards';
import { Project, Issue } from '../../types/gitea';
import { Board, BoardLane } from '../../types/board';
import { KanboardLane, Kanboard, KanboardCard } from '../../types/kanboard';
export function* moveCardSaga(action: any) {
const {
boardID, fromLaneID,
fromPosition, toLaneID,
toPosition,
} = action;
if (fromLaneID === toLaneID) return;
const { board, kanboard} = yield select(state => {
return {
kanboard: state.kanboards.byID[boardID],
board: state.boards.byID[boardID]
}
});
const toLane = kanboard.columns[toLaneID];
const card = toLane.cards[toPosition];
if (!card) return;
yield put(addLabel(card.project, card.issue.number, board.lanes[toLaneID].issueLabel));
yield put(removeLabel(card.project, card.issue.number, board.lanes[fromLaneID].issueLabel));
}
export function* buildKanboardSaga(action: any) {
const { board } = action;
let kanboard;
try {
for (let p, i = 0; (p = board.projects[i]); i++) {
const { project } = yield fetchIssues(p);
yield fetchIssuesSaga({ project });
}
const issues = yield select(state => state.issues);
kanboard = createKanboard(board, issues);
} catch(error) {
yield put({ type: BUILD_KANBOARD_FAILURE, error });
return
}
yield put({ type: BUILD_KANBOARD_SUCCESS, kanboard });
}
export function* refreshKanboardSaga(action: any) {
const { project } = action;
const boards = yield select(state => state.boards);
const boardValues = Object.values(boards.byID);
for (let b: any, i = 0; (b = boardValues[i]); i++) {
const hasProject = b.projects.indexOf(project) !== -1;
if (!hasProject) continue;
yield put(buildKanboard(b));
}
}
function createCards(projects: Project[], issues: any, lane: BoardLane, rest: Set<KanboardCard>) {
const cards: KanboardCard[] = projects.reduce((laneCards, p) => {
const projectIssues = p in issues.byProject ? issues.byProject[p] : [];
return projectIssues.reduce((projectCards: KanboardCard[], issue: any) => {
const hasLabel = issue.labels.some((l: any) => l.name === lane.issueLabel);
const { card, memoized } = getMemoizedKanboardCard(issue.id, issue.title, p, issue);
if (hasLabel) {
projectCards.push(card);
rest.delete(card);
} else {
if (!memoized) rest.add(card);
}
return projectCards;
}, laneCards);
}, []);
return cards;
}
const kanboardCardMemo: {[key: string]: KanboardCard} = {};
function getKanboardCardMemoizationKey(id: number, project: Project, issue: Issue) {
return `${project.id}-${issue.id}-${id}`;
}
function isKanboardCardMemoized(key: string) {
return kanboardCardMemo.hasOwnProperty(key)
}
function getMemoizedKanboardCard(id: number, title: string, project: Project, issue: Issue) {
const key = getKanboardCardMemoizationKey(id, project, issue);
if (isKanboardCardMemoized(key)) return { card: kanboardCardMemo[key], memoized: true };
kanboardCardMemo[key] = { id, title, project, issue };
return { card: kanboardCardMemo[key], memoized: false };
}
function resetKandboarCardMemo() {
Object.keys(kanboardCardMemo).forEach(k => delete kanboardCardMemo[k]);
}
function createKanboardLanes(board: Board, issues: any): KanboardLane[] {
const lanes: KanboardLane[] = [];
const rest = new Set<KanboardCard>();
resetKandboarCardMemo();
board.lanes.forEach((l: BoardLane, i: number) => {
const cards = createCards(board.projects, issues, l, rest);
lanes.push({
id: i,
title: l.title,
cards,
});
});
// Assign remaining issues
board.lanes.forEach((l: BoardLane, i: number) => {
if (!l.collectRemainingIssues) return;
lanes[i].cards.push(...Array.from(rest.values()));
});
resetKandboarCardMemo();
return lanes;
}
function createKanboard(board: Board, issues: any) {
if (!board) return null;
const kanboard = {
id: board.id,
columns: createKanboardLanes(board, issues),
};
return kanboard;
}

View File

@ -1,3 +0,0 @@
export function* logoutSaga() {
window.location = '/logout';
}

View File

@ -0,0 +1,17 @@
import { call, put } from 'redux-saga/effects';
import { LOGOUT_FAILURE, LOGOUT_SUCCESS } from '../actions/logout';
export function* logoutSaga() {
try {
yield call(fetch, '/logout', { mode: 'no-cors', credentials: 'include' });
} catch(err) {
yield put({ type: LOGOUT_FAILURE, error: err });
return;
}
yield put({ type: LOGOUT_SUCCESS });
}
export function* logoutSuccessSaga() {
window.location.reload();
}

View File

@ -3,10 +3,18 @@ import { FETCH_PROJECTS_SUCCESS, FETCH_PROJECTS_FAILURE } from '../actions/proje
import { gitea } from '../../util/gitea'; import { gitea } from '../../util/gitea';
export function* fetchProjectsSaga() { export function* fetchProjectsSaga() {
let projects = [];
let projects;
try { try {
projects = yield call(gitea.fetchUserProjects.bind(gitea)) let page = 1;
while(true) {
let pageProjects = yield call(gitea.fetchUserProjects.bind(gitea), page);
if (pageProjects.length === 0) {
break;
}
projects.push(...pageProjects);
page++;
}
} catch(error) { } catch(error) {
yield put({ type: FETCH_PROJECTS_FAILURE, error }); yield put({ type: FETCH_PROJECTS_FAILURE, error });
return; return;

View File

@ -0,0 +1,9 @@
import { hasReferer, getReferer, clearReferer } from '../../util/referer';
export function* navigateToRefererSaga() {
if (!hasReferer()) return;
const referer = getReferer();
console.log("Redirecting to referer", referer);
clearReferer();
window.location.hash = referer;
}

View File

@ -1,22 +1,25 @@
import { all, takeEvery, takeLatest } from 'redux-saga/effects'; import { all, takeEvery, takeLatest } from 'redux-saga/effects';
import { failuresSaga } from './failure'; import { failuresSaga } from './failure';
import { FETCH_BOARDS_REQUEST, SAVE_BOARD_REQUEST } from '../actions/boards'; import { FETCH_BOARDS_REQUEST, SAVE_BOARD_REQUEST, DELETE_BOARD_REQUEST } from '../actions/boards';
import { fetchBoardsSaga, saveBoardSaga } from './boards'; import { fetchBoardsSaga, saveBoardSaga, deleteBoardSaga } from './boards';
import { FETCH_ISSUES_REQUEST, ADD_LABEL_REQUEST, REMOVE_LABEL_REQUEST, CREATE_ISSUE_REQUEST, CREATE_ISSUE_SUCCESS } from '../actions/issues'; import { FETCH_ISSUES_REQUEST, ADD_LABEL_REQUEST, REMOVE_LABEL_REQUEST, CREATE_ISSUE_REQUEST, CREATE_ISSUE_SUCCESS } from '../actions/issues';
import { fetchIssuesSaga, addLabelSaga, removeLabelSaga, createIssueSaga } from './issues'; import { fetchIssuesSaga, addLabelSaga, removeLabelSaga, createIssueSaga } from './issues';
import { FETCH_PROJECTS_REQUEST } from '../actions/projects'; import { FETCH_PROJECTS_REQUEST } from '../actions/projects';
import { fetchProjectsSaga } from './projects'; import { fetchProjectsSaga } from './projects';
import { LOGOUT } from '../actions/logout'; import { LOGOUT_REQUEST, LOGOUT_SUCCESS } from '../actions/logout';
import { logoutSaga } from './logout'; import { logoutSaga, logoutSuccessSaga } from './logout';
import { BUILD_KANBOARD_REQUEST, MOVE_CARD } from '../actions/kanboards'; import { BUILD_KANBOARD_REQUEST, MOVE_CARD } from '../actions/kanboards';
import { buildKanboardSaga, moveCardSaga, refreshKanboardSaga } from './kanboards'; import { buildKanboardSaga, moveCardSaga, refreshKanboardSaga } from './kanboards';
import { navigateToRefererSaga } from './referer';
export function* rootSaga() { export function* rootSaga() {
yield all([ yield all([
navigateToRefererSaga(),
takeEvery(patternFromRegExp(/^.*_FAILURE/), failuresSaga), takeEvery(patternFromRegExp(/^.*_FAILURE/), failuresSaga),
takeLatest(FETCH_BOARDS_REQUEST, fetchBoardsSaga), takeLatest(FETCH_BOARDS_REQUEST, fetchBoardsSaga),
takeLatest(BUILD_KANBOARD_REQUEST, buildKanboardSaga), takeLatest(BUILD_KANBOARD_REQUEST, buildKanboardSaga),
takeLatest(SAVE_BOARD_REQUEST, saveBoardSaga), takeLatest(SAVE_BOARD_REQUEST, saveBoardSaga),
takeLatest(DELETE_BOARD_REQUEST, deleteBoardSaga),
takeLatest(FETCH_ISSUES_REQUEST, fetchIssuesSaga), takeLatest(FETCH_ISSUES_REQUEST, fetchIssuesSaga),
takeLatest(FETCH_PROJECTS_REQUEST, fetchProjectsSaga), takeLatest(FETCH_PROJECTS_REQUEST, fetchProjectsSaga),
takeEvery(MOVE_CARD, moveCardSaga), takeEvery(MOVE_CARD, moveCardSaga),
@ -24,12 +27,13 @@ export function* rootSaga() {
takeEvery(REMOVE_LABEL_REQUEST, removeLabelSaga), takeEvery(REMOVE_LABEL_REQUEST, removeLabelSaga),
takeLatest(CREATE_ISSUE_REQUEST, createIssueSaga), takeLatest(CREATE_ISSUE_REQUEST, createIssueSaga),
takeLatest(CREATE_ISSUE_SUCCESS, refreshKanboardSaga), takeLatest(CREATE_ISSUE_SUCCESS, refreshKanboardSaga),
takeLatest(LOGOUT, logoutSaga) takeLatest(LOGOUT_REQUEST, logoutSaga),
takeLatest(LOGOUT_SUCCESS, logoutSuccessSaga)
]); ]);
} }
export function patternFromRegExp(re) { export function patternFromRegExp(re: any) {
return (action) => { return (action: any) => {
return re.test(action.type); return re.test(action.type);
}; };
} }

View File

@ -1,11 +0,0 @@
export function selectBoardByUserProjects(boardsByID, projectsByName) {
const userProjects = Object.keys(projectsByName);
return Object.keys(boardsByID).reduce((filteredBoardsByID, boardID) => {
const board = boardsByID[boardID];
const hasProject = board.projects.some(p => userProjects.indexOf(p) !== -1);
if (hasProject) {
filteredBoardsByID[boardID] = board;
}
return filteredBoardsByID;
}, {});
}

View File

@ -0,0 +1,11 @@
export function selectBoardByUserProjects(boardsByID: any, projectsByName: any) {
const userProjects = Object.keys(projectsByName);
return Object.keys(boardsByID).reduce((filteredBoardsByID: any, boardID: string) => {
const board = boardsByID[boardID];
const hasProject = board.projects.length === 0 || board.projects.some((p: any) => userProjects.indexOf(p) !== -1);
if (hasProject) {
filteredBoardsByID[boardID] = board;
}
return filteredBoardsByID;
}, {});
}

View File

@ -1,4 +1,4 @@
export function selectFlagsIsLoading(state, ...actionPrefixes) { export function selectFlagsIsLoading(state: any, ...actionPrefixes: any[]) {
const { actions } = state.flags; const { actions } = state.flags;
return actionPrefixes.reduce((isLoading, prefix) => { return actionPrefixes.reduce((isLoading, prefix) => {
if (!(prefix in actions)) return isLoading; if (!(prefix in actions)) return isLoading;

View File

@ -1,4 +1,4 @@
import { createStore, applyMiddleware } from 'redux' import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga' import createSagaMiddleware from 'redux-saga'
import { rootReducer } from './reducers/root' import { rootReducer } from './reducers/root'
import { rootSaga } from './sagas/root' import { rootSaga } from './sagas/root'
@ -14,6 +14,8 @@ if (process.env.NODE_ENV !== 'production') {
reduxMiddlewares.push(loggerMiddleware); reduxMiddlewares.push(loggerMiddleware);
} }
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
// create the saga middleware // create the saga middleware
const sagaMiddleware = createSagaMiddleware() const sagaMiddleware = createSagaMiddleware()
reduxMiddlewares.push(sagaMiddleware); reduxMiddlewares.push(sagaMiddleware);
@ -21,7 +23,7 @@ reduxMiddlewares.push(sagaMiddleware);
// mount it on the Store // mount it on the Store
export const store = createStore( export const store = createStore(
rootReducer, rootReducer,
applyMiddleware(...reduxMiddlewares) composeEnhancers(applyMiddleware(...reduxMiddlewares)),
) )
// then run the saga // then run the saga

19
client/src/types/board.ts Normal file
View File

@ -0,0 +1,19 @@
import { Project } from "./gitea"
export type BoardLaneId = string
export type BoardId = string
export interface Board {
id: BoardId
title: string
description: string
lanes: BoardLane[]
projects: Project[]
};
export interface BoardLane {
id: BoardLaneId
title: string
issueLabel: string
collectRemainingIssues: boolean
};

View File

@ -0,0 +1,2 @@
export type Project = any
export type Issue = any

View File

@ -0,0 +1,21 @@
import { Project, Issue } from "./gitea";
import { BoardId } from "./board";
export interface Kanboard {
id: BoardId
lanes: KanboardLane[]
}
export interface KanboardLane {
id: number
title: string
cards: KanboardCard[]
};
export interface KanboardCard {
id: number
title: string
project: Project
issue: Issue
}

View File

@ -13,6 +13,12 @@ export class APIClient {
; ;
} }
deleteBoard(id) {
return fetch(`/api/boards/${id}`, {
method: 'DELETE'
});
}
fetchBoards() { fetchBoards() {
return fetch(`/api/boards`) return fetch(`/api/boards`)
.then(res => res.json()) .then(res => res.json())

View File

@ -1,29 +1,30 @@
export class GiteaUnauthorizedError extends Error { export class GiteaUnauthorizedError extends Error {
constructor(...args) { constructor(...args: any[]) {
super(...args) super(...args)
Error.captureStackTrace(this, GiteaUnauthorizedError) Object.setPrototypeOf(this, GiteaUnauthorizedError.prototype);
} }
} }
export class GiteaClient { export class GiteaClient {
fetchIssues(project, page = 1) { fetchIssues(project: any, page = 1) {
return fetch(`/gitea/api/v1/repos/${project}/issues?page=${page}`) return fetch(`/gitea/api/v1/repos/${project}/issues?page=${page}`)
.then(this.assertAuthorization) .then(this.assertAuthorization)
.then(res => res.json())
;
}
fetchUserProjects() {
return fetch(`/gitea/api/v1/user/repos`)
.then(this.assertOk) .then(this.assertOk)
.then(this.assertAuthorization)
.then(res => res.json()) .then(res => res.json())
; ;
} }
addIssueLabel(project, issueNumber, labelID) { fetchUserProjects(page = 1) {
return fetch(`/gitea/api/v1/user/repos?page=${page}`)
.then(this.assertAuthorization)
.then(this.assertOk)
.then(res => res.json())
;
}
addIssueLabel(project: any, issueNumber: any, labelID: any) {
return fetch(`/gitea/api/v1/repos/${project}/issues/${issueNumber}/labels`, { return fetch(`/gitea/api/v1/repos/${project}/issues/${issueNumber}/labels`, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -31,28 +32,28 @@ export class GiteaClient {
}, },
body: JSON.stringify({ labels: [labelID] }), body: JSON.stringify({ labels: [labelID] }),
}) })
.then(this.assertOk)
.then(this.assertAuthorization) .then(this.assertAuthorization)
.then(this.assertOk)
.then(res => res.json()) .then(res => res.json())
} }
fetchProjectLabels(project) { fetchProjectLabels(project: any) {
return fetch(`/gitea/api/v1/repos/${project}/labels`) return fetch(`/gitea/api/v1/repos/${project}/labels`)
.then(this.assertOk)
.then(this.assertAuthorization) .then(this.assertAuthorization)
.then(this.assertOk)
.then(res => res.json()) .then(res => res.json())
; ;
} }
removeIssueLabel(project, issueNumber, labelID) { removeIssueLabel(project: any, issueNumber: any, labelID: any) {
return fetch(`/gitea/api/v1/repos/${project}/issues/${issueNumber}/labels/${labelID}`, { return fetch(`/gitea/api/v1/repos/${project}/issues/${issueNumber}/labels/${labelID}`, {
method: 'DELETE' method: 'DELETE'
}) })
.then(this.assertOk)
.then(this.assertAuthorization) .then(this.assertAuthorization)
.then(this.assertOk)
} }
createIssue(project, title, body, labelID) { createIssue(project: any, title: any, body: any, labelID: any) {
return fetch(`/gitea/api/v1/repos/${project}/issues`, { return fetch(`/gitea/api/v1/repos/${project}/issues`, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -61,20 +62,20 @@ export class GiteaClient {
body: JSON.stringify({ body: JSON.stringify({
title, title,
body, body,
labels: [labelID], labels: labelID ? [labelID] : undefined,
}), }),
}) })
.then(this.assertOk)
.then(this.assertAuthorization) .then(this.assertAuthorization)
.then(this.assertOk)
.then(res => res.json()) .then(res => res.json())
} }
assertOk(res) { assertOk(res: any) {
if (!res.ok) return Promise.reject(new Error('Request failed')); if (!res.ok) return Promise.reject(new Error('Request failed'));
return res; return res;
} }
assertAuthorization(res) { assertAuthorization(res: any) {
if (res.status === 401 || res.status === 404) return Promise.reject(new GiteaUnauthorizedError()); if (res.status === 401 || res.status === 404) return Promise.reject(new GiteaUnauthorizedError());
return res; return res;
} }

View File

@ -0,0 +1,19 @@
const localStorage = window.localStorage;
const refererKey = 'referer';
export function getReferer() {
return localStorage.getItem(refererKey);
}
export function saveReferer() {
console.log("Saving referer", window.location.hash);
localStorage.setItem(refererKey, window.location.hash);
}
export function hasReferer() {
return !!getReferer();
}
export function clearReferer() {
localStorage.removeItem(refererKey);
}

16
client/tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"module": "es6",
"lib": ["dom", "es6"],
"moduleResolution": "node",
"jsx": "react",
"strict": false,
"sourceMap": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"files": [
"./src/custom.d.ts"
]
}

View File

@ -2,22 +2,24 @@ const path = require('path');
// Plugins // Plugins
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackCleanupPlugin = require('webpack-cleanup-plugin');
const env = process.env; const env = process.env;
module.exports = { module.exports = {
mode: `${env.NODE_ENV ? env.NODE_ENV : 'production'}`, mode: `${env.NODE_ENV ? env.NODE_ENV : 'production'}`,
entry: './src/index.js', entry: './src/index.tsx',
devtool: 'inline-source-map', devtool: 'inline-source-map',
output: { output: {
path: path.join(__dirname, 'dist') path: path.join(__dirname, 'dist')
}, },
resolve: { resolve: {
extensions: [ '.jsx', '.js' ], extensions: [".ts", ".tsx", ".js", ".jsx"]
}, },
module: { module: {
rules: [{ rules: [{
test: /\.scss$/, test: /\.s(a|c)ss$/,
use: [ use: [
MiniCssExtractPlugin.loader, MiniCssExtractPlugin.loader,
{ {
@ -46,29 +48,23 @@ module.exports = {
} }
}] }]
},{ },{
test: /\.(html)$/, test: /\.(t|j)sx?$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]'
},
},
'extract-loader',
{
loader: 'html-loader',
options: {}
}
]
},{
test: /\.(js|jsx)$/,
exclude: /node_modules/, exclude: /node_modules/,
use: ['babel-loader'] loaders: ['ts-loader']
}] }]
}, },
plugins: [ plugins: [
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: "css/[name].css", filename: "css/[name].css",
chunkFilename: "css/[id].css" chunkFilename: "css/[id].css"
}),
new HtmlWebpackPlugin({
template: './src/index.html',
inject: false,
favicon: "./src/resources/favicon.png"
}),
new WebpackCleanupPlugin({
exclude: ['resources/logo.svg']
}) })
] ]
} }

View File

@ -1,9 +1,9 @@
package main package main
import ( import (
"forge.cadoles.com/wpetit/gitea-kan/internal/config" "forge.cadoles.com/wpetit/gengitkan/internal/config"
"forge.cadoles.com/wpetit/gitea-kan/internal/repository" "forge.cadoles.com/wpetit/gengitkan/internal/repository"
stormRepo "forge.cadoles.com/wpetit/gitea-kan/internal/repository/storm" stormRepo "forge.cadoles.com/wpetit/gengitkan/internal/repository/storm"
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -28,7 +28,7 @@ func getServiceContainer(conf *config.Config) (*service.Container, error) {
ctn.Provide( ctn.Provide(
session.ServiceName, session.ServiceName,
gorilla.ServiceProvider("gitea-kan", cookieStore), gorilla.ServiceProvider("gengitkan", cookieStore),
) )
// Create and expose config service provider // Create and expose config service provider

View File

@ -6,8 +6,8 @@ import (
"net/http" "net/http"
"os" "os"
"forge.cadoles.com/wpetit/gitea-kan/internal/config" "forge.cadoles.com/wpetit/gengitkan/internal/config"
"forge.cadoles.com/wpetit/gitea-kan/internal/route" "forge.cadoles.com/wpetit/gengitkan/internal/route"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/go-chi/chi/middleware" "github.com/go-chi/chi/middleware"
"github.com/pkg/errors" "github.com/pkg/errors"

1
debian/compat vendored
View File

@ -1 +0,0 @@
9

14
debian/control vendored
View File

@ -1,14 +0,0 @@
Source: gitea-kan
Section: unknown
Priority: optional
Maintainer: William Petit <wpetit@cadoles.com>
Build-Depends: debhelper (>= 8.0.0), wget, ca-certificates, tar
Standards-Version: 3.9.4
Homepage: http://forge.cadoles.com/wpetit/gitea-kan
Vcs-Git: http://forge.cadoles.com/wpetit/gitea-kan.git
Vcs-Browser: http://forge.cadoles.com/wpetit/gitea-kan
Package: gitea-kan
Architecture: amd64
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Application type "Kanboard" connectée à Gitea

View File

@ -1 +0,0 @@
var/lib/gitea-kan

View File

@ -1,13 +0,0 @@
[Unit]
Description=Application type "Kanboard" connectée à Gitea
After=network-online.target
[Service]
Type=simple
Environment=GITEAKAN_HTTP_PUBDIR=/usr/share/gitea-kan/public
Environment=GITEAKAN_DATA_DBPATH=/var/lib/gitea-kan/data.db
ExecStart=/usr/bin/gitea-kan -workdir /usr/share/gitea-kan -config /etc/gitea-kan/server.conf
Restart=on-failure
[Install]
WantedBy=multi-user.target

54
debian/rules vendored
View File

@ -1,54 +0,0 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Uncomment this to turn on verbose mode.
export DH_VERBOSE=1
GO_VERSION := 1.13.5
OS := linux
ARCH := amd64
GOPATH=$(HOME)/go
ifeq (, $(shell which go 2>/dev/null))
override_dh_auto_build: install-go
endif
ifeq (, $(shell which node 2>/dev/null))
override_dh_auto_build: install-nodejs
endif
%:
dh $@ --with systemd
override_dh_auto_build: $(GOPATH)
cd client && npm install
GOPATH=$(GOPATH) PATH="$(PATH):/usr/local/go/bin:$(GOPATH)/bin" go mod vendor
GOPATH=$(GOPATH) PATH="$(PATH):/usr/local/go/bin:$(GOPATH)/bin" ARCH_TARGETS=$(ARCH) make release
$(GOPATH):
mkdir -p $(GOPATH)
install-go:
wget https://dl.google.com/go/go$(GO_VERSION).$(OS)-$(ARCH).tar.gz
tar -C /usr/local -xzf go$(GO_VERSION).$(OS)-$(ARCH).tar.gz
install-nodejs:
wget -O- https://deb.nodesource.com/setup_12.x | bash -
apt-get install -y nodejs
override_dh_auto_install:
mkdir -p debian/gitea-kan/usr/share/gitea-kan
mkdir -p debian/gitea-kan/etc/gitea-kan
mkdir -p debian/gitea-kan/usr/bin
cp -r release/server-$(OS)-$(ARCH)/* debian/gitea-kan/usr/share/gitea-kan/
mv debian/gitea-kan/usr/share/gitea-kan/bin/server debian/gitea-kan/usr/bin/gitea-kan
mv debian/gitea-kan/usr/share/gitea-kan/server.conf debian/gitea-kan/etc/gitea-kan/server.conf
install -d debian/gitea-kan
override_dh_strip:
override_dh_auto_test:

View File

@ -1 +0,0 @@
3.0 (native)

5
go.mod
View File

@ -1,4 +1,4 @@
module forge.cadoles.com/wpetit/gitea-kan module forge.cadoles.com/wpetit/gengitkan
go 1.13 go 1.13
@ -15,9 +15,10 @@ require (
github.com/smartystreets/assertions v1.0.1 // indirect github.com/smartystreets/assertions v1.0.1 // indirect
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
github.com/stretchr/testify v1.4.0 // indirect github.com/stretchr/testify v1.4.0 // indirect
github.com/zserge/lorca v0.1.8 // indirect
gitlab.com/wpetit/goweb v0.0.0-20190728111123-bbcb57177273 gitlab.com/wpetit/goweb v0.0.0-20190728111123-bbcb57177273
go.etcd.io/bbolt v1.3.3 go.etcd.io/bbolt v1.3.3
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/text v0.3.2 // indirect golang.org/x/text v0.3.2 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect

5
go.sum
View File

@ -37,17 +37,22 @@ github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:s
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/zserge/lorca v0.1.8 h1:gZwyvesmaoGCwxF5NssI6pdydXkCVHOoHw2nks/PBRs=
github.com/zserge/lorca v0.1.8/go.mod h1:gTrVdXKyWxNhc8aUb1Uu3s0mY343arR1T6jUtxmBxR8=
gitlab.com/wpetit/goweb v0.0.0-20190728111123-bbcb57177273 h1:YtMGT0pEGTQ5MAglg6rvu8pQVQJEtskoeEw+csUqf2o= gitlab.com/wpetit/goweb v0.0.0-20190728111123-bbcb57177273 h1:YtMGT0pEGTQ5MAglg6rvu8pQVQJEtskoeEw+csUqf2o=
gitlab.com/wpetit/goweb v0.0.0-20190728111123-bbcb57177273/go.mod h1:5Y/eVplFvdsd6zMdA3bx8KON6Ab1n90+cQeX5uJ6jIE= gitlab.com/wpetit/goweb v0.0.0-20190728111123-bbcb57177273/go.mod h1:5Y/eVplFvdsd6zMdA3bx8KON6Ab1n90+cQeX5uJ6jIE=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@ -57,11 +57,11 @@ func NewDefault() *Config {
Debug: false, Debug: false,
HTTP: HTTPConfig{ HTTP: HTTPConfig{
Address: ":3000", Address: ":3000",
PublicDir: "${GITEAKAN_HTTP_PUBDIR}", PublicDir: "${GENGITKAN_HTTP_PUBDIR}",
}, },
Gitea: GiteaConfig{}, Gitea: GiteaConfig{},
Data: DataConfig{ Data: DataConfig{
DBPath: "${GITEAKAN_DATA_DBPATH}", DBPath: "${GENGITKAN_DATA_DBPATH}",
}, },
} }
} }

View File

@ -5,7 +5,7 @@ import (
"github.com/pborman/uuid" "github.com/pborman/uuid"
"forge.cadoles.com/wpetit/gitea-kan/internal/config" "forge.cadoles.com/wpetit/gengitkan/internal/config"
"github.com/pkg/errors" "github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container" "gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service" "gitlab.com/wpetit/goweb/service"

View File

@ -23,4 +23,5 @@ type BoardLane struct {
ID BoardLaneID `json:"id"` ID BoardLaneID `json:"id"`
Title string `json:"title"` Title string `json:"title"`
IssueLabel string `json:"issueLabel"` IssueLabel string `json:"issueLabel"`
CollectRemainingIssues bool `json:"collectRemainingIssues"`
} }

View File

@ -1,5 +1,13 @@
package repository package repository
import (
"github.com/pkg/errors"
)
var (
ErrNotFound = errors.New("not found")
)
type Repository struct { type Repository struct {
boards BoardRepository boards BoardRepository
} }

View File

@ -1,7 +1,7 @@
package storm package storm
import ( import (
"forge.cadoles.com/wpetit/gitea-kan/internal/repository" "forge.cadoles.com/wpetit/gengitkan/internal/repository"
"github.com/asdine/storm" "github.com/asdine/storm"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -61,6 +61,18 @@ func (r *BoardRepository) Save(board *repository.Board) error {
} }
func (r *BoardRepository) Delete(id repository.BoardID) error { func (r *BoardRepository) Delete(id repository.BoardID) error {
b := &boardItem{
ID: string(id),
}
if err := r.db.DeleteStruct(b); err != nil {
if err == storm.ErrNotFound {
return repository.ErrNotFound
}
return errors.Wrapf(err, "could not delete board '%s'", id)
}
return nil return nil
} }

View File

@ -4,7 +4,9 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"forge.cadoles.com/wpetit/gitea-kan/internal/repository" "github.com/go-chi/chi"
"forge.cadoles.com/wpetit/gengitkan/internal/repository"
"github.com/pkg/errors" "github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container" "gitlab.com/wpetit/goweb/middleware/container"
) )
@ -18,6 +20,8 @@ func serveBoards(w http.ResponseWriter, r *http.Request) {
panic(errors.Wrap(err, "could not retrieve boards list")) panic(errors.Wrap(err, "could not retrieve boards list"))
} }
w.Header().Add("Content-Type", "application/json")
encoder := json.NewEncoder(w) encoder := json.NewEncoder(w)
if err := encoder.Encode(boards); err != nil { if err := encoder.Encode(boards); err != nil {
panic(errors.Wrap(err, "could not encode boards list")) panic(errors.Wrap(err, "could not encode boards list"))
@ -39,8 +43,28 @@ func saveBoard(w http.ResponseWriter, r *http.Request) {
panic(errors.Wrap(err, "could not save board")) panic(errors.Wrap(err, "could not save board"))
} }
w.Header().Add("Content-Type", "application/json")
encoder := json.NewEncoder(w) encoder := json.NewEncoder(w)
if err := encoder.Encode(board); err != nil { if err := encoder.Encode(board); err != nil {
panic(errors.Wrap(err, "could not encode board")) panic(errors.Wrap(err, "could not encode board"))
} }
} }
func deleteBoard(w http.ResponseWriter, r *http.Request) {
boardID := repository.BoardID(chi.URLParam(r, "boardID"))
ctn := container.Must(r.Context())
repo := repository.Must(ctn)
if err := repo.Boards().Delete(boardID); err != nil {
if err == repository.ErrNotFound {
http.NotFound(w, r)
return
}
panic(err)
}
w.WriteHeader(http.StatusNoContent)
}

View File

@ -3,7 +3,7 @@ package route
import ( import (
"net/http" "net/http"
"forge.cadoles.com/wpetit/gitea-kan/internal/config" "forge.cadoles.com/wpetit/gengitkan/internal/config"
"github.com/pkg/errors" "github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container" "gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/session" "gitlab.com/wpetit/goweb/service/session"

View File

@ -3,7 +3,7 @@ package route
import ( import (
"net/http" "net/http"
"forge.cadoles.com/wpetit/gitea-kan/internal/middleware" "forge.cadoles.com/wpetit/gengitkan/internal/middleware"
"github.com/pkg/errors" "github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container" "gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/session" "gitlab.com/wpetit/goweb/service/session"

View File

@ -6,8 +6,8 @@ import (
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"forge.cadoles.com/wpetit/gitea-kan/internal/config" "forge.cadoles.com/wpetit/gengitkan/internal/config"
"forge.cadoles.com/wpetit/gitea-kan/internal/middleware" "forge.cadoles.com/wpetit/gengitkan/internal/middleware"
"github.com/pkg/errors" "github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container" "gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/session" "gitlab.com/wpetit/goweb/service/session"

View File

@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"path" "path"
"forge.cadoles.com/wpetit/gitea-kan/internal/config" "forge.cadoles.com/wpetit/gengitkan/internal/config"
"forge.cadoles.com/wpetit/gitea-kan/internal/middleware" "forge.cadoles.com/wpetit/gengitkan/internal/middleware"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"gitlab.com/wpetit/goweb/middleware/container" "gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/static" "gitlab.com/wpetit/goweb/static"
@ -21,6 +21,7 @@ func Mount(r *chi.Mux, config *config.Config) {
r.Get("/logout", handleLogout) r.Get("/logout", handleLogout)
r.Get("/api/boards", serveBoards) r.Get("/api/boards", serveBoards)
r.Post("/api/boards", saveBoard) r.Post("/api/boards", saveBoard)
r.Delete("/api/boards/{boardID}", deleteBoard)
r.Handle("/gitea/api/*", http.StripPrefix("/gitea", http.HandlerFunc(proxyAPIRequest))) r.Handle("/gitea/api/*", http.StripPrefix("/gitea", http.HandlerFunc(proxyAPIRequest)))
r.Get("/*", static.Dir(config.HTTP.PublicDir, "", html5PushStateHandler)) r.Get("/*", static.Dir(config.HTTP.PublicDir, "", html5PushStateHandler))
}) })

View File

@ -5,8 +5,8 @@ modd.conf
!mage_output_file.go { !mage_output_file.go {
prep: make build prep: make build
prep: [ -e data/server.conf ] || ( mkdir -p data && bin/server -dump-config > data/server.conf ) prep: [ -e data/server.conf ] || ( mkdir -p data && bin/server -dump-config > data/server.conf )
daemon: GITEAKAN_HTTP_PUBDIR=./client/dist \ daemon: GENGITKAN_HTTP_PUBDIR=./client/dist \
GITEAKAN_DATA_DBPATH=./data/data.db \ GENGITKAN_DATA_DBPATH=./data/data.db \
bin/server \ bin/server \
-config ./data/server.conf -config ./data/server.conf
} }
@ -16,6 +16,8 @@ modd.conf
} }
client/webpack.config.js client/webpack.config.js
client/tsconfig.json
client/package.json
{ {
daemon: cd client && NODE_ENV=development npm run watch daemon: cd client && NODE_ENV=development npm run watch
} }