mirror of
https://github.com/plankanban/planka.git
synced 2025-12-24 17:25:00 +03:00
Compare commits
3 Commits
planka-1.0
...
planka-1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0436bd2e7b | ||
|
|
cec09b2db2 | ||
|
|
94410ee994 |
@@ -15,13 +15,13 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 1.0.3
|
||||
version: 1.0.4
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "2.0.0-rc.3"
|
||||
appVersion: "2.0.0-rc.4"
|
||||
|
||||
dependencies:
|
||||
- alias: postgresql
|
||||
|
||||
@@ -4,10 +4,6 @@ metadata:
|
||||
name: {{ include "planka.fullname" . }}
|
||||
labels:
|
||||
{{- include "planka.labels" . | nindent 4 }}
|
||||
{{- with .Values.deploymentAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
|
||||
@@ -45,9 +45,6 @@ podAnnotations: {}
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
# Annotations to add to the deployment
|
||||
deploymentAnnotations: {}
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
|
||||
1456
client/package-lock.json
generated
1456
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -79,13 +79,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ballerina/highlightjs-ballerina": "^1.0.1",
|
||||
"@diplodoc/cut-extension": "^0.7.4",
|
||||
"@diplodoc/transform": "^4.57.7",
|
||||
"@gravity-ui/components": "^4.4.0",
|
||||
"@gravity-ui/markdown-editor": "^15.13.2",
|
||||
"@gravity-ui/uikit": "^7.13.0",
|
||||
"@diplodoc/cut-extension": "^0.7.3",
|
||||
"@diplodoc/transform": "^4.57.2",
|
||||
"@gravity-ui/markdown-editor": "^15.11.0",
|
||||
"@gravity-ui/uikit": "^7.11.0",
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@vitejs/plugin-react": "^4.5.1",
|
||||
"@types/papaparse": "^5.3.16",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"classnames": "^2.5.1",
|
||||
"date-fns": "^2.30.0",
|
||||
@@ -114,7 +114,7 @@
|
||||
"highlightjs-zenscript": "^2.0.0",
|
||||
"hightlightjs-papyrus": "^0.0.4",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^25.2.1",
|
||||
"i18next": "23.15.2",
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"initials": "^3.1.2",
|
||||
"javascript-time-ago": "^2.5.11",
|
||||
@@ -137,13 +137,12 @@
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-frame-component": "^5.2.7",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-i18next": "^15.5.2",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-input-mask": "^2.0.4",
|
||||
"react-intersection-observer": "^9.16.0",
|
||||
"react-mentions": "^4.4.10",
|
||||
"react-photoswipe-gallery": "^2.2.7",
|
||||
"react-redux": "^8.1.3",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-router-dom": "^6.30.0",
|
||||
"react-textarea-autosize": "^8.5.9",
|
||||
"react-time-ago": "^7.3.3",
|
||||
"redux": "^4.2.1",
|
||||
@@ -152,10 +151,10 @@
|
||||
"redux-saga": "^1.3.0",
|
||||
"reselect": "^4.1.8",
|
||||
"sails.io.js": "^1.2.1",
|
||||
"sass-embedded": "^1.89.1",
|
||||
"sass-embedded": "^1.87.0",
|
||||
"semantic-ui-react": "^2.1.5",
|
||||
"socket.io-client": "^2.5.0",
|
||||
"validator": "^13.15.15",
|
||||
"validator": "^13.15.0",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-commonjs": "^0.10.4",
|
||||
"vite-plugin-node-polyfills": "^0.23.0",
|
||||
@@ -165,7 +164,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.27.1",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@cucumber/cucumber": "^11.3.0",
|
||||
"@cucumber/cucumber": "^11.2.0",
|
||||
"@cucumber/pretty-formatter": "^1.0.1",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"babel-jest": "^29.7.0",
|
||||
@@ -175,7 +174,7 @@
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-prettier": "^5.4.1",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"jest": "^29.7.0",
|
||||
|
||||
13
client/patches/react-photoswipe-gallery+2.2.7.patch
Normal file
13
client/patches/react-photoswipe-gallery+2.2.7.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/node_modules/react-photoswipe-gallery/dist/gallery.js b/node_modules/react-photoswipe-gallery/dist/gallery.js
|
||||
index 53cc02c..f4baccb 100644
|
||||
--- a/node_modules/react-photoswipe-gallery/dist/gallery.js
|
||||
+++ b/node_modules/react-photoswipe-gallery/dist/gallery.js
|
||||
@@ -181,7 +181,7 @@ export const Gallery = ({
|
||||
alt
|
||||
} = pswpInstance.currSlide.data;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
- el.innerHTML = caption || alt || '';
|
||||
+ el.textContent = caption || alt || '';
|
||||
});
|
||||
}
|
||||
});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 25 KiB |
@@ -16,7 +16,6 @@
|
||||
padding-bottom: 14px;
|
||||
vertical-align: top;
|
||||
width: calc(100% - 40px);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.date {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
padding-bottom: 14px;
|
||||
vertical-align: top;
|
||||
width: calc(100% - 40px);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.date {
|
||||
|
||||
@@ -3,19 +3,20 @@
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Pagination, Table } from 'semantic-ui-react';
|
||||
import Papa from 'papaparse';
|
||||
import Frame from 'react-frame-component';
|
||||
import { Loader, Pagination, Table } from 'semantic-ui-react';
|
||||
|
||||
import styles from './CsvViewer.module.scss';
|
||||
|
||||
const ROWS_PER_PAGE = 50;
|
||||
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
const CsvViewer = React.memo(({ src, className }) => {
|
||||
const [rows, setRows] = useState(null);
|
||||
const [csvData, setCsvData] = useState(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const frameStyles = useMemo(
|
||||
@@ -33,7 +34,7 @@ const CsvViewer = React.memo(({ src, className }) => {
|
||||
[],
|
||||
);
|
||||
|
||||
const handlePageChange = useCallback((_, { activePage }) => {
|
||||
const handlePageChange = useCallback((e, { activePage }) => {
|
||||
setCurrentPage(activePage);
|
||||
}, []);
|
||||
|
||||
@@ -48,46 +49,47 @@ const CsvViewer = React.memo(({ src, className }) => {
|
||||
|
||||
Papa.parse(text, {
|
||||
skipEmptyLines: true,
|
||||
complete: ({ data }) => {
|
||||
setRows(data);
|
||||
complete: (results) => {
|
||||
const rows = results.data;
|
||||
setCsvData({
|
||||
rows,
|
||||
totalRows: rows.length,
|
||||
});
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
/* empty */
|
||||
} catch (err) {
|
||||
setCsvData(null);
|
||||
}
|
||||
}
|
||||
|
||||
fetchFile();
|
||||
}, [src]);
|
||||
|
||||
if (rows === null) {
|
||||
return <Loader active size="big" />;
|
||||
if (!csvData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const startIndex = (currentPage - 1) * ROWS_PER_PAGE;
|
||||
const endIndex = startIndex + ROWS_PER_PAGE;
|
||||
const currentRows = rows.slice(startIndex, endIndex);
|
||||
const totalPages = Math.ceil(rows.length / ROWS_PER_PAGE);
|
||||
const startIdx = (currentPage - 1) * ROWS_PER_PAGE;
|
||||
const endIdx = startIdx + ROWS_PER_PAGE;
|
||||
const currentRows = csvData.rows.slice(startIdx, endIdx);
|
||||
const totalPages = Math.ceil(csvData.totalRows / ROWS_PER_PAGE);
|
||||
|
||||
return (
|
||||
<Frame
|
||||
head={<style>{frameStyles.join('')}</style>}
|
||||
className={classNames(styles.wrapper, className)}
|
||||
>
|
||||
const content = (
|
||||
<div>
|
||||
<div>
|
||||
<Table celled compact>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
{rows[0].map((cell) => (
|
||||
<Table.HeaderCell key={cell}>{cell}</Table.HeaderCell>
|
||||
{csvData.rows[0].map((header, index) => (
|
||||
<Table.HeaderCell key={index}>{header}</Table.HeaderCell>
|
||||
))}
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{currentRows.slice(1).map((row) => (
|
||||
<Table.Row key={row}>
|
||||
{row.map((cell) => (
|
||||
<Table.Cell key={cell}>{cell}</Table.Cell>
|
||||
{currentRows.slice(1).map((row, rowIndex) => (
|
||||
<Table.Row key={rowIndex}>
|
||||
{row.map((cell, cellIndex) => (
|
||||
<Table.Cell key={cellIndex}>{cell}</Table.Cell>
|
||||
))}
|
||||
</Table.Row>
|
||||
))}
|
||||
@@ -96,18 +98,30 @@ const CsvViewer = React.memo(({ src, className }) => {
|
||||
</div>
|
||||
{totalPages > 1 && (
|
||||
<Pagination
|
||||
secondary
|
||||
pointing
|
||||
totalPages={totalPages}
|
||||
activePage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={handlePageChange}
|
||||
firstItem={null}
|
||||
lastItem={null}
|
||||
onPageChange={handlePageChange}
|
||||
pointing
|
||||
secondary
|
||||
boundaryRange={1}
|
||||
siblingRange={1}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Frame
|
||||
head={<style>{frameStyles.join('')}</style>}
|
||||
className={classNames(styles.wrapper, className)}
|
||||
>
|
||||
{content}
|
||||
</Frame>
|
||||
);
|
||||
});
|
||||
/* eslint-enable react/no-array-index-key */
|
||||
|
||||
CsvViewer.propTypes = {
|
||||
src: PropTypes.string.isRequired,
|
||||
|
||||
@@ -141,7 +141,7 @@ const ActionsStep = React.memo(({ onClose }) => {
|
||||
</Menu.Item>
|
||||
{withTrashEmptier && (
|
||||
<>
|
||||
<hr className={styles.divider} />
|
||||
{(withSubscribe || withCustomFieldGroups) && <hr className={styles.divider} />}
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEmptyTrashClick}>
|
||||
<Icon name="trash alternate outline" className={styles.menuItemIcon} />
|
||||
{t('action.emptyTrash', {
|
||||
@@ -151,7 +151,7 @@ const ActionsStep = React.memo(({ onClose }) => {
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<hr className={styles.divider} />
|
||||
{(withSubscribe || withTrashEmptier) && <hr className={styles.divider} />}
|
||||
{[BoardContexts.BOARD, BoardContexts.ARCHIVE, BoardContexts.TRASH].map((context) => (
|
||||
<Menu.Item
|
||||
key={context}
|
||||
|
||||
@@ -3,18 +3,16 @@
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState, useRef } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Mention, MentionsInput } from 'react-mentions';
|
||||
import { Button, Form } from 'semantic-ui-react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
import { useClickAwayListener, useDidUpdate, useToggle } from '../../../lib/hooks';
|
||||
|
||||
import selectors from '../../../selectors';
|
||||
import entryActions from '../../../entry-actions';
|
||||
import { useEscapeInterceptor, useForm, useNestedRef } from '../../../hooks';
|
||||
import { isModifierKeyPressed } from '../../../utils/event-helpers';
|
||||
import UserAvatar from '../../users/UserAvatar';
|
||||
|
||||
import styles from './Add.module.scss';
|
||||
|
||||
@@ -23,16 +21,13 @@ const DEFAULT_DATA = {
|
||||
};
|
||||
|
||||
const Add = React.memo(() => {
|
||||
const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
const [data, , setData] = useForm(DEFAULT_DATA);
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
|
||||
const [selectTextFieldState, selectTextField] = useToggle();
|
||||
|
||||
const mentionsInputRef = useRef(null);
|
||||
const textFieldRef = useRef(null);
|
||||
const [textFieldRef, handleTextFieldRef] = useNestedRef();
|
||||
const [buttonRef, handleButtonRef] = useNestedRef();
|
||||
|
||||
const submit = useCallback(() => {
|
||||
@@ -52,11 +47,6 @@ const Add = React.memo(() => {
|
||||
}, [dispatch, data, setData, selectTextField, textFieldRef]);
|
||||
|
||||
const handleEscape = useCallback(() => {
|
||||
if (mentionsInputRef.current.isOpened()) {
|
||||
mentionsInputRef.current.clearSuggestions();
|
||||
return;
|
||||
}
|
||||
|
||||
setIsOpened(false);
|
||||
textFieldRef.current.blur();
|
||||
}, [textFieldRef]);
|
||||
@@ -72,15 +62,6 @@ const Add = React.memo(() => {
|
||||
setIsOpened(true);
|
||||
}, []);
|
||||
|
||||
const handleFieldChange = useCallback(
|
||||
(_, text) => {
|
||||
setData({
|
||||
text,
|
||||
});
|
||||
},
|
||||
[setData],
|
||||
);
|
||||
|
||||
const handleFieldKeyDown = useCallback(
|
||||
(event) => {
|
||||
if (isModifierKeyPressed(event) && event.key === 'Enter') {
|
||||
@@ -104,16 +85,6 @@ const Add = React.memo(() => {
|
||||
handleClickAwayCancel,
|
||||
);
|
||||
|
||||
const suggestionRenderer = useCallback(
|
||||
(entry, _, highlightedDisplay) => (
|
||||
<div className={styles.suggestion}>
|
||||
<UserAvatar id={entry.id} size="tiny" />
|
||||
{highlightedDisplay}
|
||||
</div>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
useDidUpdate(() => {
|
||||
if (isOpened) {
|
||||
activateEscapeInterceptor();
|
||||
@@ -128,39 +99,21 @@ const Add = React.memo(() => {
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<div className={styles.field}>
|
||||
<MentionsInput
|
||||
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
allowSpaceInQuery
|
||||
allowSuggestionsAboveCursor
|
||||
ref={mentionsInputRef}
|
||||
inputRef={textFieldRef}
|
||||
value={data.text}
|
||||
placeholder={t('common.writeComment')}
|
||||
maxLength={1048576}
|
||||
rows={isOpened ? 3 : 1}
|
||||
className="mentions-input"
|
||||
style={{
|
||||
control: {
|
||||
minHeight: isOpened ? '79px' : '37px',
|
||||
},
|
||||
}}
|
||||
onFocus={handleFieldFocus}
|
||||
onChange={handleFieldChange}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
>
|
||||
<Mention
|
||||
appendSpaceOnAdd
|
||||
data={boardMemberships.map(({ user }) => ({
|
||||
id: user.id,
|
||||
display: user.username || user.name,
|
||||
}))}
|
||||
displayTransform={(_, display) => `@${display}`}
|
||||
renderSuggestion={suggestionRenderer}
|
||||
className={styles.mention}
|
||||
/>
|
||||
</MentionsInput>
|
||||
</div>
|
||||
<TextArea
|
||||
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
ref={handleTextFieldRef}
|
||||
as={TextareaAutosize}
|
||||
name="text"
|
||||
value={data.text}
|
||||
placeholder={t('common.writeComment')}
|
||||
maxLength={1048576}
|
||||
minRows={isOpened ? 3 : 1}
|
||||
spellCheck={false}
|
||||
className={styles.field}
|
||||
onFocus={handleFieldFocus}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
{isOpened && (
|
||||
<div className={styles.controls}>
|
||||
<Button
|
||||
|
||||
@@ -17,34 +17,19 @@
|
||||
|
||||
.field {
|
||||
background: #fff;
|
||||
margin-bottom: 8px !important;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
padding: 8px 12px;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
|
||||
textarea {
|
||||
border: 1px solid rgba(9, 30, 66, 0.13);
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
padding: 8px 12px;
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mention {
|
||||
background-color: #f1f8ff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.suggestion {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
*/
|
||||
|
||||
import { dequal } from 'dequal';
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Mention, MentionsInput } from 'react-mentions';
|
||||
import { Button, Form } from 'semantic-ui-react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
import { useClickAwayListener } from '../../../lib/hooks';
|
||||
|
||||
import selectors from '../../../selectors';
|
||||
@@ -17,7 +17,6 @@ import entryActions from '../../../entry-actions';
|
||||
import { useForm, useNestedRef } from '../../../hooks';
|
||||
import { focusEnd } from '../../../utils/element-helpers';
|
||||
import { isModifierKeyPressed } from '../../../utils/event-helpers';
|
||||
import UserAvatar from '../../users/UserAvatar';
|
||||
|
||||
import styles from './Edit.module.scss';
|
||||
|
||||
@@ -25,7 +24,6 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||
const selectCommentById = useMemo(() => selectors.makeSelectCommentById(), []);
|
||||
|
||||
const comment = useSelector((state) => selectCommentById(state, commentId));
|
||||
const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
@@ -37,13 +35,12 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||
[comment.text],
|
||||
);
|
||||
|
||||
const [data, , setData] = useForm(() => ({
|
||||
const [data, handleFieldChange] = useForm(() => ({
|
||||
text: '',
|
||||
...defaultData,
|
||||
}));
|
||||
|
||||
const mentionsInputRef = useRef(null);
|
||||
const textFieldRef = useRef(null);
|
||||
const [textFieldRef, handleTextFieldRef] = useNestedRef();
|
||||
const [buttonRef, handleButtonRef] = useNestedRef();
|
||||
|
||||
const submit = useCallback(() => {
|
||||
@@ -63,15 +60,6 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||
submit();
|
||||
}, [submit]);
|
||||
|
||||
const handleFieldChange = useCallback(
|
||||
(_, text) => {
|
||||
setData({
|
||||
text,
|
||||
});
|
||||
},
|
||||
[setData],
|
||||
);
|
||||
|
||||
const handleFieldKeyDown = useCallback(
|
||||
(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
@@ -95,53 +83,25 @@ const Edit = React.memo(({ commentId, onClose }) => {
|
||||
handleClickAwayCancel,
|
||||
);
|
||||
|
||||
const suggestionRenderer = useCallback(
|
||||
(entry, _, highlightedDisplay) => (
|
||||
<div className={styles.suggestion}>
|
||||
<UserAvatar id={entry.id} size="tiny" />
|
||||
{highlightedDisplay}
|
||||
</div>
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
focusEnd(textFieldRef.current);
|
||||
}, [textFieldRef]);
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<div className={styles.field}>
|
||||
<MentionsInput
|
||||
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
allowSpaceInQuery
|
||||
allowSuggestionsAboveCursor
|
||||
ref={mentionsInputRef}
|
||||
inputRef={textFieldRef}
|
||||
value={data.text}
|
||||
maxLength={1048576}
|
||||
rows={3}
|
||||
className="mentions-input"
|
||||
style={{
|
||||
control: {
|
||||
minHeight: '79px',
|
||||
},
|
||||
}}
|
||||
onChange={handleFieldChange}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
>
|
||||
<Mention
|
||||
appendSpaceOnAdd
|
||||
data={boardMemberships.map(({ user }) => ({
|
||||
id: user.id,
|
||||
display: user.username || user.name,
|
||||
}))}
|
||||
displayTransform={(_, display) => `@${display}`}
|
||||
renderSuggestion={suggestionRenderer}
|
||||
className={styles.mention}
|
||||
/>
|
||||
</MentionsInput>
|
||||
</div>
|
||||
<TextArea
|
||||
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
ref={handleTextFieldRef}
|
||||
as={TextareaAutosize}
|
||||
name="text"
|
||||
value={data.text}
|
||||
maxLength={1048576}
|
||||
minRows={3}
|
||||
spellCheck={false}
|
||||
className={styles.field}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
<div className={styles.controls}>
|
||||
<Button
|
||||
{...clickAwayProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
|
||||
@@ -11,33 +11,21 @@
|
||||
|
||||
.field {
|
||||
background: #fff;
|
||||
border: 1px solid rgba(9, 30, 66, 0.13);
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
padding: 8px 12px;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
|
||||
textarea {
|
||||
border: 1px solid rgba(9, 30, 66, 0.13);
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
padding: 8px 12px;
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mention {
|
||||
background-color: #f1f8ff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.suggestion {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import isEmail from 'validator/lib/isEmail';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Divider, Form, Grid, Header, Message } from 'semantic-ui-react';
|
||||
import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';
|
||||
import { Input } from '../../../lib/custom-ui';
|
||||
@@ -247,14 +247,7 @@ const Content = React.memo(() => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className={styles.formFooter}>
|
||||
<Trans i18nKey="common.poweredByPlanka">
|
||||
{'Powered by '}
|
||||
<a href="https://github.com/plankanban/planka" target="_blank" rel="noreferrer">
|
||||
PLANKA
|
||||
</a>
|
||||
</Trans>
|
||||
</p>
|
||||
<p className={styles.formFooter}>{t('common.poweredByPlanka')}</p>
|
||||
</div>
|
||||
</Grid.Column>
|
||||
<Grid.Column
|
||||
|
||||
@@ -13,7 +13,6 @@ import { Button } from 'semantic-ui-react';
|
||||
|
||||
import selectors from '../../../selectors';
|
||||
import entryActions from '../../../entry-actions';
|
||||
import { formatTextWithMentions } from '../../../utils/formatters';
|
||||
import Paths from '../../../constants/Paths';
|
||||
import { StaticUserIds } from '../../../constants/StaticUsers';
|
||||
import { NotificationTypes } from '../../../constants/Enums';
|
||||
@@ -84,7 +83,7 @@ const Item = React.memo(({ id, onClose }) => {
|
||||
break;
|
||||
}
|
||||
case NotificationTypes.COMMENT_CARD: {
|
||||
const commentText = truncate(formatTextWithMentions(notification.data.text));
|
||||
const commentText = truncate(notification.data.text);
|
||||
|
||||
contentNode = (
|
||||
<Trans
|
||||
@@ -123,28 +122,6 @@ const Item = React.memo(({ id, onClose }) => {
|
||||
);
|
||||
|
||||
break;
|
||||
case NotificationTypes.MENTION_IN_COMMENT: {
|
||||
const commentText = truncate(formatTextWithMentions(notification.data.text));
|
||||
|
||||
contentNode = (
|
||||
<Trans
|
||||
i18nKey="common.userMentionedYouInCommentOnCard"
|
||||
values={{
|
||||
user: creatorUserName,
|
||||
comment: commentText,
|
||||
card: cardName,
|
||||
}}
|
||||
>
|
||||
<span className={styles.author}>{creatorUserName}</span>
|
||||
{` mentioned you in «${commentText}» on `}
|
||||
<Link to={Paths.CARDS.replace(':id', notification.cardId)} onClick={onClose}>
|
||||
{cardName}
|
||||
</Link>
|
||||
</Trans>
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
contentNode = null;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,7 @@ import styles from './AboutPane.module.scss';
|
||||
|
||||
const AboutPane = React.memo(() => (
|
||||
<Tab.Pane attached={false} className={styles.wrapper}>
|
||||
<a href="https://github.com/plankanban/planka" target="_blank" rel="noreferrer">
|
||||
<Image centered src={logo} size="large" />
|
||||
</a>
|
||||
<Image centered src={logo} size="large" />
|
||||
<div className={styles.version}>{version}</div>
|
||||
</Tab.Pane>
|
||||
));
|
||||
|
||||
@@ -23,7 +23,6 @@ import { emojiDefs } from '@gravity-ui/markdown-editor/_/bundle/emoji';
|
||||
/* eslint-enable import/no-unresolved */
|
||||
|
||||
import link from './link';
|
||||
import mention from './mention';
|
||||
|
||||
export default [
|
||||
ins,
|
||||
@@ -42,5 +41,4 @@ export default [
|
||||
meta,
|
||||
deflist,
|
||||
link,
|
||||
mention,
|
||||
];
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
const MENTION_REGEX = /@\[(.*?)\]\((.*?)\)/g;
|
||||
|
||||
export default (md) => {
|
||||
md.core.ruler.push('mention', ({ tokens }) => {
|
||||
tokens.forEach((token) => {
|
||||
if (token.type === 'inline' && token.content) {
|
||||
const matches = [...token.content.matchAll(MENTION_REGEX)];
|
||||
|
||||
if (matches.length > 0) {
|
||||
const newChildren = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
matches.forEach((match) => {
|
||||
// Add text before the mention
|
||||
if (match.index > lastIndex) {
|
||||
newChildren.push({
|
||||
type: 'text',
|
||||
content: token.content.slice(lastIndex, match.index),
|
||||
level: token.level,
|
||||
});
|
||||
}
|
||||
|
||||
// Add mention token
|
||||
newChildren.push({
|
||||
type: 'mention',
|
||||
meta: {
|
||||
display: match[1],
|
||||
userId: match[2],
|
||||
},
|
||||
level: token.level,
|
||||
});
|
||||
|
||||
lastIndex = match.index + match[0].length;
|
||||
});
|
||||
|
||||
// Add remaining text after last mention
|
||||
if (lastIndex < token.content.length) {
|
||||
newChildren.push({
|
||||
type: 'text',
|
||||
content: token.content.slice(lastIndex),
|
||||
level: token.level,
|
||||
});
|
||||
}
|
||||
|
||||
token.children = newChildren; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
md.renderer.rules.mention = (tokens, index) => {
|
||||
const { display, userId } = tokens[index].meta;
|
||||
return `<span class="mention" data-user-id="${userId}">@${display}</span>`;
|
||||
};
|
||||
};
|
||||
@@ -100,7 +100,6 @@ export const NotificationTypes = {
|
||||
MOVE_CARD: 'moveCard',
|
||||
COMMENT_CARD: 'commentCard',
|
||||
ADD_MEMBER_TO_CARD: 'addMemberToCard',
|
||||
MENTION_IN_COMMENT: 'mentionInComment',
|
||||
};
|
||||
|
||||
export const NotificationServiceFormats = {
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
or: 'Nebo',
|
||||
pageNotFound_title: 'Stránka nenalezena',
|
||||
password: 'Heslo',
|
||||
poweredByPlanka: 'Poháněno technologií <1>PLANKA</1>',
|
||||
poweredByPlanka: 'Poháněno technologií PLANKA',
|
||||
serverConnectionFailed: 'Připojení k serveru selhalo',
|
||||
unknownError: 'Neznámá chyba, zkuste to později',
|
||||
usernameAlreadyInUse: 'Uživatelské jméno se již používá',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import dateFns from 'date-fns/locale/de';
|
||||
import timeAgo from 'javascript-time-ago/locale/de';
|
||||
|
||||
import markdownEditor from './markdown-editor.json';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
or: 'Oder',
|
||||
pageNotFound_title: 'Seite nicht gefunden',
|
||||
password: 'Passwort',
|
||||
poweredByPlanka: 'Powered by <1>PLANKA</1>',
|
||||
poweredByPlanka: 'Powered by PLANKA',
|
||||
serverConnectionFailed: 'Serververbindung fehlgeschlagen',
|
||||
unknownError: 'Unbekannter Fehler, bitte später erneut versuchen',
|
||||
usernameAlreadyInUse: 'Benutzername wird bereits verwendet',
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"numbered-list_title": "Nummerierte Liste",
|
||||
"numbered-list_hint": "1. Dein Text",
|
||||
"documentation": "Dokumentation",
|
||||
"documentation_link": "https://diplodoc.com/docs/en/syntax/"
|
||||
"documentation_link": " https://diplodoc.com/docs/en/syntax/"
|
||||
},
|
||||
"menubar": {
|
||||
"bold": "Fett",
|
||||
|
||||
@@ -303,8 +303,6 @@ export default {
|
||||
userMarkedTaskIncompleteOnCard:
|
||||
'<0>{{user}}</0> marked {{task}} incomplete on <4>{{card}}</4>',
|
||||
userMarkedTaskIncompleteOnThisCard: '<0>{{user}}</0> marked {{task}} incomplete on this card',
|
||||
userMentionedYouInCommentOnCard:
|
||||
'<0>{{user}}</0> mentioned you in a comment «{{comment}}» on <2>{{card}}</2>',
|
||||
userMovedCardFromListToList:
|
||||
'<0>{{user}}</0> moved <2>{{card}}</2> from {{fromList}} to {{toList}}',
|
||||
userMovedThisCardFromListToList:
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
or: 'Or',
|
||||
pageNotFound_title: 'Page Not Found',
|
||||
password: 'Password',
|
||||
poweredByPlanka: 'Powered by <1>PLANKA</1>',
|
||||
poweredByPlanka: 'Powered by PLANKA',
|
||||
serverConnectionFailed: 'Server connection failed',
|
||||
unknownError: 'Unknown error, try again later',
|
||||
usernameAlreadyInUse: 'Username already in use',
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"numbered-list_title": "Numbered list",
|
||||
"numbered-list_hint": "1. Your text",
|
||||
"documentation": "Documentation",
|
||||
"documentation_link": "https://diplodoc.com/docs/en/syntax/"
|
||||
"documentation_link": " https://diplodoc.com/docs/en/syntax/"
|
||||
},
|
||||
"menubar": {
|
||||
"bold": "Bold",
|
||||
|
||||
@@ -298,8 +298,6 @@ export default {
|
||||
userMarkedTaskIncompleteOnCard:
|
||||
'<0>{{user}}</0> marked {{task}} incomplete on <4>{{card}}</4>',
|
||||
userMarkedTaskIncompleteOnThisCard: '<0>{{user}}</0> marked {{task}} incomplete on this card',
|
||||
userMentionedYouInCommentOnCard:
|
||||
'<0>{{user}}</0> mentioned you in a comment «{{comment}}» on <2>{{card}}</2>',
|
||||
userMovedCardFromListToList:
|
||||
'<0>{{user}}</0> moved <2>{{card}}</2> from {{fromList}} to {{toList}}',
|
||||
userMovedThisCardFromListToList:
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
or: 'Or',
|
||||
pageNotFound_title: 'Page Not Found',
|
||||
password: 'Password',
|
||||
poweredByPlanka: 'Powered by <1>PLANKA</1>',
|
||||
poweredByPlanka: 'Powered by PLANKA',
|
||||
serverConnectionFailed: 'Server connection failed',
|
||||
unknownError: 'Unknown error, try again later',
|
||||
usernameAlreadyInUse: 'Username already in use',
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"numbered-list_title": "Numbered list",
|
||||
"numbered-list_hint": "1. Your text",
|
||||
"documentation": "Documentation",
|
||||
"documentation_link": "https://diplodoc.com/docs/en/syntax/"
|
||||
"documentation_link": " https://diplodoc.com/docs/en/syntax/"
|
||||
},
|
||||
"menubar": {
|
||||
"bold": "Bold",
|
||||
|
||||
@@ -24,8 +24,8 @@ import ptBR from './pt-BR';
|
||||
import roRO from './ro-RO';
|
||||
import ruRU from './ru-RU';
|
||||
import skSK from './sk-SK';
|
||||
import srCyrlRS from './sr-Cyrl-RS';
|
||||
import srLatnRS from './sr-Latn-RS';
|
||||
import srCyrlCS from './sr-Cyrl-CS';
|
||||
import srLatnCS from './sr-Latn-CS';
|
||||
import svSE from './sv-SE';
|
||||
import trTR from './tr-TR';
|
||||
import ukUA from './uk-UA';
|
||||
@@ -55,8 +55,8 @@ const locales = [
|
||||
roRO,
|
||||
ruRU,
|
||||
skSK,
|
||||
srCyrlRS,
|
||||
srLatnRS,
|
||||
srCyrlCS,
|
||||
srLatnCS,
|
||||
svSE,
|
||||
trTR,
|
||||
ukUA,
|
||||
|
||||
@@ -284,29 +284,13 @@ export default {
|
||||
unsavedChanges: 'Modifiche non salvate',
|
||||
uploadedImages: 'Immagini caricate',
|
||||
userActions_title: 'Azioni utente',
|
||||
userAddedCardToList: '<0>{{user}}</0> ha aggiunto <2>{{card}}</2> a {{list}}',
|
||||
userAddedThisCardToList: '<0>{{user}}</0> ha aggiunto questa task a {{list}}',
|
||||
userAddedUserToCard: '<0>{{actorUser}}</0> ha aggiunto {{addedUser}} a <4>{{card}}</4>',
|
||||
userAddedUserToThisCard: '<0>{{actorUser}}</0> ha aggiunto {{addedUser}} a questa task',
|
||||
userAddedYouToCard: '<0>{{user}}</0> ti ha aggiunto a <2>{{card}}</2>',
|
||||
userCompletedTaskOnCard: '<0>{{user}}</0> ha completato {{task}} in <4>{{card}}</4>',
|
||||
userCompletedTaskOnThisCard: '<0>{{user}}</0> ha completato {{task}} in questa task',
|
||||
userJoinedCard: `<0>{{user}}</0> è entrato in <2>{{card}}</2>`,
|
||||
userJoinedThisCard: `<0>{{user}}</0> è entrato in questa task`,
|
||||
userAddedThisCardToList: '<0>{{user}}</0> ha aggiunto questa scheda a {{list}}',
|
||||
userLeftNewCommentToCard:
|
||||
'<0>{{user}}</0> ha lasciato un commento «{{comment}}» a <2>{{card}}</2>',
|
||||
userLeftCard: '<0>{{user}}</0> ha lasciato <2>{{card}}</2>',
|
||||
userLeftThisCard: '<0>{{user}}</0> ha lasciato questa task',
|
||||
userMarkedTaskIncompleteOnCard:
|
||||
'<0>{{user}}</0> ha contrassegnato {{task}} come incompleta in <4>{{card}}</4>',
|
||||
userMarkedTaskIncompleteOnThisCard:
|
||||
'<0>{{user}}</0> ha contrassegnato {{task}} come incompleta in questa task',
|
||||
userMovedCardFromListToList:
|
||||
'<0>{{user}}</0> ha spostato <2>{{card}}</2> da {{fromList}} a {{toList}}',
|
||||
userMovedThisCardFromListToList:
|
||||
'<0>{{user}}</0> ha spostato questa scheda da {{fromList}} a {{toList}}',
|
||||
userRemovedUserFromCard: '<0>{{actorUser}}</0> ha rimosso {{removedUser}} da <4>{{card}}</4>',
|
||||
userRemovedUserFromThisCard: '<0>{{actorUser}}</0> ha rimosso {{removedUser}} da questa task',
|
||||
username: 'Username',
|
||||
users: 'Utenti',
|
||||
viewer: 'Visualizzatore',
|
||||
@@ -370,7 +354,6 @@ export default {
|
||||
deleteProject_title: 'Elimina progetto',
|
||||
deleteTask: 'Elimina task',
|
||||
deleteTask_title: 'Elimina task',
|
||||
deleteTaskList: 'Elimina lista di list',
|
||||
deleteUser: 'Elimina utente',
|
||||
deleteUser_title: 'Elimina utente',
|
||||
dismissAll: 'Ignora tutto',
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
or: 'Oppure',
|
||||
pageNotFound_title: 'Pagina non trovata',
|
||||
password: 'Password',
|
||||
poweredByPlanka: 'Powered by <1>PLANKA</1>',
|
||||
poweredByPlanka: 'Powered by Planka',
|
||||
serverConnectionFailed: 'Connesione al server fallita',
|
||||
unknownError: 'Errore sconosciuto, prova ancora',
|
||||
usernameAlreadyInUse: 'Username già in uso',
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
or: 'Lub',
|
||||
pageNotFound_title: 'Strona nie znaleziona',
|
||||
password: 'Hasło',
|
||||
poweredByPlanka: 'Powered by <1>PLANKA</1>',
|
||||
poweredByPlanka: 'Powered by PLANKA',
|
||||
serverConnectionFailed: 'Błąd połączenia z serwerem',
|
||||
unknownError: 'Nieznany błąd, spróbuj ponownie później',
|
||||
usernameAlreadyInUse: 'Nazwa użytkownika nie jest dostępna',
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"numbered-list_title": "Lista numerowana",
|
||||
"numbered-list_hint": "1. Twój tekst",
|
||||
"documentation": "Dokumentacja",
|
||||
"documentation_link": "https://diplodoc.com/docs/en/syntax/"
|
||||
"documentation_link": " https://diplodoc.com/docs/en/syntax/"
|
||||
},
|
||||
"menubar": {
|
||||
"bold": "Pogrubienie",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import dateFns from 'date-fns/locale/ru';
|
||||
import timeAgo from 'javascript-time-ago/locale/ru';
|
||||
|
||||
import markdownEditor from './markdown-editor.json';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
or: 'Или',
|
||||
pageNotFound_title: 'Страница не найдена',
|
||||
password: 'Пароль',
|
||||
poweredByPlanka: 'Powered by <1>PLANKA</1>',
|
||||
poweredByPlanka: 'Powered by PLANKA',
|
||||
serverConnectionFailed: 'Не могу подключиться к серверу',
|
||||
unknownError: 'Что-то пошло не так, попробуйте позже',
|
||||
usernameAlreadyInUse: 'Имя пользователя уже занято',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"bundle": {
|
||||
"error-title": "Ошибка в редакторе markdown",
|
||||
"settings_wysiwyg": "Визуальный редактор (wysiwyg)",
|
||||
"settings_markup": "Разметка markdown",
|
||||
"settings_markup": "Разметка Markdown",
|
||||
"markup_placeholder": "Введите разметку markdown..."
|
||||
},
|
||||
"codeblock": {
|
||||
@@ -63,7 +63,7 @@
|
||||
"numbered-list_title": "Нумерованный список",
|
||||
"numbered-list_hint": "1. Ваш текст",
|
||||
"documentation": "Документация",
|
||||
"documentation_link": "https://diplodoc.com/docs/en/syntax/"
|
||||
"documentation_link": " https://diplodoc.com/docs/en/syntax/"
|
||||
},
|
||||
"menubar": {
|
||||
"bold": "Жирный",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import login from './login';
|
||||
|
||||
export default {
|
||||
language: 'sr-Cyrl-RS',
|
||||
language: 'sr-Cyrl-CS',
|
||||
country: 'rs',
|
||||
name: 'Српски (ћирилица)',
|
||||
embeddedLocale: login,
|
||||
@@ -1,7 +1,7 @@
|
||||
import login from './login';
|
||||
|
||||
export default {
|
||||
language: 'sr-Latn-RS',
|
||||
language: 'sr-Latn-CS',
|
||||
country: 'rs',
|
||||
name: 'Srpski (latinica)',
|
||||
embeddedLocale: login,
|
||||
@@ -41,10 +41,6 @@ export const isCurrentModalAvailableForCurrentUser = createSelector(
|
||||
|
||||
return boardModel.project.hasManagerWithUserId(currentUserId);
|
||||
}
|
||||
case ModalTypes.BOARD_ACTIVITIES: {
|
||||
const boardModel = Board.withId(currentModal.params.id);
|
||||
return !!boardModel && boardModel.isAvailableForUser(currentUserModel);
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,31 +12,6 @@
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.mentions-input {
|
||||
&__highlighter {
|
||||
line-height: 1.4;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
&__suggestions {
|
||||
border: 1px solid #d4d4d5;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 8px 16px -4px rgba(9, 45, 66, 0.25),
|
||||
0 0 0 1px rgba(9, 45, 66, 0.08);
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
||||
&__item {
|
||||
padding: 8px 12px;
|
||||
|
||||
&--focused {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
color: rgba(0, 0, 0, 0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.react-datepicker {
|
||||
border: 0;
|
||||
color: #444444;
|
||||
@@ -217,13 +192,6 @@
|
||||
font-size: .85em !important;
|
||||
}
|
||||
|
||||
.mention {
|
||||
color: #0366d6;
|
||||
background-color: #f1f8ff;
|
||||
border-radius: 3px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.yfm-clipboard:hover {
|
||||
.yfm-clipboard-button {
|
||||
min-height: auto;
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
const MENTIONS_REGEX = /@\[(.*?)\]\(.*?\)/g;
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const formatTextWithMentions = (text) => text.replace(MENTIONS_REGEX, '@$1');
|
||||
@@ -1 +1 @@
|
||||
export default '2.0.0-rc.3';
|
||||
export default '2.0.0-rc.4';
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
services:
|
||||
planka:
|
||||
image: ghcr.io/plankanban/planka:2.0.0-rc.3
|
||||
image: ghcr.io/plankanban/planka:2.0.0-rc.4
|
||||
restart: on-failure
|
||||
volumes:
|
||||
- favicons:/app/public/favicons
|
||||
- user-avatars:/app/public/user-avatars
|
||||
- background-images:/app/public/background-images
|
||||
- attachments:/app/private/attachments
|
||||
# Optionally override this to your user/group
|
||||
# user: 1000:1000
|
||||
# tmpfs:
|
||||
# - /app/.tmp:mode=770,uid=1000,gid=1000
|
||||
ports:
|
||||
- 3000:1337
|
||||
environment:
|
||||
|
||||
30
package-lock.json
generated
30
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "planka",
|
||||
"version": "2.0.0-rc.3",
|
||||
"version": "2.0.0-rc.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "planka",
|
||||
"version": "2.0.0-rc.3",
|
||||
"version": "2.0.0-rc.4",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"concurrently": "^8.2.2",
|
||||
@@ -16,9 +16,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.27.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.4.tgz",
|
||||
"integrity": "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
|
||||
"integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -310,9 +310,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
@@ -909,9 +909,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/shell-quote": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
|
||||
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz",
|
||||
"integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -1098,14 +1098,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
|
||||
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
|
||||
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planka",
|
||||
"version": "2.0.0-rc.3",
|
||||
"version": "2.0.0-rc.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"client:build": "npm run build --prefix client",
|
||||
|
||||
@@ -6,18 +6,10 @@
|
||||
const escapeMarkdown = require('escape-markdown');
|
||||
const escapeHtml = require('escape-html');
|
||||
|
||||
const { formatTextWithMentions } = require('../../../utils/formatters');
|
||||
|
||||
const extractMentionedUserIds = (text) => {
|
||||
const mentionRegex = /@\[.*?\]\((.*?)\)/g;
|
||||
const matches = [...text.matchAll(mentionRegex)];
|
||||
return matches.map((match) => match[1]);
|
||||
};
|
||||
|
||||
const buildAndSendNotifications = async (services, board, card, comment, actorUser, t) => {
|
||||
const markdownCardLink = `[${escapeMarkdown(card.name)}](${sails.config.custom.baseUrl}/cards/${card.id})`;
|
||||
const htmlCardLink = `<a href="${sails.config.custom.baseUrl}/cards/${card.id}}">${escapeHtml(card.name)}</a>`;
|
||||
const commentText = _.truncate(formatTextWithMentions(comment.text));
|
||||
const commentText = _.truncate(comment.text);
|
||||
|
||||
await sails.helpers.utils.sendNotifications(services, t('New Comment'), {
|
||||
text: `${t(
|
||||
@@ -99,19 +91,6 @@ module.exports = {
|
||||
user: values.user,
|
||||
});
|
||||
|
||||
let mentionedUserIds = extractMentionedUserIds(values.text);
|
||||
|
||||
if (mentionedUserIds.length > 0) {
|
||||
const boardMemberUserIds = await sails.helpers.boards.getMemberUserIds(inputs.board.id);
|
||||
|
||||
mentionedUserIds = _.difference(
|
||||
_.intersection(mentionedUserIds, boardMemberUserIds),
|
||||
comment.userId,
|
||||
);
|
||||
}
|
||||
|
||||
const mentionedUserIdsSet = new Set(mentionedUserIds);
|
||||
|
||||
const cardSubscriptionUserIds = await sails.helpers.cards.getSubscriptionUserIds(
|
||||
comment.cardId,
|
||||
comment.userId,
|
||||
@@ -122,11 +101,7 @@ module.exports = {
|
||||
comment.userId,
|
||||
);
|
||||
|
||||
const notifiableUserIds = _.union(
|
||||
mentionedUserIds,
|
||||
cardSubscriptionUserIds,
|
||||
boardSubscriptionUserIds,
|
||||
);
|
||||
const notifiableUserIds = _.union(cardSubscriptionUserIds, boardSubscriptionUserIds);
|
||||
|
||||
await Promise.all(
|
||||
notifiableUserIds.map((userId) =>
|
||||
@@ -134,9 +109,7 @@ module.exports = {
|
||||
values: {
|
||||
userId,
|
||||
comment,
|
||||
type: mentionedUserIdsSet.has(userId)
|
||||
? Notification.Types.MENTION_IN_COMMENT
|
||||
: Notification.Types.COMMENT_CARD,
|
||||
type: Notification.Types.COMMENT_CARD,
|
||||
data: {
|
||||
card: _.pick(values.card, ['name']),
|
||||
text: comment.text,
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
const escapeMarkdown = require('escape-markdown');
|
||||
const escapeHtml = require('escape-html');
|
||||
|
||||
const { formatTextWithMentions } = require('../../../utils/formatters');
|
||||
|
||||
const buildTitle = (notification, t) => {
|
||||
switch (notification.type) {
|
||||
case Notification.Types.MOVE_CARD:
|
||||
@@ -16,8 +14,6 @@ const buildTitle = (notification, t) => {
|
||||
return t('New Comment');
|
||||
case Notification.Types.ADD_MEMBER_TO_CARD:
|
||||
return t('You Were Added to Card');
|
||||
case Notification.Types.MENTION_IN_COMMENT:
|
||||
return t('You Were Mentioned in Comment');
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -60,7 +56,7 @@ const buildBodyByFormat = (board, card, notification, actorUser, t) => {
|
||||
};
|
||||
}
|
||||
case Notification.Types.COMMENT_CARD: {
|
||||
const commentText = _.truncate(formatTextWithMentions(notification.data.text));
|
||||
const commentText = _.truncate(notification.data.text);
|
||||
|
||||
return {
|
||||
text: `${t(
|
||||
@@ -99,30 +95,6 @@ const buildBodyByFormat = (board, card, notification, actorUser, t) => {
|
||||
escapeHtml(board.name),
|
||||
),
|
||||
};
|
||||
case Notification.Types.MENTION_IN_COMMENT: {
|
||||
const commentText = _.truncate(formatTextWithMentions(notification.data.text));
|
||||
|
||||
return {
|
||||
text: `${t(
|
||||
'%s mentioned you in %s on %s',
|
||||
actorUser.name,
|
||||
card.name,
|
||||
board.name,
|
||||
)}:\n${commentText}`,
|
||||
markdown: `${t(
|
||||
'%s mentioned you in %s on %s',
|
||||
escapeMarkdown(actorUser.name),
|
||||
markdownCardLink,
|
||||
escapeMarkdown(board.name),
|
||||
)}:\n\n*${escapeMarkdown(commentText)}*`,
|
||||
html: `${t(
|
||||
'%s mentioned you in %s on %s',
|
||||
escapeHtml(actorUser.name),
|
||||
htmlCardLink,
|
||||
escapeHtml(board.name),
|
||||
)}:\n\n<i>${escapeHtml(commentText)}</i>`,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -175,15 +147,6 @@ const buildAndSendEmail = async (board, card, notification, actorUser, notifiabl
|
||||
boardLink,
|
||||
)}</p>`;
|
||||
|
||||
break;
|
||||
case Notification.Types.MENTION_IN_COMMENT:
|
||||
html = `<p>${t(
|
||||
'%s mentioned you in %s on %s',
|
||||
escapeHtml(actorUser.name),
|
||||
cardLink,
|
||||
boardLink,
|
||||
)}</p><p>${escapeHtml(notification.data.text)}</p>`;
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
@@ -223,11 +186,9 @@ module.exports = {
|
||||
values.userId = values.user.id;
|
||||
}
|
||||
|
||||
const isCommentRelated =
|
||||
values.type === Notification.Types.COMMENT_CARD ||
|
||||
values.type === Notification.Types.MENTION_IN_COMMENT;
|
||||
const isCommentCard = values.type === Notification.Types.COMMENT_CARD;
|
||||
|
||||
if (isCommentRelated) {
|
||||
if (isCommentCard) {
|
||||
values.commentId = values.comment.id;
|
||||
} else {
|
||||
values.actionId = values.action.id;
|
||||
@@ -256,7 +217,7 @@ module.exports = {
|
||||
boards: [inputs.board],
|
||||
lists: [inputs.list],
|
||||
cards: [values.card],
|
||||
...(isCommentRelated
|
||||
...(isCommentCard
|
||||
? {
|
||||
comments: [values.comment],
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ const Types = {
|
||||
MOVE_CARD: 'moveCard',
|
||||
COMMENT_CARD: 'commentCard',
|
||||
ADD_MEMBER_TO_CARD: 'addMemberToCard',
|
||||
MENTION_IN_COMMENT: 'mentionInComment',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -55,8 +55,8 @@ const LANGUAGES = [
|
||||
'ro-RO',
|
||||
'ru-RU',
|
||||
'sk-SK',
|
||||
'sr-Cyrl-RS',
|
||||
'sr-Latn-RS',
|
||||
'sr-Cyrl-CS',
|
||||
'sr-Latn-CS',
|
||||
'sv-SE',
|
||||
'tr-TR',
|
||||
'uk-UA',
|
||||
|
||||
@@ -19,7 +19,7 @@ module.exports.i18n = {
|
||||
*
|
||||
*/
|
||||
|
||||
locales: ['en-GB', 'en-US', 'it-IT', 'ru-RU'],
|
||||
locales: ['en-GB', 'en-US', 'ru-RU'],
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
"This is a *test* **markdown** `message`!": "This is a *test* **markdown** `message`!",
|
||||
"This is a <i>test</i> <b>html</b> <code>message</code>": "This is a <i>test</i> <b>html</b> <code>message</code>",
|
||||
"You Were Added to Card": "Your Were Added to Card",
|
||||
"You Were Mentioned in Comment": "You Were Mentioned in Comment",
|
||||
"%s added you to %s on %s": "%s added you to %s on %s",
|
||||
"%s created %s in %s on %s": "%s created %s in %s on %s",
|
||||
"%s left a new comment to %s on %s": "%s left a new comment to %s on %s",
|
||||
"%s mentioned you in %s on %s": "%s mentioned you in %s on %s",
|
||||
"%s moved %s from %s to %s on %s": "%s moved %s from %s to %s on %s"
|
||||
}
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
"This is a *test* **markdown** `message`!": "This is a *test* **markdown** `message`!",
|
||||
"This is a <i>test</i> <b>html</b> <code>message</code>": "This is a <i>test</i> <b>html</b> <code>message</code>",
|
||||
"You Were Added to Card": "Your Were Added to Card",
|
||||
"You Were Mentioned in Comment": "You Were Mentioned in Comment",
|
||||
"%s added you to %s on %s": "%s added you to %s on %s",
|
||||
"%s created %s in %s on %s": "%s created %s in %s on %s",
|
||||
"%s left a new comment to %s on %s": "%s left a new comment to %s on %s",
|
||||
"%s mentioned you in %s on %s": "%s mentioned you in %s on %s",
|
||||
"%s moved %s from %s to %s on %s": "%s moved %s from %s to %s on %s"
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"Card Created": "Nuova task creata",
|
||||
"Card Moved": "Task spostata",
|
||||
"New Comment": "Nuovo commento",
|
||||
"Test Title": "Titolo di test",
|
||||
"This is a test text message!": "Questo è un messaggio di testo di test!",
|
||||
"This is a *test* **markdown** `message`!": "Questo è un *test* **markdown** `messaggio`!",
|
||||
"This is a <i>test</i> <b>html</b> <code>message</code>": "Questo è un <i>test</i> <b>html</b> <code>messaggio</code>",
|
||||
"You Were Added to Card": "Sei stato aggiunto alla task",
|
||||
"%s created %s in %s on %s": "%s ha creato %s in %s in %s",
|
||||
"%s left a new comment to %s on %s": "%s ha commentato %s in %s",
|
||||
"%s moved %s from %s to %s on %s": "%s ha spostato %s da %s a %s in %s"
|
||||
}
|
||||
@@ -7,10 +7,8 @@
|
||||
"This is a *test* **markdown** `message`!": "Это *тестовое* **markdown** `сообщение`!",
|
||||
"This is a <i>test</i> <b>html</b> <code>message</code>": "Это <i>тестовое</i> <b>html</b> <code>сообщение</code>",
|
||||
"You Were Added to Card": "Вы были добавлены к карточке",
|
||||
"You Were Mentioned in Comment": "Вы были упомянуты в комментарии",
|
||||
"%s added you to %s on %s": "%s добавил(а) вас к %s на %s",
|
||||
"%s created %s in %s on %s": "%s создал(а) %s в %s на %s",
|
||||
"%s left a new comment to %s on %s": "%s оставил(а) новый комментарий к %s на %s",
|
||||
"%s mentioned you in %s on %s": "%s упомянул(а) вас в %s на %s",
|
||||
"%s moved %s from %s to %s on %s": "%s переместил(а) %s из %s в %s на %s"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ exports.up = async (knex) => {
|
||||
FROM comment
|
||||
GROUP BY card_id
|
||||
) AS comments_total_by_card_id
|
||||
WHERE card.id = comments_total_by_card_id.card_id;
|
||||
WHERE card.id = comments_total_by_card_id.card_id
|
||||
`);
|
||||
|
||||
return knex.schema.alterTable('card', (table) => {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
exports.up = (knex) =>
|
||||
knex.raw(`
|
||||
UPDATE user_account
|
||||
SET language =
|
||||
CASE
|
||||
WHEN language = 'sr-Cyrl-CS' THEN 'sr-Cyrl-RS'
|
||||
WHEN language = 'sr-Latn-CS' THEN 'sr-Latn-RS'
|
||||
END
|
||||
WHERE language IN ('sr-Cyrl-CS', 'sr-Latn-CS');
|
||||
`);
|
||||
|
||||
exports.down = (knex) =>
|
||||
knex.raw(`
|
||||
UPDATE user_account
|
||||
SET language =
|
||||
CASE
|
||||
WHEN language = 'sr-Cyrl-RS' THEN 'sr-Cyrl-CS'
|
||||
WHEN language = 'sr-Latn-RS' THEN 'sr-Latn-CS'
|
||||
END
|
||||
WHERE language IN ('sr-Cyrl-RS', 'sr-Latn-RS');
|
||||
`);
|
||||
573
server/package-lock.json
generated
573
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -69,7 +69,7 @@
|
||||
"serve-static": "^1.16.2",
|
||||
"sharp": "^0.33.5",
|
||||
"uuid": "^9.0.1",
|
||||
"validator": "^13.15.15",
|
||||
"validator": "^13.15.0",
|
||||
"winston": "^3.17.0",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
@@ -79,11 +79,11 @@
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-prettier": "^5.4.1",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"mocha": "^10.8.2",
|
||||
"nodemon": "^3.1.10",
|
||||
"prettier": "3.3.3",
|
||||
"supertest": "^7.1.1"
|
||||
"supertest": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
diff --git a/node_modules/sails/lib/hooks/i18n/index.js b/node_modules/sails/lib/hooks/i18n/index.js
|
||||
index a0c74ed..582ff66 100644
|
||||
--- a/node_modules/sails/lib/hooks/i18n/index.js
|
||||
+++ b/node_modules/sails/lib/hooks/i18n/index.js
|
||||
@@ -108,7 +108,8 @@ module.exports = function(sails) {
|
||||
locales: sails.config.i18n.locales,
|
||||
defaultLocale: sails.config.i18n.defaultLocale,
|
||||
directory: resolvedLocalesDirectory,
|
||||
- extension: I18N_LOCALES_FILE_EXTENSION
|
||||
+ extension: I18N_LOCALES_FILE_EXTENSION,
|
||||
+ devMode: false
|
||||
});
|
||||
|
||||
// Add all of the i18n prototype methods into this hook.
|
||||
@@ -176,7 +177,8 @@ module.exports = function(sails) {
|
||||
defaultLocale: sails.config.i18n.defaultLocale,
|
||||
directory: resolvedLocalesDirectory,
|
||||
extension: I18N_LOCALES_FILE_EXTENSION,
|
||||
- request: req
|
||||
+ request: req,
|
||||
+ devMode: false
|
||||
});
|
||||
|
||||
// Mix translation capabilities into res.locals.
|
||||
@@ -1,7 +0,0 @@
|
||||
const MENTIONS_REGEX = /@\[(.*?)\]\(.*?\)/g;
|
||||
|
||||
const formatTextWithMentions = (text) => text.replace(MENTIONS_REGEX, '@$1');
|
||||
|
||||
module.exports = {
|
||||
formatTextWithMentions,
|
||||
};
|
||||
@@ -1 +1 @@
|
||||
module.exports = '2.0.0-rc.3';
|
||||
module.exports = '2.0.0-rc.4';
|
||||
|
||||
Reference in New Issue
Block a user