Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d86351d62 | ||
|
|
3a7e4d2e8f | ||
|
|
1bb387d315 | ||
|
|
3ad383fca6 | ||
|
|
38eab8b166 | ||
|
|
aa69bb8d1e | ||
|
|
d627a19935 | ||
|
|
b7cf1853f5 | ||
|
|
4850fc5fc8 | ||
|
|
1dfd6972bf | ||
|
|
b58b91baa1 | ||
|
|
ed89fe5351 | ||
|
|
6021d67a00 | ||
|
|
58afeb1e6b | ||
|
|
7182a09931 |
@@ -0,0 +1 @@
|
|||||||
|
REACT_APP_VERSION=1.10.1
|
||||||
Generated
+12
-12
@@ -14958,9 +14958,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/json5": {
|
"node_modules/json5": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"json5": "lib/cli.js"
|
"json5": "lib/cli.js"
|
||||||
},
|
},
|
||||||
@@ -21971,9 +21971,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tsconfig-paths/node_modules/json5": {
|
"node_modules/tsconfig-paths/node_modules/json5": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
},
|
},
|
||||||
@@ -34502,9 +34502,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
|
||||||
},
|
},
|
||||||
"jsonfile": {
|
"jsonfile": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
@@ -39416,9 +39416,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ const createBoard = (projectId, data, headers) =>
|
|||||||
const createBoardWithImport = (projectId, data, requestId, headers) =>
|
const createBoardWithImport = (projectId, data, requestId, headers) =>
|
||||||
http.post(`/projects/${projectId}/boards?requestId=${requestId}`, data, headers);
|
http.post(`/projects/${projectId}/boards?requestId=${requestId}`, data, headers);
|
||||||
|
|
||||||
const getBoard = (id, headers) =>
|
const getBoard = (id, subscribe, headers) =>
|
||||||
socket.get(`/boards/${id}`, undefined, headers).then((body) => ({
|
socket
|
||||||
|
.get(`/boards/${id}${subscribe ? '?subscribe=true' : ''}`, undefined, headers)
|
||||||
|
.then((body) => ({
|
||||||
...body,
|
...body,
|
||||||
included: {
|
included: {
|
||||||
...body.included,
|
...body.included,
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ const createUser = (data, headers) => socket.post('/users', data, headers);
|
|||||||
|
|
||||||
const getUser = (id, headers) => socket.get(`/users/${id}`, undefined, headers);
|
const getUser = (id, headers) => socket.get(`/users/${id}`, undefined, headers);
|
||||||
|
|
||||||
const getCurrentUser = (headers) => socket.get('/users/me', undefined, headers);
|
const getCurrentUser = (subscribe, headers) =>
|
||||||
|
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers);
|
||||||
|
|
||||||
const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers);
|
const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const BoardActions = React.memo(
|
|||||||
onLabelFromFilterRemove,
|
onLabelFromFilterRemove,
|
||||||
onLabelCreate,
|
onLabelCreate,
|
||||||
onLabelUpdate,
|
onLabelUpdate,
|
||||||
|
onLabelMove,
|
||||||
onLabelDelete,
|
onLabelDelete,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
@@ -54,6 +55,7 @@ const BoardActions = React.memo(
|
|||||||
onLabelRemove={onLabelFromFilterRemove}
|
onLabelRemove={onLabelFromFilterRemove}
|
||||||
onLabelCreate={onLabelCreate}
|
onLabelCreate={onLabelCreate}
|
||||||
onLabelUpdate={onLabelUpdate}
|
onLabelUpdate={onLabelUpdate}
|
||||||
|
onLabelMove={onLabelMove}
|
||||||
onLabelDelete={onLabelDelete}
|
onLabelDelete={onLabelDelete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,6 +84,7 @@ BoardActions.propTypes = {
|
|||||||
onLabelFromFilterRemove: PropTypes.func.isRequired,
|
onLabelFromFilterRemove: PropTypes.func.isRequired,
|
||||||
onLabelCreate: PropTypes.func.isRequired,
|
onLabelCreate: PropTypes.func.isRequired,
|
||||||
onLabelUpdate: PropTypes.func.isRequired,
|
onLabelUpdate: PropTypes.func.isRequired,
|
||||||
|
onLabelMove: PropTypes.func.isRequired,
|
||||||
onLabelDelete: PropTypes.func.isRequired,
|
onLabelDelete: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const Filters = React.memo(
|
|||||||
onLabelRemove,
|
onLabelRemove,
|
||||||
onLabelCreate,
|
onLabelCreate,
|
||||||
onLabelUpdate,
|
onLabelUpdate,
|
||||||
|
onLabelMove,
|
||||||
onLabelDelete,
|
onLabelDelete,
|
||||||
}) => {
|
}) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
@@ -80,6 +81,7 @@ const Filters = React.memo(
|
|||||||
onDeselect={onLabelRemove}
|
onDeselect={onLabelRemove}
|
||||||
onCreate={onLabelCreate}
|
onCreate={onLabelCreate}
|
||||||
onUpdate={onLabelUpdate}
|
onUpdate={onLabelUpdate}
|
||||||
|
onMove={onLabelMove}
|
||||||
onDelete={onLabelDelete}
|
onDelete={onLabelDelete}
|
||||||
>
|
>
|
||||||
<button type="button" className={styles.filterButton}>
|
<button type="button" className={styles.filterButton}>
|
||||||
@@ -117,6 +119,7 @@ Filters.propTypes = {
|
|||||||
onLabelRemove: PropTypes.func.isRequired,
|
onLabelRemove: PropTypes.func.isRequired,
|
||||||
onLabelCreate: PropTypes.func.isRequired,
|
onLabelCreate: PropTypes.func.isRequired,
|
||||||
onLabelUpdate: PropTypes.func.isRequired,
|
onLabelUpdate: PropTypes.func.isRequired,
|
||||||
|
onLabelMove: PropTypes.func.isRequired,
|
||||||
onLabelDelete: PropTypes.func.isRequired,
|
onLabelDelete: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const ActionsStep = React.memo(
|
|||||||
onLabelRemove,
|
onLabelRemove,
|
||||||
onLabelCreate,
|
onLabelCreate,
|
||||||
onLabelUpdate,
|
onLabelUpdate,
|
||||||
|
onLabelMove,
|
||||||
onLabelDelete,
|
onLabelDelete,
|
||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -119,6 +120,7 @@ const ActionsStep = React.memo(
|
|||||||
onDeselect={onLabelRemove}
|
onDeselect={onLabelRemove}
|
||||||
onCreate={onLabelCreate}
|
onCreate={onLabelCreate}
|
||||||
onUpdate={onLabelUpdate}
|
onUpdate={onLabelUpdate}
|
||||||
|
onMove={onLabelMove}
|
||||||
onDelete={onLabelDelete}
|
onDelete={onLabelDelete}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
/>
|
/>
|
||||||
@@ -241,6 +243,7 @@ ActionsStep.propTypes = {
|
|||||||
onLabelRemove: PropTypes.func.isRequired,
|
onLabelRemove: PropTypes.func.isRequired,
|
||||||
onLabelCreate: PropTypes.func.isRequired,
|
onLabelCreate: PropTypes.func.isRequired,
|
||||||
onLabelUpdate: PropTypes.func.isRequired,
|
onLabelUpdate: PropTypes.func.isRequired,
|
||||||
|
onLabelMove: PropTypes.func.isRequired,
|
||||||
onLabelDelete: PropTypes.func.isRequired,
|
onLabelDelete: PropTypes.func.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ const Card = React.memo(
|
|||||||
onLabelRemove,
|
onLabelRemove,
|
||||||
onLabelCreate,
|
onLabelCreate,
|
||||||
onLabelUpdate,
|
onLabelUpdate,
|
||||||
|
onLabelMove,
|
||||||
onLabelDelete,
|
onLabelDelete,
|
||||||
}) => {
|
}) => {
|
||||||
const nameEdit = useRef(null);
|
const nameEdit = useRef(null);
|
||||||
@@ -189,6 +190,7 @@ const Card = React.memo(
|
|||||||
onLabelRemove={onLabelRemove}
|
onLabelRemove={onLabelRemove}
|
||||||
onLabelCreate={onLabelCreate}
|
onLabelCreate={onLabelCreate}
|
||||||
onLabelUpdate={onLabelUpdate}
|
onLabelUpdate={onLabelUpdate}
|
||||||
|
onLabelMove={onLabelMove}
|
||||||
onLabelDelete={onLabelDelete}
|
onLabelDelete={onLabelDelete}
|
||||||
>
|
>
|
||||||
<Button className={classNames(styles.actionsButton, styles.target)}>
|
<Button className={classNames(styles.actionsButton, styles.target)}>
|
||||||
@@ -241,6 +243,7 @@ Card.propTypes = {
|
|||||||
onLabelRemove: PropTypes.func.isRequired,
|
onLabelRemove: PropTypes.func.isRequired,
|
||||||
onLabelCreate: PropTypes.func.isRequired,
|
onLabelCreate: PropTypes.func.isRequired,
|
||||||
onLabelUpdate: PropTypes.func.isRequired,
|
onLabelUpdate: PropTypes.func.isRequired,
|
||||||
|
onLabelMove: PropTypes.func.isRequired,
|
||||||
onLabelDelete: PropTypes.func.isRequired,
|
onLabelDelete: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,9 @@ const Tasks = React.memo(({ items }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* eslint-disable jsx-a11y/click-events-have-key-events,
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
||||||
jsx-a11y/no-static-element-interactions */}
|
jsx-a11y/no-static-element-interactions */}
|
||||||
<div className={styles.button} onClick={handleToggleClick}>
|
<div className={styles.button} onClick={handleToggleClick}>
|
||||||
{/* eslint-enable jsx-a11y/click-events-have-key-events,
|
|
||||||
jsx-a11y/no-static-element-interactions */}
|
|
||||||
<span className={styles.progressWrapper}>
|
<span className={styles.progressWrapper}>
|
||||||
<Progress
|
<Progress
|
||||||
autoSuccess
|
autoSuccess
|
||||||
|
|||||||
@@ -61,11 +61,9 @@ const Item = React.forwardRef(
|
|||||||
const extension = filename.slice((Math.max(0, filename.lastIndexOf('.')) || Infinity) + 1);
|
const extension = filename.slice((Math.max(0, filename.lastIndexOf('.')) || Infinity) + 1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
/* eslint-disable jsx-a11y/click-events-have-key-events,
|
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
||||||
jsx-a11y/no-static-element-interactions */
|
jsx-a11y/no-static-element-interactions */
|
||||||
<div ref={ref} className={styles.wrapper} onClick={handleClick}>
|
<div ref={ref} className={styles.wrapper} onClick={handleClick}>
|
||||||
{/* eslint-enable jsx-a11y/click-events-have-key-events,
|
|
||||||
jsx-a11y/no-static-element-interactions */}
|
|
||||||
<div
|
<div
|
||||||
className={styles.thumbnail}
|
className={styles.thumbnail}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const CardModal = React.memo(
|
|||||||
onLabelRemove,
|
onLabelRemove,
|
||||||
onLabelCreate,
|
onLabelCreate,
|
||||||
onLabelUpdate,
|
onLabelUpdate,
|
||||||
|
onLabelMove,
|
||||||
onLabelDelete,
|
onLabelDelete,
|
||||||
onTaskCreate,
|
onTaskCreate,
|
||||||
onTaskUpdate,
|
onTaskUpdate,
|
||||||
@@ -235,6 +236,7 @@ const CardModal = React.memo(
|
|||||||
onDeselect={onLabelRemove}
|
onDeselect={onLabelRemove}
|
||||||
onCreate={onLabelCreate}
|
onCreate={onLabelCreate}
|
||||||
onUpdate={onLabelUpdate}
|
onUpdate={onLabelUpdate}
|
||||||
|
onMove={onLabelMove}
|
||||||
onDelete={onLabelDelete}
|
onDelete={onLabelDelete}
|
||||||
>
|
>
|
||||||
<Label name={label.name} color={label.color} />
|
<Label name={label.name} color={label.color} />
|
||||||
@@ -252,6 +254,7 @@ const CardModal = React.memo(
|
|||||||
onDeselect={onLabelRemove}
|
onDeselect={onLabelRemove}
|
||||||
onCreate={onLabelCreate}
|
onCreate={onLabelCreate}
|
||||||
onUpdate={onLabelUpdate}
|
onUpdate={onLabelUpdate}
|
||||||
|
onMove={onLabelMove}
|
||||||
onDelete={onLabelDelete}
|
onDelete={onLabelDelete}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -419,6 +422,7 @@ const CardModal = React.memo(
|
|||||||
onDeselect={onLabelRemove}
|
onDeselect={onLabelRemove}
|
||||||
onCreate={onLabelCreate}
|
onCreate={onLabelCreate}
|
||||||
onUpdate={onLabelUpdate}
|
onUpdate={onLabelUpdate}
|
||||||
|
onMove={onLabelMove}
|
||||||
onDelete={onLabelDelete}
|
onDelete={onLabelDelete}
|
||||||
>
|
>
|
||||||
<Button fluid className={styles.actionButton}>
|
<Button fluid className={styles.actionButton}>
|
||||||
@@ -546,6 +550,7 @@ CardModal.propTypes = {
|
|||||||
onLabelRemove: PropTypes.func.isRequired,
|
onLabelRemove: PropTypes.func.isRequired,
|
||||||
onLabelCreate: PropTypes.func.isRequired,
|
onLabelCreate: PropTypes.func.isRequired,
|
||||||
onLabelUpdate: PropTypes.func.isRequired,
|
onLabelUpdate: PropTypes.func.isRequired,
|
||||||
|
onLabelMove: PropTypes.func.isRequired,
|
||||||
onLabelDelete: PropTypes.func.isRequired,
|
onLabelDelete: PropTypes.func.isRequired,
|
||||||
onTaskCreate: PropTypes.func.isRequired,
|
onTaskCreate: PropTypes.func.isRequired,
|
||||||
onTaskUpdate: PropTypes.func.isRequired,
|
onTaskUpdate: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -119,7 +119,6 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
color: #17394d;
|
color: #17394d;
|
||||||
font-size: 15px;
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
outline: none;
|
outline: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -55,14 +55,12 @@ const Item = React.memo(
|
|||||||
</span>
|
</span>
|
||||||
<NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}>
|
<NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}>
|
||||||
<div className={classNames(canEdit && styles.contentHoverable)}>
|
<div className={classNames(canEdit && styles.contentHoverable)}>
|
||||||
{/* eslint-disable jsx-a11y/click-events-have-key-events,
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
||||||
jsx-a11y/no-static-element-interactions */}
|
jsx-a11y/no-static-element-interactions */}
|
||||||
<span
|
<span
|
||||||
className={classNames(styles.text, canEdit && styles.textEditable)}
|
className={classNames(styles.text, canEdit && styles.textEditable)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{/* eslint-enable jsx-a11y/click-events-have-key-events,
|
|
||||||
jsx-a11y/no-static-element-interactions */}
|
|
||||||
<span className={classNames(styles.task, isCompleted && styles.taskCompleted)}>
|
<span className={classNames(styles.task, isCompleted && styles.taskCompleted)}>
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,37 +1,46 @@
|
|||||||
import upperFirst from 'lodash/upperFirst';
|
import upperFirst from 'lodash/upperFirst';
|
||||||
import camelCase from 'lodash/camelCase';
|
import camelCase from 'lodash/camelCase';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
import { Button } from 'semantic-ui-react';
|
import { Button } from 'semantic-ui-react';
|
||||||
|
|
||||||
import styles from './Item.module.scss';
|
import styles from './Item.module.scss';
|
||||||
import globalStyles from '../../styles.module.scss';
|
import globalStyles from '../../styles.module.scss';
|
||||||
|
|
||||||
const Item = React.memo(
|
const Item = React.memo(
|
||||||
({ name, color, isPersisted, isActive, canEdit, onSelect, onDeselect, onEdit }) => {
|
({ id, index, name, color, isPersisted, isActive, canEdit, onSelect, onDeselect, onEdit }) => {
|
||||||
const handleToggleClick = useCallback(() => {
|
const handleToggleClick = useCallback(() => {
|
||||||
|
if (isPersisted) {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
onDeselect();
|
onDeselect();
|
||||||
} else {
|
} else {
|
||||||
onSelect();
|
onSelect();
|
||||||
}
|
}
|
||||||
}, [isActive, onSelect, onDeselect]);
|
}
|
||||||
|
}, [isPersisted, isActive, onSelect, onDeselect]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<Draggable draggableId={id} index={index} isDragDisabled={!isPersisted || !canEdit}>
|
||||||
<Button
|
{({ innerRef, draggableProps, dragHandleProps }, { isDragging }) => {
|
||||||
fluid
|
const contentNode = (
|
||||||
content={name}
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
active={isActive}
|
<div {...draggableProps} ref={innerRef} className={styles.wrapper}>
|
||||||
disabled={!isPersisted}
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
||||||
|
jsx-a11y/no-static-element-interactions */}
|
||||||
|
<span
|
||||||
|
{...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||||
className={classNames(
|
className={classNames(
|
||||||
styles.labelButton,
|
styles.name,
|
||||||
isActive && styles.labelButtonActive,
|
isActive && styles.nameActive,
|
||||||
globalStyles[`background${upperFirst(camelCase(color))}`],
|
globalStyles[`background${upperFirst(camelCase(color))}`],
|
||||||
)}
|
)}
|
||||||
onClick={handleToggleClick}
|
onClick={handleToggleClick}
|
||||||
/>
|
>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<Button
|
<Button
|
||||||
icon="pencil"
|
icon="pencil"
|
||||||
@@ -44,10 +53,17 @@ const Item = React.memo(
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return isDragging ? ReactDOM.createPortal(contentNode, document.body) : contentNode;
|
||||||
|
}}
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Item.propTypes = {
|
Item.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
index: PropTypes.number.isRequired,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
color: PropTypes.string.isRequired,
|
color: PropTypes.string.isRequired,
|
||||||
isPersisted: PropTypes.bool.isRequired,
|
isPersisted: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -12,16 +12,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.labelButton {
|
.name {
|
||||||
|
border-radius: 3px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-right: 0;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 8px 32px 8px 10px;
|
padding: 8px 32px 8px 10px;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: left;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);
|
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.labelButtonActive:before {
|
.nameActive:before {
|
||||||
bottom: 1px;
|
bottom: 1px;
|
||||||
content: "Г";
|
content: "Г";
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import pick from 'lodash/pick';
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
import { Button } from 'semantic-ui-react';
|
import { Button } from 'semantic-ui-react';
|
||||||
import { Input, Popup } from '../../lib/custom-ui';
|
import { Input, Popup } from '../../lib/custom-ui';
|
||||||
|
|
||||||
import { useField, useSteps } from '../../hooks';
|
import { useField, useSteps } from '../../hooks';
|
||||||
|
import DroppableTypes from '../../constants/DroppableTypes';
|
||||||
import AddStep from './AddStep';
|
import AddStep from './AddStep';
|
||||||
import EditStep from './EditStep';
|
import EditStep from './EditStep';
|
||||||
import Item from './Item';
|
import Item from './Item';
|
||||||
@@ -27,6 +29,7 @@ const LabelsStep = React.memo(
|
|||||||
onDeselect,
|
onDeselect,
|
||||||
onCreate,
|
onCreate,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
|
onMove,
|
||||||
onDelete,
|
onDelete,
|
||||||
onBack,
|
onBack,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -74,6 +77,17 @@ const LabelsStep = React.memo(
|
|||||||
[onDeselect],
|
[onDeselect],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDragEnd = useCallback(
|
||||||
|
({ draggableId, source, destination }) => {
|
||||||
|
if (!destination || source.index === destination.index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMove(draggableId, destination.index);
|
||||||
|
},
|
||||||
|
[onMove],
|
||||||
|
);
|
||||||
|
|
||||||
const handleUpdate = useCallback(
|
const handleUpdate = useCallback(
|
||||||
(id, data) => {
|
(id, data) => {
|
||||||
onUpdate(id, data);
|
onUpdate(id, data);
|
||||||
@@ -145,10 +159,19 @@ const LabelsStep = React.memo(
|
|||||||
onChange={handleSearchChange}
|
onChange={handleSearchChange}
|
||||||
/>
|
/>
|
||||||
{filteredItems.length > 0 && (
|
{filteredItems.length > 0 && (
|
||||||
<div className={styles.items}>
|
<DragDropContext onDragEnd={handleDragEnd}>
|
||||||
{filteredItems.map((item) => (
|
<Droppable droppableId="labels" type={DroppableTypes.LABEL}>
|
||||||
|
{({ innerRef, droppableProps, placeholder }) => (
|
||||||
|
<div
|
||||||
|
{...droppableProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||||
|
ref={innerRef}
|
||||||
|
className={styles.items}
|
||||||
|
>
|
||||||
|
{filteredItems.map((item, index) => (
|
||||||
<Item
|
<Item
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
id={item.id}
|
||||||
|
index={index}
|
||||||
name={item.name}
|
name={item.name}
|
||||||
color={item.color}
|
color={item.color}
|
||||||
isPersisted={item.isPersisted}
|
isPersisted={item.isPersisted}
|
||||||
@@ -159,8 +182,23 @@ const LabelsStep = React.memo(
|
|||||||
onEdit={() => handleEdit(item.id)}
|
onEdit={() => handleEdit(item.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
{placeholder}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</Droppable>
|
||||||
|
<Droppable droppableId="labels:hack" type={DroppableTypes.LABEL}>
|
||||||
|
{({ innerRef, droppableProps, placeholder }) => (
|
||||||
|
<div
|
||||||
|
{...droppableProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||||
|
ref={innerRef}
|
||||||
|
className={styles.droppableHack}
|
||||||
|
>
|
||||||
|
{placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
)}
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<Button
|
<Button
|
||||||
fluid
|
fluid
|
||||||
@@ -186,6 +224,7 @@ LabelsStep.propTypes = {
|
|||||||
onDeselect: PropTypes.func.isRequired,
|
onDeselect: PropTypes.func.isRequired,
|
||||||
onCreate: PropTypes.func.isRequired,
|
onCreate: PropTypes.func.isRequired,
|
||||||
onUpdate: PropTypes.func.isRequired,
|
onUpdate: PropTypes.func.isRequired,
|
||||||
|
onMove: PropTypes.func.isRequired,
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func.isRequired,
|
||||||
onBack: PropTypes.func,
|
onBack: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.droppableHack {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
.items {
|
.items {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ const CardAdd = React.memo(({ isOpened, onCreate, onClose }) => {
|
|||||||
|
|
||||||
const nameField = useRef(null);
|
const nameField = useRef(null);
|
||||||
|
|
||||||
const submit = useCallback(() => {
|
const submit = useCallback(
|
||||||
|
(autoOpen) => {
|
||||||
const cleanData = {
|
const cleanData = {
|
||||||
...data,
|
...data,
|
||||||
name: data.name.trim(),
|
name: data.name.trim(),
|
||||||
@@ -32,25 +33,34 @@ const CardAdd = React.memo(({ isOpened, onCreate, onClose }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreate(cleanData);
|
onCreate(cleanData, autoOpen);
|
||||||
|
|
||||||
setData(DEFAULT_DATA);
|
setData(DEFAULT_DATA);
|
||||||
|
|
||||||
|
if (autoOpen) {
|
||||||
|
onClose();
|
||||||
|
} else {
|
||||||
focusNameField();
|
focusNameField();
|
||||||
}, [onCreate, data, setData, focusNameField]);
|
}
|
||||||
|
},
|
||||||
|
[onCreate, onClose, data, setData, focusNameField],
|
||||||
|
);
|
||||||
|
|
||||||
const handleFieldKeyDown = useCallback(
|
const handleFieldKeyDown = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Enter':
|
case 'Enter': {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
submit();
|
const autoOpen = event.ctrlKey;
|
||||||
|
submit(autoOpen);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'Escape':
|
}
|
||||||
|
case 'Escape': {
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -95,18 +95,14 @@ const List = React.memo(
|
|||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
className={styles.innerWrapper}
|
className={styles.innerWrapper}
|
||||||
>
|
>
|
||||||
{/* eslint-disable jsx-a11y/click-events-have-key-events,
|
|
||||||
jsx-a11y/no-static-element-interactions,
|
|
||||||
react/jsx-props-no-spreading */}
|
|
||||||
<div className={styles.outerWrapper}>
|
<div className={styles.outerWrapper}>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
||||||
|
jsx-a11y/no-static-element-interactions */}
|
||||||
<div
|
<div
|
||||||
{...dragHandleProps}
|
{...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||||
className={classNames(styles.header, canEdit && styles.headerEditable)}
|
className={classNames(styles.header, canEdit && styles.headerEditable)}
|
||||||
onClick={handleHeaderClick}
|
onClick={handleHeaderClick}
|
||||||
>
|
>
|
||||||
{/* eslint-enable jsx-a11y/click-events-have-key-events,
|
|
||||||
jsx-a11y/no-static-element-interactions,
|
|
||||||
react/jsx-props-no-spreading */}
|
|
||||||
<NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}>
|
<NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}>
|
||||||
<div className={styles.headerName}>{name}</div>
|
<div className={styles.headerName}>{name}</div>
|
||||||
</NameEdit>
|
</NameEdit>
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
// import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Image, Tab } from 'semantic-ui-react';
|
import { Image, Tab } from 'semantic-ui-react';
|
||||||
|
|
||||||
|
import Config from '../../constants/Config';
|
||||||
|
|
||||||
import logo from '../../assets/images/logo.png';
|
import logo from '../../assets/images/logo.png';
|
||||||
|
|
||||||
import styles from './AboutPane.module.scss';
|
import styles from './AboutPane.module.scss';
|
||||||
|
|
||||||
const AboutPane = React.memo(() => {
|
const AboutPane = React.memo(() => {
|
||||||
// const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab.Pane attached={false} className={styles.wrapper}>
|
<Tab.Pane attached={false} className={styles.wrapper}>
|
||||||
<Image centered src={logo} size="large" className={styles.logo} />
|
<Image centered src={logo} size="large" />
|
||||||
|
<div className={styles.version}>
|
||||||
|
{t('common.version')} {Config.VERSION}
|
||||||
|
</div>
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
:global(#app) {
|
:global(#app) {
|
||||||
/* .logo {
|
.version {
|
||||||
margin-bottom: 16px;
|
font-weight: bold;
|
||||||
} */
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
const VERSION = process.env.REACT_APP_VERSION;
|
||||||
|
|
||||||
const { BASE_URL } = window;
|
const { BASE_URL } = window;
|
||||||
const BASE_PATH = BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
|
const BASE_PATH = BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
|
||||||
|
|
||||||
@@ -15,6 +17,7 @@ const POSITION_GAP = 65535;
|
|||||||
const ACTIVITIES_LIMIT = 50;
|
const ACTIVITIES_LIMIT = 50;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
VERSION,
|
||||||
BASE_PATH,
|
BASE_PATH,
|
||||||
SERVER_BASE_URL,
|
SERVER_BASE_URL,
|
||||||
SERVER_HOST_NAME,
|
SERVER_HOST_NAME,
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
const BOARD = 'BOARD';
|
const BOARD = 'BOARD';
|
||||||
|
const LABEL = 'LABEL';
|
||||||
const LIST = 'LIST';
|
const LIST = 'LIST';
|
||||||
const CARD = 'CARD';
|
const CARD = 'CARD';
|
||||||
const TASK = 'TASK';
|
const TASK = 'TASK';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
BOARD,
|
BOARD,
|
||||||
|
LABEL,
|
||||||
LIST,
|
LIST,
|
||||||
CARD,
|
CARD,
|
||||||
TASK,
|
TASK,
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ export default {
|
|||||||
LABEL_CREATE_HANDLE: `${PREFIX}/LABEL_CREATE_HANDLE`,
|
LABEL_CREATE_HANDLE: `${PREFIX}/LABEL_CREATE_HANDLE`,
|
||||||
LABEL_UPDATE: `${PREFIX}/LABEL_UPDATE`,
|
LABEL_UPDATE: `${PREFIX}/LABEL_UPDATE`,
|
||||||
LABEL_UPDATE_HANDLE: `${PREFIX}/LABEL_UPDATE_HANDLE`,
|
LABEL_UPDATE_HANDLE: `${PREFIX}/LABEL_UPDATE_HANDLE`,
|
||||||
|
LABEL_MOVE: `${PREFIX}/LABEL_MOVE`,
|
||||||
LABEL_DELETE: `${PREFIX}/LABEL_DELETE`,
|
LABEL_DELETE: `${PREFIX}/LABEL_DELETE`,
|
||||||
LABEL_DELETE_HANDLE: `${PREFIX}/LABEL_DELETE_HANDLE`,
|
LABEL_DELETE_HANDLE: `${PREFIX}/LABEL_DELETE_HANDLE`,
|
||||||
LABEL_TO_CARD_ADD: `${PREFIX}/LABEL_TO_CARD_ADD`,
|
LABEL_TO_CARD_ADD: `${PREFIX}/LABEL_TO_CARD_ADD`,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const mapDispatchToProps = (dispatch) =>
|
|||||||
onLabelFromFilterRemove: entryActions.removeLabelFromFilterInCurrentBoard,
|
onLabelFromFilterRemove: entryActions.removeLabelFromFilterInCurrentBoard,
|
||||||
onLabelCreate: entryActions.createLabelInCurrentBoard,
|
onLabelCreate: entryActions.createLabelInCurrentBoard,
|
||||||
onLabelUpdate: entryActions.updateLabel,
|
onLabelUpdate: entryActions.updateLabel,
|
||||||
|
onLabelMove: entryActions.moveLabel,
|
||||||
onLabelDelete: entryActions.deleteLabel,
|
onLabelDelete: entryActions.deleteLabel,
|
||||||
},
|
},
|
||||||
dispatch,
|
dispatch,
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const mapDispatchToProps = (dispatch, { id }) =>
|
|||||||
onLabelRemove: (labelId) => entryActions.removeLabelFromCard(labelId, id),
|
onLabelRemove: (labelId) => entryActions.removeLabelFromCard(labelId, id),
|
||||||
onLabelCreate: (data) => entryActions.createLabelInCurrentBoard(data),
|
onLabelCreate: (data) => entryActions.createLabelInCurrentBoard(data),
|
||||||
onLabelUpdate: (labelId, data) => entryActions.updateLabel(labelId, data),
|
onLabelUpdate: (labelId, data) => entryActions.updateLabel(labelId, data),
|
||||||
|
onLabelMove: (labelId, index) => entryActions.moveLabel(labelId, index),
|
||||||
onLabelDelete: (labelId) => entryActions.deleteLabel(labelId),
|
onLabelDelete: (labelId) => entryActions.deleteLabel(labelId),
|
||||||
},
|
},
|
||||||
dispatch,
|
dispatch,
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ const mapDispatchToProps = (dispatch) =>
|
|||||||
onLabelRemove: entryActions.removeLabelFromCurrentCard,
|
onLabelRemove: entryActions.removeLabelFromCurrentCard,
|
||||||
onLabelCreate: entryActions.createLabelInCurrentBoard,
|
onLabelCreate: entryActions.createLabelInCurrentBoard,
|
||||||
onLabelUpdate: entryActions.updateLabel,
|
onLabelUpdate: entryActions.updateLabel,
|
||||||
|
onLabelMove: entryActions.moveLabel,
|
||||||
onLabelDelete: entryActions.deleteLabel,
|
onLabelDelete: entryActions.deleteLabel,
|
||||||
onTaskCreate: entryActions.createTaskInCurrentCard,
|
onTaskCreate: entryActions.createTaskInCurrentCard,
|
||||||
onTaskUpdate: entryActions.updateTask,
|
onTaskUpdate: entryActions.updateTask,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const mapDispatchToProps = (dispatch, { id }) =>
|
|||||||
{
|
{
|
||||||
onUpdate: (data) => entryActions.updateList(id, data),
|
onUpdate: (data) => entryActions.updateList(id, data),
|
||||||
onDelete: () => entryActions.deleteList(id),
|
onDelete: () => entryActions.deleteList(id),
|
||||||
onCardCreate: (data) => entryActions.createCard(id, data),
|
onCardCreate: (data, autoOpen) => entryActions.createCard(id, data, autoOpen),
|
||||||
},
|
},
|
||||||
dispatch,
|
dispatch,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import EntryActionTypes from '../constants/EntryActionTypes';
|
import EntryActionTypes from '../constants/EntryActionTypes';
|
||||||
|
|
||||||
const createCard = (listId, data) => ({
|
const createCard = (listId, data, autoOpen) => ({
|
||||||
type: EntryActionTypes.CARD_CREATE,
|
type: EntryActionTypes.CARD_CREATE,
|
||||||
payload: {
|
payload: {
|
||||||
listId,
|
listId,
|
||||||
data,
|
data,
|
||||||
|
autoOpen,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ const handleLabelUpdate = (label) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const moveLabel = (id, index) => ({
|
||||||
|
type: EntryActionTypes.LABEL_MOVE,
|
||||||
|
payload: {
|
||||||
|
id,
|
||||||
|
index,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const deleteLabel = (id) => ({
|
const deleteLabel = (id) => ({
|
||||||
type: EntryActionTypes.LABEL_DELETE,
|
type: EntryActionTypes.LABEL_DELETE,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -106,6 +114,7 @@ export default {
|
|||||||
handleLabelCreate,
|
handleLabelCreate,
|
||||||
updateLabel,
|
updateLabel,
|
||||||
handleLabelUpdate,
|
handleLabelUpdate,
|
||||||
|
moveLabel,
|
||||||
deleteLabel,
|
deleteLabel,
|
||||||
handleLabelDelete,
|
handleLabelDelete,
|
||||||
addLabelToCard,
|
addLabelToCard,
|
||||||
|
|||||||
@@ -14,13 +14,12 @@ const Markdown = React.memo(({ linkStopPropagation, ...props }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const linkRenderer = useCallback(
|
const linkRenderer = useCallback(
|
||||||
/* eslint-disable jsx-a11y/anchor-has-content,
|
|
||||||
jsx-a11y/click-events-have-key-events,
|
|
||||||
jsx-a11y/no-static-element-interactions,
|
|
||||||
react/jsx-props-no-spreading */
|
|
||||||
({ node, ...linkProps }) => (
|
({ node, ...linkProps }) => (
|
||||||
|
/* eslint-disable-next-line jsx-a11y/anchor-has-content,
|
||||||
|
jsx-a11y/click-events-have-key-events,
|
||||||
|
jsx-a11y/no-static-element-interactions */
|
||||||
<a
|
<a
|
||||||
{...linkProps}
|
{...linkProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||||
rel={
|
rel={
|
||||||
ABSOLUTE_URL_REGEX.test(linkProps.href) && linkProps.target === '_blank'
|
ABSOLUTE_URL_REGEX.test(linkProps.href) && linkProps.target === '_blank'
|
||||||
? 'noreferrer'
|
? 'noreferrer'
|
||||||
@@ -29,10 +28,6 @@ const Markdown = React.memo(({ linkStopPropagation, ...props }) => {
|
|||||||
onClick={linkStopPropagation ? handleLinkClick : undefined}
|
onClick={linkStopPropagation ? handleLinkClick : undefined}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
/* eslint-enable jsx-a11y/anchor-has-content,
|
|
||||||
jsx-a11y/click-events-have-key-events,
|
|
||||||
jsx-a11y/no-static-element-interactions,
|
|
||||||
react/jsx-props-no-spreading */
|
|
||||||
[linkStopPropagation, handleLinkClick],
|
[linkStopPropagation, handleLinkClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -927,16 +927,27 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body .task-list-item-checkbox {
|
.markdown-body .task-list-item [type=checkbox] {
|
||||||
margin: 0 .2em .25em -1.6em;
|
margin: 0 .2em .25em -18px; // 0 .2em .25em -1.6em
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox {
|
.markdown-body .contains-task-list:dir(rtl) .task-list-item [type=checkbox] {
|
||||||
margin: 0 -1.6em .25em .2em;
|
margin: 0 -1.6em .25em .2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-body ::-webkit-calendar-picker-indicator {
|
.markdown-body ::-webkit-calendar-picker-indicator {
|
||||||
filter: invert(50%);
|
filter: invert(50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown-body .sr-only {
|
||||||
|
border: 0;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export default {
|
|||||||
editUsername_title: 'Edit Username',
|
editUsername_title: 'Edit Username',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
emailAlreadyInUse: 'E-mail already in use',
|
emailAlreadyInUse: 'E-mail already in use',
|
||||||
enterCardTitle: 'Enter card title...',
|
enterCardTitle: 'Enter card title... [Ctrl+Enter] to auto-open',
|
||||||
enterDescription: 'Enter description...',
|
enterDescription: 'Enter description...',
|
||||||
enterFilename: 'Enter filename',
|
enterFilename: 'Enter filename',
|
||||||
enterListTitle: 'Enter list title...',
|
enterListTitle: 'Enter list title...',
|
||||||
@@ -155,6 +155,7 @@ export default {
|
|||||||
username: 'Username',
|
username: 'Username',
|
||||||
usernameAlreadyInUse: 'Username already in use',
|
usernameAlreadyInUse: 'Username already in use',
|
||||||
users: 'Users',
|
users: 'Users',
|
||||||
|
version: 'Version',
|
||||||
viewer: 'Viewer',
|
viewer: 'Viewer',
|
||||||
writeComment: 'Write a comment...',
|
writeComment: 'Write a comment...',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export default {
|
|||||||
createProject_title: 'Crea Progetto',
|
createProject_title: 'Crea Progetto',
|
||||||
createTextFile_title: 'Create File di Testo',
|
createTextFile_title: 'Create File di Testo',
|
||||||
currentPassword: 'Password corrente',
|
currentPassword: 'Password corrente',
|
||||||
dangerZone_title: 'Danger Zone',
|
dangerZone_title: 'Zona pericolosa',
|
||||||
date: 'Data',
|
date: 'Data',
|
||||||
dueDate_title: 'Data di scadenza',
|
dueDate_title: 'Data di scadenza',
|
||||||
deleteAttachment_title: 'Elimina Allegato',
|
deleteAttachment_title: 'Elimina Allegato',
|
||||||
@@ -219,10 +219,10 @@ export default {
|
|||||||
showAllAttachments: 'Mostra tutti gli allegati ({{hidden}} hidden)',
|
showAllAttachments: 'Mostra tutti gli allegati ({{hidden}} hidden)',
|
||||||
showDetails: 'Mostra dettagli',
|
showDetails: 'Mostra dettagli',
|
||||||
showFewerAttachments: 'Mostra meno allegati',
|
showFewerAttachments: 'Mostra meno allegati',
|
||||||
start: 'Start',
|
start: 'Inizio',
|
||||||
stop: 'Stop',
|
stop: 'Ferma',
|
||||||
subscribe: 'Subscribe',
|
subscribe: 'Iscriviti',
|
||||||
unsubscribe: 'Unsubscribe',
|
unsubscribe: 'Annulla iscrizione',
|
||||||
uploadNewAvatar: 'Carica nuovo avatar',
|
uploadNewAvatar: 'Carica nuovo avatar',
|
||||||
uploadNewImage: 'Carica nuova immagine',
|
uploadNewImage: 'Carica nuova immagine',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ export default {
|
|||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
emailOrUsername: 'E-mail o username',
|
emailOrUsername: 'E-mail o username',
|
||||||
invalidEmailOrUsername: 'E-mail or username invalido',
|
invalidEmailOrUsername: 'E-mail o username non valido',
|
||||||
invalidPassword: 'Password non valida',
|
invalidPassword: 'Password non valida',
|
||||||
logInToPlanka: 'Log in Planka',
|
logInToPlanka: 'Log in Planka',
|
||||||
noInternetConnection: 'Nessuna connessione internet',
|
noInternetConnection: 'Nessuna connessione internet',
|
||||||
pageNotFound_title: 'Pagina non trovata',
|
pageNotFound_title: 'Pagina non trovata',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
projectManagement: 'Project management',
|
projectManagement: 'Gestione del progetto',
|
||||||
serverConnectionFailed: 'Connesione al server fallita',
|
serverConnectionFailed: 'Connesione al server fallita',
|
||||||
unknownError: 'Errore sconosciuto, prova ancora',
|
unknownError: 'Errore sconosciuto, prova ancora',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -175,6 +175,10 @@ export default class extends BaseModel {
|
|||||||
return this.memberships.orderBy('id');
|
return this.memberships.orderBy('id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOrderedLabelsQuerySet() {
|
||||||
|
return this.labels.orderBy('position');
|
||||||
|
}
|
||||||
|
|
||||||
getOrderedListsQuerySet() {
|
getOrderedListsQuerySet() {
|
||||||
return this.lists.orderBy('position');
|
return this.lists.orderBy('position');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export default class extends BaseModel {
|
|||||||
|
|
||||||
static fields = {
|
static fields = {
|
||||||
id: attr(),
|
id: attr(),
|
||||||
|
position: attr(),
|
||||||
name: attr(),
|
name: attr(),
|
||||||
color: attr(),
|
color: attr(),
|
||||||
boardId: fk({
|
boardId: fk({
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function* fetchBoardByCurrentPath() {
|
|||||||
tasks,
|
tasks,
|
||||||
attachments,
|
attachments,
|
||||||
},
|
},
|
||||||
} = yield call(request, api.getBoard, boardId));
|
} = yield call(request, api.getBoard, boardId, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import api from '../../../api';
|
|||||||
import mergeRecords from '../../../utils/merge-records';
|
import mergeRecords from '../../../utils/merge-records';
|
||||||
|
|
||||||
export function* fetchCore() {
|
export function* fetchCore() {
|
||||||
const { item: user } = yield call(request, api.getCurrentUser);
|
const { item: user } = yield call(request, api.getCurrentUser, true);
|
||||||
const { items: users1 } = yield call(request, api.getUsers);
|
const { items: users1 } = yield call(request, api.getUsers);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export function* handleBoardMembershipCreate(boardMembership) {
|
|||||||
if (isCurrentUser) {
|
if (isCurrentUser) {
|
||||||
let board2;
|
let board2;
|
||||||
try {
|
try {
|
||||||
({ item: board2 } = yield call(request, api.getBoard, boardMembership.boardId));
|
({ item: board2 } = yield call(request, api.getBoard, boardMembership.boardId, false));
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function* fetchBoard(id) {
|
|||||||
tasks,
|
tasks,
|
||||||
attachments,
|
attachments,
|
||||||
},
|
},
|
||||||
} = yield call(request, api.getBoard, id));
|
} = yield call(request, api.getBoard, id, true));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put(actions.fetchBoard.failure(id, error));
|
yield put(actions.fetchBoard.failure(id, error));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { call, put, select } from 'redux-saga/effects';
|
import { call, put, select } from 'redux-saga/effects';
|
||||||
|
|
||||||
import { goToBoard } from './router';
|
import { goToBoard, goToCard } from './router';
|
||||||
import request from '../request';
|
import request from '../request';
|
||||||
import selectors from '../../../selectors';
|
import selectors from '../../../selectors';
|
||||||
import actions from '../../../actions';
|
import actions from '../../../actions';
|
||||||
import api from '../../../api';
|
import api from '../../../api';
|
||||||
import { createLocalId } from '../../../utils/local-id';
|
import { createLocalId } from '../../../utils/local-id';
|
||||||
|
|
||||||
export function* createCard(listId, data) {
|
export function* createCard(listId, data, autoOpen) {
|
||||||
const { boardId } = yield select(selectors.selectListById, listId);
|
const { boardId } = yield select(selectors.selectListById, listId);
|
||||||
|
|
||||||
const nextData = {
|
const nextData = {
|
||||||
@@ -35,6 +35,10 @@ export function* createCard(listId, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
yield put(actions.createCard.success(localId, card));
|
yield put(actions.createCard.success(localId, card));
|
||||||
|
|
||||||
|
if (autoOpen) {
|
||||||
|
yield call(goToCard, card.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* handleCardCreate(card) {
|
export function* handleCardCreate(card) {
|
||||||
|
|||||||
@@ -7,11 +7,16 @@ import api from '../../../api';
|
|||||||
import { createLocalId } from '../../../utils/local-id';
|
import { createLocalId } from '../../../utils/local-id';
|
||||||
|
|
||||||
export function* createLabel(boardId, data) {
|
export function* createLabel(boardId, data) {
|
||||||
|
const nextData = {
|
||||||
|
...data,
|
||||||
|
position: yield select(selectors.selectNextLabelPosition, boardId),
|
||||||
|
};
|
||||||
|
|
||||||
const localId = yield call(createLocalId);
|
const localId = yield call(createLocalId);
|
||||||
|
|
||||||
yield put(
|
yield put(
|
||||||
actions.createLabel({
|
actions.createLabel({
|
||||||
...data,
|
...nextData,
|
||||||
boardId,
|
boardId,
|
||||||
id: localId,
|
id: localId,
|
||||||
}),
|
}),
|
||||||
@@ -19,7 +24,7 @@ export function* createLabel(boardId, data) {
|
|||||||
|
|
||||||
let label;
|
let label;
|
||||||
try {
|
try {
|
||||||
({ item: label } = yield call(request, api.createLabel, boardId, data));
|
({ item: label } = yield call(request, api.createLabel, boardId, nextData));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put(actions.createLabel.failure(localId, error));
|
yield put(actions.createLabel.failure(localId, error));
|
||||||
return;
|
return;
|
||||||
@@ -56,6 +61,15 @@ export function* handleLabelUpdate(label) {
|
|||||||
yield put(actions.handleLabelUpdate(label));
|
yield put(actions.handleLabelUpdate(label));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* moveLabel(id, index) {
|
||||||
|
const { boardId } = yield select(selectors.selectLabelById, id);
|
||||||
|
const position = yield select(selectors.selectNextLabelPosition, boardId, index, id);
|
||||||
|
|
||||||
|
yield call(updateLabel, id, {
|
||||||
|
position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function* deleteLabel(id) {
|
export function* deleteLabel(id) {
|
||||||
yield put(actions.deleteLabel(id));
|
yield put(actions.deleteLabel(id));
|
||||||
|
|
||||||
@@ -150,6 +164,7 @@ export default {
|
|||||||
handleLabelCreate,
|
handleLabelCreate,
|
||||||
updateLabel,
|
updateLabel,
|
||||||
handleLabelUpdate,
|
handleLabelUpdate,
|
||||||
|
moveLabel,
|
||||||
deleteLabel,
|
deleteLabel,
|
||||||
handleLabelDelete,
|
handleLabelDelete,
|
||||||
addLabelToCard,
|
addLabelToCard,
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function* handleLocationChange() {
|
|||||||
tasks,
|
tasks,
|
||||||
attachments,
|
attachments,
|
||||||
},
|
},
|
||||||
} = yield call(request, api.getBoard, currentBoard.id));
|
} = yield call(request, api.getBoard, currentBoard.id, true));
|
||||||
} catch (error) {} // eslint-disable-line no-empty
|
} catch (error) {} // eslint-disable-line no-empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import EntryActionTypes from '../../../constants/EntryActionTypes';
|
|||||||
|
|
||||||
export default function* cardsWatchers() {
|
export default function* cardsWatchers() {
|
||||||
yield all([
|
yield all([
|
||||||
takeEvery(EntryActionTypes.CARD_CREATE, ({ payload: { listId, data } }) =>
|
takeEvery(EntryActionTypes.CARD_CREATE, ({ payload: { listId, data, autoOpen } }) =>
|
||||||
services.createCard(listId, data),
|
services.createCard(listId, data, autoOpen),
|
||||||
),
|
),
|
||||||
takeEvery(EntryActionTypes.CARD_CREATE_HANDLE, ({ payload: { card } }) =>
|
takeEvery(EntryActionTypes.CARD_CREATE_HANDLE, ({ payload: { card } }) =>
|
||||||
services.handleCardCreate(card),
|
services.handleCardCreate(card),
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ export default function* labelsWatchers() {
|
|||||||
takeEvery(EntryActionTypes.LABEL_UPDATE_HANDLE, ({ payload: { label } }) =>
|
takeEvery(EntryActionTypes.LABEL_UPDATE_HANDLE, ({ payload: { label } }) =>
|
||||||
services.handleLabelUpdate(label),
|
services.handleLabelUpdate(label),
|
||||||
),
|
),
|
||||||
|
takeEvery(EntryActionTypes.LABEL_MOVE, ({ payload: { id, index } }) =>
|
||||||
|
services.moveLabel(id, index),
|
||||||
|
),
|
||||||
takeEvery(EntryActionTypes.LABEL_DELETE, ({ payload: { id } }) => services.deleteLabel(id)),
|
takeEvery(EntryActionTypes.LABEL_DELETE, ({ payload: { id } }) => services.deleteLabel(id)),
|
||||||
takeEvery(EntryActionTypes.LABEL_DELETE_HANDLE, ({ payload: { label } }) =>
|
takeEvery(EntryActionTypes.LABEL_DELETE_HANDLE, ({ payload: { label } }) =>
|
||||||
services.handleLabelDelete(label),
|
services.handleLabelDelete(label),
|
||||||
|
|||||||
@@ -108,7 +108,10 @@ export const selectLabelsForCurrentBoard = createSelector(
|
|||||||
return boardModel;
|
return boardModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
return boardModel.labels.toRefArray().map((label) => ({
|
return boardModel
|
||||||
|
.getOrderedLabelsQuerySet()
|
||||||
|
.toRefArray()
|
||||||
|
.map((label) => ({
|
||||||
...label,
|
...label,
|
||||||
isPersisted: !isLocalId(label.id),
|
isPersisted: !isLocalId(label.id),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -49,6 +49,22 @@ export const selectNextBoardPosition = createSelector(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectNextLabelPosition = createSelector(
|
||||||
|
orm,
|
||||||
|
(_, boardId) => boardId,
|
||||||
|
(_, __, index) => index,
|
||||||
|
(_, __, ___, excludedId) => excludedId,
|
||||||
|
({ Board }, boardId, index, excludedId) => {
|
||||||
|
const boardModel = Board.withId(boardId);
|
||||||
|
|
||||||
|
if (!boardModel) {
|
||||||
|
return boardModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextPosition(boardModel.getOrderedLabelsQuerySet().toRefArray(), index, excludedId);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const selectNextListPosition = createSelector(
|
export const selectNextListPosition = createSelector(
|
||||||
orm,
|
orm,
|
||||||
(_, boardId) => boardId,
|
(_, boardId) => boardId,
|
||||||
@@ -102,6 +118,7 @@ export default {
|
|||||||
selectIsCoreInitializing,
|
selectIsCoreInitializing,
|
||||||
selectIsLogouting,
|
selectIsLogouting,
|
||||||
selectNextBoardPosition,
|
selectNextBoardPosition,
|
||||||
|
selectNextLabelPosition,
|
||||||
selectNextListPosition,
|
selectNextListPosition,
|
||||||
selectNextCardPosition,
|
selectNextCardPosition,
|
||||||
selectNextTaskPosition,
|
selectNextTaskPosition,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import projects from './projects';
|
|||||||
import projectManagers from './project-managers';
|
import projectManagers from './project-managers';
|
||||||
import boards from './boards';
|
import boards from './boards';
|
||||||
import boardMemberships from './board-memberships';
|
import boardMemberships from './board-memberships';
|
||||||
|
import labels from './labels';
|
||||||
import lists from './lists';
|
import lists from './lists';
|
||||||
import cards from './cards';
|
import cards from './cards';
|
||||||
import tasks from './tasks';
|
import tasks from './tasks';
|
||||||
@@ -22,6 +23,7 @@ export default {
|
|||||||
...projectManagers,
|
...projectManagers,
|
||||||
...boards,
|
...boards,
|
||||||
...boardMemberships,
|
...boardMemberships,
|
||||||
|
...labels,
|
||||||
...lists,
|
...lists,
|
||||||
...cards,
|
...cards,
|
||||||
...tasks,
|
...tasks,
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { createSelector } from 'redux-orm';
|
||||||
|
|
||||||
|
import orm from '../orm';
|
||||||
|
|
||||||
|
export const makeSelectLabelById = () =>
|
||||||
|
createSelector(
|
||||||
|
orm,
|
||||||
|
(_, id) => id,
|
||||||
|
({ Label }, id) => {
|
||||||
|
const labelModel = Label.withId(id);
|
||||||
|
|
||||||
|
if (!labelModel) {
|
||||||
|
return labelModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return labelModel.ref;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectLabelById = makeSelectLabelById();
|
||||||
|
|
||||||
|
export default {
|
||||||
|
makeSelectLabelById,
|
||||||
|
selectLabelById,
|
||||||
|
};
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "planka",
|
"name": "planka",
|
||||||
"version": "1.9.2",
|
"version": "1.10.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "planka",
|
"name": "planka",
|
||||||
"version": "1.9.2",
|
"version": "1.10.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "planka",
|
"name": "planka",
|
||||||
"version": "1.9.2",
|
"version": "1.10.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://plankanban.github.io/planka",
|
"homepage": "https://plankanban.github.io/planka",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"client:test": "npm test --prefix client",
|
"client:test": "npm test --prefix client",
|
||||||
"docker:build": "docker build -t ghcr.io/plankanban/planka:local -f Dockerfile .",
|
"docker:build": "docker build -t ghcr.io/plankanban/planka:local -f Dockerfile .",
|
||||||
"docker:build:base": "docker build -t ghcr.io/plankanban/planka:base-local -f Dockerfile.base .",
|
"docker:build:base": "docker build -t ghcr.io/plankanban/planka:base-local -f Dockerfile.base .",
|
||||||
"postinstall": "npm i --prefix server && npm i --prefix client",
|
"postinstall": "(cd server && npm i && cd ../client && npm i)",
|
||||||
"lint": "npm run server:lint && npm run client:lint",
|
"lint": "npm run server:lint && npm run client:lint",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"server:console": "npm run console --prefix server",
|
"server:console": "npm run console --prefix server",
|
||||||
|
|||||||
@@ -108,10 +108,6 @@ module.exports = {
|
|||||||
request: this.req,
|
request: this.req,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.req.isSocket) {
|
|
||||||
sails.sockets.join(this.req, `board:${board.id}`); // TODO: only when subscription needed
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
item: board,
|
item: board,
|
||||||
included: {
|
included: {
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ module.exports = {
|
|||||||
regex: /^[0-9]+$/,
|
regex: /^[0-9]+$/,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
subscribe: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
@@ -73,8 +76,8 @@ module.exports = {
|
|||||||
card.isSubscribed = isSubscribedByCardId[card.id] || false;
|
card.isSubscribed = isSubscribedByCardId[card.id] || false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.req.isSocket) {
|
if (inputs.subscribe && this.req.isSocket) {
|
||||||
sails.sockets.join(this.req, `board:${board.id}`); // TODO: only when subscription needed
|
sails.sockets.join(this.req, `board:${board.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ module.exports = {
|
|||||||
regex: /^[0-9]+$/,
|
regex: /^[0-9]+$/,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
position: {
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
isNotEmptyString: true,
|
isNotEmptyString: true,
|
||||||
@@ -55,7 +59,7 @@ module.exports = {
|
|||||||
throw Errors.NOT_ENOUGH_RIGHTS;
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['name', 'color']);
|
const values = _.pick(inputs, ['position', 'name', 'color']);
|
||||||
|
|
||||||
const label = await sails.helpers.labels.createOne.with({
|
const label = await sails.helpers.labels.createOne.with({
|
||||||
values: {
|
values: {
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ module.exports = {
|
|||||||
regex: /^[0-9]+$/,
|
regex: /^[0-9]+$/,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
position: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
isNotEmptyString: true,
|
isNotEmptyString: true,
|
||||||
@@ -22,7 +25,6 @@ module.exports = {
|
|||||||
color: {
|
color: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
isIn: Label.COLORS,
|
isIn: Label.COLORS,
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ module.exports = {
|
|||||||
throw Errors.NOT_ENOUGH_RIGHTS;
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = _.pick(inputs, ['name', 'color']);
|
const values = _.pick(inputs, ['position', 'name', 'color']);
|
||||||
|
|
||||||
label = await sails.helpers.labels.updateOne.with({
|
label = await sails.helpers.labels.updateOne.with({
|
||||||
values,
|
values,
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ module.exports = {
|
|||||||
regex: /^[0-9]+|me$/,
|
regex: /^[0-9]+|me$/,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
subscribe: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
exits: {
|
exits: {
|
||||||
@@ -26,8 +29,8 @@ module.exports = {
|
|||||||
if (inputs.id === CURRENT_USER_ID) {
|
if (inputs.id === CURRENT_USER_ID) {
|
||||||
({ currentUser: user } = this.req);
|
({ currentUser: user } = this.req);
|
||||||
|
|
||||||
if (this.req.isSocket) {
|
if (inputs.subscribe && this.req.isSocket) {
|
||||||
sails.sockets.join(this.req, `user:${user.id}`); // TODO: only when subscription needed
|
sails.sockets.join(this.req, `user:${user.id}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user = await sails.helpers.users.getOne(inputs.id);
|
user = await sails.helpers.users.getOne(inputs.id);
|
||||||
|
|||||||
@@ -7,11 +7,23 @@ module.exports = {
|
|||||||
custom: idOrIdsValidator,
|
custom: idOrIdsValidator,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
exceptLabelIdOrIds: {
|
||||||
|
type: 'json',
|
||||||
|
custom: idOrIdsValidator,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
return sails.helpers.labels.getMany({
|
const criteria = {
|
||||||
boardId: inputs.idOrIds,
|
boardId: inputs.idOrIds,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (!_.isUndefined(inputs.exceptLabelIdOrIds)) {
|
||||||
|
criteria.id = {
|
||||||
|
'!=': inputs.exceptLabelIdOrIds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return sails.helpers.labels.getMany(criteria);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const valuesValidator = (value) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_.isFinite(value.position)) {
|
if (!_.isUndefined(value.position) && !_.isFinite(value.position)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ module.exports = {
|
|||||||
throw 'userMustBePresent';
|
throw 'userMustBePresent';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_.isNil(values.position)) {
|
if (!_.isUndefined(values.position)) {
|
||||||
const boardId = values.boardId || inputs.record.boardId;
|
const boardId = values.boardId || inputs.record.boardId;
|
||||||
const listId = values.listId || inputs.record.listId;
|
const listId = values.listId || inputs.record.listId;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ const valuesValidator = (value) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_.isFinite(value.position)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_.isPlainObject(value.board)) {
|
if (!_.isPlainObject(value.board)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -25,8 +29,32 @@ module.exports = {
|
|||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
const { values } = inputs;
|
const { values } = inputs;
|
||||||
|
|
||||||
|
const labels = await sails.helpers.boards.getLabels(values.board.id);
|
||||||
|
|
||||||
|
const { position, repositions } = sails.helpers.utils.insertToPositionables(
|
||||||
|
values.position,
|
||||||
|
labels,
|
||||||
|
);
|
||||||
|
|
||||||
|
repositions.forEach(async ({ id, position: nextPosition }) => {
|
||||||
|
await Label.update({
|
||||||
|
id,
|
||||||
|
boardId: values.board.id,
|
||||||
|
}).set({
|
||||||
|
position: nextPosition,
|
||||||
|
});
|
||||||
|
|
||||||
|
sails.sockets.broadcast(`board:${values.board.id}`, 'labelUpdate', {
|
||||||
|
item: {
|
||||||
|
id,
|
||||||
|
position: nextPosition,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const label = await Label.create({
|
const label = await Label.create({
|
||||||
...values,
|
...values,
|
||||||
|
position,
|
||||||
boardId: values.board.id,
|
boardId: values.board.id,
|
||||||
}).fetch();
|
}).fetch();
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
return Label.find(inputs.criteria).sort('id');
|
return Label.find(inputs.criteria).sort('position');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
const valuesValidator = (value) => {
|
||||||
|
if (!_.isPlainObject(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isUndefined(value.position) && !_.isFinite(value.position)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
inputs: {
|
inputs: {
|
||||||
record: {
|
record: {
|
||||||
@@ -6,6 +18,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
values: {
|
values: {
|
||||||
type: 'json',
|
type: 'json',
|
||||||
|
custom: valuesValidator,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
request: {
|
request: {
|
||||||
@@ -16,6 +29,33 @@ module.exports = {
|
|||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
const { values } = inputs;
|
const { values } = inputs;
|
||||||
|
|
||||||
|
if (!_.isUndefined(values.position)) {
|
||||||
|
const labels = await sails.helpers.boards.getLabels(inputs.record.boardId, inputs.record.id);
|
||||||
|
|
||||||
|
const { position, repositions } = sails.helpers.utils.insertToPositionables(
|
||||||
|
values.position,
|
||||||
|
labels,
|
||||||
|
);
|
||||||
|
|
||||||
|
values.position = position;
|
||||||
|
|
||||||
|
repositions.forEach(async ({ id, position: nextPosition }) => {
|
||||||
|
await Label.update({
|
||||||
|
id,
|
||||||
|
boardId: inputs.record.boardId,
|
||||||
|
}).set({
|
||||||
|
position: nextPosition,
|
||||||
|
});
|
||||||
|
|
||||||
|
sails.sockets.broadcast(`board:${inputs.record.boardId}`, 'labelUpdate', {
|
||||||
|
item: {
|
||||||
|
id,
|
||||||
|
position: nextPosition,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const label = await Label.updateOne(inputs.record.id).set({ ...values });
|
const label = await Label.updateOne(inputs.record.id).set({ ...values });
|
||||||
|
|
||||||
if (label) {
|
if (label) {
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ const valuesValidator = (value) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.background && !_.isPlainObject(value.background)) {
|
if (!_.isNil(value.background) && !_.isPlainObject(value.background)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.backgroundImage && !_.isPlainObject(value.backgroundImage)) {
|
if (!_.isNil(value.backgroundImage) && !_.isPlainObject(value.backgroundImage)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const valuesValidator = (value) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.username && !_.isString(value.username)) {
|
if (!_.isNil(value.username) && !_.isString(value.username)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ const valuesValidator = (value) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.username && !_.isString(value.username)) {
|
if (!_.isNil(value.username) && !_.isString(value.username)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.avatar && !_.isPlainObject(value.avatar)) {
|
if (!_.isNil(value.avatar) && !_.isPlainObject(value.avatar)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ module.exports = {
|
|||||||
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
|
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
|
||||||
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
|
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
|
||||||
|
|
||||||
|
position: {
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
isNotEmptyString: true,
|
isNotEmptyString: true,
|
||||||
|
|||||||
@@ -63,11 +63,9 @@ module.exports.down = async (knex) => {
|
|||||||
.where('id', attachment.id);
|
.where('id', attachment.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await knex.schema.table('attachment', (table) => {
|
return knex.schema.table('attachment', (table) => {
|
||||||
table.dropColumn('image');
|
table.dropColumn('image');
|
||||||
});
|
|
||||||
|
|
||||||
return knex.schema.alterTable('attachment', (table) => {
|
table.dropNullable('is_image');
|
||||||
table.boolean('is_image').notNullable().alter();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,44 +1,5 @@
|
|||||||
const POSITION_GAP = 65535;
|
const { addPosition, removePosition } = require('../../utils/migrations');
|
||||||
|
|
||||||
module.exports.up = async (knex) => {
|
module.exports.up = (knex) => addPosition(knex, 'task', 'card_id');
|
||||||
await knex.schema.table('task', (table) => {
|
|
||||||
/* Columns */
|
|
||||||
|
|
||||||
table.specificType('position', 'double precision');
|
module.exports.down = (knex) => removePosition(knex, 'task');
|
||||||
|
|
||||||
/* Indexes */
|
|
||||||
|
|
||||||
table.index('position');
|
|
||||||
});
|
|
||||||
|
|
||||||
const tasks = await knex('task').orderBy(['card_id', 'id']);
|
|
||||||
|
|
||||||
let prevCardId;
|
|
||||||
let position;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
for (task of tasks) {
|
|
||||||
if (task.card_id === prevCardId) {
|
|
||||||
position += POSITION_GAP;
|
|
||||||
} else {
|
|
||||||
prevCardId = task.card_id;
|
|
||||||
position = POSITION_GAP;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await knex('task')
|
|
||||||
.update({
|
|
||||||
position,
|
|
||||||
})
|
|
||||||
.where('id', task.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return knex.schema.table('task', (table) => {
|
|
||||||
table.dropNullable('position');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.down = async (knex) =>
|
|
||||||
knex.schema.table('task', (table) => {
|
|
||||||
table.dropColumn('position');
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ module.exports.up = async (knex) => {
|
|||||||
table.dropColumn('type');
|
table.dropColumn('type');
|
||||||
});
|
});
|
||||||
|
|
||||||
return knex.schema.alterTable('card', (table) => {
|
return knex.schema.table('card', (table) => {
|
||||||
table.bigInteger('list_id').notNullable().alter();
|
table.dropNullable('list_id');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -12,10 +12,10 @@ module.exports.down = async (knex) => {
|
|||||||
await knex.schema.table('board', (table) => {
|
await knex.schema.table('board', (table) => {
|
||||||
/* Columns */
|
/* Columns */
|
||||||
|
|
||||||
table.text('type').notNullable().defaultTo('kanban');
|
table.text('type').notNullable().defaultTo('kanban'); // FIXME: drop default
|
||||||
});
|
});
|
||||||
|
|
||||||
return knex.schema.alterTable('card', (table) => {
|
return knex.schema.table('card', (table) => {
|
||||||
table.bigInteger('list_id').alter();
|
table.setNullable('list_id');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ const processAttachmentImage = async (attachment, attachmentsPath) => {
|
|||||||
|
|
||||||
module.exports.up = async (knex) => {
|
module.exports.up = async (knex) => {
|
||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
|
|
||||||
const users = await knex('user_account').whereNotNull('avatar');
|
const users = await knex('user_account').whereNotNull('avatar');
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const { addPosition, removePosition } = require('../../utils/migrations');
|
||||||
|
|
||||||
|
module.exports.up = (knex) => addPosition(knex, 'label', 'board_id');
|
||||||
|
|
||||||
|
module.exports.down = (knex) => removePosition(knex, 'label');
|
||||||
Generated
+19
-116
@@ -10,7 +10,7 @@
|
|||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"dotenv-cli": "^6.0.0",
|
"dotenv-cli": "^6.0.0",
|
||||||
"filenamify": "^4.3.0",
|
"filenamify": "^4.3.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"knex": "^2.3.0",
|
"knex": "^2.3.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
@@ -3665,9 +3665,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/json5": {
|
"node_modules/json5": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
@@ -3685,32 +3685,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsonwebtoken": {
|
"node_modules/jsonwebtoken": {
|
||||||
"version": "8.5.1",
|
"version": "9.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
|
||||||
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
|
"integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jws": "^3.2.2",
|
"jws": "^3.2.2",
|
||||||
"lodash.includes": "^4.3.0",
|
"lodash": "^4.17.21",
|
||||||
"lodash.isboolean": "^3.0.3",
|
|
||||||
"lodash.isinteger": "^4.0.4",
|
|
||||||
"lodash.isnumber": "^3.0.3",
|
|
||||||
"lodash.isplainobject": "^4.0.6",
|
|
||||||
"lodash.isstring": "^4.0.1",
|
|
||||||
"lodash.once": "^4.0.0",
|
|
||||||
"ms": "^2.1.1",
|
"ms": "^2.1.1",
|
||||||
"semver": "^5.6.0"
|
"semver": "^7.3.8"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4",
|
"node": ">=12",
|
||||||
"npm": ">=1.4.28"
|
"npm": ">=6"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/jsonwebtoken/node_modules/semver": {
|
|
||||||
"version": "5.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
|
||||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jwa": {
|
"node_modules/jwa": {
|
||||||
@@ -3881,52 +3867,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.includes": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
|
||||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isboolean": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isinteger": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isnumber": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isplainobject": {
|
|
||||||
"version": "4.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
|
||||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
|
||||||
},
|
|
||||||
"node_modules/lodash.issafeinteger": {
|
"node_modules/lodash.issafeinteger": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.issafeinteger/-/lodash.issafeinteger-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.issafeinteger/-/lodash.issafeinteger-4.0.4.tgz",
|
||||||
"integrity": "sha512-VyybxpvKqtJKs4+RibsKP1qqbFsTZ6aKDsJfzqrobfMrzMRCHhXAMlKWGKD7QHy2OwGSuKuzSAv8pDyi62huWQ=="
|
"integrity": "sha512-VyybxpvKqtJKs4+RibsKP1qqbFsTZ6aKDsJfzqrobfMrzMRCHhXAMlKWGKD7QHy2OwGSuKuzSAv8pDyi62huWQ=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isstring": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
|
||||||
},
|
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/lodash.once": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
|
||||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
|
||||||
},
|
|
||||||
"node_modules/log-symbols": {
|
"node_modules/log-symbols": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
||||||
@@ -10783,9 +10734,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "^1.2.0"
|
"minimist": "^1.2.0"
|
||||||
@@ -10800,27 +10751,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jsonwebtoken": {
|
"jsonwebtoken": {
|
||||||
"version": "8.5.1",
|
"version": "9.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
|
||||||
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
|
"integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"jws": "^3.2.2",
|
"jws": "^3.2.2",
|
||||||
"lodash.includes": "^4.3.0",
|
"lodash": "^4.17.21",
|
||||||
"lodash.isboolean": "^3.0.3",
|
|
||||||
"lodash.isinteger": "^4.0.4",
|
|
||||||
"lodash.isnumber": "^3.0.3",
|
|
||||||
"lodash.isplainobject": "^4.0.6",
|
|
||||||
"lodash.isstring": "^4.0.1",
|
|
||||||
"lodash.once": "^4.0.0",
|
|
||||||
"ms": "^2.1.1",
|
"ms": "^2.1.1",
|
||||||
"semver": "^5.6.0"
|
"semver": "^7.3.8"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"semver": {
|
|
||||||
"version": "5.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
|
||||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jwa": {
|
"jwa": {
|
||||||
@@ -10945,52 +10883,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
},
|
},
|
||||||
"lodash.includes": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
|
||||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
|
||||||
},
|
|
||||||
"lodash.isboolean": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
|
||||||
},
|
|
||||||
"lodash.isinteger": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
|
||||||
},
|
|
||||||
"lodash.isnumber": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
|
||||||
},
|
|
||||||
"lodash.isplainobject": {
|
|
||||||
"version": "4.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
|
||||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
|
||||||
},
|
|
||||||
"lodash.issafeinteger": {
|
"lodash.issafeinteger": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.issafeinteger/-/lodash.issafeinteger-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.issafeinteger/-/lodash.issafeinteger-4.0.4.tgz",
|
||||||
"integrity": "sha512-VyybxpvKqtJKs4+RibsKP1qqbFsTZ6aKDsJfzqrobfMrzMRCHhXAMlKWGKD7QHy2OwGSuKuzSAv8pDyi62huWQ=="
|
"integrity": "sha512-VyybxpvKqtJKs4+RibsKP1qqbFsTZ6aKDsJfzqrobfMrzMRCHhXAMlKWGKD7QHy2OwGSuKuzSAv8pDyi62huWQ=="
|
||||||
},
|
},
|
||||||
"lodash.isstring": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
|
||||||
},
|
|
||||||
"lodash.merge": {
|
"lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.once": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
|
||||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
|
||||||
},
|
|
||||||
"log-symbols": {
|
"log-symbols": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@
|
|||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"dotenv-cli": "^6.0.0",
|
"dotenv-cli": "^6.0.0",
|
||||||
"filenamify": "^4.3.0",
|
"filenamify": "^4.3.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"knex": "^2.3.0",
|
"knex": "^2.3.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
const POSITION_GAP = 65535;
|
||||||
|
|
||||||
|
const addPosition = async (knex, tableName, parentFieldName) => {
|
||||||
|
await knex.schema.table(tableName, (table) => {
|
||||||
|
/* Columns */
|
||||||
|
|
||||||
|
table.specificType('position', 'double precision');
|
||||||
|
|
||||||
|
/* Indexes */
|
||||||
|
|
||||||
|
table.index('position');
|
||||||
|
});
|
||||||
|
|
||||||
|
const records = await knex(tableName).orderBy([parentFieldName, 'id']);
|
||||||
|
|
||||||
|
let prevParentId;
|
||||||
|
let position;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (record of records) {
|
||||||
|
if (record[parentFieldName] === prevParentId) {
|
||||||
|
position += POSITION_GAP;
|
||||||
|
} else {
|
||||||
|
prevParentId = record[parentFieldName];
|
||||||
|
position = POSITION_GAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await knex(tableName)
|
||||||
|
.update({
|
||||||
|
position,
|
||||||
|
})
|
||||||
|
.where('id', record.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return knex.schema.table(tableName, (table) => {
|
||||||
|
table.dropNullable('position');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePosition = (knex, tableName) =>
|
||||||
|
knex.schema.table(tableName, (table) => {
|
||||||
|
table.dropColumn('position');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addPosition,
|
||||||
|
removePosition,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user