Compare commits

..

16 Commits

Author SHA1 Message Date
61ef77cf16 Merge branch 'develop' into dist/ubuntu/bionic/develop 2020-01-13 14:18:30 +01:00
acde7436da Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-13 15:50:00 +01:00
b3d458ebac Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-13 14:21:32 +01:00
5284633e7f Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-13 13:32:10 +01:00
5c77f22c26 Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-06 17:15:38 +01:00
204d6869ec Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-06 10:02:46 +01:00
35683a4584 Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-05 22:46:30 +01:00
64dd215038 Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-05 22:37:35 +01:00
d8192e6953 Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-05 17:09:25 +01:00
4d3175a25e Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-05 16:15:49 +01:00
ca7b8aac47 Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-05 14:44:48 +01:00
971bf25f16 Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-05 13:57:27 +01:00
a2ab012c5f Create /var/lib/gitea-kan dir 2019-12-05 13:20:19 +01:00
fb815c4d83 Define gitea-kan configuration env vars 2019-12-05 13:19:54 +01:00
88a0a5bebe Merge branch 'develop' into dist/ubuntu/bionic/develop 2019-12-05 13:12:10 +01:00
f54b579ae3 Add Debian packaging recipe 2019-12-05 13:10:02 +01:00
75 changed files with 873 additions and 11256 deletions

View File

@ -9,7 +9,6 @@
### 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
```
@ -22,7 +21,7 @@ Debug = false
[HTTP]
Address = :3000
PublicDir = ${GENGITKAN_HTTP_PUBDIR}
PublicDir = ${GITEAKAN_HTTP_PUBDIR}
[Gitea]
BaseURL = https://forge.cadoles.com
@ -37,7 +36,7 @@ Scopes = api
[Data]
DBPath = ${GENGITKAN_DATA_DBPATH}
DBPath = ${GITEAKAN_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`.

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" ]
]
}

10254
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -25,11 +25,6 @@
"@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-redux": "^7.1.7",
"@types/react-router-dom": "^5.1.5",
"@types/uuid": "^7.0.3",
"babel-loader": "^8.0.6",
"css-loader": "^1.0.1",
"extract-loader": "^3.1.0",
@ -37,20 +32,18 @@
"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",
"ts-loader": "^7.0.2",
"webpack": "^4.25.0",
"webpack-cleanup-plugin": "^0.5.1",
"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 +52,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

@ -0,0 +1,242 @@
import React from 'react';
import { Page } from '../Page';
import { connect } from 'react-redux';
import Board 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 class BoardPage extends React.Component {
state = {
newCardModalActive: false,
newCardLaneID: 0,
newCard: {
title: "",
body: "",
project: ""
}
}
constructor(props) {
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);
}
render() {
const { board } = this.props;
return (
<Page title={`${board ? (board.title + ' - ') : ''}GenGitKan`}>
<div className="container is-fluid">
{this.renderBoard()}
</div>
</Page>
);
}
renderBoard() {
const { kanboard } = this.props;
if (!kanboard) {
return <Loader></Loader>
}
return (
<div className="kanboard-container is-fullheight">
<Board disableLaneDrag={true}
renderCard={this.renderCard}
renderLaneHeader={this.renderLaneHeader}
onCardDragEnd={this.onCardDragEnd.bind(this)}>
{kanboard}
</Board>
{ this.renderNewCardModal() }
</div>
);
}
renderNewCardModal() {
const { newCardModalActive, newCardLaneID } = this.state;
const { board } = this.props;
if (!board || !newCardLaneID) 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"
value={this.state.newCard.project}
onChange={this.onNewCardProjectChange}>
<select>
{
board.projects.map((p, i) => {
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) {
return <IssueCard card={card} />;
}
renderLaneHeader(lane) {
return (
<div className="kanboard-lane">
<div className="level">
<div className="level-left">
<h3 className="level-item is-size-3">{lane.title}</h3>
</div>
<div className="level-right">
<button className="button is-light level-item is-medium"
onClick={this.onNewCardClick.bind(this, lane.id)}>
<span className="icon">
<i className="fas fa-plus" aria-hidden="true"></i>
</span>
</button>
</div>
</div>
</div>
)
}
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();
}
onNewCardClick(laneID) {
const { board } = this.props;
this.setState({
newCardModalActive: true,
newCardLaneID: laneID,
newCard: {
title: "",
body: "",
project: board.projects[0],
}
});
}
onNewCardCloseClick() {
this.setState({ newCardModalActive: false });
}
onNewCardAttrChange(attrName, evt) {
const value = evt.target.value;
this.setState(state => {
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) {
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,349 +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, fetchIssues } from '../../store/actions/issues';
import { buildKanboard, moveCard } from '../../store/actions/kanboards';
import { Loader } from '../Loader';
import { IssueCard } from './IssueCard';
import { Modal } from '../Modal';
import { fetchProjectMilestones } from '../../store/actions/projects';
export interface BoardPageProps extends DispatchProp {
board: any
kanboard: any
milestones: any
}
export class BoardPage extends React.Component<BoardPageProps> {
state = {
newCardModalActive: false,
newCardLaneID: 0,
newCard: {
title: "",
body: "",
project: ""
},
compactMode: true,
hasError: false,
selectedMilestone: "",
}
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);
}
handleMilestonesChange(e: any) {
console.log("EVT VALUE", e.target.value);
let m = (e.target as HTMLInputElement).value;
this.setState(state => ({ ...state, selectedMilestone: m }), this.requestBuildKanboard);
//this.requestBuildKanboard();
}
render() {
const { board } = this.props;
return (
<Page title={`${board ? (board.title + ' - ') : ''}GenGitKan`}>
{this.renderBoard()}
</Page>
);
}
renderBoard() {
const { kanboard, board, milestones } = 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="select">
<select onChange={this.handleMilestonesChange.bind(this)}>
<option value="">Sélectionner un jalon</option>
{
milestones.length > 0 ? milestones.map((k: any) => {
return (
<option value={k.title} key={k.id}>{k.title}</option>
)
}) : ""
}
</select>
</div>
</div>
<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;
const { selectedMilestone } = this.state;
console.log("SELECTED MILESTONE", selectedMilestone)
if (!board) return;
this.props.dispatch(fetchProjectMilestones(board.projects[0]));
this.props.dispatch(buildKanboard(board, selectedMilestone));
}
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],
milestones: state.projects.milestones
};
})(BoardPage);

View File

@ -1,19 +1,13 @@
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 { 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,
@ -26,13 +20,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,18 +32,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);
}
@ -142,15 +129,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 +179,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 +212,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 +266,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 +282,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 +297,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 +321,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 +336,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 +352,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 +366,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,
@ -436,7 +406,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

@ -0,0 +1,62 @@
import React from 'react';
const issueURLPattern = /(^https?:\/\/([^\/]))$/i;
export class IssueCard extends React.PureComponent {
render() {
const { card } = 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">
<div className="media">
{
card.issue.assignee ?
<div className="media-left">
<figure className="image is-64x64">
<img src={card.issue.assignee.avatar_url} alt="Image" />
</figure>
<small>{`@${card.issue.assignee.login}`}</small>
</div>
: null
}
<div className="media-content">
<div className="content">
<p>
<strong>{`#${card.issue.number}`}</strong>&nbsp;
{ card.issue.milestone ? <small>{`- ${card.issue.milestone.title}`}</small> : null }
<br />
<span className="is-size-6">{card.issue.title}</span>
</p>
</div>
</div>
</div>
<div className="level is-mobile" style={{marginTop:'1rem'}}>
<div className="level-left">
<small className="level-item"><a href={projectURL}>{card.project}</a></small>
</div>
<div className="level-right">
<a className="level-item" target="_blank" href={issueURL}>
<span className="icon is-small has-text-info">
<i className="fas fa-search" aria-hidden="true"></i>
</span>
</a>
</div>
</div>
</div>
</div>
);
}
}
function extractInfoFromIssueURL(issueURL) {
const pattern = /^(https?:\/\/[^\/]+)\/api\/v1\/repos\/([^\/]+)\/([^\/]+)\/.*$/;
const matches = pattern.exec(issueURL);
return {
baseURL: matches[1],
owner: matches[2],
projectName: matches[3],
};
}

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 (

View File

@ -1,17 +1,12 @@
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">
@ -45,8 +40,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">
@ -77,7 +72,7 @@ export class HomePage extends React.Component<HomePageProps> {
}
export const ConnectedHomePage = connect(function(state: any) {
export const ConnectedHomePage = connect(function(state) {
return {
boards: selectBoardByUserProjects(state.boards.byID, state.projects.byName)
};

View File

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

View File

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

View File

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

View File

@ -1,6 +1,4 @@
@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';

View File

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

View File

@ -44,12 +44,6 @@
.kanboard-lane {
margin-bottom: $size-small;
background: $white;
top: 0;
.expand {
display: none;
}
}
}
@ -58,141 +52,3 @@
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

@ -10,7 +10,7 @@ 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 };
};
@ -18,6 +18,6 @@ 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) {
export function deleteBoard(id) {
return { type: DELETE_BOARD_REQUEST, id };
};

View File

@ -2,15 +2,15 @@ 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, milestones: string) {
return { type: FETCH_ISSUES_REQUEST, project, milestones };
export function fetchIssues(project) {
return { type: FETCH_ISSUES_REQUEST, project };
};
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,7 +18,7 @@ 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 };
}
@ -26,6 +26,6 @@ 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) {
export function createIssue(project, title, body, label) {
return { type: CREATE_ISSUE_REQUEST, project, title, body, label };
};

View File

@ -2,12 +2,12 @@ export const BUILD_KANBOARD_REQUEST = "BUILD_KANBOARD_REQUEST";
export const BUILD_KANBOARD_SUCCESS = "BUILD_KANBOARD_SUCCESS";
export const BUILD_KANBOARD_FAILURE = "BUILD_KANBOARD_FAILURE";
export function buildKanboard(board: string, milestones: string) {
return { type: BUILD_KANBOARD_REQUEST, board, milestones };
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,7 @@
export const FETCH_PROJECTS_REQUEST = "FETCH_PROJECTS_REQUEST";
export const FETCH_PROJECTS_SUCCESS = "FETCH_PROJECTS_SUCCESS";
export const FETCH_PROJECTS_FAILURE = "FETCH_PROJECTS_FAILURE";
export function fetchProjects() {
return { type: FETCH_PROJECTS_REQUEST };
};

View File

@ -1,15 +0,0 @@
export const FETCH_PROJECTS_REQUEST = "FETCH_PROJECTS_REQUEST";
export const FETCH_PROJECTS_SUCCESS = "FETCH_PROJECTS_SUCCESS";
export const FETCH_PROJECTS_FAILURE = "FETCH_PROJECTS_FAILURE";
export const FETCH_PROJECT_MILESTONES_REQUEST = "FETCH_PROJECTS_MILESTONES_REQUEST";
export const FETCH_PROJECT_MILESTONES_SUCCESS = "FETCH_PROJECTS_MILESTONES_SUCCESS";
export const FETCH_PROJECT_MILESTONES_FAILURE = "FETCH_PROJECTS_MILESTONES_FAILURE";
export function fetchProjects() {
return { type: FETCH_PROJECTS_REQUEST };
};
export function fetchProjectMilestones(project: any) {
return { type: FETCH_PROJECT_MILESTONES_REQUEST, project };
};

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,7 +15,7 @@ export function boardsReducer(state = defaultState, action: any) {
}
}
function handleSaveBoardSuccess(state: any, action: any) {
function handleSaveBoardSuccess(state, action) {
const { board } = action;
return {
...state,
@ -28,8 +28,8 @@ function handleSaveBoardSuccess(state: any, action: any) {
};
}
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

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

View File

@ -5,7 +5,7 @@ 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 +16,7 @@ export function kanboardsReducer(state = defaultState, action: any) {
}
}
function handleBuildKanboardSuccess(state: any, action: any) {
function handleBuildKanboardSuccess(state, action) {
return {
...state,
byID: {
@ -28,7 +28,7 @@ function handleBuildKanboardSuccess(state: any, action: any) {
};
}
function handleMoveCard(state: any, action: any) {
function handleMoveCard(state, action) {
const {
boardID, fromLaneID,
fromPosition, toLaneID,
@ -37,22 +37,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 +67,7 @@ function handleMoveCard(state: any, action: any) {
...state.byID,
[boardID]: {
...state.byID[boardID],
columns,
lanes,
},
}
};

View File

@ -0,0 +1,27 @@
import { FETCH_PROJECTS_SUCCESS } from "../actions/projects";
export const defaultState = {
byName: {},
};
export function projectsReducer(state = defaultState, action) {
switch(action.type) {
case FETCH_PROJECTS_SUCCESS:
return handleFetchProjectsSuccess(state, action);
default:
return state;
}
}
function handleFetchProjectsSuccess(state, action) {
const projectsByName = action.projects.reduce((byName, project) => {
byName[project.full_name] = project;
return byName;
}, {});
return {
...state,
byName: {
...projectsByName,
}
};
}

View File

@ -1,39 +0,0 @@
import { FETCH_PROJECTS_SUCCESS, FETCH_PROJECT_MILESTONES_SUCCESS } from "../actions/projects";
export const defaultState = {
byName: {},
milestones: ""
};
export function projectsReducer(state = defaultState, action: any) {
switch (action.type) {
case FETCH_PROJECTS_SUCCESS:
return handleFetchProjectsSuccess(state, action);
case FETCH_PROJECT_MILESTONES_SUCCESS:
return handleFetchProjectMilestonesSuccess(state, action);
default:
return state;
}
}
function handleFetchProjectsSuccess(state: any, action: any) {
const projectsByName = action.projects.reduce((byName: any, project: any) => {
byName[project.full_name] = project;
return byName;
}, {});
return {
...state,
byName: {
...projectsByName,
}
};
}
function handleFetchProjectMilestonesSuccess(state: any, action: any) {
return {
...state,
milestones: action.milestones,
};
}

View File

@ -1,9 +1,5 @@
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, DELETE_BOARD_FAILURE, DELETE_BOARD_SUCCESS } from '../actions/boards';
import { api } from '../../util/api';
export function* fetchBoardsSaga() {
@ -19,7 +15,7 @@ export function* fetchBoardsSaga() {
yield put({ type: FETCH_BOARDS_SUCCESS, boards });
}
export function* saveBoardSaga(action: any) {
export function* saveBoardSaga(action) {
let { board } = action;
try {
@ -33,11 +29,11 @@ export function* saveBoardSaga(action: any) {
}
export function* deleteBoardSaga(action: any) {
export function* deleteBoardSaga(action) {
let { id } = action;
try {
yield call(api.deleteBoard, id)
board = yield call(api.deleteBoard, id)
} catch(error) {
yield put({ type: DELETE_BOARD_FAILURE, error });
return

View File

@ -1,11 +1,10 @@
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();
const err = action.error;
if (err instanceof GiteaUnauthorizedError) {
yield put(logout());
}
}

View File

@ -1,28 +1,23 @@
import { put, call, retry } from 'redux-saga/effects';
import {
FETCH_ISSUES_SUCCESS, FETCH_ISSUES_FAILURE,
ADD_LABEL_FAILURE, ADD_LABEL_SUCCESS,
REMOVE_LABEL_FAILURE, REMOVE_LABEL_SUCCESS,
CREATE_ISSUE_FAILURE, CREATE_ISSUE_SUCCESS
} from '../actions/issues';
import { FETCH_ISSUES_SUCCESS, FETCH_ISSUES_FAILURE, ADD_LABEL_FAILURE, ADD_LABEL_SUCCESS, REMOVE_LABEL_FAILURE, REMOVE_LABEL_SUCCESS, CREATE_ISSUE_FAILURE, CREATE_ISSUE_SUCCESS } from '../actions/issues';
import { gitea } from '../../util/gitea';
export function* fetchIssuesSaga(action: any) {
const { project, milestones } = action;
export function* fetchIssuesSaga(action) {
const { project } = action;
let issues = [];
try {
let page = 1;
while (true) {
let pageIssues = yield call(gitea.fetchIssues.bind(gitea), project, page, milestones);
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));
issues.push(...pageIssues);
page++;
}
} catch (error) {
} catch(error) {
yield put({ type: FETCH_ISSUES_FAILURE, project, error });
return;
}
@ -30,10 +25,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 !`) });
@ -42,7 +37,7 @@ export function* addLabelSaga(action: any) {
try {
yield retry(5, 250, gitea.addIssueLabel.bind(gitea), project, issueNumber, giteaLabel.id);
} catch (error) {
} catch(error) {
yield put({ type: ADD_LABEL_FAILURE, error });
return;
}
@ -50,10 +45,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 !`) });
@ -62,7 +57,7 @@ export function* removeLabelSaga(action: any) {
try {
yield retry(5, 250, gitea.removeIssueLabel.bind(gitea), project, issueNumber, giteaLabel.id);
} catch (error) {
} catch(error) {
yield put({ type: REMOVE_LABEL_FAILURE, error });
return;
}
@ -71,15 +66,20 @@ export function* removeLabelSaga(action: any) {
yield put({ type: REMOVE_LABEL_SUCCESS, project, issueNumber, label });
}
export function* createIssueSaga(action: any) {
export function* createIssueSaga(action) {
const { project, title, label, body } = 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: CREATE_ISSUE_FAILURE, error: new Error(`Label "${label}" not found !`) });
return;
}
let issue;
try {
issue = yield call(gitea.createIssue.bind(gitea), project, title, body, giteaLabel ? giteaLabel.id : null);
} catch (error) {
issue = yield call(gitea.createIssue.bind(gitea), project, title, body, giteaLabel.id);
} catch(error) {
yield put({ type: CREATE_ISSUE_FAILURE, error });
return;
}

View File

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

View File

@ -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, milestones } = action;
console.log("milestones", milestones);
let kanboard;
try {
for (let p, i = 0; (p = board.projects[i]); i++) {
const { project } = yield fetchIssues(p, "test");
yield fetchIssuesSaga({ project: project, milestones: milestones });
}
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, milestones } = 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, milestones));
}
}
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,16 @@
import { put, call } from 'redux-saga/effects';
import { FETCH_PROJECTS_SUCCESS, FETCH_PROJECTS_FAILURE } from '../actions/projects';
import { gitea } from '../../util/gitea';
export function* fetchProjectsSaga() {
let projects;
try {
projects = yield call(gitea.fetchUserProjects.bind(gitea))
} catch(error) {
yield put({ type: FETCH_PROJECTS_FAILURE, error });
return;
}
yield put({ type: FETCH_PROJECTS_SUCCESS, projects });
}

View File

@ -1,29 +0,0 @@
import { put, call } from 'redux-saga/effects';
import { FETCH_PROJECTS_SUCCESS, FETCH_PROJECTS_FAILURE, FETCH_PROJECT_MILESTONES_FAILURE, FETCH_PROJECT_MILESTONES_SUCCESS } from '../actions/projects';
import { gitea } from '../../util/gitea';
export function* fetchProjectsSaga() {
let projects;
try {
projects = yield call(gitea.fetchUserProjects.bind(gitea))
} catch (error) {
yield put({ type: FETCH_PROJECTS_FAILURE, error });
return;
}
yield put({ type: FETCH_PROJECTS_SUCCESS, projects });
}
export function* fetchProjectMilestonesSaga(action: any) {
const { project } = action;
let milestones;
try {
milestones = yield call(gitea.fetchMilestones.bind(gitea), project)
} catch (error) {
yield put({ type: FETCH_PROJECT_MILESTONES_FAILURE, error });
return;
}
yield put({ type: FETCH_PROJECT_MILESTONES_SUCCESS, milestones });
}

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

@ -4,17 +4,15 @@ import { FETCH_BOARDS_REQUEST, SAVE_BOARD_REQUEST, DELETE_BOARD_REQUEST } from '
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, FETCH_PROJECT_MILESTONES_REQUEST } from '../actions/projects';
import { fetchProjectMilestonesSaga, fetchProjectsSaga } from './projects';
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),
@ -22,7 +20,6 @@ export function* rootSaga() {
takeLatest(DELETE_BOARD_REQUEST, deleteBoardSaga),
takeLatest(FETCH_ISSUES_REQUEST, fetchIssuesSaga),
takeLatest(FETCH_PROJECTS_REQUEST, fetchProjectsSaga),
takeLatest(FETCH_PROJECT_MILESTONES_REQUEST, fetchProjectMilestonesSaga),
takeEvery(MOVE_CARD, moveCardSaga),
takeEvery(ADD_LABEL_REQUEST, addLabelSaga),
takeEvery(REMOVE_LABEL_REQUEST, removeLabelSaga),
@ -33,8 +30,8 @@ export function* rootSaga() {
]);
}
export function patternFromRegExp(re: any) {
return (action: any) => {
export function patternFromRegExp(re) {
return (action) => {
return re.test(action.type);
};
}

View File

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

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

@ -1,23 +1,15 @@
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, milestones = "") {
return fetch(`/gitea/api/v1/repos/${project}/issues?page=${page}&milestones=${milestones}`)
.then(this.assertAuthorization)
.then(this.assertOk)
.then(res => res.json())
;
}
fetchMilestones(project: any) {
return fetch(`/gitea/api/v1/repos/${project}/milestones`)
fetchIssues(project, page = 1) {
return fetch(`/gitea/api/v1/repos/${project}/issues?page=${page}`)
.then(this.assertAuthorization)
.then(this.assertOk)
.then(res => res.json())
@ -32,7 +24,7 @@ export class GiteaClient {
;
}
addIssueLabel(project: any, issueNumber: any, labelID: any) {
addIssueLabel(project, issueNumber, labelID) {
return fetch(`/gitea/api/v1/repos/${project}/issues/${issueNumber}/labels`, {
method: 'POST',
headers: {
@ -45,7 +37,7 @@ export class GiteaClient {
.then(res => res.json())
}
fetchProjectLabels(project: any) {
fetchProjectLabels(project) {
return fetch(`/gitea/api/v1/repos/${project}/labels`)
.then(this.assertAuthorization)
.then(this.assertOk)
@ -53,7 +45,7 @@ export class GiteaClient {
;
}
removeIssueLabel(project: any, issueNumber: any, labelID: any) {
removeIssueLabel(project, issueNumber, labelID) {
return fetch(`/gitea/api/v1/repos/${project}/issues/${issueNumber}/labels/${labelID}`, {
method: 'DELETE'
})
@ -61,7 +53,7 @@ export class GiteaClient {
.then(this.assertOk)
}
createIssue(project: any, title: any, body: any, labelID: any) {
createIssue(project, title, body, labelID) {
return fetch(`/gitea/api/v1/repos/${project}/issues`, {
method: 'POST',
headers: {
@ -70,7 +62,7 @@ export class GiteaClient {
body: JSON.stringify({
title,
body,
labels: labelID ? [labelID] : undefined,
labels: [labelID],
}),
})
.then(this.assertAuthorization)
@ -78,12 +70,12 @@ export class GiteaClient {
.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

@ -9,17 +9,19 @@ const env = process.env;
module.exports = {
mode: `${env.NODE_ENV ? env.NODE_ENV : 'production'}`,
entry: './src/index.tsx',
devtool: 'inline-source-map',
entry: './src/index.js',
devtool: 'source-map',
output: {
filename: '[name].[hash:8].js',
sourceMapFilename: '[name].[hash:8].map',
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,
{
@ -48,9 +50,9 @@ module.exports = {
}
}]
},{
test: /\.(t|j)sx?$/,
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loaders: ['ts-loader']
use: ['babel-loader']
}]
},
plugins: [

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

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

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"
)

View File

@ -6,7 +6,7 @@ import (
"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"
)

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"

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
}