Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01535ecf51 | ||
|
|
1217969e22 | ||
|
|
e410e21363 | ||
|
|
a0f584e814 | ||
|
|
135f5eccb5 | ||
|
|
10001db8cf | ||
|
|
7ee351fe30 | ||
|
|
73746b83d1 | ||
|
|
f9a7b38ce9 | ||
|
|
1f261fe7b6 | ||
|
|
80bee30463 | ||
|
|
a9957481b6 | ||
|
|
36b05a5478 | ||
|
|
af91512abf | ||
|
|
8fd0f682d9 | ||
|
|
9699fbe76a | ||
|
|
4176a62f1a | ||
|
|
c12f0844d7 | ||
|
|
e6644eb745 | ||
|
|
b2e1fba9a0 |
@@ -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: 0.2.7
|
||||
version: 0.2.8
|
||||
|
||||
# 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: "1.21.1"
|
||||
appVersion: "1.22.0"
|
||||
|
||||
dependencies:
|
||||
- alias: postgresql
|
||||
|
||||
@@ -11,6 +11,10 @@ spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "planka.selectorLabels" . | nindent 6 }}
|
||||
{{- if .Values.persistence.enabled }}
|
||||
strategy:
|
||||
type: Recreate
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
|
||||
Generated
+2052
-7107
File diff suppressed because it is too large
Load Diff
+12
-11
@@ -65,7 +65,7 @@
|
||||
"dequal": "^2.0.3",
|
||||
"easymde": "^2.18.0",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^23.12.2",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"initials": "^3.1.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
@@ -77,18 +77,18 @@
|
||||
"node-sass": "^9.0.0",
|
||||
"photoswipe": "^5.4.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react": "18.2.0",
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-datepicker": "^4.25.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-i18next": "^15.0.0",
|
||||
"react-i18next": "^15.0.2",
|
||||
"react-input-mask": "^2.0.4",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-photoswipe-gallery": "^2.2.7",
|
||||
"react-redux": "^8.1.3",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-simplemde-editor": "^5.2.0",
|
||||
"react-textarea-autosize": "^8.5.3",
|
||||
@@ -107,17 +107,18 @@
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^6.4.5",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"@testing-library/react": "^15.0.7",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"babel-preset-airbnb": "^5.0.0",
|
||||
"chai": "^4.4.1",
|
||||
"chai": "^4.5.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.34.2",
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.0",
|
||||
"eslint-plugin-react": "^7.36.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"react-test-renderer": "^18.2.0"
|
||||
"react-test-renderer": "18.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import http from './http';
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createAccessToken = (data, headers) => http.post('/access-tokens', data, headers);
|
||||
const createAccessToken = (data, headers) =>
|
||||
http.post('/access-tokens?withHttpOnlyToken=true', data, headers);
|
||||
|
||||
const exchangeForAccessTokenUsingOidc = (data, headers) =>
|
||||
http.post('/access-tokens/exchange-using-oidc', data, headers);
|
||||
http.post('/access-tokens/exchange-using-oidc?withHttpOnlyToken=true', data, headers);
|
||||
|
||||
const deleteCurrentAccessToken = (headers) =>
|
||||
socket.delete('/access-tokens/me', undefined, headers);
|
||||
const deleteCurrentAccessToken = (headers) => http.delete('/access-tokens/me', undefined, headers);
|
||||
|
||||
export default {
|
||||
createAccessToken,
|
||||
|
||||
@@ -5,7 +5,7 @@ import Config from '../constants/Config';
|
||||
const http = {};
|
||||
|
||||
// TODO: add all methods
|
||||
['GET', 'POST'].forEach((method) => {
|
||||
['GET', 'POST', 'DELETE'].forEach((method) => {
|
||||
http[method.toLowerCase()] = (url, data, headers) => {
|
||||
const formData =
|
||||
data &&
|
||||
@@ -19,6 +19,7 @@ const http = {};
|
||||
method,
|
||||
headers,
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
})
|
||||
.then((response) =>
|
||||
response.json().then((body) => ({
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 559 KiB After Width: | Height: | Size: 184 KiB |
@@ -2,7 +2,7 @@
|
||||
.dropzone {
|
||||
background: white;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
height: 100%;
|
||||
line-height: 30px;
|
||||
opacity: 0.7;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
.contentError {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
height: 20px;
|
||||
width: 470px;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
color: #5e6c84;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
height: 100%;
|
||||
line-height: 80px;
|
||||
overflow: hidden;
|
||||
@@ -50,7 +50,7 @@
|
||||
.name {
|
||||
color: #17394d;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
.actionsTitle {
|
||||
color: #8c8c8c;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.04em;
|
||||
margin-top: 16px;
|
||||
text-transform: uppercase;
|
||||
@@ -158,7 +158,7 @@
|
||||
.headerTitle {
|
||||
color: #17394d;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
box-shadow: none;
|
||||
color: #17394d;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
margin: -5px;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
font-weight: normal;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 797px) {
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
background: rgba(9, 30, 66, 0.13);
|
||||
|
||||
@@ -18,6 +18,11 @@ const createMessage = (error) => {
|
||||
}
|
||||
|
||||
switch (error.message) {
|
||||
case 'Invalid credentials':
|
||||
return {
|
||||
type: 'error',
|
||||
content: 'common.invalidCredentials',
|
||||
};
|
||||
case 'Invalid email or username':
|
||||
return {
|
||||
type: 'error',
|
||||
@@ -116,6 +121,7 @@ const Login = React.memo(
|
||||
useEffect(() => {
|
||||
if (wasSubmitting && !isSubmitting && error) {
|
||||
switch (error.message) {
|
||||
case 'Invalid credentials':
|
||||
case 'Invalid email or username':
|
||||
emailOrUsernameField.current.select();
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
.wrapperTiny {
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
height: 24px;
|
||||
line-height: 20px;
|
||||
padding: 2px 0;
|
||||
@@ -39,7 +38,6 @@
|
||||
|
||||
.wrapperSmall {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
height: 28px;
|
||||
padding: 8px 0;
|
||||
width: 28px;
|
||||
@@ -47,7 +45,6 @@
|
||||
|
||||
.wrapperMedium {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
height: 32px;
|
||||
padding: 10px 0;
|
||||
width: 32px;
|
||||
@@ -55,7 +52,6 @@
|
||||
|
||||
.wrapperLarge {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
height: 36px;
|
||||
padding: 12px 0 10px;
|
||||
width: 36px;
|
||||
@@ -63,7 +59,6 @@
|
||||
|
||||
.wrapperMassive {
|
||||
font-size: 36px;
|
||||
font-weight: 500;
|
||||
height: 100px;
|
||||
padding: 32px 0 10px;
|
||||
width: 100px;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
const { BASE_URL } = window;
|
||||
|
||||
const BASE_PATH = BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
|
||||
|
||||
const SERVER_BASE_URL =
|
||||
process.env.REACT_APP_SERVER_BASE_URL ||
|
||||
(process.env.NODE_ENV === 'production' ? BASE_URL : 'http://localhost:1337');
|
||||
const SERVER_BASE_PATH = SERVER_BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
|
||||
|
||||
const SERVER_BASE_PATH = SERVER_BASE_URL.replace(/^.*\/\/[^/]*(.*)[^?#]*.*$/, '$1');
|
||||
const SERVER_HOST_NAME = SERVER_BASE_URL.replace(/^(.*\/\/[^/?#]*).*$/, '$1');
|
||||
|
||||
const ACCESS_TOKEN_KEY = 'accessToken';
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -64,7 +64,7 @@
|
||||
|
||||
.markdown-body b,
|
||||
.markdown-body strong {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
}
|
||||
|
||||
.markdown-body dfn {
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
.markdown-body h1 {
|
||||
margin: .67em 0;
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
padding-bottom: .3em;
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid hsla(210, 18%, 87%, 1);
|
||||
@@ -253,34 +253,34 @@
|
||||
.markdown-body h6 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
padding-bottom: .3em;
|
||||
font-size: 1.5em;
|
||||
border-bottom: 1px solid hsla(210, 18%, 87%, 1);
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
.markdown-body h4 {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.markdown-body h5 {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
font-size: .875em;
|
||||
}
|
||||
|
||||
.markdown-body h6 {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
font-size: .85em;
|
||||
color: #57606a;
|
||||
}
|
||||
@@ -481,7 +481,7 @@
|
||||
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 1em;
|
||||
font-style: normal !important;
|
||||
font-weight: 400;
|
||||
font-weight: normal; // 400
|
||||
line-height: 1;
|
||||
vertical-align: -0.075em;
|
||||
}
|
||||
@@ -650,7 +650,7 @@
|
||||
margin-top: 16px;
|
||||
font-size: 1em;
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
}
|
||||
|
||||
.markdown-body dl dd {
|
||||
@@ -659,7 +659,7 @@
|
||||
}
|
||||
|
||||
.markdown-body table th {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
}
|
||||
|
||||
.markdown-body table th,
|
||||
@@ -868,7 +868,7 @@
|
||||
}
|
||||
|
||||
.markdown-body .csv-data th {
|
||||
font-weight: 600;
|
||||
font-weight: bold; // 600
|
||||
background: #f6f8fa;
|
||||
border-top: 0;
|
||||
}
|
||||
@@ -912,7 +912,7 @@
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item label {
|
||||
font-weight: 400;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item.enabled label {
|
||||
|
||||
Vendored
+2568
-2729
File diff suppressed because one or more lines are too long
@@ -0,0 +1,252 @@
|
||||
import dateFns from 'date-fns/locale/ar';
|
||||
|
||||
export default {
|
||||
dateFns,
|
||||
|
||||
format: {
|
||||
date: 'M/d/yyyy',
|
||||
time: 'p',
|
||||
dateTime: '$t(format:date) $t(format:time)',
|
||||
longDate: 'MMM d',
|
||||
longDateTime: "MMMM d 'at' p",
|
||||
fullDate: 'MMM d, y',
|
||||
fullDateTime: "MMMM d, y 'at' p",
|
||||
},
|
||||
|
||||
translation: {
|
||||
common: {
|
||||
aboutPlanka: 'حول Planka',
|
||||
account: 'الحساب',
|
||||
actions: 'إجراءات',
|
||||
addAttachment_title: 'إضافة مرفق',
|
||||
addComment: 'إضافة تعليق',
|
||||
addManager_title: 'إضافة مدير',
|
||||
addMember_title: 'إضافة عضو',
|
||||
addUser_title: 'إضافة مستخدم',
|
||||
administrator: 'المدير',
|
||||
all: 'الكل',
|
||||
allChangesWillBeAutomaticallySavedAfterConnectionRestored:
|
||||
'سيتم حفظ جميع التغييرات تلقائياً<br />بعد استعادة الإتصال.',
|
||||
areYouSureYouWantToDeleteThisAttachment: 'هل أنت متأكد أنك تريد حذف هذا المرفق؟',
|
||||
areYouSureYouWantToDeleteThisBoard: 'هل أنت متأكد أنك تريد حذف هذه اللوحة؟',
|
||||
areYouSureYouWantToDeleteThisCard: 'هل أنت متأكد أنك تريد حذف هذه البطاقة؟',
|
||||
areYouSureYouWantToDeleteThisComment: 'هل أنت متأكد أنك تريد حذف هذا التعليق؟',
|
||||
areYouSureYouWantToDeleteThisLabel: 'هل أنت متأكد أنك تريد حذف هذا الملصق؟',
|
||||
areYouSureYouWantToDeleteThisList: 'هل أنت متأكد أنك تريد حذف هذه القائمة؟',
|
||||
areYouSureYouWantToDeleteThisProject: 'هل أنت متأكد أنك تريد حذف هذا المشروع؟',
|
||||
areYouSureYouWantToDeleteThisTask: 'هل أنت متأكد أنك تريد حذف هذه المهمة؟',
|
||||
areYouSureYouWantToDeleteThisUser: 'هل أنت متأكد أنك تريد حذف هذا المستخدم؟',
|
||||
areYouSureYouWantToLeaveBoard: 'هل أنت متأكد أنك تريد مغادرة اللوحة؟',
|
||||
areYouSureYouWantToLeaveProject: 'هل أنت متأكد أنك تريد مغادرة المشروع؟',
|
||||
areYouSureYouWantToRemoveThisManagerFromProject:
|
||||
'هل أنت متأكد أنك تريد إزالة هذا المدير من المشروع؟',
|
||||
areYouSureYouWantToRemoveThisMemberFromBoard:
|
||||
'هل أنت متأكد أنك تريد إزالة هذا العضو من اللوحة؟',
|
||||
attachment: 'مرفق',
|
||||
attachments: 'مرفقات',
|
||||
authentication: 'المصادقة',
|
||||
background: 'الخلفية',
|
||||
board: 'لوحة',
|
||||
boardNotFound_title: 'لم يتم العثور على اللوحة',
|
||||
canComment: 'يمكن التعليق',
|
||||
canEditContentOfBoard: 'يمكن تعديل محتوى اللوحة.',
|
||||
canOnlyViewBoard: 'يمكن فقط عرض اللوحة.',
|
||||
cardActions_title: 'إجراءات البطاقة',
|
||||
cardNotFound_title: 'لم يتم العثور على البطاقة',
|
||||
cardOrActionAreDeleted: 'تم حذف البطاقة أو الإجراء.',
|
||||
color: 'اللون',
|
||||
copy_inline: 'نسخ',
|
||||
createBoard_title: 'إنشاء لوحة',
|
||||
createLabel_title: 'إنشاء ملصق',
|
||||
createNewOneOrSelectExistingOne: 'أنشئ واحدة جديدة أو اختر<br />واحدة موجودة.',
|
||||
createProject_title: 'إنشاء مشروع',
|
||||
createTextFile_title: 'إنشاء ملف نصي',
|
||||
currentPassword: 'كلمة المرور الحالية',
|
||||
dangerZone_title: 'منطقة الخطر',
|
||||
date: 'تاريخ',
|
||||
dueDate: 'تاريخ الاستحقاق',
|
||||
dueDate_title: 'تاريخ الاستحقاق',
|
||||
deleteAttachment_title: 'حذف المرفق',
|
||||
deleteBoard_title: 'حذف اللوحة',
|
||||
deleteCard_title: 'حذف البطاقة',
|
||||
deleteComment_title: 'حذف التعليق',
|
||||
deleteLabel_title: 'حذف الملصق',
|
||||
deleteList_title: 'حذف القائمة',
|
||||
deleteProject_title: 'حذف المشروع',
|
||||
deleteTask_title: 'حذف المهمة',
|
||||
deleteUser_title: 'حذف المستخدم',
|
||||
description: 'الوصف',
|
||||
detectAutomatically: 'الكشف تلقائياً',
|
||||
dropFileToUpload: 'أفلت الملف لرفعه',
|
||||
editor: 'محرر',
|
||||
editAttachment_title: 'تعديل المرفق',
|
||||
editAvatar_title: 'تحرير الصورة الرمزية',
|
||||
editBoard_title: 'تعديل اللوحة',
|
||||
editDueDate_title: 'تعديل تاريخ الاستحقاق',
|
||||
editEmail_title: 'تعديل البريد الإلكتروني',
|
||||
editInformation_title: 'تعديل المعلومات',
|
||||
editLabel_title: 'تعديل الملصق',
|
||||
editPassword_title: 'تعديل كلمة المرور',
|
||||
editPermissions_title: 'تعديل الأذونات',
|
||||
editStopwatch_title: 'تعديل المؤقت',
|
||||
editUsername_title: 'تعديل اسم المستخدم',
|
||||
email: 'البريد الإلكتروني',
|
||||
emailAlreadyInUse: 'البريد الإلكتروني مستخدم بالفعل',
|
||||
enterCardTitle: 'أدخل عنوان البطاقة... [Ctrl+Enter] لفتحها تلقائيًا.',
|
||||
enterDescription: 'أدخل الوصف...',
|
||||
enterFilename: 'أدخل اسم الملف',
|
||||
enterListTitle: 'أدخل عنوان القائمة...',
|
||||
enterProjectTitle: 'أدخل عنوان المشروع',
|
||||
enterTaskDescription: 'أدخل وصف المهمة...',
|
||||
filterByLabels_title: 'تصفية حسب الملصقات',
|
||||
filterByMembers_title: 'تصفية حسب الأعضاء',
|
||||
fromComputer_title: 'من الكمبيوتر',
|
||||
fromTrello: 'من Trello',
|
||||
general: 'عام',
|
||||
hours: 'ساعات',
|
||||
importBoard_title: 'استيراد اللوحة',
|
||||
invalidCurrentPassword: 'كلمة المرور الحالية غير صالحة',
|
||||
labels: 'الملصقات',
|
||||
language: 'اللغة',
|
||||
leaveBoard_title: 'غادر اللوحة',
|
||||
leaveProject_title: 'غادر المشروع',
|
||||
linkIsCopied: 'تم نسخ الرابط',
|
||||
list: 'القائمة',
|
||||
listActions_title: 'قائمة الإجراءات',
|
||||
managers: 'المديرون',
|
||||
managerActions_title: 'إجراءات المدير',
|
||||
members: 'الأعضاء',
|
||||
memberActions_title: 'إجراءات العضو',
|
||||
minutes: 'الدقائق',
|
||||
moveCard_title: 'نقل البطاقة',
|
||||
name: 'الاسم',
|
||||
newestFirst: 'الأحدث أولاً',
|
||||
newEmail: 'بريد إلكتروني جديد',
|
||||
newPassword: 'كلمة سر جديدة',
|
||||
newUsername: 'مستخدم جديد',
|
||||
noConnectionToServer: 'لا يوجد اتصال بالخادم',
|
||||
noBoards: 'لا توجد لوحات',
|
||||
noLists: 'لاتوجد قوائم',
|
||||
noProjects: 'لاتوجد مشاريع',
|
||||
notifications: 'الإشعارات',
|
||||
noUnreadNotifications: 'لاتوجد إشعارات غير مقروءة',
|
||||
oldestFirst: 'الأقدم أولاً',
|
||||
openBoard_title: 'فتح اللوحة',
|
||||
optional_inline: 'اختياري',
|
||||
organization: 'المنظمة',
|
||||
phone: 'الهاتف',
|
||||
preferences: 'التفضيلات',
|
||||
pressPasteShortcutToAddAttachmentFromClipboard:
|
||||
'نصيحة: اضغط على Ctrl-V (Cmd-V على Mac) لإضافة مرفق من الحافظة.',
|
||||
project: 'مشروع',
|
||||
projectNotFound_title: 'المشروع غير موجود',
|
||||
removeManager_title: 'إزالة المدير',
|
||||
removeMember_title: 'إزالة العضو',
|
||||
searchLabels: 'البحث عن التصنيفات...',
|
||||
searchMembers: 'البحث عن الأعضاء...',
|
||||
searchUsers: 'البحث عن المستخدمين...',
|
||||
searchCards: 'البحث عن البطاقات...',
|
||||
seconds: 'ثواني',
|
||||
selectBoard: 'اختر لوحة',
|
||||
selectList: 'اختر قائمة',
|
||||
selectPermissions_title: 'حدد الأذونات',
|
||||
selectProject: 'حدد المشروع',
|
||||
settings: 'الإعدادات',
|
||||
sortList_title: 'فرز القائمة',
|
||||
stopwatch: 'المؤقت',
|
||||
subscribeToMyOwnCardsByDefault: 'الاشتراك في بطاقاتي الخاصة إفتراضياً',
|
||||
taskActions_title: 'إجراءات المهمة',
|
||||
tasks: 'المهام',
|
||||
thereIsNoPreviewAvailableForThisAttachment: 'لا يوجد معاينة متاحة لهذا المرفق.',
|
||||
time: 'الوقت',
|
||||
title: 'العنوان',
|
||||
userActions_title: 'إجراءات المستخدم',
|
||||
userAddedThisCardToList: '<0>{{user}}</0><1> تمت إضافة هذه البطاقة إلى {{list}}</1>',
|
||||
userLeftNewCommentToCard: '{{user}} ترك تعليق جديد «{{comment}}» إلى <2>{{card}}</2>',
|
||||
userMovedCardFromListToList: '{{user}} انتقل <2>{{card}}</2> من {{fromList}} إلى {{toList}}',
|
||||
userMovedThisCardFromListToList:
|
||||
'<0>{{user}}</0><1> نُقلت هذه البطاقة من {{fromList}} إلى {{toList}}</1>',
|
||||
username: 'اسم المستخدم',
|
||||
usernameAlreadyInUse: 'اسم المستخدم تم استخدامه بالفعل',
|
||||
users: 'المستخدمين',
|
||||
version: 'الإصدار',
|
||||
viewer: 'مشاهد',
|
||||
writeComment: 'اكتب تعليقاً...',
|
||||
},
|
||||
|
||||
action: {
|
||||
addAnotherCard: 'إضافة بطاقة أخرى',
|
||||
addAnotherList: 'إضافة قائمة أخرى',
|
||||
addAnotherTask: 'إضافة مهمة أخرى',
|
||||
addCard: 'إضافة بطاقة',
|
||||
addCard_title: 'إضافة بطاقة',
|
||||
addComment: 'إضافة تعليق',
|
||||
addList: 'إضافة قائمة',
|
||||
addMember: 'إضافة عضو',
|
||||
addMoreDetailedDescription: 'إضافة وصف أكثر تفصيلاً',
|
||||
addTask: 'إضافة مهمة',
|
||||
addToCard: 'إضافة إلى البطاقة',
|
||||
addUser: 'إضافة مستخدم',
|
||||
copyLink_title: 'نسخ الرابط',
|
||||
createBoard: 'إنشاء لوحة',
|
||||
createFile: 'إنشاء ملف',
|
||||
createLabel: 'إنشاء ملصق',
|
||||
createNewLabel: 'إنشاء ملصق جديد',
|
||||
createProject: 'إنشاء مشروع',
|
||||
delete: 'حذف',
|
||||
deleteAttachment: 'حذف المرفق',
|
||||
deleteAvatar: 'حذف الصورة الرمزية',
|
||||
deleteBoard: 'حذف اللوحة',
|
||||
deleteCard: 'حذف البطاقة',
|
||||
deleteCard_title: 'حذف البطاقة',
|
||||
deleteComment: 'حذف التعليق',
|
||||
deleteImage: 'حذف الصورة',
|
||||
deleteLabel: 'حذف الملصق',
|
||||
deleteList: 'حذف القائمة',
|
||||
deleteList_title: 'حذف القائمة',
|
||||
deleteProject: 'حذف المشروع',
|
||||
deleteProject_title: 'حذف المشروع',
|
||||
deleteTask: 'حذف المهمة',
|
||||
deleteTask_title: 'حذف المهمة',
|
||||
deleteUser: 'حذف المستخدم',
|
||||
duplicate: 'تكرار',
|
||||
duplicateCard_title: 'تكرار البطاقة',
|
||||
edit: 'تعديل',
|
||||
editDueDate_title: 'تعديل تاريخ الاستحقاق',
|
||||
editDescription_title: 'تعديل الوصف',
|
||||
editEmail_title: 'تعديل البريد الإلكتروني',
|
||||
editInformation_title: 'تعديل المعلومات',
|
||||
editPassword_title: 'تعديل كلمة المرور',
|
||||
editPermissions: 'تعديل الأذونات',
|
||||
editStopwatch_title: 'تعديل المؤقت',
|
||||
editTitle_title: 'تعديل العنوان',
|
||||
editUsername_title: 'تعديل اسم المستخدم',
|
||||
hideDetails: 'إخفاء التفاصيل',
|
||||
import: 'استيراد',
|
||||
leaveBoard: 'غادر اللوحة',
|
||||
leaveProject: 'غادر المشروع',
|
||||
logOut_title: 'تسجيل الخروج',
|
||||
makeCover_title: 'إصنع غلافاً',
|
||||
move: 'نقل',
|
||||
moveCard_title: 'نقل البطاقة',
|
||||
remove: 'حذف',
|
||||
removeBackground: 'إزالة الخلفية',
|
||||
removeCover_title: 'إزالة الغلاف',
|
||||
removeFromBoard: 'إزالة اللوحة',
|
||||
removeFromProject: 'إزالة المشروع',
|
||||
removeManager: 'إزالة المدير',
|
||||
removeMember: 'إزالة العضو',
|
||||
save: 'حفظ',
|
||||
showAllAttachments: 'إظهار جميع المرفقات ({{hidden}} hidden)',
|
||||
showDetails: 'إظهار التفاصيل',
|
||||
showFewerAttachments: 'عرض مرفقات أقل',
|
||||
sortList_title: 'فرز القائمة',
|
||||
start: 'ابدأ',
|
||||
stop: 'توقف',
|
||||
subscribe: 'اشترك',
|
||||
unsubscribe: 'إلغاء الاشتراك',
|
||||
uploadNewAvatar: 'رفع صورة رمزية جديدة',
|
||||
uploadNewImage: 'رفع صورة جديدة',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import login from './login';
|
||||
|
||||
export default {
|
||||
language: 'ar-YE',
|
||||
country: 'ye',
|
||||
name: 'العربية',
|
||||
embeddedLocale: login,
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
export default {
|
||||
translation: {
|
||||
common: {
|
||||
emailOrUsername: 'البريد الإلكتروني أو اسم المستخدم',
|
||||
invalidEmailOrUsername: 'البريد الإلكتروني أو اسم المستخدم غير صالح',
|
||||
invalidCredentials: 'بيانات الاعتماد غير صالحة',
|
||||
invalidPassword: 'كلمة المرور غير صالحة',
|
||||
logInToPlanka: 'تسجيل الدخول إلى Planka',
|
||||
noInternetConnection: 'لا يوجد اتصال بالإنترنت',
|
||||
pageNotFound_title: 'الصفحة غير موجودة',
|
||||
password: 'كلمة المرور',
|
||||
projectManagement: 'إدارة المشروع',
|
||||
serverConnectionFailed: 'فشل الاتصال بالخادم',
|
||||
unknownError: 'خطأ غير معروف، يرجى المحاولة لاحقاً',
|
||||
useSingleSignOn: 'استخدم تسجيل الدخول الموحد',
|
||||
},
|
||||
|
||||
action: {
|
||||
logIn: 'تسجيل الدخول',
|
||||
logInWithSSO: 'تسجيل الدخول باستخدام SSO',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -3,6 +3,7 @@ export default {
|
||||
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',
|
||||
|
||||
@@ -43,7 +43,7 @@ export default {
|
||||
boardNotFound_title: 'Tablero no encontrado',
|
||||
cardActions_title: 'Acciones de las tarjetas',
|
||||
cardNotFound_title: 'tarjeta no encontrada',
|
||||
cardOrActionAreDeleted: 'La tarjeta o la acción está eliminada.',
|
||||
cardOrActionAreDeleted: 'La tarjeta o la acción está borrada.',
|
||||
color: 'Color',
|
||||
createBoard_title: 'Crear Tablero',
|
||||
createLabel_title: 'Crear Etiqueta',
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
currentPassword: 'Contraseña Actual',
|
||||
date: 'Fecha',
|
||||
dueDate_title: 'Fecha de Vencimiento',
|
||||
deleteAttachment_title: 'Eliminar Adjunto',
|
||||
deleteAttachment_title: 'Borrar Adjunto',
|
||||
deleteBoard_title: 'Borrar Tablero',
|
||||
deleteCard_title: 'Borrar tarjeta',
|
||||
deleteComment_title: 'Borrar Comentario',
|
||||
@@ -72,7 +72,7 @@ export default {
|
||||
editLabel_title: 'Editar Etiqueta',
|
||||
editPassword_title: 'Editar Contraseña',
|
||||
editStopwatch_title: 'Editar Temporizador',
|
||||
editUsername_title: 'Edit nombre de usuario',
|
||||
editUsername_title: 'Editar nombre de usuario',
|
||||
email: 'Correo',
|
||||
emailAlreadyInUse: 'El correo ya está en uso',
|
||||
enterCardTitle: 'Escribe el título de la tarjeta...',
|
||||
@@ -85,8 +85,8 @@ export default {
|
||||
filterByMembers_title: 'Filtrar por Miembros',
|
||||
fromComputer_title: 'Desde Computador',
|
||||
hours: 'Horas',
|
||||
invalidCurrentPassword: 'Contraseña actual invalida',
|
||||
labels: 'Etíquetas',
|
||||
invalidCurrentPassword: 'Contraseña actual inválida',
|
||||
labels: 'Etiquetas',
|
||||
list: 'Lista',
|
||||
listActions_title: 'Acciónes de Lista',
|
||||
members: 'Miembros',
|
||||
@@ -168,14 +168,14 @@ export default {
|
||||
deleteProject_title: 'Borrar Proyecto',
|
||||
deleteTask: 'Borrar tarea',
|
||||
deleteTask_title: 'Borrar Tarea',
|
||||
deleteUser: 'Delete usuario',
|
||||
deleteUser: 'Borrar usuario',
|
||||
edit: 'Editar',
|
||||
editDueDate_title: 'Editar Fecha de Vencimiento',
|
||||
editDescription_title: 'Editar Descripción',
|
||||
editEmail_title: 'Editar Correo',
|
||||
editPassword_title: 'Editar Contraseña',
|
||||
editStopwatch_title: 'Edit Temporizador',
|
||||
editTitle_title: 'Edit Título',
|
||||
editStopwatch_title: 'Editar Temporizador',
|
||||
editTitle_title: 'Editar Título',
|
||||
editUsername_title: 'Editar Nombre de Usuario',
|
||||
logOut_title: 'Cerrar Sesión',
|
||||
makeCover_title: 'Hacer Cubierta',
|
||||
|
||||
@@ -3,6 +3,7 @@ export default {
|
||||
common: {
|
||||
emailOrUsername: "Email ou nom d'utilisateur",
|
||||
invalidEmailOrUsername: "Email ou nom d'utilisateur invalide",
|
||||
invalidCredentials: 'Identifiants invalides',
|
||||
invalidPassword: 'Mot de passe invalide',
|
||||
logInToPlanka: 'Se connecter à Planka',
|
||||
noInternetConnection: 'Aucune connection internet',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import arYE from './ar-YE';
|
||||
import bgBG from './bg-BG';
|
||||
import csCZ from './cs-CZ';
|
||||
import daDK from './da-DK';
|
||||
@@ -25,6 +26,7 @@ import zhCN from './zh-CN';
|
||||
import zhTW from './zh-TW';
|
||||
|
||||
const locales = [
|
||||
arYE,
|
||||
bgBG,
|
||||
csCZ,
|
||||
daDK,
|
||||
|
||||
@@ -180,6 +180,7 @@ export default {
|
||||
addAnotherCard: 'Добавить еще одну карточку',
|
||||
addAnotherList: 'Добавить еще один список',
|
||||
addAnotherTask: 'Добавить еще одну задачу',
|
||||
addCard: 'Добавить карточку',
|
||||
addCard_title: 'Добавить карточку',
|
||||
addComment: 'Добавить комментарий',
|
||||
addList: 'Добавить список',
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
border: 0;
|
||||
color: #444444;
|
||||
font-size: 14px;
|
||||
font-family: "Museo Sans", "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
font-weight: 400;
|
||||
font-family: "Nunitoga", "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
// font-weight: 400;
|
||||
width: 100%;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
.react-datepicker__current-month,
|
||||
.react-datepicker-time__header {
|
||||
color: #444;
|
||||
font-weight: 500;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
padding-bottom: 4px;
|
||||
@@ -38,7 +38,7 @@
|
||||
color: #444;
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
font-weight: 500;
|
||||
font-weight: normal;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default '1.21.1';
|
||||
export default '1.22.0';
|
||||
|
||||
@@ -24,6 +24,8 @@ services:
|
||||
# Configure knex to accept SSL certificates
|
||||
# - KNEX_REJECT_UNAUTHORIZED_SSL_CERTIFICATE=false
|
||||
|
||||
# - SHOW_DETAILED_AUTH_ERRORS=false # Set to true to show more detailed authentication error messages. It should not be enabled without a rate limiter for security reasons.
|
||||
|
||||
# - ALLOW_ALL_TO_CREATE_PROJECTS=true
|
||||
|
||||
# - OIDC_ISSUER=
|
||||
@@ -35,6 +37,7 @@ services:
|
||||
# - OIDC_RESPONSE_MODE=fragment
|
||||
# - OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
||||
# - OIDC_ADMIN_ROLES=admin
|
||||
# - OIDC_CLAIMS_SOURCE=userinfo
|
||||
# - OIDC_EMAIL_ATTRIBUTE=email
|
||||
# - OIDC_NAME_ATTRIBUTE=name
|
||||
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||
|
||||
@@ -31,6 +31,8 @@ services:
|
||||
# - DEFAULT_ADMIN_NAME=Demo Demo
|
||||
# - DEFAULT_ADMIN_USERNAME=demo
|
||||
|
||||
# - SHOW_DETAILED_AUTH_ERRORS=false # Set to true to show more detailed authentication error messages. It should not be enabled without a rate limiter for security reasons.
|
||||
|
||||
# - ALLOW_ALL_TO_CREATE_PROJECTS=true
|
||||
|
||||
# - OIDC_ISSUER=
|
||||
@@ -42,6 +44,7 @@ services:
|
||||
# - OIDC_RESPONSE_MODE=fragment
|
||||
# - OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
||||
# - OIDC_ADMIN_ROLES=admin
|
||||
# - OIDC_CLAIMS_SOURCE=userinfo
|
||||
# - OIDC_EMAIL_ATTRIBUTE=email
|
||||
# - OIDC_NAME_ATTRIBUTE=name
|
||||
# - OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||
@@ -70,6 +73,7 @@ services:
|
||||
|
||||
# - SLACK_BOT_TOKEN=
|
||||
# - SLACK_CHANNEL_ID=
|
||||
# - GOOGLE_CHAT_WEBHOOK_URL=
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
Generated
+156
-142
@@ -1,31 +1,31 @@
|
||||
{
|
||||
"name": "planka",
|
||||
"version": "1.21.1",
|
||||
"version": "1.22.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "planka",
|
||||
"version": "1.21.1",
|
||||
"version": "1.22.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"concurrently": "^8.2.2",
|
||||
"genversion": "^3.2.0",
|
||||
"husky": "^9.0.1",
|
||||
"lint-staged": "^15.2.5"
|
||||
"husky": "^9.1.6",
|
||||
"lint-staged": "^15.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"prettier": "^3.3.0"
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"prettier": "^3.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz",
|
||||
"integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==",
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
|
||||
"integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@@ -49,9 +49,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/regexpp": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
|
||||
"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz",
|
||||
"integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
|
||||
@@ -93,6 +93,7 @@
|
||||
"version": "0.11.14",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
|
||||
"deprecated": "Use @eslint/config-array instead",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@humanwhocodes/object-schema": "^2.0.2",
|
||||
@@ -120,6 +121,7 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
|
||||
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
|
||||
"deprecated": "Use @eslint/object-schema instead",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
@@ -176,9 +178,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@@ -213,11 +215,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz",
|
||||
"integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
|
||||
"integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
|
||||
"dependencies": {
|
||||
"environment": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -252,9 +257,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
@@ -317,14 +322,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
|
||||
"integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
||||
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
|
||||
"dependencies": {
|
||||
"restore-cursor": "^4.0.0"
|
||||
"restore-cursor": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -422,11 +427,11 @@
|
||||
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
@@ -489,11 +494,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
||||
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
@@ -537,14 +542,25 @@
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
|
||||
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
|
||||
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="
|
||||
},
|
||||
"node_modules/environment": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
|
||||
"integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -629,13 +645,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
|
||||
"integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz",
|
||||
"integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"prettier-linter-helpers": "^1.0.0",
|
||||
"synckit": "^0.8.6"
|
||||
"synckit": "^0.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
@@ -704,9 +720,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esquery": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
|
||||
"integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
||||
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"estraverse": "^5.1.0"
|
||||
@@ -921,14 +937,6 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/genversion/node_modules/commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@@ -1030,11 +1038,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/husky": {
|
||||
"version": "9.0.11",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz",
|
||||
"integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==",
|
||||
"version": "9.1.6",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz",
|
||||
"integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==",
|
||||
"bin": {
|
||||
"husky": "bin.mjs"
|
||||
"husky": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -1044,9 +1052,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
@@ -1229,9 +1237,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
|
||||
"integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
|
||||
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
@@ -1240,20 +1248,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged": {
|
||||
"version": "15.2.5",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.5.tgz",
|
||||
"integrity": "sha512-j+DfX7W9YUvdzEZl3Rk47FhDF6xwDBV5wwsCPw6BwWZVPYJemusQmvb9bRsW23Sqsaa+vRloAWogbK4BUuU2zA==",
|
||||
"version": "15.2.10",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.10.tgz",
|
||||
"integrity": "sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==",
|
||||
"dependencies": {
|
||||
"chalk": "~5.3.0",
|
||||
"commander": "~12.1.0",
|
||||
"debug": "~4.3.4",
|
||||
"debug": "~4.3.6",
|
||||
"execa": "~8.0.1",
|
||||
"lilconfig": "~3.1.1",
|
||||
"listr2": "~8.2.1",
|
||||
"micromatch": "~4.0.7",
|
||||
"lilconfig": "~3.1.2",
|
||||
"listr2": "~8.2.4",
|
||||
"micromatch": "~4.0.8",
|
||||
"pidtree": "~0.6.0",
|
||||
"string-argv": "~0.3.2",
|
||||
"yaml": "~2.4.2"
|
||||
"yaml": "~2.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"lint-staged": "bin/lint-staged.js"
|
||||
@@ -1276,16 +1284,24 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged/node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2": {
|
||||
"version": "8.2.1",
|
||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz",
|
||||
"integrity": "sha512-irTfvpib/rNiD637xeevjO2l3Z5loZmuaRi0L0YE5LfijwVY96oyVn0DFD3o/teAok7nfobMG1THvvcHh/BP6g==",
|
||||
"version": "8.2.4",
|
||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz",
|
||||
"integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==",
|
||||
"dependencies": {
|
||||
"cli-truncate": "^4.0.0",
|
||||
"colorette": "^2.0.20",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"log-update": "^6.0.0",
|
||||
"rfdc": "^1.3.1",
|
||||
"log-update": "^6.1.0",
|
||||
"rfdc": "^1.4.1",
|
||||
"wrap-ansi": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1319,13 +1335,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/log-update": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz",
|
||||
"integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
|
||||
"integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
|
||||
"dependencies": {
|
||||
"ansi-escapes": "^6.2.0",
|
||||
"cli-cursor": "^4.0.0",
|
||||
"slice-ansi": "^7.0.0",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
"cli-cursor": "^5.0.0",
|
||||
"slice-ansi": "^7.1.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"wrap-ansi": "^9.0.0"
|
||||
},
|
||||
@@ -1337,9 +1353,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -1407,9 +1423,9 @@
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
|
||||
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
@@ -1429,6 +1445,17 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-function": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
|
||||
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@@ -1441,9 +1468,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
"version": "1.4.0",
|
||||
@@ -1632,9 +1659,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.0.tgz",
|
||||
"integrity": "sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
||||
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
@@ -1710,47 +1737,34 @@
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
|
||||
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
|
||||
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
|
||||
"dependencies": {
|
||||
"onetime": "^5.1.0",
|
||||
"signal-exit": "^3.0.2"
|
||||
"onetime": "^7.0.0",
|
||||
"signal-exit": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor/node_modules/mimic-fn": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor/node_modules/onetime": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
|
||||
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^2.1.0"
|
||||
"mimic-function": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor/node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
@@ -1762,9 +1776,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz",
|
||||
"integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg=="
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
@@ -1891,9 +1905,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz",
|
||||
"integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^10.3.0",
|
||||
"get-east-asian-width": "^1.0.0",
|
||||
@@ -1907,9 +1921,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/string-width/node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -1980,9 +1994,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz",
|
||||
"integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==",
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz",
|
||||
"integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@pkgr/core": "^0.1.0",
|
||||
@@ -2021,9 +2035,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
@@ -2098,9 +2112,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -2148,9 +2162,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
|
||||
"integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planka",
|
||||
"version": "1.21.1",
|
||||
"version": "1.22.0",
|
||||
"private": true,
|
||||
"homepage": "https://plankanban.github.io/planka",
|
||||
"repository": {
|
||||
@@ -59,13 +59,13 @@
|
||||
"dependencies": {
|
||||
"concurrently": "^8.2.2",
|
||||
"genversion": "^3.2.0",
|
||||
"husky": "^9.0.1",
|
||||
"lint-staged": "^15.2.5"
|
||||
"husky": "^9.1.6",
|
||||
"lint-staged": "^15.2.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"prettier": "^3.3.0"
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"prettier": "^3.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ SECRET_KEY=notsecretkey
|
||||
# DEFAULT_ADMIN_NAME=Demo Demo
|
||||
# DEFAULT_ADMIN_USERNAME=demo
|
||||
|
||||
# SHOW_DETAILED_AUTH_ERRORS=false # Set to true to show more detailed authentication error messages. It should not be enabled without a rate limiter for security reasons.
|
||||
|
||||
# ALLOW_ALL_TO_CREATE_PROJECTS=true
|
||||
|
||||
# OIDC_ISSUER=
|
||||
@@ -33,6 +35,7 @@ SECRET_KEY=notsecretkey
|
||||
# OIDC_RESPONSE_MODE=fragment
|
||||
# OIDC_USE_DEFAULT_RESPONSE_MODE=true
|
||||
# OIDC_ADMIN_ROLES=admin
|
||||
# OIDC_CLAIMS_SOURCE=userinfo
|
||||
# OIDC_EMAIL_ATTRIBUTE=email
|
||||
# OIDC_NAME_ATTRIBUTE=name
|
||||
# OIDC_USERNAME_ATTRIBUTE=preferred_username
|
||||
@@ -59,6 +62,7 @@ SECRET_KEY=notsecretkey
|
||||
|
||||
# SLACK_BOT_TOKEN=
|
||||
# SLACK_CHANNEL_ID=
|
||||
# GOOGLE_CHAT_WEBHOOK_URL=
|
||||
|
||||
## Do not edit this
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
const bcrypt = require('bcrypt');
|
||||
const validator = require('validator');
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
const { getRemoteAddress } = require('../../../utils/remoteAddress');
|
||||
|
||||
const Errors = {
|
||||
INVALID_CREDENTIALS: {
|
||||
invalidCredentials: 'Invalid credentials',
|
||||
},
|
||||
INVALID_EMAIL_OR_USERNAME: {
|
||||
invalidEmailOrUsername: 'Invalid email or username',
|
||||
},
|
||||
@@ -31,9 +35,16 @@ module.exports = {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
withHttpOnlyToken: {
|
||||
type: 'boolean',
|
||||
defaultsTo: false,
|
||||
},
|
||||
},
|
||||
|
||||
exits: {
|
||||
invalidCredentials: {
|
||||
responseType: 'unauthorized',
|
||||
},
|
||||
invalidEmailOrUsername: {
|
||||
responseType: 'unauthorized',
|
||||
},
|
||||
@@ -57,7 +68,10 @@ module.exports = {
|
||||
sails.log.warn(
|
||||
`Invalid email or username: "${inputs.emailOrUsername}"! (IP: ${remoteAddress})`,
|
||||
);
|
||||
throw Errors.INVALID_EMAIL_OR_USERNAME;
|
||||
|
||||
throw sails.config.custom.showDetailedAuthErrors
|
||||
? Errors.INVALID_EMAIL_OR_USERNAME
|
||||
: Errors.INVALID_CREDENTIALS;
|
||||
}
|
||||
|
||||
if (user.isSso) {
|
||||
@@ -66,18 +80,30 @@ module.exports = {
|
||||
|
||||
if (!bcrypt.compareSync(inputs.password, user.password)) {
|
||||
sails.log.warn(`Invalid password! (IP: ${remoteAddress})`);
|
||||
throw Errors.INVALID_PASSWORD;
|
||||
|
||||
throw sails.config.custom.showDetailedAuthErrors
|
||||
? Errors.INVALID_PASSWORD
|
||||
: Errors.INVALID_CREDENTIALS;
|
||||
}
|
||||
|
||||
const accessToken = sails.helpers.utils.createToken(user.id);
|
||||
const { token: accessToken, payload: accessTokenPayload } = sails.helpers.utils.createJwtToken(
|
||||
user.id,
|
||||
);
|
||||
|
||||
const httpOnlyToken = inputs.withHttpOnlyToken ? uuid() : null;
|
||||
|
||||
await Session.create({
|
||||
accessToken,
|
||||
httpOnlyToken,
|
||||
remoteAddress,
|
||||
userId: user.id,
|
||||
userAgent: this.req.headers['user-agent'],
|
||||
});
|
||||
|
||||
if (httpOnlyToken && !this.req.isSocket) {
|
||||
sails.helpers.utils.setHttpOnlyTokenCookie(httpOnlyToken, accessTokenPayload, this.res);
|
||||
}
|
||||
|
||||
return {
|
||||
item: accessToken,
|
||||
};
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
module.exports = {
|
||||
async fn() {
|
||||
const { accessToken } = this.req;
|
||||
const { currentSession } = this.req;
|
||||
|
||||
await Session.updateOne({
|
||||
accessToken,
|
||||
id: currentSession.id,
|
||||
deletedAt: null,
|
||||
}).set({
|
||||
deletedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
if (this.req.isSocket) {
|
||||
sails.sockets.leaveAll(`@accessToken:${accessToken}`);
|
||||
sails.sockets.leaveAll(`@accessToken:${currentSession.accessToken}`);
|
||||
|
||||
if (currentSession.httpOnlyToken && !this.req.isSocket) {
|
||||
sails.helpers.utils.clearHttpOnlyTokenCookie(this.res);
|
||||
}
|
||||
|
||||
return {
|
||||
item: accessToken,
|
||||
item: currentSession.accessToken,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
const { getRemoteAddress } = require('../../../utils/remoteAddress');
|
||||
|
||||
const Errors = {
|
||||
INVALID_CODE_OR_NONCE: {
|
||||
invalidCodeOrNonce: 'Invalid code or nonce',
|
||||
},
|
||||
INVALID_USERINFO_SIGNATURE: {
|
||||
invalidUserinfoSignature: 'Invalid signature on userinfo due to client misconfiguration',
|
||||
INVALID_USERINFO_CONFIGURATION: {
|
||||
invalidUserinfoConfiguration: 'Invalid userinfo configuration',
|
||||
},
|
||||
EMAIL_ALREADY_IN_USE: {
|
||||
emailAlreadyInUse: 'Email already in use',
|
||||
@@ -28,13 +30,17 @@ module.exports = {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
withHttpOnlyToken: {
|
||||
type: 'boolean',
|
||||
defaultsTo: false,
|
||||
},
|
||||
},
|
||||
|
||||
exits: {
|
||||
invalidCodeOrNonce: {
|
||||
responseType: 'unauthorized',
|
||||
},
|
||||
invalidUserinfoSignature: {
|
||||
invalidUserinfoConfiguration: {
|
||||
responseType: 'unauthorized',
|
||||
},
|
||||
emailAlreadyInUse: {
|
||||
@@ -57,20 +63,29 @@ module.exports = {
|
||||
sails.log.warn(`Invalid code or nonce! (IP: ${remoteAddress})`);
|
||||
return Errors.INVALID_CODE_OR_NONCE;
|
||||
})
|
||||
.intercept('invalidUserinfoSignature', () => Errors.INVALID_USERINFO_SIGNATURE)
|
||||
.intercept('invalidUserinfoConfiguration', () => Errors.INVALID_USERINFO_CONFIGURATION)
|
||||
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)
|
||||
.intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE)
|
||||
.intercept('missingValues', () => Errors.MISSING_VALUES);
|
||||
|
||||
const accessToken = sails.helpers.utils.createToken(user.id);
|
||||
const { token: accessToken, payload: accessTokenPayload } = sails.helpers.utils.createJwtToken(
|
||||
user.id,
|
||||
);
|
||||
|
||||
const httpOnlyToken = inputs.withHttpOnlyToken ? uuid() : null;
|
||||
|
||||
await Session.create({
|
||||
accessToken,
|
||||
httpOnlyToken,
|
||||
remoteAddress,
|
||||
userId: user.id,
|
||||
userAgent: this.req.headers['user-agent'],
|
||||
});
|
||||
|
||||
if (httpOnlyToken && !this.req.isSocket) {
|
||||
sails.helpers.utils.setHttpOnlyTokenCookie(httpOnlyToken, accessTokenPayload, this.res);
|
||||
}
|
||||
|
||||
return {
|
||||
item: accessToken,
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
const { currentUser } = this.req;
|
||||
const { currentSession, currentUser } = this.req;
|
||||
|
||||
if (inputs.id === currentUser.id) {
|
||||
if (!inputs.currentPassword) {
|
||||
@@ -89,10 +89,14 @@ module.exports = {
|
||||
}
|
||||
|
||||
if (user.id === currentUser.id) {
|
||||
const accessToken = sails.helpers.utils.createToken(user.id, user.passwordUpdatedAt);
|
||||
const { token: accessToken } = sails.helpers.utils.createJwtToken(
|
||||
user.id,
|
||||
user.passwordUpdatedAt,
|
||||
);
|
||||
|
||||
await Session.create({
|
||||
accessToken,
|
||||
httpOnlyToken: currentSession.httpOnlyToken,
|
||||
userId: user.id,
|
||||
remoteAddress: getRemoteAddress(this.req),
|
||||
userAgent: this.req.headers['user-agent'],
|
||||
|
||||
@@ -14,7 +14,7 @@ const valuesValidator = (value) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const buildAndSendSlackMessage = async (card, action, actorUser) => {
|
||||
const buildAndSendMessage = async (card, action, actorUser, send) => {
|
||||
const cardLink = `<${sails.config.custom.baseUrl}/cards/${card.id}|${card.name}>`;
|
||||
|
||||
let markdown;
|
||||
@@ -35,7 +35,7 @@ const buildAndSendSlackMessage = async (card, action, actorUser) => {
|
||||
return;
|
||||
}
|
||||
|
||||
await sails.helpers.utils.sendSlackMessage(markdown);
|
||||
await send(markdown);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
@@ -94,10 +94,6 @@ module.exports = {
|
||||
user: values.user,
|
||||
});
|
||||
|
||||
if (sails.config.custom.slackBotToken) {
|
||||
buildAndSendSlackMessage(values.card, action, values.user);
|
||||
}
|
||||
|
||||
const subscriptionUserIds = await sails.helpers.cards.getSubscriptionUserIds(
|
||||
action.cardId,
|
||||
action.userId,
|
||||
@@ -119,6 +115,18 @@ module.exports = {
|
||||
),
|
||||
);
|
||||
|
||||
if (sails.config.custom.slackBotToken) {
|
||||
buildAndSendMessage(values.card, action, values.user, sails.helpers.utils.sendSlackMessage);
|
||||
}
|
||||
|
||||
if (sails.config.custom.googleChatWebhookUrl) {
|
||||
buildAndSendMessage(
|
||||
values.card,
|
||||
action,
|
||||
values.user,
|
||||
sails.helpers.utils.sendGoogleChatMessage,
|
||||
);
|
||||
}
|
||||
return action;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,10 +2,11 @@ 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');
|
||||
|
||||
const filenamify = require('../../../utils/filenamify');
|
||||
|
||||
module.exports = {
|
||||
inputs: {
|
||||
file: {
|
||||
@@ -17,7 +18,6 @@ module.exports = {
|
||||
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);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const buildAndSendSlackMessage = async (card, actorUser) => {
|
||||
await sails.helpers.utils.sendSlackMessage(`*${card.name}* was deleted by ${actorUser.name}`);
|
||||
const buildAndSendMessage = async (card, actorUser, send) => {
|
||||
await send(`*${card.name}* was deleted by ${actorUser.name}`);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
@@ -56,7 +56,11 @@ module.exports = {
|
||||
});
|
||||
|
||||
if (sails.config.custom.slackBotToken) {
|
||||
buildAndSendSlackMessage(card, inputs.actorUser);
|
||||
buildAndSendMessage(card, inputs.actorUser, sails.helpers.utils.sendSlackMessage);
|
||||
}
|
||||
|
||||
if (sails.config.custom.googleChatWebhookUrl) {
|
||||
buildAndSendMessage(card, inputs.actorUser, sails.helpers.utils.sendGoogleChatMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = {
|
||||
|
||||
exits: {
|
||||
invalidCodeOrNonce: {},
|
||||
invalidUserinfoSignature: {},
|
||||
invalidUserinfoConfiguration: {},
|
||||
missingValues: {},
|
||||
emailAlreadyInUse: {},
|
||||
usernameAlreadyInUse: {},
|
||||
@@ -21,9 +21,9 @@ module.exports = {
|
||||
async fn(inputs) {
|
||||
const client = sails.hooks.oidc.getClient();
|
||||
|
||||
let userInfo;
|
||||
let tokenSet;
|
||||
try {
|
||||
const tokenSet = await client.callback(
|
||||
tokenSet = await client.callback(
|
||||
sails.config.custom.oidcRedirectUri,
|
||||
{
|
||||
iss: sails.config.custom.oidcIssuer,
|
||||
@@ -33,23 +33,36 @@ module.exports = {
|
||||
nonce: inputs.nonce,
|
||||
},
|
||||
);
|
||||
userInfo = await client.userinfo(tokenSet);
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof SyntaxError &&
|
||||
e.message.includes('Unexpected token e in JSON at position 0')
|
||||
) {
|
||||
sails.log.warn('Error while exchanging OIDC code: userinfo response is signed');
|
||||
throw 'invalidUserinfoSignature';
|
||||
}
|
||||
|
||||
sails.log.warn(`Error while exchanging OIDC code: ${e}`);
|
||||
} catch (error) {
|
||||
sails.log.warn(`Error while exchanging OIDC code: ${error}`);
|
||||
throw 'invalidCodeOrNonce';
|
||||
}
|
||||
|
||||
let claims;
|
||||
if (sails.config.custom.oidcClaimsSource === 'id_token') {
|
||||
claims = tokenSet.claims();
|
||||
} else {
|
||||
try {
|
||||
claims = await client.userinfo(tokenSet);
|
||||
} catch (error) {
|
||||
let errorText;
|
||||
if (
|
||||
error instanceof SyntaxError &&
|
||||
error.message.includes('Unexpected token e in JSON at position 0')
|
||||
) {
|
||||
errorText = 'response is signed';
|
||||
} else {
|
||||
errorText = error.toString();
|
||||
}
|
||||
|
||||
sails.log.warn(`Error while fetching OIDC userinfo: ${errorText}`);
|
||||
throw 'invalidUserinfoConfiguration';
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!userInfo[sails.config.custom.oidcEmailAttribute] ||
|
||||
!userInfo[sails.config.custom.oidcNameAttribute]
|
||||
!claims[sails.config.custom.oidcEmailAttribute] ||
|
||||
!claims[sails.config.custom.oidcNameAttribute]
|
||||
) {
|
||||
throw 'missingValues';
|
||||
}
|
||||
@@ -58,23 +71,23 @@ module.exports = {
|
||||
if (sails.config.custom.oidcAdminRoles.includes('*')) {
|
||||
isAdmin = true;
|
||||
} else {
|
||||
const roles = userInfo[sails.config.custom.oidcRolesAttribute];
|
||||
const roles = claims[sails.config.custom.oidcRolesAttribute];
|
||||
if (Array.isArray(roles)) {
|
||||
// Use a Set here to avoid quadratic time complexity
|
||||
const userRoles = new Set(userInfo[sails.config.custom.oidcRolesAttribute]);
|
||||
const userRoles = new Set(claims[sails.config.custom.oidcRolesAttribute]);
|
||||
isAdmin = sails.config.custom.oidcAdminRoles.findIndex((role) => userRoles.has(role)) > -1;
|
||||
}
|
||||
}
|
||||
|
||||
const values = {
|
||||
isAdmin,
|
||||
email: userInfo[sails.config.custom.oidcEmailAttribute],
|
||||
email: claims[sails.config.custom.oidcEmailAttribute],
|
||||
isSso: true,
|
||||
name: userInfo[sails.config.custom.oidcNameAttribute],
|
||||
name: claims[sails.config.custom.oidcNameAttribute],
|
||||
subscribeToOwnCards: false,
|
||||
};
|
||||
if (!sails.config.custom.oidcIgnoreUsername) {
|
||||
values.username = userInfo[sails.config.custom.oidcUsernameAttribute];
|
||||
values.username = claims[sails.config.custom.oidcUsernameAttribute];
|
||||
}
|
||||
|
||||
let user;
|
||||
@@ -84,7 +97,7 @@ module.exports = {
|
||||
// concurrently with logging in via OIDC.
|
||||
let identityProviderUser = await IdentityProviderUser.findOne({
|
||||
issuer: sails.config.custom.oidcIssuer,
|
||||
sub: userInfo.sub,
|
||||
sub: claims.sub,
|
||||
});
|
||||
|
||||
if (identityProviderUser) {
|
||||
@@ -108,7 +121,7 @@ module.exports = {
|
||||
identityProviderUser = await IdentityProviderUser.create({
|
||||
userId: user.id,
|
||||
issuer: sails.config.custom.oidcIssuer,
|
||||
sub: userInfo.sub,
|
||||
sub: claims.sub,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ module.exports = {
|
||||
|
||||
Object.assign(values, {
|
||||
password: bcrypt.hashSync(values.password, 10),
|
||||
passwordChangedAt: new Date().toISOString(),
|
||||
passwordChangedAt: new Date().toUTCString(), // FIXME: hack
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
sync: true,
|
||||
|
||||
inputs: {
|
||||
response: {
|
||||
type: 'ref',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
fn(inputs) {
|
||||
inputs.response.clearCookie('httpOnlyToken', {
|
||||
path: sails.config.custom.baseUrlPath,
|
||||
});
|
||||
},
|
||||
};
|
||||
+17
-12
@@ -16,18 +16,23 @@ module.exports = {
|
||||
|
||||
fn(inputs) {
|
||||
const { issuedAt = new Date() } = inputs;
|
||||
const iat = Math.floor(issuedAt / 1000);
|
||||
|
||||
return jwt.sign(
|
||||
{
|
||||
iat,
|
||||
sub: inputs.subject,
|
||||
exp: iat + sails.config.custom.tokenExpiresIn * 24 * 60 * 60,
|
||||
},
|
||||
sails.config.session.secret,
|
||||
{
|
||||
keyid: uuid(),
|
||||
},
|
||||
);
|
||||
const iat = Math.floor(issuedAt / 1000);
|
||||
const exp = iat + sails.config.custom.tokenExpiresIn * 24 * 60 * 60;
|
||||
|
||||
const payload = {
|
||||
iat,
|
||||
exp,
|
||||
sub: inputs.subject,
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, sails.config.session.secret, {
|
||||
keyid: uuid(),
|
||||
});
|
||||
|
||||
return {
|
||||
token,
|
||||
payload,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
module.exports = {
|
||||
inputs: {
|
||||
markdown: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
async fn(inputs) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
};
|
||||
|
||||
const body = {
|
||||
text: inputs.markdown,
|
||||
};
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(sails.config.custom.googleChatWebhookUrl, {
|
||||
headers,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
} catch (error) {
|
||||
sails.log.error(`Error sending to Google Chat: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
sails.log.error(`Error sending to Google Chat: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const responseJson = await response.json();
|
||||
|
||||
if (!responseJson.ok) {
|
||||
sails.log.error(`Error sending to Google Chat: ${responseJson.error}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -130,8 +130,8 @@ async function sendWebhook(webhook, event, data, user) {
|
||||
`Webhook ${webhook.url} failed with status ${response.status} and message: ${message}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
sails.log.error(`Webhook ${webhook.url} failed with error message: ${e.message}`);
|
||||
} catch (error) {
|
||||
sails.log.error(`Webhook ${webhook.url} failed with error: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
module.exports = {
|
||||
sync: true,
|
||||
|
||||
inputs: {
|
||||
value: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
accessTokenPayload: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
},
|
||||
response: {
|
||||
type: 'ref',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
fn(inputs) {
|
||||
inputs.response.cookie('httpOnlyToken', inputs.value, {
|
||||
expires: new Date(inputs.accessTokenPayload.exp * 1000),
|
||||
path: sails.config.custom.baseUrlPath,
|
||||
secure: sails.config.custom.baseUrlSecure,
|
||||
httpOnly: true,
|
||||
sameSite: 'strict',
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -9,10 +9,10 @@
|
||||
module.exports = function defineCurrentUserHook(sails) {
|
||||
const TOKEN_PATTERN = /^Bearer /;
|
||||
|
||||
const getUser = async (accessToken) => {
|
||||
const getSessionAndUser = async (accessToken, httpOnlyToken) => {
|
||||
let payload;
|
||||
try {
|
||||
payload = sails.helpers.utils.verifyToken(accessToken);
|
||||
payload = sails.helpers.utils.verifyJwtToken(accessToken);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
@@ -26,13 +26,20 @@ module.exports = function defineCurrentUserHook(sails) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (session.httpOnlyToken && httpOnlyToken !== session.httpOnlyToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const user = await sails.helpers.users.getOne(payload.subject);
|
||||
|
||||
if (user && user.passwordChangedAt > payload.issuedAt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return user;
|
||||
return {
|
||||
session,
|
||||
user,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -52,17 +59,21 @@ module.exports = function defineCurrentUserHook(sails) {
|
||||
|
||||
if (authorizationHeader && TOKEN_PATTERN.test(authorizationHeader)) {
|
||||
const accessToken = authorizationHeader.replace(TOKEN_PATTERN, '');
|
||||
const currentUser = await getUser(accessToken);
|
||||
const { httpOnlyToken } = req.cookies;
|
||||
|
||||
const sessionAndUser = await getSessionAndUser(accessToken, httpOnlyToken);
|
||||
|
||||
if (sessionAndUser) {
|
||||
const { session, user } = sessionAndUser;
|
||||
|
||||
if (currentUser) {
|
||||
Object.assign(req, {
|
||||
accessToken,
|
||||
currentUser,
|
||||
currentSession: session,
|
||||
currentUser: user,
|
||||
});
|
||||
|
||||
if (req.isSocket) {
|
||||
sails.sockets.join(req, `@accessToken:${accessToken}`);
|
||||
sails.sockets.join(req, `@user:${currentUser.id}`);
|
||||
sails.sockets.join(req, `@accessToken:${session.accessToken}`);
|
||||
sails.sockets.join(req, `@user:${user.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,15 +83,17 @@ module.exports = function defineCurrentUserHook(sails) {
|
||||
},
|
||||
'/attachments/*': {
|
||||
async fn(req, res, next) {
|
||||
const { accessToken } = req.cookies;
|
||||
const { accessToken, httpOnlyToken } = req.cookies;
|
||||
|
||||
if (accessToken) {
|
||||
const currentUser = await getUser(accessToken);
|
||||
const sessionAndUser = await getSessionAndUser(accessToken, httpOnlyToken);
|
||||
|
||||
if (sessionAndUser) {
|
||||
const { session, user } = sessionAndUser;
|
||||
|
||||
if (currentUser) {
|
||||
Object.assign(req, {
|
||||
accessToken,
|
||||
currentUser,
|
||||
currentSession: session,
|
||||
currentUser: user,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ module.exports = function defineWatcherHook(sails) {
|
||||
const accessToken = room.split(':')[1];
|
||||
|
||||
try {
|
||||
sails.helpers.utils.verifyToken(accessToken);
|
||||
sails.helpers.utils.verifyJwtToken(accessToken);
|
||||
} catch (error) {
|
||||
sails.sockets.broadcast(room, 'logout');
|
||||
sails.sockets.leaveAll(room);
|
||||
|
||||
@@ -16,6 +16,12 @@ module.exports = {
|
||||
required: true,
|
||||
columnName: 'access_token',
|
||||
},
|
||||
httpOnlyToken: {
|
||||
type: 'string',
|
||||
isNotEmptyString: true,
|
||||
allowNull: true,
|
||||
columnName: 'http_only_token',
|
||||
},
|
||||
remoteAddress: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
const LANGUAGES = [
|
||||
'ar-YE',
|
||||
'bg-BG',
|
||||
'cs-CZ',
|
||||
'da-DK',
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
* https://sailsjs.com/config/custom
|
||||
*/
|
||||
|
||||
const url = require('url');
|
||||
const path = require('path');
|
||||
const sails = require('sails');
|
||||
|
||||
const parsedBasedUrl = new url.URL(process.env.BASE_URL);
|
||||
|
||||
module.exports.custom = {
|
||||
/**
|
||||
*
|
||||
@@ -19,6 +22,8 @@ module.exports.custom = {
|
||||
*/
|
||||
|
||||
baseUrl: process.env.BASE_URL,
|
||||
baseUrlPath: parsedBasedUrl.pathname,
|
||||
baseUrlSecure: parsedBasedUrl.protocol === 'https:',
|
||||
|
||||
tokenExpiresIn: parseInt(process.env.TOKEN_EXPIRES_IN, 10) || 365,
|
||||
|
||||
@@ -34,6 +39,8 @@ module.exports.custom = {
|
||||
defaultAdminEmail:
|
||||
process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase(),
|
||||
|
||||
showDetailedAuthErrors: process.env.SHOW_DETAILED_AUTH_ERRORS === 'true',
|
||||
|
||||
allowAllToCreateProjects: process.env.ALLOW_ALL_TO_CREATE_PROJECTS === 'true',
|
||||
|
||||
oidcIssuer: process.env.OIDC_ISSUER,
|
||||
@@ -45,6 +52,7 @@ module.exports.custom = {
|
||||
oidcResponseMode: process.env.OIDC_RESPONSE_MODE || 'fragment',
|
||||
oidcUseDefaultResponseMode: process.env.OIDC_USE_DEFAULT_RESPONSE_MODE === 'true',
|
||||
oidcAdminRoles: process.env.OIDC_ADMIN_ROLES ? process.env.OIDC_ADMIN_ROLES.split(',') : [],
|
||||
oidcClaimsSource: process.env.OIDC_CLAIMS_SOURCE || 'userinfo',
|
||||
oidcEmailAttribute: process.env.OIDC_EMAIL_ATTRIBUTE || 'email',
|
||||
oidcNameAttribute: process.env.OIDC_NAME_ATTRIBUTE || 'name',
|
||||
oidcUsernameAttribute: process.env.OIDC_USERNAME_ATTRIBUTE || 'preferred_username',
|
||||
@@ -70,4 +78,5 @@ module.exports.custom = {
|
||||
|
||||
slackBotToken: process.env.SLACK_BOT_TOKEN,
|
||||
slackChannelId: process.env.SLACK_CHANNEL_ID,
|
||||
googleChatWebhookUrl: process.env.GOOGLE_CHAT_WEBHOOK_URL,
|
||||
};
|
||||
|
||||
Vendored
+7
-4
@@ -23,6 +23,8 @@ const url = require('url');
|
||||
|
||||
const { customLogger } = require('../../utils/logger');
|
||||
|
||||
const parsedBasedUrl = new url.URL(process.env.BASE_URL);
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
*
|
||||
@@ -131,9 +133,10 @@ module.exports = {
|
||||
*/
|
||||
|
||||
cors: {
|
||||
// allowOrigins: [
|
||||
// 'https://example.com',
|
||||
// ],
|
||||
allRoutes: false,
|
||||
allowOrigins: '*',
|
||||
allowRequestHeaders: 'content-type',
|
||||
allowCredentials: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -218,7 +221,7 @@ module.exports = {
|
||||
*
|
||||
*/
|
||||
|
||||
onlyAllowOrigins: [new url.URL(process.env.BASE_URL).origin],
|
||||
onlyAllowOrigins: [parsedBasedUrl.origin],
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
module.exports.up = async (knex) =>
|
||||
knex.schema.table('session', (table) => {
|
||||
/* Columns */
|
||||
|
||||
table.text('http_only_token');
|
||||
});
|
||||
|
||||
module.exports.down = (knex) =>
|
||||
knex.schema.table('session', (table) => {
|
||||
table.dropColumn('http_only_token');
|
||||
});
|
||||
Generated
+7042
-7321
File diff suppressed because it is too large
Load Diff
+16
-11
@@ -8,6 +8,7 @@
|
||||
"db:migrate": "knex migrate:latest --cwd db",
|
||||
"db:seed": "knex seed:run --cwd db",
|
||||
"lint": "eslint . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look good.'",
|
||||
"preinstall": "npx npm-force-resolutions",
|
||||
"start": "nodemon",
|
||||
"start:prod": "node app.js --prod",
|
||||
"test": "mocha test/lifecycle.test.js test/integration/**/*.test.js test/utils/**/*.test.js"
|
||||
@@ -30,33 +31,37 @@
|
||||
"bcrypt": "^5.1.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"filenamify": "^4.3.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"knex": "^3.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
"move-file": "^2.1.0",
|
||||
"nodemailer": "^6.9.13",
|
||||
"openid-client": "^5.6.5",
|
||||
"rimraf": "^5.0.7",
|
||||
"nodemailer": "^6.9.15",
|
||||
"openid-client": "^5.7.0",
|
||||
"rimraf": "^5.0.10",
|
||||
"sails": "^1.5.11",
|
||||
"sails-hook-orm": "^4.0.3",
|
||||
"sails-hook-sockets": "^3.0.0",
|
||||
"sails-hook-sockets": "^3.0.1",
|
||||
"sails-postgresql": "^5.0.1",
|
||||
"sharp": "^0.33.4",
|
||||
"sharp": "^0.33.5",
|
||||
"stream-to-array": "^2.3.0",
|
||||
"uuid": "^9.0.1",
|
||||
"validator": "^13.12.0",
|
||||
"winston": "^3.13.0",
|
||||
"winston": "^3.14.2",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.4.1",
|
||||
"chai": "^4.5.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"mocha": "^10.4.0",
|
||||
"nodemon": "^3.1.2"
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
"mocha": "^10.7.3",
|
||||
"nodemon": "^3.1.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"body-parser": "1.20.3",
|
||||
"express": "4.21.0",
|
||||
"send": "0.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/* https://github.com/sindresorhus/filename-reserved-regex */
|
||||
|
||||
function filenameReservedRegex() {
|
||||
return /[<>:"/\\|?*\u0000-\u001F]/g; // eslint-disable-line no-control-regex
|
||||
}
|
||||
|
||||
function windowsReservedNameRegex() {
|
||||
return /^(con|prn|aux|nul|com\d|lpt\d)$/i;
|
||||
}
|
||||
|
||||
/* https://github.com/sindresorhus/filenamify */
|
||||
|
||||
// Doesn't make sense to have longer filenames
|
||||
const MAX_FILENAME_LENGTH = 100;
|
||||
|
||||
const reRelativePath = /^\.+(\\|\/)|^\.+$/;
|
||||
const reTrailingPeriods = /\.+$/;
|
||||
|
||||
function filenamify(string, options = {}) {
|
||||
const reControlChars = /[\u0000-\u001F\u0080-\u009F]/g; // eslint-disable-line no-control-regex
|
||||
const reRepeatedReservedCharacters = /([<>:"/\\|?*\u0000-\u001F]){2,}/g; // eslint-disable-line no-control-regex
|
||||
|
||||
if (typeof string !== 'string') {
|
||||
throw new TypeError('Expected a string');
|
||||
}
|
||||
|
||||
const replacement = options.replacement === undefined ? '!' : options.replacement;
|
||||
|
||||
if (filenameReservedRegex().test(replacement) && reControlChars.test(replacement)) {
|
||||
throw new Error('Replacement string cannot contain reserved filename characters');
|
||||
}
|
||||
|
||||
/* eslint-disable no-param-reassign */
|
||||
if (replacement.length > 0) {
|
||||
string = string.replace(reRepeatedReservedCharacters, '$1');
|
||||
}
|
||||
|
||||
string = string.normalize('NFD');
|
||||
string = string.replace(reRelativePath, replacement);
|
||||
string = string.replace(filenameReservedRegex(), replacement);
|
||||
string = string.replace(reControlChars, replacement);
|
||||
string = string.replace(reTrailingPeriods, '');
|
||||
|
||||
if (replacement.length > 0) {
|
||||
const startedWithDot = string[0] === '.';
|
||||
|
||||
// We removed the whole filename
|
||||
if (!startedWithDot && string[0] === '.') {
|
||||
string = replacement + string;
|
||||
}
|
||||
|
||||
// We removed the whole extension
|
||||
if (string[string.length - 1] === '.') {
|
||||
string += replacement;
|
||||
}
|
||||
}
|
||||
|
||||
string = windowsReservedNameRegex().test(string) ? string + replacement : string;
|
||||
const allowedLength =
|
||||
typeof options.maxLength === 'number' ? options.maxLength : MAX_FILENAME_LENGTH;
|
||||
if (string.length > allowedLength) {
|
||||
const extensionIndex = string.lastIndexOf('.');
|
||||
if (extensionIndex === -1) {
|
||||
string = string.slice(0, allowedLength);
|
||||
} else {
|
||||
const filename = string.slice(0, extensionIndex);
|
||||
const extension = string.slice(extensionIndex);
|
||||
string = filename.slice(0, Math.max(1, allowedLength - extension.length)) + extension;
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-param-reassign */
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
module.exports = filenamify;
|
||||
Reference in New Issue
Block a user