mirror of
https://github.com/plankanban/planka.git
synced 2025-07-18 20:59:44 +02:00
feat: Implemented moving a list between boards with instant UI update. Fixed authorization for socket requests (automatic token injection). After moving a list, user is automatically switched to the target board. Added translations for the new move list action to all locale files.
This commit is contained in:
parent
18c7ff093b
commit
9c08ce51f1
39 changed files with 331 additions and 171 deletions
22
client/src/api/client.js
Normal file
22
client/src/api/client.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
const API_BASE = 'http://localhost:1337/api';
|
||||||
|
|
||||||
|
const client = {
|
||||||
|
post: async (url, data, headers = {}) => {
|
||||||
|
const isAbsolute = url.startsWith('http://') || url.startsWith('https://');
|
||||||
|
const response = await fetch(isAbsolute ? url : `${API_BASE}${url}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...headers,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default client;
|
|
@ -57,6 +57,8 @@ const deleteList = (id, headers) =>
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const moveToBoard = (id, data, headers) => socket.post(`/lists/${id}/move-to-board`, data, headers);
|
||||||
|
|
||||||
/* Event handlers */
|
/* Event handlers */
|
||||||
|
|
||||||
const makeHandleListDelete = (next) => (body) => {
|
const makeHandleListDelete = (next) => (body) => {
|
||||||
|
@ -78,4 +80,5 @@ export default {
|
||||||
clearList,
|
clearList,
|
||||||
deleteList,
|
deleteList,
|
||||||
makeHandleListDelete,
|
makeHandleListDelete,
|
||||||
|
moveToBoard,
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@ import socketIOClient from 'socket.io-client';
|
||||||
import sailsIOClient from 'sails.io.js';
|
import sailsIOClient from 'sails.io.js';
|
||||||
|
|
||||||
import Config from '../constants/Config';
|
import Config from '../constants/Config';
|
||||||
|
import { getAccessToken } from '../utils/access-token-storage';
|
||||||
|
|
||||||
const io = sailsIOClient(socketIOClient);
|
const io = sailsIOClient(socketIOClient);
|
||||||
|
|
||||||
|
@ -21,13 +22,18 @@ const { socket } = io;
|
||||||
socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
|
socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
|
||||||
|
|
||||||
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
|
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
|
||||||
socket[method.toLowerCase()] = (url, data, headers) =>
|
socket[method.toLowerCase()] = (url, data, headers = {}) => {
|
||||||
new Promise((resolve, reject) => {
|
const accessToken = getAccessToken();
|
||||||
|
const mergedHeaders = { ...headers };
|
||||||
|
if (accessToken && !mergedHeaders.Authorization) {
|
||||||
|
mergedHeaders.Authorization = `Bearer ${accessToken}`;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
socket.request(
|
socket.request(
|
||||||
{
|
{
|
||||||
method,
|
method,
|
||||||
data,
|
data,
|
||||||
headers,
|
headers: mergedHeaders,
|
||||||
url: `/api${url}`,
|
url: `/api${url}`,
|
||||||
},
|
},
|
||||||
(_, { body, error }) => {
|
(_, { body, error }) => {
|
||||||
|
@ -39,6 +45,7 @@ socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export default socket;
|
export default socket;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import PropTypes from 'prop-types';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Menu } from 'semantic-ui-react';
|
import { Menu } from 'semantic-ui-react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Popup } from '../../../lib/custom-ui';
|
import { Popup } from '../../../lib/custom-ui';
|
||||||
|
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
|
@ -19,6 +20,8 @@ import SortStep from './SortStep';
|
||||||
import SelectListTypeStep from '../SelectListTypeStep';
|
import SelectListTypeStep from '../SelectListTypeStep';
|
||||||
import ConfirmationStep from '../../common/ConfirmationStep';
|
import ConfirmationStep from '../../common/ConfirmationStep';
|
||||||
import ArchiveCardsStep from '../../cards/ArchiveCardsStep';
|
import ArchiveCardsStep from '../../cards/ArchiveCardsStep';
|
||||||
|
import BoardSelectStep from './BoardSelectStep';
|
||||||
|
import api from '../../../api/lists';
|
||||||
|
|
||||||
import styles from './ActionsStep.module.scss';
|
import styles from './ActionsStep.module.scss';
|
||||||
|
|
||||||
|
@ -28,6 +31,7 @@ const StepTypes = {
|
||||||
SORT: 'SORT',
|
SORT: 'SORT',
|
||||||
ARCHIVE_CARDS: 'ARCHIVE_CARDS',
|
ARCHIVE_CARDS: 'ARCHIVE_CARDS',
|
||||||
DELETE: 'DELETE',
|
DELETE: 'DELETE',
|
||||||
|
MOVE_TO_BOARD: 'MOVE_TO_BOARD',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
|
const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
|
||||||
|
@ -38,6 +42,7 @@ const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const [step, openStep, handleBack] = useSteps();
|
const [step, openStep, handleBack] = useSteps();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleTypeSelect = useCallback(
|
const handleTypeSelect = useCallback(
|
||||||
(type) => {
|
(type) => {
|
||||||
|
@ -84,6 +89,25 @@ const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
|
||||||
openStep(StepTypes.DELETE);
|
openStep(StepTypes.DELETE);
|
||||||
}, [openStep]);
|
}, [openStep]);
|
||||||
|
|
||||||
|
const handleMoveToBoard = useCallback(
|
||||||
|
async (targetBoardId) => {
|
||||||
|
try {
|
||||||
|
const { item: updatedList, included } = await api.moveToBoard(listId, { targetBoardId });
|
||||||
|
dispatch(entryActions.handleListUpdate(updatedList));
|
||||||
|
if (included && included.cards) {
|
||||||
|
dispatch(entryActions.handleCardsUpdate(included.cards, []));
|
||||||
|
}
|
||||||
|
sessionStorage.setItem('movedListId', listId);
|
||||||
|
onClose();
|
||||||
|
navigate(`/boards/${targetBoardId}`);
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[listId, onClose, dispatch, navigate],
|
||||||
|
);
|
||||||
|
|
||||||
if (step) {
|
if (step) {
|
||||||
switch (step.type) {
|
switch (step.type) {
|
||||||
case StepTypes.EDIT_TYPE:
|
case StepTypes.EDIT_TYPE:
|
||||||
|
@ -114,6 +138,14 @@ const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case StepTypes.MOVE_TO_BOARD:
|
||||||
|
return (
|
||||||
|
<BoardSelectStep
|
||||||
|
currentBoardId={list.boardId}
|
||||||
|
onSelect={handleMoveToBoard}
|
||||||
|
onBack={handleBack}
|
||||||
|
/>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,6 +196,9 @@ const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
|
||||||
context: 'title',
|
context: 'title',
|
||||||
})}
|
})}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
<Menu.Item className={styles.menuItem} onClick={() => openStep(StepTypes.MOVE_TO_BOARD)}>
|
||||||
|
{t('action.moveListToBoard', { context: 'title' })}
|
||||||
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Popup.Content>
|
</Popup.Content>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -14,3 +14,34 @@
|
||||||
padding-left: 14px;
|
padding-left: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.boardSelectStep {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boardButton {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 9px 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.28571429rem !important;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #234;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
background: none;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boardButton:hover,
|
||||||
|
.boardButton:focus {
|
||||||
|
background: #f4f6f8;
|
||||||
|
color: #16324a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noBoards {
|
||||||
|
color: #888;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
52
client/src/components/lists/List/BoardSelectStep.jsx
Normal file
52
client/src/components/lists/List/BoardSelectStep.jsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import PopupHeader from '../../../lib/custom-ui/components/Popup/PopupHeader';
|
||||||
|
import styles from './ActionsStep.module.scss';
|
||||||
|
import selectors from '../../../selectors';
|
||||||
|
|
||||||
|
function BoardSelectStep({ currentBoardId, onSelect, onBack, onClose }) {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const projectId = useSelector((state) => selectors.selectPath(state).projectId);
|
||||||
|
const boardIds = useSelector((state) => selectors.selectBoardIdsByProjectId(state, projectId));
|
||||||
|
const boards = useSelector((state) => boardIds.map((id) => selectors.selectBoardById(state, id)));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.boardSelectStep}>
|
||||||
|
<PopupHeader onBack={onBack} onClose={onClose}>
|
||||||
|
{t('action.moveListToBoard', { context: 'title' })}
|
||||||
|
</PopupHeader>
|
||||||
|
<div className={styles.menu}>
|
||||||
|
{boards
|
||||||
|
.filter((b) => b && b.id !== currentBoardId)
|
||||||
|
.map((board) => (
|
||||||
|
<button
|
||||||
|
key={board.id}
|
||||||
|
type="button"
|
||||||
|
className={styles.boardButton}
|
||||||
|
onClick={() => onSelect(board.id)}
|
||||||
|
>
|
||||||
|
{board.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
{boards.filter((b) => b && b.id !== currentBoardId).length === 0 && (
|
||||||
|
<div className={styles.noBoards}>{t('common.noOtherBoards')}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoardSelectStep.propTypes = {
|
||||||
|
currentBoardId: PropTypes.string.isRequired,
|
||||||
|
onSelect: PropTypes.func.isRequired,
|
||||||
|
onBack: PropTypes.func.isRequired,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
BoardSelectStep.defaultProps = {
|
||||||
|
onClose: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoardSelectStep;
|
|
@ -232,6 +232,7 @@ export default {
|
||||||
unsubscribe: 'إلغاء الاشتراك',
|
unsubscribe: 'إلغاء الاشتراك',
|
||||||
uploadNewAvatar: 'رفع صورة رمزية جديدة',
|
uploadNewAvatar: 'رفع صورة رمزية جديدة',
|
||||||
uploadNewImage: 'رفع صورة جديدة',
|
uploadNewImage: 'رفع صورة جديدة',
|
||||||
|
moveListToBoard: 'نقل القائمة إلى لوحة أخرى',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -406,6 +406,7 @@ export default {
|
||||||
unsubscribe: 'Neodebírat',
|
unsubscribe: 'Neodebírat',
|
||||||
uploadNewAvatar: 'Nahrát nový avatar',
|
uploadNewAvatar: 'Nahrát nový avatar',
|
||||||
uploadNewImage: 'Nahrát nový obrázek',
|
uploadNewImage: 'Nahrát nový obrázek',
|
||||||
|
moveListToBoard: 'Přesunout seznam na jinou nástěnku',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -434,6 +434,7 @@ export default {
|
||||||
unsubscribe: 'Opsig abonnement',
|
unsubscribe: 'Opsig abonnement',
|
||||||
uploadNewAvatar: 'Tilføj nyt profilbillede',
|
uploadNewAvatar: 'Tilføj nyt profilbillede',
|
||||||
uploadNewImage: 'Tilføj nyt billede',
|
uploadNewImage: 'Tilføj nyt billede',
|
||||||
|
moveListToBoard: 'Flyt liste til anden tavle',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -423,6 +423,7 @@ export default {
|
||||||
unsubscribe: 'De-abonnieren',
|
unsubscribe: 'De-abonnieren',
|
||||||
uploadNewAvatar: 'Neuen Avatar hochladen',
|
uploadNewAvatar: 'Neuen Avatar hochladen',
|
||||||
uploadNewImage: 'Neues Bild hochladen',
|
uploadNewImage: 'Neues Bild hochladen',
|
||||||
|
moveListToBoard: 'Liste auf andere Arbeitsbereich verschieben',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -450,6 +450,7 @@ export default {
|
||||||
unsubscribe: 'Απεγγραφή',
|
unsubscribe: 'Απεγγραφή',
|
||||||
uploadNewAvatar: 'Μεταφόρτωση νέου avatar',
|
uploadNewAvatar: 'Μεταφόρτωση νέου avatar',
|
||||||
uploadNewImage: 'Μεταφόρτωση νέας εικόνας',
|
uploadNewImage: 'Μεταφόρτωση νέας εικόνας',
|
||||||
|
moveListToBoard: 'Μετακίνηση λίστας σε άλλο πίνακα',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -431,6 +431,7 @@ export default {
|
||||||
unsubscribe: 'Unsubscribe',
|
unsubscribe: 'Unsubscribe',
|
||||||
uploadNewAvatar: 'Upload new avatar',
|
uploadNewAvatar: 'Upload new avatar',
|
||||||
uploadNewImage: 'Upload new image',
|
uploadNewImage: 'Upload new image',
|
||||||
|
moveListToBoard: 'Move list to another board',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -426,6 +426,7 @@ export default {
|
||||||
unsubscribe: 'Unsubscribe',
|
unsubscribe: 'Unsubscribe',
|
||||||
uploadNewAvatar: 'Upload new avatar',
|
uploadNewAvatar: 'Upload new avatar',
|
||||||
uploadNewImage: 'Upload new image',
|
uploadNewImage: 'Upload new image',
|
||||||
|
moveListToBoard: 'Move list to another board',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -423,6 +423,7 @@ export default {
|
||||||
unsubscribe: 'Desuscribirse',
|
unsubscribe: 'Desuscribirse',
|
||||||
uploadNewAvatar: 'Subir un nuevo avatar',
|
uploadNewAvatar: 'Subir un nuevo avatar',
|
||||||
uploadNewImage: 'Subir una nueva imagen',
|
uploadNewImage: 'Subir una nueva imagen',
|
||||||
|
moveListToBoard: 'Mover lista a otro tablero',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -234,6 +234,7 @@ export default {
|
||||||
unsubscribe: 'لغو اشتراک',
|
unsubscribe: 'لغو اشتراک',
|
||||||
uploadNewAvatar: 'آپلود آواتار جدید',
|
uploadNewAvatar: 'آپلود آواتار جدید',
|
||||||
uploadNewImage: 'آپلود تصویر جدید',
|
uploadNewImage: 'آپلود تصویر جدید',
|
||||||
|
moveListToBoard: 'انتقال لیست به برد دیگر',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -432,6 +432,7 @@ export default {
|
||||||
unsubscribe: 'Peru tilaus',
|
unsubscribe: 'Peru tilaus',
|
||||||
uploadNewAvatar: 'Lataa uusi avatar',
|
uploadNewAvatar: 'Lataa uusi avatar',
|
||||||
uploadNewImage: 'Lataa uusi kuva',
|
uploadNewImage: 'Lataa uusi kuva',
|
||||||
|
moveListToBoard: 'Siirrä lista toiselle taululle',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -239,6 +239,7 @@ export default {
|
||||||
unsubscribe: 'Se désabonner',
|
unsubscribe: 'Se désabonner',
|
||||||
uploadNewAvatar: 'Télécharger un nouvel avatar',
|
uploadNewAvatar: 'Télécharger un nouvel avatar',
|
||||||
uploadNewImage: 'Télécharger une nouvelle image',
|
uploadNewImage: 'Télécharger une nouvelle image',
|
||||||
|
moveListToBoard: 'Déplacer la liste vers un autre tableau',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -235,6 +235,7 @@ export default {
|
||||||
unsubscribe: 'Leiratkozás',
|
unsubscribe: 'Leiratkozás',
|
||||||
uploadNewAvatar: 'Új avatar feltöltése',
|
uploadNewAvatar: 'Új avatar feltöltése',
|
||||||
uploadNewImage: 'Új kép feltöltése',
|
uploadNewImage: 'Új kép feltöltése',
|
||||||
|
moveListToBoard: 'Lista áthelyezése másik táblára',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -227,6 +227,7 @@ export default {
|
||||||
unsubscribe: 'Berhenti berlangganan',
|
unsubscribe: 'Berhenti berlangganan',
|
||||||
uploadNewAvatar: 'Unggah avatar baru',
|
uploadNewAvatar: 'Unggah avatar baru',
|
||||||
uploadNewImage: 'Unggah gambar baru',
|
uploadNewImage: 'Unggah gambar baru',
|
||||||
|
moveListToBoard: 'Pindahkan daftar ke papan lain',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -426,6 +426,7 @@ export default {
|
||||||
unsubscribe: 'Annulla iscrizione',
|
unsubscribe: 'Annulla iscrizione',
|
||||||
uploadNewAvatar: 'Carica nuovo avatar',
|
uploadNewAvatar: 'Carica nuovo avatar',
|
||||||
uploadNewImage: 'Carica nuova immagine',
|
uploadNewImage: 'Carica nuova immagine',
|
||||||
|
moveListToBoard: 'Muovi lista a un altra bacheca',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -224,6 +224,7 @@ export default {
|
||||||
unsubscribe: '購読解除',
|
unsubscribe: '購読解除',
|
||||||
uploadNewAvatar: '新しいアバターをアップロード',
|
uploadNewAvatar: '新しいアバターをアップロード',
|
||||||
uploadNewImage: '新しい画像をアップロード',
|
uploadNewImage: '新しい画像をアップロード',
|
||||||
|
moveListToBoard: 'リストを別のボードに移動',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -234,6 +234,7 @@ export default {
|
||||||
unsubscribe: '구독 취소',
|
unsubscribe: '구독 취소',
|
||||||
uploadNewAvatar: '새 아바타 업로드',
|
uploadNewAvatar: '새 아바타 업로드',
|
||||||
uploadNewImage: '새 이미지 업로드',
|
uploadNewImage: '새 이미지 업로드',
|
||||||
|
moveListToBoard: '목록을 다른 보드로 이동',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -225,6 +225,7 @@ export default {
|
||||||
unsubscribe: 'Afmelden',
|
unsubscribe: 'Afmelden',
|
||||||
uploadNewAvatar: 'Nieuwe avatar uploaden',
|
uploadNewAvatar: 'Nieuwe avatar uploaden',
|
||||||
uploadNewImage: 'Nieuwe afbeelding uploaden',
|
uploadNewImage: 'Nieuwe afbeelding uploaden',
|
||||||
|
moveListToBoard: 'Lijst verplaatsen naar ander bord',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -406,6 +406,7 @@ export default {
|
||||||
unsubscribe: 'Odsubskrybuj',
|
unsubscribe: 'Odsubskrybuj',
|
||||||
uploadNewAvatar: 'Wgraj nowy awatar',
|
uploadNewAvatar: 'Wgraj nowy awatar',
|
||||||
uploadNewImage: 'Wgraj nowy obraz',
|
uploadNewImage: 'Wgraj nowy obraz',
|
||||||
|
moveListToBoard: 'Przenieś listę na inną tablicę',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -225,6 +225,7 @@ export default {
|
||||||
unsubscribe: 'Cancelar inscrição',
|
unsubscribe: 'Cancelar inscrição',
|
||||||
uploadNewAvatar: 'Enviar novo avatar',
|
uploadNewAvatar: 'Enviar novo avatar',
|
||||||
uploadNewImage: 'Enviar nova imagem',
|
uploadNewImage: 'Enviar nova imagem',
|
||||||
|
moveListToBoard: 'Mover lista para outro quadro',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -412,6 +412,7 @@ export default {
|
||||||
unsubscribe: 'Отписаться',
|
unsubscribe: 'Отписаться',
|
||||||
uploadNewAvatar: 'Загрузить новый аватар',
|
uploadNewAvatar: 'Загрузить новый аватар',
|
||||||
uploadNewImage: 'Загрузить новое изображение',
|
uploadNewImage: 'Загрузить новое изображение',
|
||||||
|
moveListToBoard: 'Переместить в другую доску',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -206,6 +206,7 @@ export default {
|
||||||
unsubscribe: 'Neodoberať',
|
unsubscribe: 'Neodoberať',
|
||||||
uploadNewAvatar: 'Nahrať nový avatar',
|
uploadNewAvatar: 'Nahrať nový avatar',
|
||||||
uploadNewImage: 'Nahrať nový obrázok',
|
uploadNewImage: 'Nahrať nový obrázok',
|
||||||
|
moveListToBoard: 'Presunúť zoznam na inú tabuľu',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -233,6 +233,7 @@ export default {
|
||||||
unsubscribe: 'Укини претплату',
|
unsubscribe: 'Укини претплату',
|
||||||
uploadNewAvatar: 'Постави нови аватар',
|
uploadNewAvatar: 'Постави нови аватар',
|
||||||
uploadNewImage: 'Постави нову слику',
|
uploadNewImage: 'Постави нову слику',
|
||||||
|
moveListToBoard: 'Премести на другу таблу',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
import dateFns from 'date-fns/locale/sr-Latn';
|
import dateFns from 'date-fns/locale/sr-Latn';
|
||||||
import timeAgo from 'javascript-time-ago/locale/sr-Latn';
|
import timeAgo from 'javascript-time-ago/locale/sr-Latn';
|
||||||
|
|
||||||
import markdownEditor from './markdown-editor.json';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
dateFns,
|
dateFns,
|
||||||
timeAgo,
|
timeAgo,
|
||||||
markdownEditor,
|
|
||||||
|
|
||||||
format: {
|
format: {
|
||||||
date: 'd.M.yyyy.',
|
date: 'd.M.yyyy.',
|
||||||
|
@ -236,6 +233,7 @@ export default {
|
||||||
unsubscribe: 'Ukini pretplatu',
|
unsubscribe: 'Ukini pretplatu',
|
||||||
uploadNewAvatar: 'Postavi novi avatar',
|
uploadNewAvatar: 'Postavi novi avatar',
|
||||||
uploadNewImage: 'Postavi novu sliku',
|
uploadNewImage: 'Postavi novu sliku',
|
||||||
|
moveListToBoard: 'Premesti spisak na drugu tablu',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
{
|
|
||||||
"action-previews": {
|
|
||||||
"text": "Ovo je tekst bez naslova.\nI naslov i tekst\nmogu biti istaknuti podebljano, kurzivom, bojom,\nprecrtan i podvučen.",
|
|
||||||
"text-with-head": "Ovo je tekst sa naslovom.\nI naslov i tekst\nmogu biti istaknuti podebljano, kurzivom, bojom,\nprecrtan i podvučen.",
|
|
||||||
"heading": "Naslov"
|
|
||||||
},
|
|
||||||
"bundle": {
|
|
||||||
"error-title": "Greška u markdown editoru",
|
|
||||||
"settings_wysiwyg": "Vizuelni editor (wysiwyg)",
|
|
||||||
"settings_markup": "Markdown oznake",
|
|
||||||
"markup_placeholder": "Unesite markdown oznake..."
|
|
||||||
},
|
|
||||||
"codeblock": {
|
|
||||||
"remove": "Ukloni",
|
|
||||||
"empty_option": "Nema pronađenih podudaranja"
|
|
||||||
},
|
|
||||||
"common": {
|
|
||||||
"delete": "Ukloni",
|
|
||||||
"edit": "Uredi",
|
|
||||||
"toolbar_action_disabled": "Neusaglašen element oznake"
|
|
||||||
},
|
|
||||||
"forms": {
|
|
||||||
"common_action_cancel": "Otkaži",
|
|
||||||
"common_action_submit": "Pošalji",
|
|
||||||
"common_action_upload": "Izaberi",
|
|
||||||
"common_tab_attach": "Dodaj sa uređaja",
|
|
||||||
"common_tab_link": "Dodaj preko linka",
|
|
||||||
"common_link": "Link",
|
|
||||||
"common_sizes": "Veličina, px",
|
|
||||||
"image_name": "Naslov",
|
|
||||||
"image_link_href": "Link slike",
|
|
||||||
"image_link_href_help": "Adresa na koju vodi link slike.",
|
|
||||||
"image_alt": "Alternativni tekst",
|
|
||||||
"image_alt_help": "Alternativni tekst se prikazuje ako slika ne može biti učitana.",
|
|
||||||
"image_upload_help": "JPEG, GIF ili PNG slika do 1 MB.",
|
|
||||||
"image_upload_failed": "Neuspešno dodavanje slike",
|
|
||||||
"image_size_width": "Širina",
|
|
||||||
"image_size_height": "Visina",
|
|
||||||
"link_url_help": "Adresa na koju vodi link.",
|
|
||||||
"link_text": "Tekst linka",
|
|
||||||
"link_text_help": "Tekst koji se prikazuje kao link.",
|
|
||||||
"link_open_help": "Otvori link u novom tabu"
|
|
||||||
},
|
|
||||||
"md-hints": {
|
|
||||||
"header_title": "Zaglavlje",
|
|
||||||
"header_hint": "# Vaš tekst",
|
|
||||||
"italic_title": "Kurziv",
|
|
||||||
"italic_hint": "_Vaš tekst_",
|
|
||||||
"bold_title": "Podebljano",
|
|
||||||
"bold_hint": "**Vaš tekst**",
|
|
||||||
"strikethrough_title": "Precrtano",
|
|
||||||
"strikethrough_hint": "~~Vaš tekst~~",
|
|
||||||
"blockquote_title": "Citat",
|
|
||||||
"blockquote_hint": "> Vaš tekst",
|
|
||||||
"code_title": "Kod",
|
|
||||||
"code_hint": "```Vaš tekst```",
|
|
||||||
"link_title": "Link",
|
|
||||||
"link_hint": "[Vaš tekst](url)",
|
|
||||||
"image_title": "Slika",
|
|
||||||
"image_hint": "",
|
|
||||||
"list_title": "Stavka liste",
|
|
||||||
"list_hint": "- Vaš tekst",
|
|
||||||
"numbered-list_title": "Numerisana lista",
|
|
||||||
"numbered-list_hint": "1. Vaš tekst",
|
|
||||||
"documentation": "Dokumentacija",
|
|
||||||
"documentation_link": "https://diplodoc.com/docs/sr/syntax/"
|
|
||||||
},
|
|
||||||
"menubar": {
|
|
||||||
"bold": "Podebljano",
|
|
||||||
"code": "Kod",
|
|
||||||
"code_inline": "Inline kod",
|
|
||||||
"codeblock": "Blok koda",
|
|
||||||
"colorify": "Boja teksta",
|
|
||||||
"colorify__color_blue": "Plava",
|
|
||||||
"colorify__color_default": "Podrazumevano",
|
|
||||||
"colorify__color_gray": "Siva",
|
|
||||||
"colorify__color_green": "Zelena",
|
|
||||||
"colorify__color_orange": "Narandžasta",
|
|
||||||
"colorify__color_red": "Crvena",
|
|
||||||
"colorify__color_violet": "Ljubičasta",
|
|
||||||
"colorify__color_yellow": "Žuta",
|
|
||||||
"colorify__group_text": "Tekst",
|
|
||||||
"cut": "Iseci",
|
|
||||||
"emoji": "Emodži",
|
|
||||||
"emoji__hint": "Emodžiji se mogu dodati u WYSIWYG ili ručno preko oznaka",
|
|
||||||
"heading": "Zaglavlje",
|
|
||||||
"heading1": "Zaglavlje 1",
|
|
||||||
"heading2": "Zaglavlje 2",
|
|
||||||
"heading3": "Zaglavlje 3",
|
|
||||||
"heading4": "Zaglavlje 4",
|
|
||||||
"heading5": "Zaglavlje 5",
|
|
||||||
"heading6": "Zaglavlje 6",
|
|
||||||
"hrule": "Razdvajač",
|
|
||||||
"image": "Slika",
|
|
||||||
"italic": "Kurziv",
|
|
||||||
"link": "Link",
|
|
||||||
"list": "Lista",
|
|
||||||
"list__action_lift": "Podigni stavku",
|
|
||||||
"list__action_sink": "Spusti stavku",
|
|
||||||
"list_action_disabled": "Suprotno logici liste",
|
|
||||||
"mark": "Označeno",
|
|
||||||
"mono": "Monospace",
|
|
||||||
"more_action": "Više radnji",
|
|
||||||
"note": "Beleška",
|
|
||||||
"olist": "Numerisana lista",
|
|
||||||
"quote": "Citat",
|
|
||||||
"redo": "Ponovi",
|
|
||||||
"strike": "Precrtano",
|
|
||||||
"table": "Tabela",
|
|
||||||
"text": "Tekst",
|
|
||||||
"ulist": "Markirana lista",
|
|
||||||
"underline": "Podvučeno",
|
|
||||||
"undo": "Opozovi"
|
|
||||||
},
|
|
||||||
"placeholder": {
|
|
||||||
"doc_empty": "Ukucajte / za korišćenje komandi...",
|
|
||||||
"checkbox": "Unesite opis zadatka...",
|
|
||||||
"deflist_term": "Termin",
|
|
||||||
"deflist_desc": "Opis definicije",
|
|
||||||
"heading": "Zaglavlje",
|
|
||||||
"cut_title": "Naslov",
|
|
||||||
"cut_content": "Sadržaj koji se prikazuje na klik",
|
|
||||||
"note_title": "Naslov",
|
|
||||||
"note_content": "Sadržaj beleške",
|
|
||||||
"table_cell": "Sadržaj ćelije",
|
|
||||||
"select_filter": "Pretraži jezike..."
|
|
||||||
},
|
|
||||||
"search": {
|
|
||||||
"label_case-sensitive": "Razlikuj velika/mala slova",
|
|
||||||
"label_whole-word": "Cela reč",
|
|
||||||
"title": "Pretraži u kodu"
|
|
||||||
},
|
|
||||||
"suggest": {
|
|
||||||
"empty-msg": "Nije pronađeno"
|
|
||||||
},
|
|
||||||
"widgets": {
|
|
||||||
"image": "Dodaj sliku",
|
|
||||||
"link": "Dodaj link"
|
|
||||||
},
|
|
||||||
"yfm-note": {
|
|
||||||
"info": "Beleška",
|
|
||||||
"tip": "Savet",
|
|
||||||
"warning": "Upozorenje",
|
|
||||||
"alert": "Alarm",
|
|
||||||
"remove": "Ukloni"
|
|
||||||
},
|
|
||||||
"yfm-table": {
|
|
||||||
"column.add.before": "Dodaj kolonu pre",
|
|
||||||
"column.add.after": "Dodaj kolonu posle",
|
|
||||||
"column.remove": "Ukloni kolonu",
|
|
||||||
"row.add.before": "Dodaj red pre",
|
|
||||||
"row.add.after": "Dodaj red posle",
|
|
||||||
"row.remove": "Ukloni red",
|
|
||||||
"table.remove": "Ukloni tabelu",
|
|
||||||
"table.menu.cell.align.left": "Poravnaj sadržaj ćelije levo",
|
|
||||||
"table.menu.cell.align.right": "Poravnaj sadržaj ćelije desno",
|
|
||||||
"table.menu.cell.align.center": "Centriraj sadržaj ćelije",
|
|
||||||
"table.menu.row.add": "Dodaj red posle",
|
|
||||||
"table.menu.row.remove": "Ukloni red",
|
|
||||||
"table.menu.column.add": "Dodaj kolonu posle",
|
|
||||||
"table.menu.column.remove": "Ukloni kolonu",
|
|
||||||
"table.menu.convert.yfm": "Konvertuj u YFM tabelu",
|
|
||||||
"table.menu.table.remove": "Ukloni tabelu"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -207,6 +207,7 @@ export default {
|
||||||
unsubscribe: 'Avprenumerera',
|
unsubscribe: 'Avprenumerera',
|
||||||
uploadNewAvatar: 'Ladda upp ny avatar',
|
uploadNewAvatar: 'Ladda upp ny avatar',
|
||||||
uploadNewImage: 'Ladda upp ny bild',
|
uploadNewImage: 'Ladda upp ny bild',
|
||||||
|
moveListToBoard: 'Flytta lista till annan tavla',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -207,6 +207,7 @@ export default {
|
||||||
unsubscribe: 'Abonelikten çık',
|
unsubscribe: 'Abonelikten çık',
|
||||||
uploadNewAvatar: 'Yeni avatar yükle',
|
uploadNewAvatar: 'Yeni avatar yükle',
|
||||||
uploadNewImage: 'Yeni resim yükle',
|
uploadNewImage: 'Yeni resim yükle',
|
||||||
|
moveListToBoard: 'Listeyi başka bir panoya taşı',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -408,6 +408,7 @@ export default {
|
||||||
unsubscribe: 'Відписатися',
|
unsubscribe: 'Відписатися',
|
||||||
uploadNewAvatar: 'Завантажити новий аватар',
|
uploadNewAvatar: 'Завантажити новий аватар',
|
||||||
uploadNewImage: 'Завантажити нове зображення',
|
uploadNewImage: 'Завантажити нове зображення',
|
||||||
|
moveListToBoard: 'Перемістити список на іншу дошку',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -203,6 +203,7 @@ export default {
|
||||||
unsubscribe: 'Obunani bekor qilish',
|
unsubscribe: 'Obunani bekor qilish',
|
||||||
uploadNewAvatar: 'Yangi avatar yuklash',
|
uploadNewAvatar: 'Yangi avatar yuklash',
|
||||||
uploadNewImage: 'Yangi rasm yuklash',
|
uploadNewImage: 'Yangi rasm yuklash',
|
||||||
|
moveListToBoard: "Ro'yxatni boshqa doskaga ko'chirish",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -220,6 +220,7 @@ export default {
|
||||||
unsubscribe: '取消关注',
|
unsubscribe: '取消关注',
|
||||||
uploadNewAvatar: '上传新头像',
|
uploadNewAvatar: '上传新头像',
|
||||||
uploadNewImage: '上传图片',
|
uploadNewImage: '上传图片',
|
||||||
|
moveListToBoard: '移动列表到另一个面板',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -220,6 +220,7 @@ export default {
|
||||||
unsubscribe: '取消訂閱',
|
unsubscribe: '取消訂閱',
|
||||||
uploadNewAvatar: '上傳新頭像',
|
uploadNewAvatar: '上傳新頭像',
|
||||||
uploadNewImage: '上傳圖片',
|
uploadNewImage: '上傳圖片',
|
||||||
|
moveListToBoard: '移動列表到另一個面板',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
82
server/api/controllers/lists/move-to-board.js
Normal file
82
server/api/controllers/lists/move-to-board.js
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
const { idInput } = require('../../../utils/inputs');
|
||||||
|
|
||||||
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
|
LIST_NOT_FOUND: {
|
||||||
|
listNotFound: 'List not found',
|
||||||
|
},
|
||||||
|
BOARD_NOT_FOUND: {
|
||||||
|
boardNotFound: 'Board not found',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
inputs: {
|
||||||
|
id: {
|
||||||
|
...idInput,
|
||||||
|
required: true, // listId
|
||||||
|
},
|
||||||
|
targetBoardId: {
|
||||||
|
...idInput,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
|
listNotFound: {
|
||||||
|
responseType: 'notFound',
|
||||||
|
},
|
||||||
|
boardNotFound: {
|
||||||
|
responseType: 'notFound',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
async fn(inputs) {
|
||||||
|
const { currentUser } = this.req;
|
||||||
|
|
||||||
|
const { list, board: sourceBoard } = await sails.helpers.lists
|
||||||
|
.getPathToProjectById(inputs.id)
|
||||||
|
.intercept('pathNotFound', () => Errors.LIST_NOT_FOUND);
|
||||||
|
|
||||||
|
const targetBoard = await Board.qm.getOneById(inputs.targetBoardId);
|
||||||
|
if (!targetBoard) {
|
||||||
|
throw Errors.BOARD_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(
|
||||||
|
sourceBoard.id,
|
||||||
|
currentUser.id,
|
||||||
|
);
|
||||||
|
const targetMembership = await BoardMembership.qm.getOneByBoardIdAndUserId(
|
||||||
|
targetBoard.id,
|
||||||
|
currentUser.id,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!sourceMembership ||
|
||||||
|
!targetMembership ||
|
||||||
|
sourceMembership.role !== BoardMembership.Roles.EDITOR ||
|
||||||
|
targetMembership.role !== BoardMembership.Roles.EDITOR
|
||||||
|
) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { updatedList, updatedCards } = await sails.helpers.lists.moveToBoard.with({
|
||||||
|
list,
|
||||||
|
targetBoard,
|
||||||
|
actorUser: currentUser,
|
||||||
|
request: this.req,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
item: updatedList,
|
||||||
|
included: {
|
||||||
|
cards: updatedCards,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
66
server/api/helpers/lists/move-to-board.js
Normal file
66
server/api/helpers/lists/move-to-board.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
module.exports = {
|
||||||
|
inputs: {
|
||||||
|
list: {
|
||||||
|
type: 'ref',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
targetBoard: {
|
||||||
|
type: 'ref',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
actorUser: {
|
||||||
|
type: 'ref',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
type: 'ref',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
async fn(inputs) {
|
||||||
|
const updatedList = await List.updateOne(
|
||||||
|
{ id: inputs.list.id },
|
||||||
|
{ boardId: inputs.targetBoard.id },
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedCards = await Card.update(
|
||||||
|
{ listId: inputs.list.id },
|
||||||
|
{ boardId: inputs.targetBoard.id },
|
||||||
|
).fetch();
|
||||||
|
|
||||||
|
const migrateLabelsPromises = updatedCards.map(async (card) => {
|
||||||
|
const cardLabels = await CardLabel.find({ cardId: card.id });
|
||||||
|
return Promise.all(
|
||||||
|
cardLabels.map(async (cardLabel) => {
|
||||||
|
const oldLabel = await Label.findOne({ id: cardLabel.labelId });
|
||||||
|
if (!oldLabel) return;
|
||||||
|
let newLabel = await Label.findOne({
|
||||||
|
boardId: inputs.targetBoard.id,
|
||||||
|
name: oldLabel.name,
|
||||||
|
color: oldLabel.color,
|
||||||
|
});
|
||||||
|
if (!newLabel) {
|
||||||
|
const maxPosArr = await Label.find({ boardId: inputs.targetBoard.id })
|
||||||
|
.sort('position DESC')
|
||||||
|
.limit(1);
|
||||||
|
const maxPos = maxPosArr.length > 0 ? maxPosArr[0].position : 0;
|
||||||
|
newLabel = await Label.create({
|
||||||
|
boardId: inputs.targetBoard.id,
|
||||||
|
name: oldLabel.name,
|
||||||
|
color: oldLabel.color,
|
||||||
|
position: maxPos + 65536,
|
||||||
|
}).fetch();
|
||||||
|
}
|
||||||
|
await CardLabel.destroy({ cardId: card.id, labelId: cardLabel.labelId });
|
||||||
|
await CardLabel.create({ cardId: card.id, labelId: newLabel.id });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await Promise.all(migrateLabelsPromises);
|
||||||
|
|
||||||
|
return {
|
||||||
|
updatedList,
|
||||||
|
updatedCards,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
|
@ -128,6 +128,7 @@ module.exports.routes = {
|
||||||
'DELETE /api/cards/:cardId/card-labels/labelId::labelId': 'card-labels/delete',
|
'DELETE /api/cards/:cardId/card-labels/labelId::labelId': 'card-labels/delete',
|
||||||
|
|
||||||
'POST /api/cards/:cardId/task-lists': 'task-lists/create',
|
'POST /api/cards/:cardId/task-lists': 'task-lists/create',
|
||||||
|
'POST /api/lists/:id/move-to-board': 'lists/move-to-board',
|
||||||
'GET /api/task-lists/:id': 'task-lists/show',
|
'GET /api/task-lists/:id': 'task-lists/show',
|
||||||
'PATCH /api/task-lists/:id': 'task-lists/update',
|
'PATCH /api/task-lists/:id': 'task-lists/update',
|
||||||
'DELETE /api/task-lists/:id': 'task-lists/delete',
|
'DELETE /api/task-lists/:id': 'task-lists/delete',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue