Compare commits

...
12 Commits
35 changed files with 500 additions and 363 deletions
@@ -142,15 +142,20 @@ const UserAddStep = React.memo(
onChange={handleFieldChange}
/>
<div className={styles.text}>{t('common.password')}</div>
<Input.Password
fluid
ref={passwordField}
name="password"
value={data.password}
readOnly={isSubmitting}
className={styles.field}
onChange={handleFieldChange}
/>
<div className={styles.field}>
<Input.Password
fluid
ref={passwordField}
name="password"
value={data.password}
readOnly={isSubmitting}
className={styles.field}
onChange={handleFieldChange}
/>
<div className={styles.note}>
{t('common.mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber')}
</div>
</div>
<div className={styles.text}>{t('common.name')}</div>
<Input
fluid
@@ -3,6 +3,12 @@
margin-bottom: 8px;
}
.note {
font-size: 11px;
margin-top: 4px;
opacity: 0.5;
}
.text {
color: #444444;
font-size: 12px;
@@ -7,6 +7,7 @@ import { useDidUpdate, usePrevious, useToggle } from '../../lib/hooks';
import { Input, Popup } from '../../lib/custom-ui';
import { useForm } from '../../hooks';
import { isPassword } from '../../utils/validator';
import styles from './UserPasswordEditStep.module.scss';
@@ -56,7 +57,7 @@ const UserPasswordEditStep = React.memo(
const currentPasswordField = useRef(null);
const handleSubmit = useCallback(() => {
if (!data.password) {
if (!data.password || !isPassword(data.password)) {
passwordField.current.select();
return;
}
@@ -112,14 +113,18 @@ const UserPasswordEditStep = React.memo(
)}
<Form onSubmit={handleSubmit}>
<div className={styles.text}>{t('common.newPassword')}</div>
<Input.Password
fluid
ref={passwordField}
name="password"
value={data.password}
className={styles.field}
onChange={handleFieldChange}
/>
<div className={styles.field}>
<Input.Password
fluid
ref={passwordField}
name="password"
value={data.password}
onChange={handleFieldChange}
/>
<div className={styles.note}>
{t('common.mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber')}
</div>
</div>
{usePasswordConfirmation && (
<>
<div className={styles.text}>{t('common.currentPassword')}</div>
@@ -3,6 +3,12 @@
margin-bottom: 8px;
}
.note {
font-size: 11px;
margin-top: 4px;
opacity: 0.5;
}
.text {
color: #444444;
font-size: 12px;
@@ -1,6 +1,7 @@
:global(#app) {
.action {
border: none;
border-radius: 0.28571429rem;
display: inline-block;
height: 36px;
overflow: hidden;
@@ -6,6 +6,8 @@ import remarkBreaks from 'remark-breaks';
import './Markdown.module.scss'; // FIXME: import as styles?
const ABSOLUTE_URL_REGEX = /^(?:https?:)?\/\//i;
const Markdown = React.memo(({ linkStopPropagation, ...props }) => {
const handleLinkClick = useCallback((event) => {
event.stopPropagation();
@@ -16,25 +18,30 @@ const Markdown = React.memo(({ linkStopPropagation, ...props }) => {
jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions,
react/jsx-props-no-spreading */
({ node, ...linkProps }) => <a {...linkProps} onClick={handleLinkClick} />,
({ node, ...linkProps }) => (
<a
{...linkProps}
rel={
ABSOLUTE_URL_REGEX.test(linkProps.href) && linkProps.target === '_blank'
? 'noreferrer'
: 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 */
[handleLinkClick],
[linkStopPropagation, handleLinkClick],
);
let components;
if (linkStopPropagation) {
components = {
a: linkRenderer,
};
}
return (
<ReactMarkdown
{...props} // eslint-disable-line react/jsx-props-no-spreading
components={components}
components={{
a: linkRenderer,
}}
remarkPlugins={[remarkGfm, remarkBreaks]}
className="markdown-body"
/>
+1 -1
View File
@@ -117,7 +117,7 @@ export default {
phone: 'Telefon',
preferences: 'Volby',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: dejte Ctrl-V (Cmd-V na Mac) pro vložení přílohy ze schránky.',
'Tip: dejte Ctrl-V (Cmd-V na Mac) pro vložení přílohy ze schránky',
project: 'Projekt',
projectNotFound_title: 'Projekt nenalezen',
removeManager_title: 'Odstranit vedoucího',
+2 -2
View File
@@ -23,7 +23,7 @@ export default {
administrator: 'Administrator',
all: 'Alle',
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
'Alle Änderungen werden automatisch gespeichert,<br />sobald die Verbindung wiederhergestellt wurde.',
'Alle Änderungen werden automatisch gespeichert,<br />sobald die Verbindung wiederhergestellt wurde',
areYouSureYouWantToDeleteThisAttachment:
'Sind Sie sicher, dass Sie diesen Anhang löschen möchten?',
areYouSureYouWantToDeleteThisBoard: 'Sind Sie sicher, dass Sie dieses Board löschen möchten?',
@@ -123,7 +123,7 @@ export default {
phone: 'Telefon',
preferences: 'Voreinstellungen',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tipp: Drücken Sie STRG-V (Cmd-V auf Mac), um einen Anhang aus der Zwischenablage hinzuzufügen.',
'Tipp: Drücken Sie STRG-V (Cmd-V auf Mac), um einen Anhang aus der Zwischenablage hinzuzufügen',
project: 'Projekt',
projectNotFound_title: 'Projekt nicht gefunden',
removeManager_title: 'Manager entfernen',
+3 -1
View File
@@ -105,6 +105,8 @@ export default {
members: 'Members',
minutes: 'Minutes',
moveCard_title: 'Move Card',
mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber:
'Must be at least 6 characters long and contain at least one letter and number',
name: 'Name',
newEmail: 'New e-mail',
newPassword: 'New password',
@@ -121,7 +123,7 @@ export default {
phone: 'Phone',
preferences: 'Preferences',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: press Ctrl-V (Cmd-V on Mac) to add an attachment from the clipboard.',
'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',
+2 -2
View File
@@ -106,7 +106,7 @@ export default {
phone: 'Teléfono',
preferences: 'Preferencias',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: presiona Ctrl-V (Cmd-V en Mac) para añadir adjuntos desde el portapapeles.',
'Tip: presiona Ctrl-V (Cmd-V en Mac) para añadir adjuntos desde el portapapeles',
project: 'Proyecto',
projectNotFound_title: 'Proyecto no encontrado',
removeMember_title: 'Remover Miembro',
@@ -115,7 +115,7 @@ export default {
selectList: 'Seleccionar Lista',
selectProject: 'Seleccionar Proyecto',
settings: 'Configuraciones',
subscribeToMyOwnCardsByDefault: 'Suscribirme a mis propias tarjetas por defecto.',
subscribeToMyOwnCardsByDefault: 'Suscribirme a mis propias tarjetas por defecto',
taskActions_title: 'Acciones de Tareas',
tasks: 'Tareas',
time: 'Tiempo',
+1 -1
View File
@@ -121,7 +121,7 @@ export default {
phone: 'Telefono',
preferences: 'Preferenze',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: prmi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard.',
'Tip: prmi Ctrl-V (Cmd-V on Mac) per aggiungere un allegato dalla clipboard',
project: 'Progetto',
projectNotFound_title: 'Progetto non trovato',
removeManager_title: 'Rimuovi Manager',
+1 -1
View File
@@ -117,7 +117,7 @@ export default {
phone: '電話番号',
preferences: '環境設定',
pressPasteShortcutToAddAttachmentFromClipboard:
'ヒント: Ctrl-V(MacではCmd-V)を押して、クリップボードから添付ファイルを追加します。',
'ヒント: Ctrl-V(MacではCmd-V)を押して、クリップボードから添付ファイルを追加します',
project: 'プロジェクト',
projectNotFound_title: 'プロジェクトがありません',
removeManager_title: 'マネージャーを削除',
+1 -1
View File
@@ -106,7 +106,7 @@ export default {
phone: 'Telefon',
preferences: 'Preferencje',
pressPasteShortcutToAddAttachmentFromClipboard:
'Podpowiedź: naciśnij Ctrl-V (Cmd-V na Macu) aby dodać załącznik ze schowka.',
'Podpowiedź: naciśnij Ctrl-V (Cmd-V na Macu) aby dodać załącznik ze schowka',
project: 'Projekt',
projectNotFound_title: 'Projekt Nie Znaleziony',
removeMember_title: 'Usuń Członka',
+3 -1
View File
@@ -100,6 +100,8 @@ export default {
members: 'Участники',
minutes: 'Минуты',
moveCard: 'Перемещение карточки',
mustBeAtLeast6CharactersLongAndContainAtLeastOneLetterAndNumber:
'Должен быть не менее 6 символов и содержать хотя бы одну букву и цифру',
name: 'Имя',
newEmail: 'Новый e-mail',
newPassword: 'Новый пароль',
@@ -116,7 +118,7 @@ export default {
phone: 'Телефон',
preferences: 'Предпочтения',
pressPasteShortcutToAddAttachmentFromClipboard:
'Совет: нажмите Ctrl-V (Cmd-V на Mac), чтобы добавить вложение из буфера обмена.',
'Совет: нажмите Ctrl-V (Cmd-V на Mac), чтобы добавить вложение из буфера обмена',
project: 'Проект',
projectActions: 'Действия с проектом',
projectNotFound: 'Проект не найден',
+1 -1
View File
@@ -118,7 +118,7 @@ export default {
phone: 'Telefón',
preferences: 'Voľby',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tip: stlačte Ctrl-V (Cmd-V na Mac) pre vloženie prílohy zo schránky.',
'Tip: stlačte Ctrl-V (Cmd-V na Mac) pre vloženie prílohy zo schránky',
project: 'Projekt',
projectNotFound_title: 'Projekt neexistuje',
removeManager_title: 'Odstrániť správcu',
+1 -1
View File
@@ -119,7 +119,7 @@ export default {
phone: 'Telefon',
preferences: 'Preferenser',
pressPasteShortcutToAddAttachmentFromClipboard:
'Tips: tryck på Ctrl-V (Cmd-V på Mac) för att lägga till en bilaga från urklipp.',
'Tips: tryck på Ctrl-V (Cmd-V på Mac) för att lägga till en bilaga från urklipp',
project: 'Projekt',
projectNotFound_title: 'Projekt Ej Funnet',
removeManager_title: 'Ta Bort Projektledare',
+1 -1
View File
@@ -112,7 +112,7 @@ export default {
phone: 'Telefon',
preferences: 'Afzalliklar',
pressPasteShortcutToAddAttachmentFromClipboard:
"Tip: Buferdan ilova qo'shish uchun Ctrl-V (Mac da Cmd-V) ni bosing.",
"Tip: Buferdan ilova qo'shish uchun Ctrl-V (Mac da Cmd-V) ni bosing",
project: 'Loyiha',
projectNotFound_title: 'Loyiha Topilmadi',
removeManager_title: "Boshqaruvchini O'chirish",
+1 -1
View File
@@ -110,7 +110,7 @@ export default {
phone: '电话',
preferences: '偏好',
pressPasteShortcutToAddAttachmentFromClipboard:
'提示: 点击 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件.',
'提示: 点击 Ctrl-V (Mac: Cmd-V) 从剪切板添加附件',
project: '项目',
projectNotFound_title: '项目未找到',
removeManager_title: '删除管理员',
+5 -1
View File
@@ -1,6 +1,10 @@
const PASSWORD_REGEX = /^(?=.*[A-Za-z])(?=.*\d).+$/;
const USERNAME_REGEX = /^[a-zA-Z0-9]+((_|\.)?[a-zA-Z0-9])*$/;
// eslint-disable-next-line import/prefer-default-export
export const isPassword = (string) => {
return string.length >= 6 && PASSWORD_REGEX.test(string);
};
export const isUsername = (string) => {
return string.length >= 3 && string.length <= 16 && USERNAME_REGEX.test(string);
};
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "planka",
"version": "1.7.1",
"version": "1.7.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "planka",
"version": "1.7.1",
"version": "1.7.5",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "planka",
"version": "1.7.1",
"version": "1.7.5",
"private": true,
"homepage": "https://plankanban.github.io/planka",
"repository": {
+39 -26
View File
@@ -1,3 +1,6 @@
const util = require('util');
const { v4: uuid } = require('uuid');
const Errors = {
NOT_ENOUGH_RIGHTS: {
notEnoughRights: 'Not enough rights',
@@ -5,6 +8,9 @@ const Errors = {
CARD_NOT_FOUND: {
cardNotFound: 'Card not found',
},
NO_FILE_WAS_UPLOADED: {
noFileWasUploaded: 'No file was uploaded',
},
};
module.exports = {
@@ -27,6 +33,9 @@ module.exports = {
cardNotFound: {
responseType: 'notFound',
},
noFileWasUploaded: {
responseType: 'unprocessableEntity',
},
uploadError: {
responseType: 'unprocessableEntity',
},
@@ -52,33 +61,37 @@ module.exports = {
throw Errors.NOT_ENOUGH_RIGHTS;
}
this.req
.file('file')
.upload(sails.helpers.utils.createAttachmentReceiver(), async (error, files) => {
if (error) {
return exits.uploadError(error.message);
}
const upload = util.promisify((options, callback) =>
this.req.file('file').upload(options, (error, files) => callback(error, files)),
);
if (files.length === 0) {
return exits.uploadError('No file was uploaded');
}
const file = files[0];
const attachment = await sails.helpers.attachments.createOne(
{
...file.extra,
filename: file.filename,
},
currentUser,
card,
inputs.requestId,
this.req,
);
return exits.success({
item: attachment.toJSON(),
});
let files;
try {
files = await upload({
saveAs: uuid(),
maxBytes: null,
});
} catch (error) {
return exits.uploadError(error.message); // TODO: add error
}
if (files.length === 0) {
throw Errors.NO_FILE_WAS_UPLOADED;
}
const file = _.last(files);
const fileData = await sails.helpers.attachments.processUploadedFile(file);
const attachment = await sails.helpers.attachments.createOne(
fileData,
currentUser,
card,
inputs.requestId,
this.req,
);
return exits.success({
item: attachment,
});
},
};
+3 -3
View File
@@ -23,7 +23,7 @@ module.exports = {
},
},
async fn(inputs, exits) {
async fn(inputs) {
const { currentUser } = this.req;
const { board } = await sails.helpers.boards
@@ -61,7 +61,7 @@ module.exports = {
const tasks = await sails.helpers.cards.getTasks(cardIds);
const attachments = await sails.helpers.cards.getAttachments(cardIds);
return exits.success({
return {
items: cards,
included: {
cardMemberships,
@@ -69,6 +69,6 @@ module.exports = {
tasks,
attachments,
},
});
};
},
};
@@ -1,7 +1,17 @@
const util = require('util');
const rimraf = require('rimraf');
const { v4: uuid } = require('uuid');
const Errors = {
PROJECT_NOT_FOUND: {
projectNotFound: 'Project not found',
},
NO_FILE_WAS_UPLOADED: {
noFileWasUploaded: 'No file was uploaded',
},
FILE_IS_NOT_IMAGE: {
fileIsNotImage: 'File is not image',
},
};
module.exports = {
@@ -17,6 +27,12 @@ module.exports = {
projectNotFound: {
responseType: 'notFound',
},
noFileWasUploaded: {
responseType: 'unprocessableEntity',
},
fileIsNotImage: {
responseType: 'unprocessableEntity',
},
uploadError: {
responseType: 'unprocessableEntity',
},
@@ -37,32 +53,52 @@ module.exports = {
throw Errors.PROJECT_NOT_FOUND; // Forbidden
}
this.req
.file('file')
.upload(sails.helpers.utils.createProjectBackgroundImageReceiver(), async (error, files) => {
if (error) {
return exits.uploadError(error.message);
}
const upload = util.promisify((options, callback) =>
this.req.file('file').upload(options, (error, files) => callback(error, files)),
);
if (files.length === 0) {
return exits.uploadError('No file was uploaded');
}
project = await sails.helpers.projects.updateOne(
project,
{
backgroundImageDirname: files[0].extra.dirname,
},
this.req,
);
if (!project) {
throw Errors.PROJECT_NOT_FOUND;
}
return exits.success({
item: project.toJSON(),
});
let files;
try {
files = await upload({
saveAs: uuid(),
maxBytes: null,
});
} catch (error) {
return exits.uploadError(error.message); // TODO: add error
}
if (files.length === 0) {
throw Errors.NO_FILE_WAS_UPLOADED;
}
const file = _.last(files);
const fileData = await sails.helpers.projects
.processUploadedBackgroundImageFile(file)
.intercept('fileIsNotImage', () => {
try {
rimraf.sync(file.fd);
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
return Errors.FILE_IS_NOT_IMAGE;
});
project = await sails.helpers.projects.updateOne(
project,
{
backgroundImageDirname: fileData.dirname,
},
this.req,
);
if (!project) {
throw Errors.PROJECT_NOT_FOUND;
}
return exits.success({
item: project,
});
},
};
+2
View File
@@ -16,6 +16,8 @@ module.exports = {
},
password: {
type: 'string',
minLength: 6,
regex: /^(?=.*[A-Za-z])(?=.*\d).+$/,
required: true,
},
name: {
+62 -26
View File
@@ -1,7 +1,17 @@
const util = require('util');
const rimraf = require('rimraf');
const { v4: uuid } = require('uuid');
const Errors = {
USER_NOT_FOUND: {
userNotFound: 'User not found',
},
NO_FILE_WAS_UPLOADED: {
noFileWasUploaded: 'No file was uploaded',
},
FILE_IS_NOT_IMAGE: {
fileIsNotImage: 'File is not image',
},
};
module.exports = {
@@ -17,6 +27,12 @@ module.exports = {
userNotFound: {
responseType: 'notFound',
},
noFileWasUploaded: {
responseType: 'unprocessableEntity',
},
fileIsNotImage: {
responseType: 'unprocessableEntity',
},
uploadError: {
responseType: 'unprocessableEntity',
},
@@ -38,33 +54,53 @@ module.exports = {
user = currentUser;
}
this.req
.file('file')
.upload(sails.helpers.utils.createUserAvatarReceiver(), async (error, files) => {
if (error) {
return exits.uploadError(error.message);
}
const upload = util.promisify((options, callback) =>
this.req.file('file').upload(options, (error, files) => callback(error, files)),
);
if (files.length === 0) {
return exits.uploadError('No file was uploaded');
}
user = await sails.helpers.users.updateOne(
user,
{
avatarDirname: files[0].extra.dirname,
},
currentUser,
this.req,
);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
return exits.success({
item: user.toJSON(),
});
let files;
try {
files = await upload({
saveAs: uuid(),
maxBytes: null,
});
} catch (error) {
return exits.uploadError(error.message); // TODO: add error
}
if (files.length === 0) {
throw Errors.NO_FILE_WAS_UPLOADED;
}
const file = _.last(files);
const fileData = await sails.helpers.users
.processUploadedAvatarFile(file)
.intercept('fileIsNotImage', () => {
try {
rimraf.sync(file.fd);
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
return Errors.FILE_IS_NOT_IMAGE;
});
user = await sails.helpers.users.updateOne(
user,
{
avatarDirname: fileData.dirname,
},
currentUser,
this.req,
);
if (!user) {
throw Errors.USER_NOT_FOUND;
}
return exits.success({
item: user,
});
},
};
@@ -18,6 +18,8 @@ module.exports = {
},
password: {
type: 'string',
minLength: 6,
regex: /^(?=.*[A-Za-z])(?=.*\d).+$/,
required: true,
},
currentPassword: {
@@ -0,0 +1,77 @@
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const moveFile = require('move-file');
const filenamify = require('filenamify');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
module.exports = {
inputs: {
file: {
type: 'json',
required: true,
},
},
async fn(inputs) {
const dirname = uuid();
// FIXME: https://github.com/sindresorhus/filenamify/issues/13
const filename = filenamify(inputs.file.filename);
const rootPath = path.join(sails.config.custom.attachmentsPath, dirname);
const filePath = path.join(rootPath, filename);
fs.mkdirSync(rootPath);
await moveFile(inputs.file.fd, filePath);
const image = sharp(filePath);
let metadata;
try {
metadata = await image.metadata();
} catch (error) {} // eslint-disable-line no-empty
const fileData = {
dirname,
filename,
image: null,
name: inputs.file.filename,
};
if (metadata && !['svg', 'pdf'].includes(metadata.format)) {
const thumbnailsPath = path.join(rootPath, 'thumbnails');
fs.mkdirSync(thumbnailsPath);
try {
await image
.resize(
metadata.height > metadata.width
? {
width: 256,
height: 320,
}
: {
width: 256,
},
)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toFile(path.join(thumbnailsPath, 'cover-256.jpg'));
fileData.image = _.pick(metadata, ['width', 'height']);
} catch (error1) {
try {
rimraf.sync(thumbnailsPath);
} catch (error2) {
console.warn(error2.stack); // eslint-disable-line no-console
}
}
}
return fileData;
},
};
@@ -0,0 +1,63 @@
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
module.exports = {
inputs: {
file: {
type: 'json',
required: true,
},
},
exits: {
fileIsNotImage: {},
},
async fn(inputs) {
const image = sharp(inputs.file.fd);
try {
await image.metadata();
} catch (error) {
throw 'fileIsNotImage';
}
const dirname = uuid();
const rootPath = path.join(sails.config.custom.projectBackgroundImagesPath, dirname);
fs.mkdirSync(rootPath);
try {
await image.jpeg().toFile(path.join(rootPath, 'original.jpg'));
await image
.resize(336, 200)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toFile(path.join(rootPath, 'cover-336.jpg'));
} catch (error1) {
try {
rimraf.sync(rootPath);
} catch (error2) {
console.warn(error2.stack); // eslint-disable-line no-console
}
throw 'fileIsNotImage';
}
try {
rimraf.sync(inputs.file.fd);
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
return {
dirname,
};
},
};
@@ -0,0 +1,68 @@
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
module.exports = {
inputs: {
file: {
type: 'json',
required: true,
},
},
exits: {
fileIsNotImage: {},
},
async fn(inputs) {
const image = sharp(inputs.file.fd);
try {
await image.metadata();
} catch (error) {
throw 'fileIsNotImage';
}
const dirname = uuid();
const rootPath = path.join(sails.config.custom.userAvatarsPath, dirname);
fs.mkdirSync(rootPath);
try {
await image
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toFile(path.join(rootPath, 'original.jpg'));
await image
.resize(100, 100)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toFile(path.join(rootPath, 'square-100.jpg'));
} catch (error1) {
try {
rimraf.sync(rootPath);
} catch (error2) {
console.warn(error2.stack); // eslint-disable-line no-console
}
throw 'fileIsNotImage';
}
try {
rimraf.sync(inputs.file.fd);
} catch (error) {
console.warn(error.stack); // eslint-disable-line no-console
}
return {
dirname,
};
},
};
@@ -1,102 +0,0 @@
const fs = require('fs');
const path = require('path');
const util = require('util');
const stream = require('stream');
const streamToArray = require('stream-to-array');
const filenamify = require('filenamify');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
const writeFile = util.promisify(fs.writeFile);
module.exports = {
sync: true,
fn() {
const receiver = stream.Writable({
objectMode: true,
});
let firstFileHandled = false;
// eslint-disable-next-line no-underscore-dangle
receiver._write = async (file, receiverEncoding, done) => {
if (firstFileHandled) {
file.pipe(new stream.Writable());
return done();
}
firstFileHandled = true;
const buffer = await streamToArray(file).then((parts) =>
Buffer.concat(parts.map((part) => (Buffer.isBuffer(part) ? part : Buffer.from(part)))),
);
try {
const dirname = uuid();
// FIXME: https://github.com/sindresorhus/filenamify/issues/13
const filename = filenamify(file.filename);
const rootPath = path.join(sails.config.custom.attachmentsPath, dirname);
fs.mkdirSync(rootPath);
await writeFile(path.join(rootPath, filename), buffer);
const image = sharp(buffer);
let metadata;
try {
metadata = await image.metadata();
} catch (error) {} // eslint-disable-line no-empty
const extra = {
dirname,
name: file.filename,
};
if (metadata && !['svg', 'pdf'].includes(metadata.format)) {
let cover256Buffer;
if (metadata.height > metadata.width) {
cover256Buffer = await image
.resize(256, 320)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toBuffer();
} else {
cover256Buffer = await image
.resize({
width: 256,
})
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toBuffer();
}
const thumbnailsPath = path.join(rootPath, 'thumbnails');
fs.mkdirSync(thumbnailsPath);
await writeFile(path.join(thumbnailsPath, 'cover-256.jpg'), cover256Buffer);
extra.image = _.pick(metadata, ['width', 'height']);
} else {
extra.image = null;
}
Object.assign(file, {
filename,
extra,
});
return done();
} catch (error) {
return done(error);
}
};
return receiver;
},
};
@@ -1,65 +0,0 @@
const fs = require('fs');
const path = require('path');
const util = require('util');
const stream = require('stream');
const streamToArray = require('stream-to-array');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
const writeFile = util.promisify(fs.writeFile);
module.exports = {
sync: true,
fn() {
const receiver = stream.Writable({
objectMode: true,
});
let firstFileHandled = false;
// eslint-disable-next-line no-underscore-dangle
receiver._write = async (file, receiverEncoding, done) => {
if (firstFileHandled) {
file.pipe(new stream.Writable());
return done();
}
firstFileHandled = true;
const buffer = await streamToArray(file).then((parts) =>
Buffer.concat(parts.map((part) => (Buffer.isBuffer(part) ? part : Buffer.from(part)))),
);
try {
const originalBuffer = await sharp(buffer).jpeg().toBuffer();
const cover336Buffer = await sharp(buffer)
.resize(336, 200)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toBuffer();
const dirname = uuid();
const rootPath = path.join(sails.config.custom.projectBackgroundImagesPath, dirname);
fs.mkdirSync(rootPath);
await writeFile(path.join(rootPath, 'original.jpg'), originalBuffer);
await writeFile(path.join(rootPath, 'cover-336.jpg'), cover336Buffer);
// eslint-disable-next-line no-param-reassign
file.extra = {
dirname,
};
return done();
} catch (error) {
return done(error);
}
};
return receiver;
},
};
@@ -1,70 +0,0 @@
const fs = require('fs');
const path = require('path');
const util = require('util');
const stream = require('stream');
const streamToArray = require('stream-to-array');
const { v4: uuid } = require('uuid');
const sharp = require('sharp');
const writeFile = util.promisify(fs.writeFile);
module.exports = {
sync: true,
fn() {
const receiver = stream.Writable({
objectMode: true,
});
let firstFileHandled = false;
// eslint-disable-next-line no-underscore-dangle
receiver._write = async (file, receiverEncoding, done) => {
if (firstFileHandled) {
file.pipe(new stream.Writable());
return done();
}
firstFileHandled = true;
const buffer = await streamToArray(file).then((parts) =>
Buffer.concat(parts.map((part) => (Buffer.isBuffer(part) ? part : Buffer.from(part)))),
);
try {
const originalBuffer = await sharp(buffer)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toBuffer();
const square100Buffer = await sharp(buffer)
.resize(100, 100)
.jpeg({
quality: 100,
chromaSubsampling: '4:4:4',
})
.toBuffer();
const dirname = uuid();
const rootPath = path.join(sails.config.custom.userAvatarsPath, dirname);
fs.mkdirSync(rootPath);
await writeFile(path.join(rootPath, 'original.jpg'), originalBuffer);
await writeFile(path.join(rootPath, 'square-100.jpg'), square100Buffer);
// eslint-disable-next-line no-param-reassign
file.extra = {
dirname,
};
return done();
} catch (error) {
return done(error);
}
};
return receiver;
},
};
+38
View File
@@ -14,6 +14,7 @@
"knex": "^2.2.0",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"move-file": "^2.1.0",
"rimraf": "^3.0.2",
"sails": "^1.5.2",
"sails-hook-orm": "^4.0.1",
@@ -4398,6 +4399,28 @@
"node": "*"
}
},
"node_modules/move-file": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/move-file/-/move-file-2.1.0.tgz",
"integrity": "sha512-i9qLW6gqboJ5Ht8bauZi7KlTnQ3QFpBCvMvFfEcHADKgHGeJ9BZMO7SFCTwHPV9Qa0du9DYY1Yx3oqlGt30nXA==",
"dependencies": {
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=10.17"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/move-file/node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"engines": {
"node": ">=8"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -11370,6 +11393,21 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
},
"move-file": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/move-file/-/move-file-2.1.0.tgz",
"integrity": "sha512-i9qLW6gqboJ5Ht8bauZi7KlTnQ3QFpBCvMvFfEcHADKgHGeJ9BZMO7SFCTwHPV9Qa0du9DYY1Yx3oqlGt30nXA==",
"requires": {
"path-exists": "^4.0.0"
},
"dependencies": {
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
}
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+1
View File
@@ -46,6 +46,7 @@
"knex": "^2.2.0",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"move-file": "^2.1.0",
"rimraf": "^3.0.2",
"sails": "^1.5.2",
"sails-hook-orm": "^4.0.1",