diff --git a/client/src/api/client.js b/client/src/api/client.js
new file mode 100644
index 00000000..04dae615
--- /dev/null
+++ b/client/src/api/client.js
@@ -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;
diff --git a/client/src/api/lists.js b/client/src/api/lists.js
index 228fabbe..d342d969 100755
--- a/client/src/api/lists.js
+++ b/client/src/api/lists.js
@@ -57,6 +57,8 @@ const deleteList = (id, headers) =>
},
}));
+const moveToBoard = (id, data, headers) => socket.post(`/lists/${id}/move-to-board`, data, headers);
+
/* Event handlers */
const makeHandleListDelete = (next) => (body) => {
@@ -78,4 +80,5 @@ export default {
clearList,
deleteList,
makeHandleListDelete,
+ moveToBoard,
};
diff --git a/client/src/api/socket.js b/client/src/api/socket.js
index f3a4dd9f..7478d8c9 100755
--- a/client/src/api/socket.js
+++ b/client/src/api/socket.js
@@ -7,6 +7,7 @@ import socketIOClient from 'socket.io-client';
import sailsIOClient from 'sails.io.js';
import Config from '../constants/Config';
+import { getAccessToken } from '../utils/access-token-storage';
const io = sailsIOClient(socketIOClient);
@@ -21,13 +22,18 @@ const { socket } = io;
socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
- socket[method.toLowerCase()] = (url, data, headers) =>
- new Promise((resolve, reject) => {
+ socket[method.toLowerCase()] = (url, data, headers = {}) => {
+ const accessToken = getAccessToken();
+ const mergedHeaders = { ...headers };
+ if (accessToken && !mergedHeaders.Authorization) {
+ mergedHeaders.Authorization = `Bearer ${accessToken}`;
+ }
+ return new Promise((resolve, reject) => {
socket.request(
{
method,
data,
- headers,
+ headers: mergedHeaders,
url: `/api${url}`,
},
(_, { body, error }) => {
@@ -39,6 +45,7 @@ socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
},
);
});
+ };
});
export default socket;
diff --git a/client/src/components/lists/List/ActionsStep.jsx b/client/src/components/lists/List/ActionsStep.jsx
index 66b63c3c..88b6bf39 100755
--- a/client/src/components/lists/List/ActionsStep.jsx
+++ b/client/src/components/lists/List/ActionsStep.jsx
@@ -8,6 +8,7 @@ import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Menu } from 'semantic-ui-react';
+import { useNavigate } from 'react-router-dom';
import { Popup } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
@@ -19,6 +20,8 @@ import SortStep from './SortStep';
import SelectListTypeStep from '../SelectListTypeStep';
import ConfirmationStep from '../../common/ConfirmationStep';
import ArchiveCardsStep from '../../cards/ArchiveCardsStep';
+import BoardSelectStep from './BoardSelectStep';
+import api from '../../../api/lists';
import styles from './ActionsStep.module.scss';
@@ -28,6 +31,7 @@ const StepTypes = {
SORT: 'SORT',
ARCHIVE_CARDS: 'ARCHIVE_CARDS',
DELETE: 'DELETE',
+ MOVE_TO_BOARD: 'MOVE_TO_BOARD',
};
const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
@@ -38,6 +42,7 @@ const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
const dispatch = useDispatch();
const [t] = useTranslation();
const [step, openStep, handleBack] = useSteps();
+ const navigate = useNavigate();
const handleTypeSelect = useCallback(
(type) => {
@@ -84,6 +89,25 @@ const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
openStep(StepTypes.DELETE);
}, [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) {
switch (step.type) {
case StepTypes.EDIT_TYPE:
@@ -114,6 +138,14 @@ const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
onBack={handleBack}
/>
);
+ case StepTypes.MOVE_TO_BOARD:
+ return (
+
+ );
default:
}
}
@@ -164,6 +196,9 @@ const ActionsStep = React.memo(({ listId, onNameEdit, onCardAdd, onClose }) => {
context: 'title',
})}
+
openStep(StepTypes.MOVE_TO_BOARD)}>
+ {t('action.moveListToBoard', { context: 'title' })}
+
>
diff --git a/client/src/components/lists/List/ActionsStep.module.scss b/client/src/components/lists/List/ActionsStep.module.scss
index 10966717..4b635b79 100644
--- a/client/src/components/lists/List/ActionsStep.module.scss
+++ b/client/src/components/lists/List/ActionsStep.module.scss
@@ -14,3 +14,34 @@
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;
+}
diff --git a/client/src/components/lists/List/BoardSelectStep.jsx b/client/src/components/lists/List/BoardSelectStep.jsx
new file mode 100644
index 00000000..40c99dc5
--- /dev/null
+++ b/client/src/components/lists/List/BoardSelectStep.jsx
@@ -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 (
+
+
+ {t('action.moveListToBoard', { context: 'title' })}
+
+
+ {boards
+ .filter((b) => b && b.id !== currentBoardId)
+ .map((board) => (
+
+ ))}
+ {boards.filter((b) => b && b.id !== currentBoardId).length === 0 && (
+
{t('common.noOtherBoards')}
+ )}
+
+
+ );
+}
+
+BoardSelectStep.propTypes = {
+ currentBoardId: PropTypes.string.isRequired,
+ onSelect: PropTypes.func.isRequired,
+ onBack: PropTypes.func.isRequired,
+ onClose: PropTypes.func,
+};
+
+BoardSelectStep.defaultProps = {
+ onClose: undefined,
+};
+
+export default BoardSelectStep;
diff --git a/client/src/locales/ar-YE/core.js b/client/src/locales/ar-YE/core.js
index 560742a3..b9e027e2 100644
--- a/client/src/locales/ar-YE/core.js
+++ b/client/src/locales/ar-YE/core.js
@@ -232,6 +232,7 @@ export default {
unsubscribe: 'إلغاء الاشتراك',
uploadNewAvatar: 'رفع صورة رمزية جديدة',
uploadNewImage: 'رفع صورة جديدة',
+ moveListToBoard: 'نقل القائمة إلى لوحة أخرى',
},
},
};
diff --git a/client/src/locales/cs-CZ/core.js b/client/src/locales/cs-CZ/core.js
index 4461ac75..2866e7a8 100644
--- a/client/src/locales/cs-CZ/core.js
+++ b/client/src/locales/cs-CZ/core.js
@@ -406,6 +406,7 @@ export default {
unsubscribe: 'Neodebírat',
uploadNewAvatar: 'Nahrát nový avatar',
uploadNewImage: 'Nahrát nový obrázek',
+ moveListToBoard: 'Přesunout seznam na jinou nástěnku',
},
},
};
diff --git a/client/src/locales/da-DK/core.js b/client/src/locales/da-DK/core.js
index 3e7c5c2c..b2cd9f6c 100644
--- a/client/src/locales/da-DK/core.js
+++ b/client/src/locales/da-DK/core.js
@@ -434,6 +434,7 @@ export default {
unsubscribe: 'Opsig abonnement',
uploadNewAvatar: 'Tilføj nyt profilbillede',
uploadNewImage: 'Tilføj nyt billede',
+ moveListToBoard: 'Flyt liste til anden tavle',
},
},
};
diff --git a/client/src/locales/de-DE/core.js b/client/src/locales/de-DE/core.js
index af3e3537..a9c241f3 100644
--- a/client/src/locales/de-DE/core.js
+++ b/client/src/locales/de-DE/core.js
@@ -423,6 +423,7 @@ export default {
unsubscribe: 'De-abonnieren',
uploadNewAvatar: 'Neuen Avatar hochladen',
uploadNewImage: 'Neues Bild hochladen',
+ moveListToBoard: 'Liste auf andere Arbeitsbereich verschieben',
},
},
};
diff --git a/client/src/locales/el-GR/core.js b/client/src/locales/el-GR/core.js
index 58e45276..e4805324 100644
--- a/client/src/locales/el-GR/core.js
+++ b/client/src/locales/el-GR/core.js
@@ -450,6 +450,7 @@ export default {
unsubscribe: 'Απεγγραφή',
uploadNewAvatar: 'Μεταφόρτωση νέου avatar',
uploadNewImage: 'Μεταφόρτωση νέας εικόνας',
+ moveListToBoard: 'Μετακίνηση λίστας σε άλλο πίνακα',
},
},
};
diff --git a/client/src/locales/en-GB/core.js b/client/src/locales/en-GB/core.js
index 21643d5f..014357b1 100644
--- a/client/src/locales/en-GB/core.js
+++ b/client/src/locales/en-GB/core.js
@@ -431,6 +431,7 @@ export default {
unsubscribe: 'Unsubscribe',
uploadNewAvatar: 'Upload new avatar',
uploadNewImage: 'Upload new image',
+ moveListToBoard: 'Move list to another board',
},
},
};
diff --git a/client/src/locales/en-US/core.js b/client/src/locales/en-US/core.js
index 23c7c899..159731af 100644
--- a/client/src/locales/en-US/core.js
+++ b/client/src/locales/en-US/core.js
@@ -426,6 +426,7 @@ export default {
unsubscribe: 'Unsubscribe',
uploadNewAvatar: 'Upload new avatar',
uploadNewImage: 'Upload new image',
+ moveListToBoard: 'Move list to another board',
},
},
};
diff --git a/client/src/locales/es-ES/core.js b/client/src/locales/es-ES/core.js
index 84206466..1179b311 100644
--- a/client/src/locales/es-ES/core.js
+++ b/client/src/locales/es-ES/core.js
@@ -423,6 +423,7 @@ export default {
unsubscribe: 'Desuscribirse',
uploadNewAvatar: 'Subir un nuevo avatar',
uploadNewImage: 'Subir una nueva imagen',
+ moveListToBoard: 'Mover lista a otro tablero',
},
},
};
diff --git a/client/src/locales/fa-IR/core.js b/client/src/locales/fa-IR/core.js
index f01aa0cf..22c2b73a 100644
--- a/client/src/locales/fa-IR/core.js
+++ b/client/src/locales/fa-IR/core.js
@@ -234,6 +234,7 @@ export default {
unsubscribe: 'لغو اشتراک',
uploadNewAvatar: 'آپلود آواتار جدید',
uploadNewImage: 'آپلود تصویر جدید',
+ moveListToBoard: 'انتقال لیست به برد دیگر',
},
},
};
diff --git a/client/src/locales/fi-FI/core.js b/client/src/locales/fi-FI/core.js
index 945ce70a..bf033168 100644
--- a/client/src/locales/fi-FI/core.js
+++ b/client/src/locales/fi-FI/core.js
@@ -432,6 +432,7 @@ export default {
unsubscribe: 'Peru tilaus',
uploadNewAvatar: 'Lataa uusi avatar',
uploadNewImage: 'Lataa uusi kuva',
+ moveListToBoard: 'Siirrä lista toiselle taululle',
},
},
};
diff --git a/client/src/locales/fr-FR/core.js b/client/src/locales/fr-FR/core.js
index 51312a4e..7852f7db 100644
--- a/client/src/locales/fr-FR/core.js
+++ b/client/src/locales/fr-FR/core.js
@@ -239,6 +239,7 @@ export default {
unsubscribe: 'Se désabonner',
uploadNewAvatar: 'Télécharger un nouvel avatar',
uploadNewImage: 'Télécharger une nouvelle image',
+ moveListToBoard: 'Déplacer la liste vers un autre tableau',
},
},
};
diff --git a/client/src/locales/hu-HU/core.js b/client/src/locales/hu-HU/core.js
index fb5a778f..6d17ee50 100644
--- a/client/src/locales/hu-HU/core.js
+++ b/client/src/locales/hu-HU/core.js
@@ -235,6 +235,7 @@ export default {
unsubscribe: 'Leiratkozás',
uploadNewAvatar: 'Új avatar feltöltése',
uploadNewImage: 'Új kép feltöltése',
+ moveListToBoard: 'Lista áthelyezése másik táblára',
},
},
};
diff --git a/client/src/locales/id-ID/core.js b/client/src/locales/id-ID/core.js
index 6ab44b06..798df2b5 100644
--- a/client/src/locales/id-ID/core.js
+++ b/client/src/locales/id-ID/core.js
@@ -227,6 +227,7 @@ export default {
unsubscribe: 'Berhenti berlangganan',
uploadNewAvatar: 'Unggah avatar baru',
uploadNewImage: 'Unggah gambar baru',
+ moveListToBoard: 'Pindahkan daftar ke papan lain',
},
},
};
diff --git a/client/src/locales/it-IT/core.js b/client/src/locales/it-IT/core.js
index 3fcbddee..d9ec1016 100644
--- a/client/src/locales/it-IT/core.js
+++ b/client/src/locales/it-IT/core.js
@@ -426,6 +426,7 @@ export default {
unsubscribe: 'Annulla iscrizione',
uploadNewAvatar: 'Carica nuovo avatar',
uploadNewImage: 'Carica nuova immagine',
+ moveListToBoard: 'Muovi lista a un altra bacheca',
},
},
};
diff --git a/client/src/locales/ja-JP/core.js b/client/src/locales/ja-JP/core.js
index f99f465b..2ca8000b 100644
--- a/client/src/locales/ja-JP/core.js
+++ b/client/src/locales/ja-JP/core.js
@@ -224,6 +224,7 @@ export default {
unsubscribe: '購読解除',
uploadNewAvatar: '新しいアバターをアップロード',
uploadNewImage: '新しい画像をアップロード',
+ moveListToBoard: 'リストを別のボードに移動',
},
},
};
diff --git a/client/src/locales/ko-KR/core.js b/client/src/locales/ko-KR/core.js
index 4249a3aa..593b332f 100644
--- a/client/src/locales/ko-KR/core.js
+++ b/client/src/locales/ko-KR/core.js
@@ -234,6 +234,7 @@ export default {
unsubscribe: '구독 취소',
uploadNewAvatar: '새 아바타 업로드',
uploadNewImage: '새 이미지 업로드',
+ moveListToBoard: '목록을 다른 보드로 이동',
},
},
};
diff --git a/client/src/locales/nl-NL/core.js b/client/src/locales/nl-NL/core.js
index 3d9c976e..3bcd159c 100644
--- a/client/src/locales/nl-NL/core.js
+++ b/client/src/locales/nl-NL/core.js
@@ -225,6 +225,7 @@ export default {
unsubscribe: 'Afmelden',
uploadNewAvatar: 'Nieuwe avatar uploaden',
uploadNewImage: 'Nieuwe afbeelding uploaden',
+ moveListToBoard: 'Lijst verplaatsen naar ander bord',
},
},
};
diff --git a/client/src/locales/pl-PL/core.js b/client/src/locales/pl-PL/core.js
index a26656ec..aa7dcfb1 100644
--- a/client/src/locales/pl-PL/core.js
+++ b/client/src/locales/pl-PL/core.js
@@ -406,6 +406,7 @@ export default {
unsubscribe: 'Odsubskrybuj',
uploadNewAvatar: 'Wgraj nowy awatar',
uploadNewImage: 'Wgraj nowy obraz',
+ moveListToBoard: 'Przenieś listę na inną tablicę',
},
},
};
diff --git a/client/src/locales/pt-BR/core.js b/client/src/locales/pt-BR/core.js
index 384cbfc3..544a2ca2 100644
--- a/client/src/locales/pt-BR/core.js
+++ b/client/src/locales/pt-BR/core.js
@@ -225,6 +225,7 @@ export default {
unsubscribe: 'Cancelar inscrição',
uploadNewAvatar: 'Enviar novo avatar',
uploadNewImage: 'Enviar nova imagem',
+ moveListToBoard: 'Mover lista para outro quadro',
},
},
};
diff --git a/client/src/locales/ru-RU/core.js b/client/src/locales/ru-RU/core.js
index d15d76a5..eb1ac881 100644
--- a/client/src/locales/ru-RU/core.js
+++ b/client/src/locales/ru-RU/core.js
@@ -412,6 +412,7 @@ export default {
unsubscribe: 'Отписаться',
uploadNewAvatar: 'Загрузить новый аватар',
uploadNewImage: 'Загрузить новое изображение',
+ moveListToBoard: 'Переместить в другую доску',
},
},
};
diff --git a/client/src/locales/sk-SK/core.js b/client/src/locales/sk-SK/core.js
index a11fafd6..b2d3dd4e 100644
--- a/client/src/locales/sk-SK/core.js
+++ b/client/src/locales/sk-SK/core.js
@@ -206,6 +206,7 @@ export default {
unsubscribe: 'Neodoberať',
uploadNewAvatar: 'Nahrať nový avatar',
uploadNewImage: 'Nahrať nový obrázok',
+ moveListToBoard: 'Presunúť zoznam na inú tabuľu',
},
},
};
diff --git a/client/src/locales/sr-Cyrl-RS/core.js b/client/src/locales/sr-Cyrl-RS/core.js
index 4a1803f9..7711e42f 100644
--- a/client/src/locales/sr-Cyrl-RS/core.js
+++ b/client/src/locales/sr-Cyrl-RS/core.js
@@ -233,6 +233,7 @@ export default {
unsubscribe: 'Укини претплату',
uploadNewAvatar: 'Постави нови аватар',
uploadNewImage: 'Постави нову слику',
+ moveListToBoard: 'Премести на другу таблу',
},
},
};
diff --git a/client/src/locales/sr-Latn-RS/core.js b/client/src/locales/sr-Latn-RS/core.js
index ede7abce..1cefd649 100644
--- a/client/src/locales/sr-Latn-RS/core.js
+++ b/client/src/locales/sr-Latn-RS/core.js
@@ -1,12 +1,9 @@
import dateFns from 'date-fns/locale/sr-Latn';
import timeAgo from 'javascript-time-ago/locale/sr-Latn';
-import markdownEditor from './markdown-editor.json';
-
export default {
dateFns,
timeAgo,
- markdownEditor,
format: {
date: 'd.M.yyyy.',
@@ -236,6 +233,7 @@ export default {
unsubscribe: 'Ukini pretplatu',
uploadNewAvatar: 'Postavi novi avatar',
uploadNewImage: 'Postavi novu sliku',
+ moveListToBoard: 'Premesti spisak na drugu tablu',
},
},
};
diff --git a/client/src/locales/sr-Latn-RS/markdown-editor.json b/client/src/locales/sr-Latn-RS/markdown-editor.json
deleted file mode 100644
index 1d8ff203..00000000
--- a/client/src/locales/sr-Latn-RS/markdown-editor.json
+++ /dev/null
@@ -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"
- }
-}
diff --git a/client/src/locales/sv-SE/core.js b/client/src/locales/sv-SE/core.js
index 78e091df..5d0610c4 100644
--- a/client/src/locales/sv-SE/core.js
+++ b/client/src/locales/sv-SE/core.js
@@ -207,6 +207,7 @@ export default {
unsubscribe: 'Avprenumerera',
uploadNewAvatar: 'Ladda upp ny avatar',
uploadNewImage: 'Ladda upp ny bild',
+ moveListToBoard: 'Flytta lista till annan tavla',
},
},
};
diff --git a/client/src/locales/tr-TR/core.js b/client/src/locales/tr-TR/core.js
index dd782673..c0075be2 100644
--- a/client/src/locales/tr-TR/core.js
+++ b/client/src/locales/tr-TR/core.js
@@ -207,6 +207,7 @@ export default {
unsubscribe: 'Abonelikten çık',
uploadNewAvatar: 'Yeni avatar yükle',
uploadNewImage: 'Yeni resim yükle',
+ moveListToBoard: 'Listeyi başka bir panoya taşı',
},
},
};
diff --git a/client/src/locales/uk-UA/core.js b/client/src/locales/uk-UA/core.js
index 32392c3c..a510ffdb 100644
--- a/client/src/locales/uk-UA/core.js
+++ b/client/src/locales/uk-UA/core.js
@@ -408,6 +408,7 @@ export default {
unsubscribe: 'Відписатися',
uploadNewAvatar: 'Завантажити новий аватар',
uploadNewImage: 'Завантажити нове зображення',
+ moveListToBoard: 'Перемістити список на іншу дошку',
},
},
};
diff --git a/client/src/locales/uz-UZ/core.js b/client/src/locales/uz-UZ/core.js
index 3bee8806..9d04eb09 100644
--- a/client/src/locales/uz-UZ/core.js
+++ b/client/src/locales/uz-UZ/core.js
@@ -203,6 +203,7 @@ export default {
unsubscribe: 'Obunani bekor qilish',
uploadNewAvatar: 'Yangi avatar yuklash',
uploadNewImage: 'Yangi rasm yuklash',
+ moveListToBoard: "Ro'yxatni boshqa doskaga ko'chirish",
},
},
};
diff --git a/client/src/locales/zh-CN/core.js b/client/src/locales/zh-CN/core.js
index 8ccccb02..0b14bd56 100644
--- a/client/src/locales/zh-CN/core.js
+++ b/client/src/locales/zh-CN/core.js
@@ -220,6 +220,7 @@ export default {
unsubscribe: '取消关注',
uploadNewAvatar: '上传新头像',
uploadNewImage: '上传图片',
+ moveListToBoard: '移动列表到另一个面板',
},
},
};
diff --git a/client/src/locales/zh-TW/core.js b/client/src/locales/zh-TW/core.js
index 00107c14..4561a834 100644
--- a/client/src/locales/zh-TW/core.js
+++ b/client/src/locales/zh-TW/core.js
@@ -220,6 +220,7 @@ export default {
unsubscribe: '取消訂閱',
uploadNewAvatar: '上傳新頭像',
uploadNewImage: '上傳圖片',
+ moveListToBoard: '移動列表到另一個面板',
},
},
};
diff --git a/server/api/controllers/lists/move-to-board.js b/server/api/controllers/lists/move-to-board.js
new file mode 100644
index 00000000..b3572db9
--- /dev/null
+++ b/server/api/controllers/lists/move-to-board.js
@@ -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,
+ },
+ };
+ },
+};
diff --git a/server/api/helpers/lists/move-to-board.js b/server/api/helpers/lists/move-to-board.js
new file mode 100644
index 00000000..434c235a
--- /dev/null
+++ b/server/api/helpers/lists/move-to-board.js
@@ -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,
+ };
+ },
+};
diff --git a/server/config/routes.js b/server/config/routes.js
index ba2292bb..5cda7a6b 100644
--- a/server/config/routes.js
+++ b/server/config/routes.js
@@ -128,6 +128,7 @@ module.exports.routes = {
'DELETE /api/cards/:cardId/card-labels/labelId::labelId': 'card-labels/delete',
'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',
'PATCH /api/task-lists/:id': 'task-lists/update',
'DELETE /api/task-lists/:id': 'task-lists/delete',