Compare commits

..

5 Commits

Author SHA1 Message Date
Maksim Eltyshev
2173c3657c chore: Update version 2024-10-09 14:25:19 +02:00
Maksim Eltyshev
a2b81f6d59 feat: Add British English translation 2024-10-09 14:22:22 +02:00
Maksim Eltyshev
1948bd4fbe feat: Ability to upload multiple attachments at once
Closes #908
2024-10-09 12:38:18 +02:00
Maksim Eltyshev
52f8abc1f8 docs: Add information about mobile app 2024-10-03 19:07:59 +02:00
Maksim Eltyshev
dd7574f134 ci: Omit version from prebuild asset label 2024-10-02 15:20:10 +02:00
15 changed files with 352 additions and 47 deletions

View File

@@ -4,7 +4,6 @@ on:
release: release:
types: [created] types: [created]
jobs: jobs:
build-and-publish-release-package: build-and-publish-release-package:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -52,6 +51,6 @@ jobs:
- name: Dist upload assets - name: Dist upload assets
run: | run: |
gh release upload ${{ github.event.release.tag_name }} planka-prebuild-${{ github.event.release.tag_name }}.zip gh release upload ${{ github.event.release.tag_name }} planka-prebuild-${{ github.event.release.tag_name }}.zip#planka-prebuild.zip
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}

View File

@@ -25,6 +25,12 @@ There are many ways to install Planka, [check them out](https://docs.planka.clou
For configuration, please see the [configuration section](https://docs.planka.cloud/docs/category/configuration). For configuration, please see the [configuration section](https://docs.planka.cloud/docs/category/configuration).
## Mobile app
Here is the [mobile app repository](https://github.com/LouisHDev/planka_app) maintained by the community, where you can build an app for iOS and Android.
Alternatively, you can download the [Android APK](https://github.com/LouisHDev/planka_app/releases/latest/download/app-release.apk) directly.
## Contact ## Contact
- If you want to get a hosted version of Planka, you can contact us via email contact@planka.cloud - If you want to get a hosted version of Planka, you can contact us via email contact@planka.cloud

View File

@@ -15,13 +15,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # 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. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.2.10 version: 0.2.11
# This is the version number of the application being deployed. This version number should be # 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 # 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. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "1.23.1" appVersion: "1.23.2"
dependencies: dependencies:
- alias: postgresql - alias: postgresql

View File

@@ -28,7 +28,7 @@ const AttachmentAddStep = React.memo(({ onCreate, onClose }) => {
</Popup.Header> </Popup.Header>
<Popup.Content> <Popup.Content>
<Menu secondary vertical className={styles.menu}> <Menu secondary vertical className={styles.menu}>
<FilePicker onSelect={handleFileSelect}> <FilePicker multiple onSelect={handleFileSelect}>
<Menu.Item className={styles.menuItem}> <Menu.Item className={styles.menuItem}>
{t('common.fromComputer', { {t('common.fromComputer', {
context: 'title', context: 'title',

View File

@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import { closePopup } from '../../../lib/popup'; import { closePopup } from '../../../lib/popup';
import { useModal } from '../../../hooks'; import { useModal } from '../../../hooks';
import { isActiveTextElement } from '../../../utils/element-helpers';
import TextFileAddModal from './TextFileAddModal'; import TextFileAddModal from './TextFileAddModal';
import styles from './AttachmentAddZone.module.scss'; import styles from './AttachmentAddZone.module.scss';
@@ -24,13 +25,14 @@ const AttachmentAddZone = React.memo(({ children, onCreate }) => {
const handleDropAccepted = useCallback( const handleDropAccepted = useCallback(
(files) => { (files) => {
submit(files[0]); files.forEach((file) => {
submit(file);
});
}, },
[submit], [submit],
); );
const { getRootProps, getInputProps, isDragActive } = useDropzone({ const { getRootProps, getInputProps, isDragActive } = useDropzone({
multiple: false,
noClick: true, noClick: true,
noKeyboard: true, noKeyboard: true,
onDropAccepted: handleDropAccepted, onDropAccepted: handleDropAccepted,
@@ -49,38 +51,43 @@ const AttachmentAddZone = React.memo(({ children, onCreate }) => {
return; return;
} }
const file = event.clipboardData.files[0]; const { files, items } = event.clipboardData;
if (file) { if (files.length > 0) {
submit(file); [...files].forEach((file) => {
return; submit(file);
}
const item = event.clipboardData.items[0];
if (!item) {
return;
}
if (item.kind === 'file') {
submit(item.getAsFile());
return;
}
if (
['input', 'textarea'].includes(event.target.tagName.toLowerCase()) &&
event.target === document.activeElement
) {
return;
}
closePopup();
event.preventDefault();
item.getAsString((content) => {
openModal({
content,
}); });
return;
}
if (items.length === 0) {
return;
}
if (items[0].kind === 'string') {
if (isActiveTextElement(event.target)) {
return;
}
closePopup();
event.preventDefault();
items[0].getAsString((content) => {
openModal({
content,
});
});
return;
}
[...items].forEach((item) => {
if (item.kind !== 'file') {
return;
}
submit(item.getAsFile());
}); });
}; };

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import styles from './FilePicker.module.css'; import styles from './FilePicker.module.css';
const FilePicker = React.memo(({ children, accept, onSelect }) => { const FilePicker = React.memo(({ children, accept, multiple, onSelect }) => {
const field = useRef(null); const field = useRef(null);
const handleTriggerClick = useCallback(() => { const handleTriggerClick = useCallback(() => {
@@ -12,11 +12,11 @@ const FilePicker = React.memo(({ children, accept, onSelect }) => {
const handleFieldChange = useCallback( const handleFieldChange = useCallback(
({ target }) => { ({ target }) => {
if (target.files[0]) { [...target.files].forEach((file) => {
onSelect(target.files[0]); onSelect(file);
});
target.value = null; // eslint-disable-line no-param-reassign target.value = null; // eslint-disable-line no-param-reassign
}
}, },
[onSelect], [onSelect],
); );
@@ -32,6 +32,7 @@ const FilePicker = React.memo(({ children, accept, onSelect }) => {
ref={field} ref={field}
type="file" type="file"
accept={accept} accept={accept}
multiple={multiple}
className={styles.field} className={styles.field}
onChange={handleFieldChange} onChange={handleFieldChange}
/> />
@@ -42,11 +43,13 @@ const FilePicker = React.memo(({ children, accept, onSelect }) => {
FilePicker.propTypes = { FilePicker.propTypes = {
children: PropTypes.element.isRequired, children: PropTypes.element.isRequired,
accept: PropTypes.string, accept: PropTypes.string,
multiple: PropTypes.bool,
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
}; };
FilePicker.defaultProps = { FilePicker.defaultProps = {
accept: undefined, accept: undefined,
multiple: false,
}; };
export default FilePicker; export default FilePicker;

View File

@@ -0,0 +1,253 @@
import dateFns from 'date-fns/locale/en-GB';
export default {
dateFns,
format: {
date: 'P',
time: 'p',
dateTime: '$t(format:date) $t(format:time)',
longDate: 'd MMM',
longDateTime: "d MMMM 'at' p",
fullDate: 'd MMM y',
fullDateTime: "d MMMM y 'at' p",
},
translation: {
common: {
aboutPlanka: 'About Planka',
account: 'Account',
actions: 'Actions',
addAttachment_title: 'Add Attachment',
addComment: 'Add comment',
addManager_title: 'Add Manager',
addMember_title: 'Add Member',
addUser_title: 'Add User',
administrator: 'Administrator',
all: 'All',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'All changes will be automatically saved<br />after connection restored.',
areYouSureYouWantToDeleteThisAttachment: 'Are you sure you want to delete this attachment?',
areYouSureYouWantToDeleteThisBoard: 'Are you sure you want to delete this board?',
areYouSureYouWantToDeleteThisCard: 'Are you sure you want to delete this card?',
areYouSureYouWantToDeleteThisComment: 'Are you sure you want to delete this comment?',
areYouSureYouWantToDeleteThisLabel: 'Are you sure you want to delete this label?',
areYouSureYouWantToDeleteThisList: 'Are you sure you want to delete this list?',
areYouSureYouWantToDeleteThisProject: 'Are you sure you want to delete this project?',
areYouSureYouWantToDeleteThisTask: 'Are you sure you want to delete this task?',
areYouSureYouWantToDeleteThisUser: 'Are you sure you want to delete this user?',
areYouSureYouWantToLeaveBoard: 'Are you sure you want to leave the board?',
areYouSureYouWantToLeaveProject: 'Are you sure you want to leave the project?',
areYouSureYouWantToRemoveThisManagerFromProject:
'Are you sure you want to remove this manager from the project?',
areYouSureYouWantToRemoveThisMemberFromBoard:
'Are you sure you want to remove this member from the board?',
attachment: 'Attachment',
attachments: 'Attachments',
authentication: 'Authentication',
background: 'Background',
board: 'Board',
boardNotFound_title: 'Board Not Found',
canComment: 'Can comment',
canEditContentOfBoard: 'Can edit the content of the board.',
canOnlyViewBoard: 'Can only view the board.',
cardActions_title: 'Card Actions',
cardNotFound_title: 'Card Not Found',
cardOrActionAreDeleted: 'Card or action are deleted.',
color: 'Color',
copy_inline: 'copy',
createBoard_title: 'Create Board',
createLabel_title: 'Create Label',
createNewOneOrSelectExistingOne: 'Create a new one or select<br />an existing one.',
createProject_title: 'Create Project',
createTextFile_title: 'Create Text File',
currentPassword: 'Current password',
dangerZone_title: 'Danger Zone',
date: 'Date',
dueDate: 'Due date',
dueDate_title: 'Due Date',
deleteAttachment_title: 'Delete Attachment',
deleteBoard_title: 'Delete Board',
deleteCard_title: 'Delete Card',
deleteComment_title: 'Delete Comment',
deleteLabel_title: 'Delete Label',
deleteList_title: 'Delete List',
deleteProject_title: 'Delete Project',
deleteTask_title: 'Delete Task',
deleteUser_title: 'Delete User',
description: 'Description',
detectAutomatically: 'Detect automatically',
dropFileToUpload: 'Drop file to upload',
editor: 'Editor',
editAttachment_title: 'Edit Attachment',
editAvatar_title: 'Edit Avatar',
editBoard_title: 'Edit Board',
editDueDate_title: 'Edit Due Date',
editEmail_title: 'Edit E-mail',
editInformation_title: 'Edit Information',
editLabel_title: 'Edit Label',
editPassword_title: 'Edit Password',
editPermissions_title: 'Edit Permissions',
editStopwatch_title: 'Edit Stopwatch',
editUsername_title: 'Edit Username',
email: 'E-mail',
emailAlreadyInUse: 'E-mail already in use',
enterCardTitle: 'Enter card title... [Ctrl+Enter] to auto-open.',
enterDescription: 'Enter description...',
enterFilename: 'Enter filename',
enterListTitle: 'Enter list title...',
enterProjectTitle: 'Enter project title',
enterTaskDescription: 'Enter task description...',
filterByLabels_title: 'Filter By Labels',
filterByMembers_title: 'Filter By Members',
fromComputer_title: 'From Computer',
fromTrello: 'From Trello',
general: 'General',
hours: 'Hours',
importBoard_title: 'Import Board',
invalidCurrentPassword: 'Invalid current password',
labels: 'Labels',
language: 'Language',
leaveBoard_title: 'Leave Board',
leaveProject_title: 'Leave Project',
linkIsCopied: 'Link is copied',
list: 'List',
listActions_title: 'List Actions',
managers: 'Managers',
managerActions_title: 'Manager Actions',
members: 'Members',
memberActions_title: 'Member Actions',
minutes: 'Minutes',
moveCard_title: 'Move Card',
name: 'Name',
newestFirst: 'Newest first',
newEmail: 'New e-mail',
newPassword: 'New password',
newUsername: 'New username',
noConnectionToServer: 'No connection to server',
noBoards: 'No boards',
noLists: 'No lists',
noProjects: 'No projects',
notifications: 'Notifications',
noUnreadNotifications: 'No unread notifications.',
oldestFirst: 'Oldest first',
openBoard_title: 'Open Board',
optional_inline: 'optional',
organization: 'Organization',
phone: 'Phone',
preferences: 'Preferences',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: press Ctrl-V (Cmd-V on Mac) to add an attachment from the clipboard.',
project: 'Project',
projectNotFound_title: 'Project Not Found',
removeManager_title: 'Remove Manager',
removeMember_title: 'Remove Member',
searchLabels: 'Search labels...',
searchMembers: 'Search members...',
searchUsers: 'Search users...',
searchCards: 'Search cards...',
seconds: 'Seconds',
selectBoard: 'Select board',
selectList: 'Select list',
selectPermissions_title: 'Select Permissions',
selectProject: 'Select project',
settings: 'Settings',
sortList_title: 'Sort List',
stopwatch: 'Stopwatch',
subscribeToMyOwnCardsByDefault: 'Subscribe to my own cards by default',
taskActions_title: 'Task Actions',
tasks: 'Tasks',
thereIsNoPreviewAvailableForThisAttachment:
'There is no preview available for this attachment.',
time: 'Time',
title: 'Title',
userActions_title: 'User Actions',
userAddedThisCardToList: '<0>{{user}}</0><1> added this card to {{list}}</1>',
userLeftNewCommentToCard: '{{user}} left a new comment «{{comment}}» to <2>{{card}}</2>',
userMovedCardFromListToList: '{{user}} moved <2>{{card}}</2> from {{fromList}} to {{toList}}',
userMovedThisCardFromListToList:
'<0>{{user}}</0><1> moved this card from {{fromList}} to {{toList}}</1>',
username: 'Username',
usernameAlreadyInUse: 'Username already in use',
users: 'Users',
version: 'Version',
viewer: 'Viewer',
writeComment: 'Write a comment...',
},
action: {
addAnotherCard: 'Add another card',
addAnotherList: 'Add another list',
addAnotherTask: 'Add another task',
addCard: 'Add card',
addCard_title: 'Add Card',
addComment: 'Add comment',
addList: 'Add list',
addMember: 'Add member',
addMoreDetailedDescription: 'Add more detailed description',
addTask: 'Add task',
addToCard: 'Add to card',
addUser: 'Add user',
copyLink_title: 'Copy Link',
createBoard: 'Create board',
createFile: 'Create file',
createLabel: 'Create label',
createNewLabel: 'Create new label',
createProject: 'Create project',
delete: 'Delete',
deleteAttachment: 'Delete attachment',
deleteAvatar: 'Delete avatar',
deleteBoard: 'Delete board',
deleteCard: 'Delete card',
deleteCard_title: 'Delete Card',
deleteComment: 'Delete comment',
deleteImage: 'Delete image',
deleteLabel: 'Delete label',
deleteList: 'Delete list',
deleteList_title: 'Delete List',
deleteProject: 'Delete project',
deleteProject_title: 'Delete Project',
deleteTask: 'Delete task',
deleteTask_title: 'Delete Task',
deleteUser: 'Delete user',
duplicate: 'Duplicate',
duplicateCard_title: 'Duplicate Card',
edit: 'Edit',
editDueDate_title: 'Edit Due Date',
editDescription_title: 'Edit Description',
editEmail_title: 'Edit E-mail',
editInformation_title: 'Edit Information',
editPassword_title: 'Edit Password',
editPermissions: 'Edit permissions',
editStopwatch_title: 'Edit Stopwatch',
editTitle_title: 'Edit Title',
editUsername_title: 'Edit Username',
hideDetails: 'Hide details',
import: 'Import',
leaveBoard: 'Leave board',
leaveProject: 'Leave project',
logOut_title: 'Log Out',
makeCover_title: 'Make Cover',
move: 'Move',
moveCard_title: 'Move Card',
remove: 'Remove',
removeBackground: 'Remove background',
removeCover_title: 'Remove Cover',
removeFromBoard: 'Remove from board',
removeFromProject: 'Remove from project',
removeManager: 'Remove manager',
removeMember: 'Remove member',
save: 'Save',
showAllAttachments: 'Show all attachments ({{hidden}} hidden)',
showDetails: 'Show details',
showFewerAttachments: 'Show fewer attachments',
sortList_title: 'Sort List',
start: 'Start',
stop: 'Stop',
subscribe: 'Subscribe',
unsubscribe: 'Unsubscribe',
uploadNewAvatar: 'Upload new avatar',
uploadNewImage: 'Upload new image',
},
},
};

View File

@@ -0,0 +1,8 @@
import login from './login';
export default {
language: 'en-GB',
country: 'gb',
name: 'English',
embeddedLocale: login,
};

View File

@@ -0,0 +1,23 @@
export default {
translation: {
common: {
emailOrUsername: 'E-mail or username',
invalidEmailOrUsername: 'Invalid e-mail or username',
invalidCredentials: 'Invalid credentials',
invalidPassword: 'Invalid password',
logInToPlanka: 'Log in to Planka',
noInternetConnection: 'No internet connection',
pageNotFound_title: 'Page Not Found',
password: 'Password',
projectManagement: 'Project management',
serverConnectionFailed: 'Server connection failed',
unknownError: 'Unknown error, try again later',
useSingleSignOn: 'Use single sign-on',
},
action: {
logIn: 'Log in',
logInWithSSO: 'Log in with SSO',
},
},
};

View File

@@ -3,6 +3,7 @@ import bgBG from './bg-BG';
import csCZ from './cs-CZ'; import csCZ from './cs-CZ';
import daDK from './da-DK'; import daDK from './da-DK';
import deDE from './de-DE'; import deDE from './de-DE';
import enGB from './en-GB';
import enUS from './en-US'; import enUS from './en-US';
import esES from './es-ES'; import esES from './es-ES';
import faIR from './fa-IR'; import faIR from './fa-IR';
@@ -31,6 +32,7 @@ const locales = [
csCZ, csCZ,
daDK, daDK,
deDE, deDE,
enGB,
enUS, enUS,
esES, esES,
faIR, faIR,

View File

@@ -1,5 +1,8 @@
// eslint-disable-next-line import/prefer-default-export
export const focusEnd = (element) => { export const focusEnd = (element) => {
element.focus(); element.focus();
element.setSelectionRange(element.value.length + 1, element.value.length + 1); element.setSelectionRange(element.value.length + 1, element.value.length + 1);
}; };
export const isActiveTextElement = (element) =>
['input', 'textarea'].includes(element.tagName.toLowerCase()) &&
element === document.activeElement;

View File

@@ -1 +1 @@
export default '1.23.1'; export default '1.23.2';

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "planka", "name": "planka",
"version": "1.23.1", "version": "1.23.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "planka", "name": "planka",
"version": "1.23.1", "version": "1.23.2",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "planka", "name": "planka",
"version": "1.23.1", "version": "1.23.2",
"private": true, "private": true,
"homepage": "https://plankanban.github.io/planka", "homepage": "https://plankanban.github.io/planka",
"repository": { "repository": {

View File

@@ -11,6 +11,7 @@ const LANGUAGES = [
'cs-CZ', 'cs-CZ',
'da-DK', 'da-DK',
'de-DE', 'de-DE',
'en-GB',
'en-US', 'en-US',
'es-ES', 'es-ES',
'fa-IR', 'fa-IR',