Compare commits

..

4 Commits

82 changed files with 696 additions and 2191 deletions

View File

@ -1,4 +1,4 @@
# GenGitKan
# Gitea Kan
## Démarrer avec les sources
@ -9,41 +9,9 @@
### Procédure
```bash
cd client && npm install # Installation des dépendances client
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
[AGPL-3.0](./LICENSE)

10
client/.babelrc Normal file
View File

@ -0,0 +1,10 @@
{
"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,6 +1162,22 @@
}
}
},
"@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": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz",
@ -1243,11 +1259,11 @@
"dev": true
},
"@lourenci/react-kanban": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@lourenci/react-kanban/-/react-kanban-2.0.0.tgz",
"integrity": "sha512-ieNi7d/01wgT9t8kN7Z/RBubyZq9VvKXyU/5sj+UlrY8h4GPRNrN11jLV/ykg55lhyXGKgXDk3ObYurOfrmu3w==",
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@lourenci/react-kanban/-/react-kanban-0.15.0.tgz",
"integrity": "sha512-/2XjB26iXcvpwDwlT3sz8/ptQ7QyTpMGlrPf1f02+V1Z4jdbVMo6Luz1sGlHe/TP68N8yz69/YT9qwqHZ6YYmQ==",
"requires": {
"react-beautiful-dnd": "^13.0.0"
"react-beautiful-dnd": "^11.0.0"
}
},
"@redux-saga/core": {
@ -1297,92 +1313,6 @@
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.1.0.tgz",
"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": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
@ -1578,9 +1508,9 @@
"dev": true
},
"acorn": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
"integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
"integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
"dev": true
},
"adjust-sourcemap-loader": {
@ -1797,9 +1727,9 @@
"dev": true
},
"aws4": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
"babel-code-frame": {
@ -1979,12 +1909,6 @@
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"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": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -2152,11 +2076,6 @@
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.5.tgz",
"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": {
"version": "12.0.3",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz",
@ -2514,8 +2433,7 @@
"core-js": {
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz",
"integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==",
"dev": true
"integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA=="
},
"core-js-compat": {
"version": "3.4.1",
@ -2620,9 +2538,9 @@
}
},
"css-box-model": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.0.tgz",
"integrity": "sha512-lri0br+jSNV0kkkiGEp9y9y3Njq2PmpqbeGWRFQJuZteZzY9iC9GZhQ8Y4WpPwM/2YocjHePxy14igJY7YKzkA==",
"requires": {
"tiny-invariant": "^1.0.6"
}
@ -2652,18 +2570,6 @@
"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": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz",
@ -2685,24 +2591,12 @@
"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": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
"integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
"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": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@ -2853,64 +2747,12 @@
"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": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
"integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
"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": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
@ -2986,12 +2828,6 @@
"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": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
@ -3010,36 +2846,6 @@
"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": {
"version": "0.10.52",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.52.tgz",
@ -3520,8 +3326,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -3542,14 +3347,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -3564,20 +3367,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -3694,8 +3494,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -3707,7 +3506,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -3722,7 +3520,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -3730,14 +3527,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -3756,7 +3551,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -3837,8 +3631,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -3850,7 +3643,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -3936,8 +3728,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -3973,7 +3764,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -3993,7 +3783,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -4037,14 +3826,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@ -4204,13 +3991,13 @@
"dev": true
},
"globule": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz",
"integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
"integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
"dev": true,
"requires": {
"glob": "~7.1.1",
"lodash": "~4.17.12",
"lodash": "~4.17.10",
"minimatch": "~3.0.2"
}
},
@ -4241,15 +4028,6 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
@ -4376,9 +4154,9 @@
}
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz",
"integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==",
"dev": true
},
"html-loader": {
@ -4409,80 +4187,6 @@
"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": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@ -4544,9 +4248,9 @@
"dev": true
},
"in-publish": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz",
"integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
"integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=",
"dev": true
},
"indent-string": {
@ -4647,12 +4351,6 @@
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"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": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
@ -4673,12 +4371,6 @@
}
}
},
"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": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
@ -4711,10 +4403,13 @@
"dev": true
},
"is-finite": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz",
"integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==",
"dev": true
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
"integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
"dev": true,
"requires": {
"number-is-nan": "^1.0.0"
}
},
"is-fullwidth-code-point": {
"version": "1.0.0",
@ -4763,30 +4458,12 @@
"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": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@ -4841,9 +4518,9 @@
"dev": true
},
"js-base64": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
"integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
"integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==",
"dev": true
},
"js-levenshtein": {
@ -4915,9 +4592,9 @@
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true
},
"lcid": {
@ -4982,12 +4659,6 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"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": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -5171,18 +4842,18 @@
}
},
"mime-db": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
"version": "1.42.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz",
"integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==",
"dev": true
},
"mime-types": {
"version": "2.1.27",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
"integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
"version": "2.1.25",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz",
"integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==",
"dev": true,
"requires": {
"mime-db": "1.44.0"
"mime-db": "1.42.0"
}
},
"mimic-fn": {
@ -5234,9 +4905,9 @@
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
"mississippi": {
@ -5279,12 +4950,20 @@
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "^1.2.5"
"minimist": "0.0.8"
},
"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": {
@ -5459,9 +5138,9 @@
}
},
"node-sass": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.0.tgz",
"integrity": "sha512-AxqU+DFpk0lEz95sI6jO0hU0Rwyw7BXVEv6o9OItoXLyeygPeaSpiV4rwQb10JiTghHaa0gZeD21sz+OsQluaw==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.0.tgz",
"integrity": "sha512-W1XBrvoJ1dy7VsvTAS5q1V45lREbTlZQqFbiHb3R3OTTCma0XBtuG6xZ6Z4506nR4lmHPTqVRwxT6KgtWC97CA==",
"dev": true,
"requires": {
"async-foreach": "^0.1.3",
@ -5531,15 +5210,6 @@
"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": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
@ -5588,12 +5258,6 @@
}
}
},
"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": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@ -5627,16 +5291,6 @@
"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": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
@ -5879,12 +5533,6 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"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": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@ -6018,16 +5666,6 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"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": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
@ -6075,9 +5713,9 @@
"dev": true
},
"psl": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
"integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==",
"dev": true
},
"public-encrypt": {
@ -6186,32 +5824,18 @@
}
},
"react-beautiful-dnd": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz",
"integrity": "sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg==",
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-11.0.5.tgz",
"integrity": "sha512-7llby9U+jIfkINcyxPHVWU0HFYzqxMemUYgGHsFsbx4fZo1n/pW6sYKYzhxGxR3Ap5HxqswcQkKUZX4uEUWhlw==",
"requires": {
"@babel/runtime": "^7.8.4",
"css-box-model": "^1.2.0",
"memoize-one": "^5.1.1",
"raf-schd": "^4.0.2",
"react-redux": "^7.1.1",
"redux": "^4.0.4",
"use-memo-one": "^1.1.1"
},
"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=="
}
"@babel/runtime-corejs2": "^7.4.5",
"css-box-model": "^1.1.2",
"memoize-one": "^5.0.4",
"raf-schd": "^4.0.0",
"react-redux": "^7.0.3",
"redux": "^4.0.1",
"tiny-invariant": "^1.0.4",
"use-memo-one": "^1.1.0"
}
},
"react-dom": {
@ -6341,12 +5965,6 @@
}
}
},
"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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
@ -6467,19 +6085,6 @@
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
"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": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
@ -6502,9 +6107,9 @@
}
},
"request": {
"version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
@ -6514,7 +6119,7 @@
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"har-validator": "~5.1.0",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
@ -6524,7 +6129,7 @@
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tough-cookie": "~2.4.3",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
}
@ -6841,6 +6446,12 @@
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@ -7081,9 +6692,9 @@
}
},
"spdx-exceptions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
"integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
"dev": true
},
"spdx-expression-parse": {
@ -7217,26 +6828,6 @@
"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": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@ -7377,28 +6968,20 @@
}
},
"terser-webpack-plugin": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz",
"integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==",
"dev": true,
"requires": {
"cacache": "^12.0.2",
"find-cache-dir": "^2.1.0",
"is-wsl": "^1.1.0",
"schema-utils": "^1.0.0",
"serialize-javascript": "^2.1.2",
"serialize-javascript": "^1.7.0",
"source-map": "^0.6.1",
"terser": "^4.1.2",
"webpack-sources": "^1.4.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": {
@ -7489,20 +7072,22 @@
"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": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"dev": true,
"requires": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
"psl": "^1.1.24",
"punycode": "^1.4.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": {
@ -7520,99 +7105,6 @@
"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": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
@ -7652,11 +7144,6 @@
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
"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": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
@ -7873,22 +7360,6 @@
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"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": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
@ -7986,28 +7457,6 @@
}
}
},
"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": {
"version": "3.3.10",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.10.tgz",

View File

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

View File

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

View File

@ -0,0 +1,103 @@
import React from 'react';
import { Page } from '../Page';
import { connect } from 'react-redux';
import Board from '@lourenci/react-kanban';
import { fetchIssues } from '../../store/actions/issues';
import { fetchBoards } from '../../store/actions/boards';
import { buildKanboard, moveCard } from '../../store/actions/kanboards';
export class BoardPage extends React.Component {
render() {
return (
<Page>
<div className="container is-fluid">
<div className="kanboard-container is-fullheight">
{this.renderBoard()}
</div>
</div>
</Page>
);
}
renderBoard() {
const { kanboard } = this.props;
if (!kanboard) {
return <p>Loading</p>
}
return (
<Board disableLaneDrag={true}
renderCard={this.renderCard}
renderLaneHeader={this.renderLaneHeader}
onCardDragEnd={this.onCardDragEnd.bind(this)}>
{kanboard}
</Board>
);
}
renderCard(card) {
return (
<div className="kanboard-card">
<div className="box">
<div className="media">
<div className="media-content">
<div className="content">
<h5 className="is-size-5 is-marginless">{card.title}</h5>
</div>
</div>
</div>
</div>
</div>
);
}
renderLaneHeader(lane) {
return (
<h3 className="kanboard-lane-title is-size-3">{lane.title}</h3>
)
}
onCardDragEnd(source, dest) {
const { board } = this.props;
this.props.dispatch(moveCard(
board.id,
source.fromLaneId,
source.fromPosition,
dest.toLaneId,
dest.toPosition
));
}
componentDidMount() {
const { board } = this.props;
if (!board) {
this.requestBoardsUpdate();
return
}
this.requestBuildKanboard();
}
componentDidUpdate(prevProps) {
if (prevProps.board !== this.props.board) this.requestBuildKanboard();
}
requestBoardsUpdate() {
this.props.dispatch(fetchBoards());
}
requestBuildKanboard() {
const { board } = this.props;
if (!board) return;
this.props.dispatch(buildKanboard(board));
}
}
export const ConnectedBoardPage = connect(function(state, props) {
const boardID = props.match.params.id;
return {
board: state.boards.byID[boardID],
kanboard: state.kanboards.byID[boardID]
};
})(BoardPage);

View File

@ -1,321 +0,0 @@
import React, { Fragment } from 'react';
import { Page } from '../Page';
import { connect, DispatchProp } from 'react-redux';
import Board, { addColumn } from '@lourenci/react-kanban';
import { fetchBoards } from '../../store/actions/boards';
import { createIssue } from '../../store/actions/issues';
import { buildKanboard, moveCard } from '../../store/actions/kanboards';
import { Loader } from '../Loader';
import { IssueCard } from './IssueCard';
import { Modal } from '../Modal';
export interface BoardPageProps extends DispatchProp {
board: any
kanboard: any
}
export class BoardPage extends React.Component<BoardPageProps> {
state = {
newCardModalActive: false,
newCardLaneID: 0,
newCard: {
title: "",
body: "",
project: ""
},
compactMode: true,
hasError: false,
}
onNewCardTitleChange: (evt: any) => void;
onNewCardBodyChange: (evt: any) => void;
onNewCardProjectChange: (evt: any) => void;
constructor(props: BoardPageProps) {
super(props);
this.renderLaneHeader = this.renderLaneHeader.bind(this);
this.onNewCardClick = this.onNewCardClick.bind(this);
this.onNewCardCloseClick = this.onNewCardCloseClick.bind(this);
this.onNewCardTitleChange = this.onNewCardAttrChange.bind(this, 'title');
this.onNewCardBodyChange = this.onNewCardAttrChange.bind(this, 'body');
this.onNewCardProjectChange = this.onNewCardAttrChange.bind(this, 'project');
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() {
const { board } = this.props;
return (
<Page title={`${board ? (board.title + ' - ') : ''}GenGitKan`}>
{this.renderBoard()}
</Page>
);
}
renderBoard() {
const { kanboard, board } = this.props;
if (!kanboard) {
return <Loader></Loader>
}
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">
<Board
renderCard={this.renderCard.bind(this)}
renderColumnHeader={this.renderLaneHeader.bind(this)}
onCardDragEnd={this.onCardDragEnd.bind(this)}
disableColumnDrag={true}
>
{kanboard}
</Board>
{ this.renderNewCardModal() }
</div>
</div>
</Fragment>
);
}
renderNewCardModal() {
const { newCardModalActive, newCardLaneID } = this.state;
const { board } = this.props;
if (!board || newCardLaneID === undefined) return null;
return (
<Modal active={newCardModalActive}>
<div className="new-card-modal">
<article className="message">
<div className="message-header">
<p><span>"{board.lanes[newCardLaneID].title}" - Nouveau ticket</span></p>
<button className="delete" aria-label="delete" onClick={this.onNewCardCloseClick}></button>
</div>
<div className="message-body">
<div className="field">
<div className="control">
<input className="input is-medium" type="text"
placeholder="Titre de votre ticket..."
value={this.state.newCard.title}
onChange={this.onNewCardTitleChange} />
</div>
</div>
<div className="field">
<div className="control">
<textarea className="textarea"
placeholder="Description du nouveau ticket..."
value={this.state.newCard.body}
onChange={this.onNewCardBodyChange}
rows={10}>
</textarea>
</div>
</div>
<div className="field">
<div className="control is-expanded">
<div className="select is-fullwidth">
<select
value={this.state.newCard.project}
onChange={this.onNewCardProjectChange}>
{
board.projects.map((p: any, i: number) => {
return <option key={`new-card-project-${i}`} value={p}>{p}</option>
})
}
</select>
</div>
</div>
</div>
<div className="field is-grouped is-grouped-right">
<p className="control">
<a className="button is-light"
onClick={this.onNewCardCloseClick}>
Annuler
</a>
</p>
<p className="control">
<a className="button is-primary"
onClick={this.onNewCardSaveClick}>
Enregistrer
</a>
</p>
</div>
</div>
</article>
</div>
</Modal>
)
}
renderCard(card: any) {
return <IssueCard compact={this.state.compactMode} card={card} />;
}
renderLaneHeader(lane: any) {
return (
<div className="kanboard-lane">
<div className="level">
<div className="level-left">
<div className="level-item">
<span className="tag is-primary is-light is-normal">{lane.cards.length}</span>
</div>
<button className="button is-light level-item is-small expand"
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)}>
<span className="icon">
<i className="fas fa-plus" aria-hidden="true"></i>
</span>
</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>
)
}
onMinimizeColumn(e: any) {
e.currentTarget.closest('.react-kanban-column').classList.toggle('minimized');
}
onCardDragEnd(card: any, source: any, dest: any) {
const { board } = this.props;
this.props.dispatch(moveCard(
board.id,
source.fromColumnId,
source.fromPosition,
dest.toColumnId,
dest.toPosition
));
}
componentDidMount() {
const { board } = this.props;
if (!board) {
this.requestBoardsUpdate();
return
}
this.requestBuildKanboard();
}
onNewCardClick(laneID: string) {
const { board } = this.props;
this.setState({
newCardModalActive: true,
newCardLaneID: laneID,
newCard: {
title: "",
body: "",
project: board.projects[0],
}
});
}
onNewCardCloseClick() {
this.setState({ newCardModalActive: false });
}
onNewCardAttrChange(attrName: string, evt: React.ChangeEvent) {
const value = (evt.target as HTMLInputElement).value;
this.setState((state: any) => {
return {
...state,
newCard: {
...state.newCard,
[attrName]: value,
}
}
})
}
onNewCardSaveClick() {
const { newCard, newCardLaneID } = this.state;
const { board } = this.props;
this.setState({ newCardModalActive: false });
this.props.dispatch(createIssue(
newCard.project,
newCard.title,
newCard.body,
board.lanes[newCardLaneID].issueLabel
));
}
componentDidUpdate(prevProps: any) {
if (prevProps.board !== this.props.board) this.requestBuildKanboard();
}
requestBoardsUpdate() {
this.props.dispatch(fetchBoards());
}
requestBuildKanboard() {
const { board } = this.props;
if (!board) return;
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: any, props: any) {
const boardID = props.match.params.id;
return {
board: state.boards.byID[boardID],
kanboard: state.kanboards.byID[boardID]
};
})(BoardPage);

View File

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

View File

@ -1,80 +0,0 @@
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,10 +1,6 @@
import React from 'react';
export interface BoardProps {
board: any
}
export class BoardCard extends React.PureComponent<BoardProps> {
export class BoardCard extends React.PureComponent {
render() {
const { board } = this.props;
return (
@ -12,13 +8,9 @@ export class BoardCard extends React.PureComponent<BoardProps> {
<div className="media">
<div className="media-content">
<div className="content">
<p>
<a href={`#/boards/${board.id}`}>
<strong className="is-size-4">{board.title}</strong>
</a>
<br />
{board.description}
</p>
<a href={`#/boards/${board.id}`}>
<h3 className="is-size-3">{board.title}</h3>
</a>
</div>
</div>
</div>
@ -30,6 +22,11 @@ export class BoardCard extends React.PureComponent<BoardProps> {
<i className="fas fa-edit" aria-hidden="true"></i>
</span>
</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>

View File

@ -1,22 +1,15 @@
import React from 'react';
import { Page } from '../Page';
import { BoardCard } from './BoardCard';
import { connect, DispatchProp } from 'react-redux';
import { connect } from 'react-redux';
import { fetchBoards } from '../../store/actions/boards';
import { fetchProjects } from '../../store/actions/projects';
import { selectBoardByUserProjects } from '../../store/selectors/boards';
export interface HomePageProps extends DispatchProp {
boards: any[]
}
export class HomePage extends React.Component<HomePageProps> {
export class HomePage extends React.Component {
render() {
return (
<Page title="GenGitKan - Accueil">
<Page>
<div className="container is-fluid">
<div className="level has-margin-top-normal">
<div className="level">
<div className="level-left"></div>
<div className="level-right">
<div className="buttons">
@ -45,8 +38,8 @@ export class HomePage extends React.Component<HomePageProps> {
boardRows[boardRows.length-1].push(board);
return boardRows;
}, [])
.map((row: any, rowIndex: number) => {
const tiles = row.map((board: any) => {
.map((row, rowIndex) => {
const tiles = row.map((board) => {
return (
<div key={`board-${board.id}`} className={`tile is-parent is-4`}>
<div className="tile is-child">
@ -72,13 +65,12 @@ export class HomePage extends React.Component<HomePageProps> {
componentDidMount() {
this.props.dispatch(fetchBoards());
this.props.dispatch(fetchProjects());
}
}
export const ConnectedHomePage = connect(function(state: any) {
export const ConnectedHomePage = connect(function(state) {
return {
boards: selectBoardByUserProjects(state.boards.byID, state.projects.byName)
boards: state.boards.byID,
};
})(HomePage);

View File

@ -1,14 +0,0 @@
import React from 'react';
export class Loader extends React.Component {
render() {
return (
<div className="loader-container">
<div className="lds-ripple">
<div></div>
<div></div>
</div>
</div>
)
}
}

View File

@ -1,26 +0,0 @@
import React, { PropsWithChildren } from 'react';
export interface ModalProps {
active: boolean
showCloseButton?: boolean
onClose?: (evt: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
}
export class Modal extends React.PureComponent<PropsWithChildren<ModalProps>> {
render() {
const { children, active, showCloseButton, onClose } = this.props;
return (
<div className={`modal ${active ? 'is-active': ''}`}>
<div className="modal-background"></div>
<div className="modal-content">
{children}
</div>
{
showCloseButton ?
<button onClick={onClose} className="modal-close is-large" aria-label="close"></button> :
null
}
</div>
);
}
}

View File

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

View File

@ -0,0 +1,13 @@
import React from 'react';
import { Navbar } from './Navbar';
export class Page extends React.Component {
render() {
return (
<React.Fragment>
<Navbar />
{this.props.children}
</React.Fragment>
);
}
}

View File

@ -1,30 +0,0 @@
import React from 'react';
import { Navbar } from './Navbar';
export interface PageProps {
title?: string
}
export class Page extends React.PureComponent<PageProps> {
render() {
return (
<React.Fragment>
<Navbar />
{this.props.children}
</React.Fragment>
);
}
componentDidMount() {
this.updateTitle();
}
componentDidUpdate() {
this.updateTitle();
}
updateTitle() {
const { title } = this.props;
if (title !== undefined) window.document.title = title;
}
}

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 887 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="height: 512px; width: 512px;"><g class="" transform="translate(0,0)" style="touch-action: none;"><path d="M69.313 18.75c-1.574 2.353-3.376 4.513-4.813 7C29.19 86.9 50.184 165.194 111.344 200.5c.822.475 1.64.92 2.47 1.375 6.942-20.223 16.297-38.746 27.592-54.844-46.972-27.407-73.09-77.42-72.094-128.28zm366.218 0c1 51.074-25.34 101.293-72.686 128.625-.06.035-.128.06-.188.094 11.25 16.126 20.54 34.673 27.438 54.905 1.143-.615 2.275-1.222 3.406-1.875 61.16-35.306 82.154-113.6 46.844-174.75-1.437-2.487-3.24-4.647-4.813-7zm-183.655 83.063c-49.747 0-94.34 38.94-117.188 97.687 34.868-8.738 76.5-12.77 118.282-12.78 40.775-.013 81.443 3.814 115.843 12.124-22.932-58.378-67.38-97.03-116.938-97.03zm1.094 103.593c-61.1.017-122.17 10.173-156.44 27.875v59.69c38.836-8.845 89.384-13.424 140.626-14.158L219.28 395h59.97l-17.875-116.22c55.228.506 109.26 5.38 148.25 14.158V233.28c-34.38-17.77-95.545-27.89-156.656-27.874zm-61.064 94.78c-11.582.002-20.094 8.333-20.094 18.002 0 9.668 8.512 18 20.094 18 11.583 0 20.125-8.332 20.125-18 0-9.67-8.54-18-20.124-18zm114.688.002c-11.583 0-20.094 8.33-20.094 18 0 9.668 8.51 18 20.094 18 11.582 0 20.125-8.332 20.125-18 0-9.67-8.544-18-20.126-18zm44.625 2.625L300.06 493.938l81.844-21.25V306.75c-9.6-1.504-19.885-2.81-30.687-3.938zm-203.25.593c-10.817 1.254-21.174 2.733-30.845 4.438v164.844l81.844 21.25-51-190.532z" fill="#4a4a4a" fill-opacity="1"></path></g></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,6 +1,3 @@
@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 '_loader.scss';
@import '_kanboard.scss';
@import '_kanboard.scss';

View File

@ -1,25 +1,12 @@
html, body {
height: 100%;
background-color: #f7f7f7;
}
.is-fullheight {
height: 100%;
}
.has-margin-top-normal {
margin-top: $size-normal;
}
.has-padding-small {
padding: 1rem;
}
#app {
display: flex;
flex-direction: column;
}
.mr-1 {
margin-right: 5px;
}

View File

@ -8,9 +8,6 @@
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: stretch;
// Lanes
& > div {
@ -20,11 +17,6 @@
flex-grow: 1;
flex-basis: 100%;
background-color: transparent;
display: flex;
flex-direction: column;
bottom: 0;
height: 100% !important;
min-height: 100% !important;
// Card container
& > div > div > div {
@ -35,164 +27,14 @@
}
[data-react-beautiful-dnd-droppable] {
min-height: calc(100vh) !important;
height: 100%;
}
.kanboard-card {
margin-bottom: $size-small;
}
.kanboard-lane {
.kanboard-lane-title {
margin-bottom: $size-small;
background: $white;
top: 0;
.expand {
display: none;
}
}
}
.modal-content {
justify-content: center;
align-items: center;
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

@ -1,44 +0,0 @@
.loader-container {
display: flex;
width: 100%;
justify-content: center;
height: 100%;
align-items: center;
}
.lds-ripple {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
transform: scale(2);
}
.lds-ripple div {
position: absolute;
border: 4px solid $grey;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.lds-ripple div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes lds-ripple {
0% {
top: 36px;
left: 36px;
width: 0;
height: 0;
opacity: 1;
}
100% {
top: 0px;
left: 0px;
width: 72px;
height: 72px;
opacity: 0;
}
}

View File

@ -10,14 +10,6 @@ export const SAVE_BOARD_REQUEST = "SAVE_BOARD_REQUEST";
export const SAVE_BOARD_SUCCESS = "SAVE_BOARD_SUCCESS";
export const SAVE_BOARD_FAILURE = "SAVE_BOARD_FAILURE";
export function saveBoard(board: any) {
export function saveBoard(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_FAILURE = "FETCH_ISSUES_FAILURE";
export function fetchIssues(project: any) {
export function fetchIssues(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_FAILURE = "ADD_LABEL_FAILURE";
export function addLabel(project: any, issueNumber: any, label: string) {
export function addLabel(project, issueNumber, label) {
return { type: ADD_LABEL_REQUEST, project, issueNumber, label };
}
@ -18,14 +18,6 @@ export const REMOVE_LABEL_REQUEST = "REMOVE_LABEL_REQUEST";
export const REMOVE_LABEL_SUCCESS = "REMOVE_LABEL_SUCCESS";
export const REMOVE_LABEL_FAILURE = "REMOVE_LABEL_FAILURE";
export function removeLabel(project: any, issueNumber: any, label: string) {
export function removeLabel(project, issueNumber, label) {
return { type: REMOVE_LABEL_REQUEST, project, issueNumber, label };
}
export const CREATE_ISSUE_REQUEST = "CREATE_ISSUE_REQUEST";
export const CREATE_ISSUE_SUCCESS = "CREATE_ISSUE_SUCCESS";
export const CREATE_ISSUE_FAILURE = "CREATE_ISSUE_FAILURE";
export function createIssue(project: any, title: string, body: any, label: string) {
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_FAILURE = "BUILD_KANBOARD_FAILURE";
export function buildKanboard(board: string) {
export function buildKanboard(board) {
return { type: BUILD_KANBOARD_REQUEST, board };
};
export const MOVE_CARD = "MOVE_CARD";
export function moveCard(boardID: string, fromLaneID: string, fromPosition: any, toLaneID: any, toPosition: any) {
export function moveCard(boardID, fromLaneID, fromPosition, toLaneID, toPosition) {
return { type: MOVE_CARD, boardID, fromLaneID, fromPosition, toLaneID, toPosition };
};

View File

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

View File

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

View File

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

View File

@ -0,0 +1,27 @@
import { FETCH_ISSUES_SUCCESS } from "../actions/issues";
const defaultState = {
byProject: {}
};
export function issuesReducer(state = defaultState, action) {
switch(action.type) {
case FETCH_ISSUES_SUCCESS:
return handleFetchIssuesSuccess(state, action);
default:
return state;
}
}
function handleFetchIssuesSuccess(state, action) {
return {
...state,
byProject: {
...state.byProject,
[action.project]: [
...action.issues,
]
}
}
}

View File

@ -1,42 +0,0 @@
import { FETCH_ISSUES_SUCCESS, CREATE_ISSUE_SUCCESS } from "../actions/issues";
const defaultState = {
byProject: {}
};
export function issuesReducer(state = defaultState, action: any) {
switch(action.type) {
case FETCH_ISSUES_SUCCESS:
return handleFetchIssuesSuccess(state, action);
case CREATE_ISSUE_SUCCESS:
return handleCreateIssueSuccess(state, action);
default:
return state;
}
}
function handleFetchIssuesSuccess(state: any, action: any) {
return {
...state,
byProject: {
...state.byProject,
[action.project]: [
...action.issues,
]
}
}
}
function handleCreateIssueSuccess(state: any, action: any) {
return {
...state,
byProject: {
...state.byProject,
[action.project]: [
...state.byProject[action.project],
action.issue
]
}
}
}

View File

@ -1,11 +1,10 @@
import { BUILD_KANBOARD_SUCCESS, MOVE_CARD } from "../actions/kanboards";
import { CREATE_ISSUE_SUCCESS } from "../actions/issues";
export const defaultState = {
byID: {},
};
export function kanboardsReducer(state = defaultState, action: any) {
export function kanboardsReducer(state = defaultState, action) {
switch(action.type) {
case BUILD_KANBOARD_SUCCESS:
return handleBuildKanboardSuccess(state, action);
@ -16,7 +15,7 @@ export function kanboardsReducer(state = defaultState, action: any) {
}
}
function handleBuildKanboardSuccess(state: any, action: any) {
function handleBuildKanboardSuccess(state, action) {
return {
...state,
byID: {
@ -28,7 +27,7 @@ function handleBuildKanboardSuccess(state: any, action: any) {
};
}
function handleMoveCard(state: any, action: any) {
function handleMoveCard(state, action) {
const {
boardID, fromLaneID,
fromPosition, toLaneID,
@ -37,22 +36,22 @@ function handleMoveCard(state: any, action: any) {
const kanboard = state.byID[boardID];
const columns = [ ...kanboard.columns ];
const fromLane = columns[fromLaneID];
const toLane = columns[toLaneID];
const lanes = [ ...kanboard.lanes ];
const fromLane = lanes[fromLaneID];
const toLane = lanes[toLaneID];
const card = fromLane.cards[fromPosition];
const fromCards = [ ...fromLane.cards ];
if (fromLaneID !== toLaneID) {
fromCards.splice(fromPosition, 1);
columns[fromLaneID] = {
lanes[fromLaneID] = {
...fromLane,
cards: fromCards,
};
const toCards = [ ...toLane.cards ];
toCards.splice(toPosition, 0, card);
columns[toLaneID] = {
lanes[toLaneID] = {
...toLane,
cards: toCards,
};
@ -67,7 +66,7 @@ function handleMoveCard(state: any, action: any) {
...state.byID,
[boardID]: {
...state.byID[boardID],
columns,
lanes,
},
}
};

View File

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

View File

@ -1,11 +1,9 @@
import { put, call } from 'redux-saga/effects';
import {
FETCH_BOARDS_SUCCESS, SAVE_BOARD_SUCCESS,
SAVE_BOARD_FAILURE, FETCH_BOARDS_FAILURE,
DELETE_BOARD_FAILURE, DELETE_BOARD_SUCCESS
} from '../actions/boards';
import { FETCH_BOARDS_SUCCESS, SAVE_BOARD_SUCCESS, SAVE_BOARD_FAILURE, FETCH_BOARDS_FAILURE } from '../actions/boards';
import { api } from '../../util/api';
const boardsLocalStorageKey = 'giteakan.boards';
export function* fetchBoardsSaga() {
let boards;
@ -19,7 +17,7 @@ export function* fetchBoardsSaga() {
yield put({ type: FETCH_BOARDS_SUCCESS, boards });
}
export function* saveBoardSaga(action: any) {
export function* saveBoardSaga(action) {
let { board } = action;
try {
@ -31,17 +29,3 @@ export function* saveBoardSaga(action: any) {
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

@ -0,0 +1,10 @@
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

@ -1,11 +0,0 @@
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,27 +1,13 @@
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 } from '../actions/issues';
import { gitea } from '../../util/gitea';
export function* fetchIssuesSaga(action: any) {
export function* fetchIssuesSaga(action) {
const { project } = action;
let issues = [];
let issues;
try {
let page = 1;
while(true) {
let pageIssues = yield call(gitea.fetchIssues.bind(gitea), action.project, page);
if (pageIssues.length === 0) {
break;
}
issues.push(...pageIssues.filter(issue => issue.pull_request === null));
page++;
}
issues = yield call(gitea.fetchIssues.bind(gitea), action.project);
} catch(error) {
yield put({ type: FETCH_ISSUES_FAILURE, project, error });
return;
@ -30,10 +16,10 @@ export function* fetchIssuesSaga(action: any) {
yield put({ type: FETCH_ISSUES_SUCCESS, project, issues });
}
export function* addLabelSaga(action: any) {
export function* addLabelSaga(action) {
const { project, issueNumber, label } = action;
const labels = yield call(gitea.fetchProjectLabels.bind(gitea), project);
const giteaLabel = labels.find((l: any) => l.name === label)
const giteaLabel = labels.find(l => l.name === label)
if (!giteaLabel) {
yield put({ type: ADD_LABEL_FAILURE, error: new Error(`Label "${label}" not found !`) });
@ -50,10 +36,10 @@ export function* addLabelSaga(action: any) {
yield put({ type: ADD_LABEL_SUCCESS, project, issueNumber, label });
}
export function* removeLabelSaga(action: any) {
export function* removeLabelSaga(action) {
const { project, issueNumber, label } = action;
const labels = yield call(gitea.fetchProjectLabels.bind(gitea), project);
const giteaLabel = labels.find((l: any) => l.name === label)
const giteaLabel = labels.find(l => l.name === label)
if (!giteaLabel) {
yield put({ type: REMOVE_LABEL_FAILURE, error: new Error(`Label "${label}" not found !`) });
@ -69,21 +55,5 @@ export function* removeLabelSaga(action: any) {
yield put({ type: REMOVE_LABEL_SUCCESS, project, issueNumber, label });
}
export function* createIssueSaga(action: any) {
const { project, title, label, body } = action;
const labels = yield call(gitea.fetchProjectLabels.bind(gitea), project);
const giteaLabel = labels.find((l: any) => l.name === label)
let issue;
try {
issue = yield call(gitea.createIssue.bind(gitea), project, title, body, giteaLabel ? giteaLabel.id : null);
} catch(error) {
yield put({ type: CREATE_ISSUE_FAILURE, error });
return;
}
yield put({ type: CREATE_ISSUE_SUCCESS, project, title, label, body, issue });
}

View File

@ -0,0 +1,100 @@
import { select, put } from 'redux-saga/effects';
import { fetchIssues, addLabel, removeLabel } from '../actions/issues';
import { fetchIssuesSaga } from './issues';
import { BUILD_KANBOARD_SUCCESS } 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.number, board.lanes[toLaneID].issueLabel));
yield put(removeLabel(card.project, card.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 });
}
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.number} - ${issue.title}`,
description: "",
project: p,
labels: issue.labels,
assignee: issue.assignee,
number: issue.number
});
}
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

@ -1,152 +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, 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

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

View File

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

View File

@ -1,9 +0,0 @@
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

@ -0,0 +1,33 @@
import { all, takeEvery, takeLatest } from 'redux-saga/effects';
import { failuresSaga } from './failure';
import { FETCH_BOARDS_REQUEST, SAVE_BOARD_REQUEST } from '../actions/boards';
import { fetchBoardsSaga, saveBoardSaga } from './boards';
import { FETCH_ISSUES_REQUEST, ADD_LABEL_REQUEST, REMOVE_LABEL_REQUEST } from '../actions/issues';
import { fetchIssuesSaga, addLabelSaga, removeLabelSaga } from './issues';
import { FETCH_PROJECTS_REQUEST } from '../actions/projects';
import { fetchProjectsSaga } from './projects';
import { LOGOUT } from '../actions/logout';
import { logoutSaga } from './logout';
import { BUILD_KANBOARD_REQUEST, MOVE_CARD } from '../actions/kanboards';
import { buildKanboardSaga, moveCardSaga } from './kanboards';
export function* rootSaga() {
yield all([
takeEvery(patternFromRegExp(/^.*_FAILURE/), failuresSaga),
takeLatest(FETCH_BOARDS_REQUEST, fetchBoardsSaga),
takeLatest(BUILD_KANBOARD_REQUEST, buildKanboardSaga),
takeLatest(SAVE_BOARD_REQUEST, saveBoardSaga),
takeLatest(FETCH_ISSUES_REQUEST, fetchIssuesSaga),
takeLatest(FETCH_PROJECTS_REQUEST, fetchProjectsSaga),
takeEvery(MOVE_CARD, moveCardSaga),
takeEvery(ADD_LABEL_REQUEST, addLabelSaga),
takeEvery(REMOVE_LABEL_REQUEST, removeLabelSaga),
takeLatest(LOGOUT, logoutSaga)
]);
}
export function patternFromRegExp(re) {
return (action) => {
return re.test(action.type);
};
}

View File

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

View File

@ -1,11 +0,0 @@
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: any, ...actionPrefixes: any[]) {
export function selectFlagsIsLoading(state, ...actionPrefixes) {
const { actions } = state.flags;
return actionPrefixes.reduce((isLoading, prefix) => {
if (!(prefix in actions)) return isLoading;

View File

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

View File

@ -1,19 +0,0 @@
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

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

View File

@ -1,21 +0,0 @@
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,12 +13,6 @@ export class APIClient {
;
}
deleteBoard(id) {
return fetch(`/api/boards/${id}`, {
method: 'DELETE'
});
}
fetchBoards() {
return fetch(`/api/boards`)
.then(res => res.json())

View File

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

View File

@ -1,19 +0,0 @@
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);
}

View File

@ -1,16 +0,0 @@
{
"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,24 +2,22 @@ const path = require('path');
// Plugins
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackCleanupPlugin = require('webpack-cleanup-plugin');
const env = process.env;
module.exports = {
mode: `${env.NODE_ENV ? env.NODE_ENV : 'production'}`,
entry: './src/index.tsx',
entry: './src/index.js',
devtool: 'inline-source-map',
output: {
path: path.join(__dirname, 'dist')
},
resolve: {
extensions: [".ts", ".tsx", ".js", ".jsx"]
extensions: [ '.jsx', '.js' ],
},
module: {
rules: [{
test: /\.s(a|c)ss$/,
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
@ -39,32 +37,38 @@ module.exports = {
}
]
},{
test: /\.(woff(2)?|ttf|eot|svg|png)(\?v=\d+\.\d+\.\d+)?$/,
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: '/resources/'
outputPath: '/fonts/'
}
}]
},{
test: /\.(t|j)sx?$/,
test: /\.(html)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]'
},
},
'extract-loader',
{
loader: 'html-loader',
options: {}
}
]
},{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loaders: ['ts-loader']
use: ['babel-loader']
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].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
import (
"forge.cadoles.com/wpetit/gengitkan/internal/config"
"forge.cadoles.com/wpetit/gengitkan/internal/repository"
stormRepo "forge.cadoles.com/wpetit/gengitkan/internal/repository/storm"
"forge.cadoles.com/wpetit/gitea-kan/internal/config"
"forge.cadoles.com/wpetit/gitea-kan/internal/repository"
stormRepo "forge.cadoles.com/wpetit/gitea-kan/internal/repository/storm"
"github.com/asdine/storm"
"github.com/gorilla/sessions"
"github.com/pkg/errors"
@ -28,7 +28,7 @@ func getServiceContainer(conf *config.Config) (*service.Container, error) {
ctn.Provide(
session.ServiceName,
gorilla.ServiceProvider("gengitkan", cookieStore),
gorilla.ServiceProvider("gitea-kan", cookieStore),
)
// Create and expose config service provider

View File

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

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
9

14
debian/control vendored Normal file
View File

@ -0,0 +1,14 @@
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

1
debian/gitea-kan.dirs vendored Normal file
View File

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

13
debian/gitea-kan.service vendored Normal file
View File

@ -0,0 +1,13 @@
[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 Normal file
View File

@ -0,0 +1,54 @@
#!/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:

1
debian/source/format vendored Normal file
View File

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

5
go.mod
View File

@ -1,4 +1,4 @@
module forge.cadoles.com/wpetit/gengitkan
module forge.cadoles.com/wpetit/gitea-kan
go 1.13
@ -15,10 +15,9 @@ require (
github.com/smartystreets/assertions v1.0.1 // indirect
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // 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
go.etcd.io/bbolt v1.3.3
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
golang.org/x/text v0.3.2 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect

5
go.sum
View File

@ -37,22 +37,17 @@ 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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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/go.mod h1:5Y/eVplFvdsd6zMdA3bx8KON6Ab1n90+cQeX5uJ6jIE=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
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/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-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-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-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/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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,
HTTP: HTTPConfig{
Address: ":3000",
PublicDir: "${GENGITKAN_HTTP_PUBDIR}",
PublicDir: "${GITEAKAN_HTTP_PUBDIR}",
},
Gitea: GiteaConfig{},
Data: DataConfig{
DBPath: "${GENGITKAN_DATA_DBPATH}",
DBPath: "${GITEAKAN_DATA_DBPATH}",
},
}
}

View File

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

View File

@ -20,8 +20,7 @@ type Board struct {
type BoardLaneID string
type BoardLane struct {
ID BoardLaneID `json:"id"`
Title string `json:"title"`
IssueLabel string `json:"issueLabel"`
CollectRemainingIssues bool `json:"collectRemainingIssues"`
ID BoardLaneID `json:"id"`
Title string `json:"title"`
IssueLabel string `json:"issueLabel"`
}

View File

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

View File

@ -1,7 +1,7 @@
package storm
import (
"forge.cadoles.com/wpetit/gengitkan/internal/repository"
"forge.cadoles.com/wpetit/gitea-kan/internal/repository"
"github.com/asdine/storm"
"github.com/pkg/errors"
)
@ -61,18 +61,6 @@ func (r *BoardRepository) Save(board *repository.Board) 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
}

View File

@ -4,9 +4,7 @@ import (
"encoding/json"
"net/http"
"github.com/go-chi/chi"
"forge.cadoles.com/wpetit/gengitkan/internal/repository"
"forge.cadoles.com/wpetit/gitea-kan/internal/repository"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
)
@ -20,8 +18,6 @@ func serveBoards(w http.ResponseWriter, r *http.Request) {
panic(errors.Wrap(err, "could not retrieve boards list"))
}
w.Header().Add("Content-Type", "application/json")
encoder := json.NewEncoder(w)
if err := encoder.Encode(boards); err != nil {
panic(errors.Wrap(err, "could not encode boards list"))
@ -43,28 +39,8 @@ func saveBoard(w http.ResponseWriter, r *http.Request) {
panic(errors.Wrap(err, "could not save board"))
}
w.Header().Add("Content-Type", "application/json")
encoder := json.NewEncoder(w)
if err := encoder.Encode(board); err != nil {
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 (
"net/http"
"forge.cadoles.com/wpetit/gengitkan/internal/config"
"forge.cadoles.com/wpetit/gitea-kan/internal/config"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/middleware/container"
"gitlab.com/wpetit/goweb/service/session"

View File

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

View File

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

View File

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

View File

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