Compare commits
40
Commits
14b38af8da
...
7d1011400e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d1011400e | ||
|
|
3a4794bacb | ||
|
|
56790262f3 | ||
|
|
fa436b61be | ||
|
|
bb1a3f4cea | ||
|
|
36cafbe87b | ||
|
|
a84a5a677a | ||
|
|
6bec95e084 | ||
|
|
290571f305 | ||
|
|
7ce9470bf3 | ||
|
|
9eba65b711 | ||
|
|
0400c804aa | ||
|
|
7ea1a24857 | ||
|
|
80d3f5336b | ||
|
|
6a208cf040 | ||
|
|
9cef61d447 | ||
|
|
2cb9f69a9d | ||
|
|
276f2bed34 | ||
|
|
2f05cd0651 | ||
|
|
1ba3671873 | ||
|
|
6d02cc903c | ||
|
|
ff60b3902e | ||
|
|
e641dc0d32 | ||
|
|
8535da995f | ||
|
|
baad48f044 | ||
|
|
72009a693b | ||
|
|
b3adf15f12 | ||
|
|
e8b48dfd84 | ||
|
|
7c6fb38b02 | ||
|
|
28f332f56a | ||
|
|
6fdc208ca7 | ||
|
|
2f74357aac | ||
|
|
e692f80bcf | ||
|
|
a411b80fc2 | ||
|
|
837013e97c | ||
|
|
360b37ae13 | ||
|
|
c69229934e | ||
|
|
8288ad4733 | ||
|
|
7d627edfce | ||
|
|
04279395bc |
@@ -41,10 +41,12 @@ The app is fully internationalized using [React-Intl](https://github.com/formatj
|
|||||||
|
|
||||||
The app is currently available in the following languages:
|
The app is currently available in the following languages:
|
||||||
* English - default
|
* English - default
|
||||||
|
* Arabic
|
||||||
* Chinese simplified
|
* Chinese simplified
|
||||||
* Chinese traditional
|
* Chinese traditional
|
||||||
* French
|
* French
|
||||||
* German
|
* German
|
||||||
|
* Italian
|
||||||
* Korean
|
* Korean
|
||||||
* Romanian
|
* Romanian
|
||||||
* Russian
|
* Russian
|
||||||
|
|||||||
+694
-505
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg height="24" viewBox="0 -960 960 960" width="24" fill="#5f6368" xmlns="http://www.w3.org/2000/svg"><path d="M197-706l-84-85 56-57 85 85zM791-112l-85-85 57-57 84 85zM0-440v-80h120v80zM840-440v-80h120v80zM169-113l-57-56 85-85 57 57zM763-706l-57-57 85-84 57 56zM440 0v-120h80V0zM440-840v-120h80v120zM743-380a766.09 766.09 0 01-39.5-11c-13-4-25.833-8.667-38.5-14 5.333-12 9.167-24.167 11.5-36.5 2.333-12.333 3.5-25.167 3.5-38.5 0-55.333-19.5-102.5-58.5-141.5S535.333-680 480-680c-13.333 0-26.167 1.167-38.5 3.5-12.333 2.333-24.5 6.167-36.5 11.5-5.333-12.667-9.833-25.333-13.5-38a1098.034 1098.034 0 01-10.5-39c16-6 32.333-10.5 49-13.5s33.667-4.5 51-4.5c78 0 144.167 27.167 198.5 81.5S761-558 761-480c0 17.333-1.5 34.333-4.5 51s-7.5 33-13.5 49z" fill="orange"/><path d="M453-452c34 34 72 63.167 114 87.5s87 43.167 135 56.5c-26.667 34-59.333 60.5-98 79.5S524.333-200 481-200c-78 0-144.167-27.167-198.5-81.5S201-402 201-480c0-43.333 9.5-84.333 28.5-123s45.5-71.333 79.5-98c13.333 48 32.167 93 56.5 135S419-486 453-452z" fill="#0c80d6"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
Generated
+2599
-2541
File diff suppressed because it is too large
Load Diff
+7
-5
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "tinode-webapp",
|
"name": "tinode-webapp",
|
||||||
"description": "Tinode messenger for the web",
|
"description": "Tinode messenger for the web",
|
||||||
"version": "0.24.0-rc1",
|
"version": "0.25.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/tinode/webapp.git"
|
"url": "git+https://github.com/tinode/webapp.git"
|
||||||
@@ -46,10 +46,12 @@
|
|||||||
"web"
|
"web"
|
||||||
],
|
],
|
||||||
"locales": [
|
"locales": [
|
||||||
|
"ar",
|
||||||
"de",
|
"de",
|
||||||
"en",
|
"en",
|
||||||
"es",
|
"es",
|
||||||
"fr",
|
"fr",
|
||||||
|
"it",
|
||||||
"ko",
|
"ko",
|
||||||
"ro",
|
"ro",
|
||||||
"ru",
|
"ru",
|
||||||
@@ -73,7 +75,7 @@
|
|||||||
"react": "^18.3.0",
|
"react": "^18.3.0",
|
||||||
"react-dom": "^18.3.0",
|
"react-dom": "^18.3.0",
|
||||||
"react-intl": "^7.0.0",
|
"react-intl": "^7.0.0",
|
||||||
"tinode-sdk": "^0.24.0-rc1",
|
"tinode-sdk": "^0.24.4",
|
||||||
"webm-duration-fix": "^1.0.4"
|
"webm-duration-fix": "^1.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -81,13 +83,13 @@
|
|||||||
"@babel/preset-env": "^7.22.10",
|
"@babel/preset-env": "^7.22.10",
|
||||||
"@babel/preset-react": "^7.22.5",
|
"@babel/preset-react": "^7.22.5",
|
||||||
"@formatjs/cli": "^6.1.3",
|
"@formatjs/cli": "^6.1.3",
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^10.0.0",
|
||||||
"babel-plugin-formatjs": "^10.5.3",
|
"babel-plugin-formatjs": "^10.5.3",
|
||||||
"browserslist": "^4.21.10",
|
"browserslist": "^4.21.10",
|
||||||
"copy-webpack-plugin": "^12.0.0",
|
"copy-webpack-plugin": "^13.0.0",
|
||||||
"cssnano": "^7.0.6",
|
"cssnano": "^7.0.6",
|
||||||
"glob": "^11.0.0",
|
"glob": "^11.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^30.0.0",
|
||||||
"postcss": "^8.4.27",
|
"postcss": "^8.4.27",
|
||||||
"postcss-cli": "^11.0.0",
|
"postcss-cli": "^11.0.0",
|
||||||
"webpack": "^5.88.2",
|
"webpack": "^5.88.2",
|
||||||
|
|||||||
+13
-4
@@ -26,6 +26,10 @@ const i18n = {
|
|||||||
'new_message': "Nuevo mensaje",
|
'new_message': "Nuevo mensaje",
|
||||||
'new_chat': "Nueva conversación",
|
'new_chat': "Nueva conversación",
|
||||||
},
|
},
|
||||||
|
'it': {
|
||||||
|
'new_message': "Nuovo Messaggio",
|
||||||
|
'new_chat': "Nuova Chat",
|
||||||
|
},
|
||||||
'ko': {
|
'ko': {
|
||||||
'new_message': "새로운 메시지",
|
'new_message': "새로운 메시지",
|
||||||
'new_chat': "새로운 채팅",
|
'new_chat': "새로운 채팅",
|
||||||
@@ -53,10 +57,14 @@ const i18n = {
|
|||||||
'zh-TW': {
|
'zh-TW': {
|
||||||
'new_message': "新訊息",
|
'new_message': "新訊息",
|
||||||
'new_chat': "新聊天",
|
'new_chat': "新聊天",
|
||||||
|
},
|
||||||
|
'ar': {
|
||||||
|
'new_message': "رسالة جديدة",
|
||||||
|
'new_chat': "محادثة جديدة",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.i18nMessage = function(id) {
|
self.i18nMessage = function (id) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -82,7 +90,7 @@ fbMessaging.onBackgroundMessage(payload => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const titles = {'msg': 'new_message', 'sub': 'new_chat'};
|
const titles = { 'msg': 'new_message', 'sub': 'new_chat' };
|
||||||
const pushType = payload.data.what || 'msg';
|
const pushType = payload.data.what || 'msg';
|
||||||
const title = payload.data.title || self.i18nMessage(titles[pushType]);
|
const title = payload.data.title || self.i18nMessage(titles[pushType]);
|
||||||
if (title) {
|
if (title) {
|
||||||
@@ -119,7 +127,8 @@ self.addEventListener('notificationclick', event => {
|
|||||||
|
|
||||||
event.waitUntil(self.clients.matchAll({
|
event.waitUntil(self.clients.matchAll({
|
||||||
type: 'window',
|
type: 'window',
|
||||||
includeUncontrolled: true})
|
includeUncontrolled: true
|
||||||
|
})
|
||||||
.then(windowClients => {
|
.then(windowClients => {
|
||||||
let anyClient = null;
|
let anyClient = null;
|
||||||
for (let i = 0; i < windowClients.length; i++) {
|
for (let i = 0; i < windowClients.length; i++) {
|
||||||
@@ -162,7 +171,7 @@ self.addEventListener('fetch', event => {
|
|||||||
|
|
||||||
const reqUrl = new URL(event.request.url);
|
const reqUrl = new URL(event.request.url);
|
||||||
// Using ignoreSearch=true to read cached images and docs despite different auth signatures.
|
// Using ignoreSearch=true to read cached images and docs despite different auth signatures.
|
||||||
const cachedResponse = await cache.match(event.request, {ignoreSearch: (self.location.origin == reqUrl.origin)});
|
const cachedResponse = await cache.match(event.request, { ignoreSearch: (self.location.origin == reqUrl.origin) });
|
||||||
if (cachedResponse) {
|
if (cachedResponse) {
|
||||||
return cachedResponse;
|
return cachedResponse;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,3 +148,12 @@ export const SELF_AVATAR_URI = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS
|
|||||||
|
|
||||||
// Toast display duration, milliseconds.
|
// Toast display duration, milliseconds.
|
||||||
export const TOAST_DURATION = 3_000;
|
export const TOAST_DURATION = 3_000;
|
||||||
|
|
||||||
|
// Initial/default color scheme.
|
||||||
|
export const DEFAULT_COLOR_SCHEME = 'light dark';
|
||||||
|
|
||||||
|
// Default text size in points.
|
||||||
|
export const DEFAULT_TEXT_SIZE = 10;
|
||||||
|
|
||||||
|
// MIME type for the legacy Drafty form-response data.
|
||||||
|
export const DRAFTY_FR_MIME_TYPE_LEGACY = 'application/json'; // Remove in 2026.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1791
File diff suppressed because it is too large
Load Diff
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "Dunkel",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "Hell",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "Systemstandard",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "Thema:",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "Erscheinungsbild",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "Textgröße:",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "Dark",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "Light",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "System default",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "Theme:",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "Appearance",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "Text size:",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "Hide your online status and typing indicators.",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "Oscuro",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "Claro",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "Predeterminado del sistema",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "Tema:",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "Apariencia",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "Tamaño del texto:",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,6 +211,18 @@
|
|||||||
"defaultMessage": "Code does not match",
|
"defaultMessage": "Code does not match",
|
||||||
"description": "Error message when the credential validation code is incorrect."
|
"description": "Error message when the credential validation code is incorrect."
|
||||||
},
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema"
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema"
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema"
|
||||||
|
},
|
||||||
"contacts_not_found": {
|
"contacts_not_found": {
|
||||||
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
"defaultMessage": "You have no chats<br />¯∖_(ツ)_/¯",
|
||||||
"description": "HTML message shown in ContactList when no contacts are found"
|
"description": "HTML message shown in ContactList when no contacts are found"
|
||||||
@@ -374,6 +386,10 @@
|
|||||||
"defaultMessage": "Image caption",
|
"defaultMessage": "Image caption",
|
||||||
"description": "Prompt in SendMessage for attached image"
|
"description": "Prompt in SendMessage for attached image"
|
||||||
},
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle"
|
||||||
|
},
|
||||||
"invalid_content": {
|
"invalid_content": {
|
||||||
"defaultMessage": "invalid content",
|
"defaultMessage": "invalid content",
|
||||||
"description": "Shown when the message is unreadable"
|
"description": "Shown when the message is unreadable"
|
||||||
@@ -394,6 +410,10 @@
|
|||||||
"defaultMessage": "Client:",
|
"defaultMessage": "Client:",
|
||||||
"description": "Label for a client version"
|
"description": "Label for a client version"
|
||||||
},
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings"
|
||||||
|
},
|
||||||
"label_content_type": {
|
"label_content_type": {
|
||||||
"defaultMessage": "Content type:",
|
"defaultMessage": "Content type:",
|
||||||
"description": "Label for file content type (mime)"
|
"description": "Label for file content type (mime)"
|
||||||
@@ -480,6 +500,10 @@
|
|||||||
"defaultMessage": "Size:",
|
"defaultMessage": "Size:",
|
||||||
"description": "Label for file size"
|
"description": "Label for file size"
|
||||||
},
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size"
|
||||||
|
},
|
||||||
"label_topic_name": {
|
"label_topic_name": {
|
||||||
"defaultMessage": "Name",
|
"defaultMessage": "Name",
|
||||||
"description": "Label for editing topic name"
|
"description": "Label for editing topic name"
|
||||||
@@ -868,6 +892,10 @@
|
|||||||
"defaultMessage": "Account Settings",
|
"defaultMessage": "Account Settings",
|
||||||
"description": "Sidepanel title for AccountSettingsView."
|
"description": "Sidepanel title for AccountSettingsView."
|
||||||
},
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView."
|
||||||
|
},
|
||||||
"sidepanel_title_archive": {
|
"sidepanel_title_archive": {
|
||||||
"defaultMessage": "Archived Chats",
|
"defaultMessage": "Archived Chats",
|
||||||
"description": "Sidepanel title for ContactsView-Archive."
|
"description": "Sidepanel title for ContactsView-Archive."
|
||||||
|
|||||||
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "Sombre",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "Clair",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "Thème du système",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "Thème :",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "Apparence",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "Taille du texte :",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1791
File diff suppressed because it is too large
Load Diff
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "어둡게",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "밝게",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "시스템 기본값",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "테마:",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "모양",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "텍스트 크기:",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "Întunecat",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "Luminos",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "Implicit sistem",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "Temă:",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "Aspect",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "Dimensiune text:",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "Тёмная",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "Светлая",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "Системная",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "Тема:",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "Оформление",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "Размер текста:",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "มืด",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "สว่าง",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "ค่าเริ่มต้นของระบบ",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "ธีม:",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "ลักษณะ",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "ขนาดตัวอักษร:",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "Темна",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "Світла",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "Системна",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "Тема:",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "Вигляд",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "Розмір тексту:",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "深色",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "淺色",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "系統預設",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "主題:",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "外觀",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "文字大小:",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1738,5 +1738,54 @@
|
|||||||
"description": "Notification that text has been copied to clipboard",
|
"description": "Notification that text has been copied to clipboard",
|
||||||
"missing": false,
|
"missing": false,
|
||||||
"obsolete": false
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_dark": {
|
||||||
|
"translation": "深色",
|
||||||
|
"defaultMessage": "Dark",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_light": {
|
||||||
|
"translation": "浅色",
|
||||||
|
"defaultMessage": "Light",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"color_schema_system": {
|
||||||
|
"translation": "系统默认",
|
||||||
|
"defaultMessage": "System default",
|
||||||
|
"description": "Name of the color schema",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_color_schema": {
|
||||||
|
"translation": "主题:",
|
||||||
|
"defaultMessage": "Theme:",
|
||||||
|
"description": "Label for selecting color scheme (dark, light) in Settings",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"sidepanel_title_appearance": {
|
||||||
|
"translation": "外观",
|
||||||
|
"defaultMessage": "Appearance",
|
||||||
|
"description": "Sidepanel title for AccAppearanceView.",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"label_text_size": {
|
||||||
|
"translation": "文字大小:",
|
||||||
|
"defaultMessage": "Text size:",
|
||||||
|
"description": "Label adjusting text size",
|
||||||
|
"missing": false,
|
||||||
|
"obsolete": false
|
||||||
|
},
|
||||||
|
"incognito_mode_description": {
|
||||||
|
"translation": "",
|
||||||
|
"defaultMessage": "Hide your online status and typing indicators.",
|
||||||
|
"description": "Description for incognito mode toggle",
|
||||||
|
"missing": true,
|
||||||
|
"obsolete": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -24,6 +24,7 @@ if (typeof FIREBASE_INIT != 'undefined' && FIREBASE_INIT && FIREBASE_INIT.measur
|
|||||||
|
|
||||||
// Allow loading translation strings for just one language.
|
// Allow loading translation strings for just one language.
|
||||||
const messageLoader = {
|
const messageLoader = {
|
||||||
|
'ar': _ => import('./i18n.min/ar.json'),
|
||||||
'de': _ => import('./i18n.min/de.json'),
|
'de': _ => import('./i18n.min/de.json'),
|
||||||
'en': _ => import('./i18n.min/en.json'),
|
'en': _ => import('./i18n.min/en.json'),
|
||||||
'es': _ => import('./i18n.min/es.json'),
|
'es': _ => import('./i18n.min/es.json'),
|
||||||
@@ -37,6 +38,9 @@ const messageLoader = {
|
|||||||
'zh-TW': _ => import('./i18n.min/zh-TW.json')
|
'zh-TW': _ => import('./i18n.min/zh-TW.json')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// List of languages that are written right-to-left.
|
||||||
|
const rtl = ['ar', 'fa', 'he', 'ur'];
|
||||||
|
|
||||||
// Detect human language to use in the UI:
|
// Detect human language to use in the UI:
|
||||||
// Check parameters from URL hash #?hl=ru, then browser, then use 'en' as a fallback.
|
// Check parameters from URL hash #?hl=ru, then browser, then use 'en' as a fallback.
|
||||||
const { params } = HashNavigation.parseUrlHash(window.location.hash);
|
const { params } = HashNavigation.parseUrlHash(window.location.hash);
|
||||||
@@ -54,9 +58,13 @@ const baseLanguage = normalized.split('-')[0].toLowerCase();
|
|||||||
// Try the full locale first, then the locale without the region code, fallback to 'en'.
|
// Try the full locale first, then the locale without the region code, fallback to 'en'.
|
||||||
const htmlLang = messageLoader[normalized] ? language : messageLoader[baseLanguage] ? baseLanguage : 'en';
|
const htmlLang = messageLoader[normalized] ? language : messageLoader[baseLanguage] ? baseLanguage : 'en';
|
||||||
|
|
||||||
|
const html = document.getElementsByTagName('html')[0];
|
||||||
// Set lang attribute of the HTML element: <html lang="XX">
|
// Set lang attribute of the HTML element: <html lang="XX">
|
||||||
document.getElementsByTagName('html')[0].setAttribute('lang', htmlLang);
|
html.setAttribute('lang', htmlLang);
|
||||||
|
if (rtl.includes(baseLanguage)) {
|
||||||
|
// Set right-to-left dir attribute of the HTML element: <html dir="rtl">
|
||||||
|
html.setAttribute('dir', 'rtl');
|
||||||
|
}
|
||||||
// Render the app.
|
// Render the app.
|
||||||
const root = createRoot(document.getElementById('mountPoint'));
|
const root = createRoot(document.getElementById('mountPoint'));
|
||||||
messageLoader[htmlLang]().then(messages =>
|
messageLoader[htmlLang]().then(messages =>
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import InlineVideo from '../widgets/inline-video.jsx';
|
|||||||
import LazyImage from '../widgets/lazy-image.jsx'
|
import LazyImage from '../widgets/lazy-image.jsx'
|
||||||
import UploadingImage from '../widgets/uploading-image.jsx'
|
import UploadingImage from '../widgets/uploading-image.jsx'
|
||||||
|
|
||||||
import { BROKEN_IMAGE_SIZE, CLICKABLE_URL_SCHEMES, IMAGE_THUMBNAIL_DIM, NO_DIMENSIONS_VIDEO, REM_SIZE,
|
import { BROKEN_IMAGE_SIZE, CLICKABLE_URL_SCHEMES, IMAGE_THUMBNAIL_DIM, NO_DIMENSIONS_VIDEO,
|
||||||
VIDEO_THUMBNAIL_WIDTH } from '../config.js';
|
REM_SIZE, VIDEO_THUMBNAIL_WIDTH } from '../config.js';
|
||||||
import { base64ToBlob, blobToBase64, fitImageSize, imageScaled } from './blob-helpers.js';
|
import { base64ToBlob, blobToBase64, fitImageSize, imageScaled } from './blob-helpers.js';
|
||||||
import { idToColorClass, secondsToTime, shortenFileName } from './strformat.js';
|
import { idToColorClass, secondsToTime, shortenFileName } from './strformat.js';
|
||||||
import { cancelablePromise, sanitizeUrl, sanitizeUrlForMime } from './utils.js';
|
import { cancelablePromise, sanitizeUrl, sanitizeUrlForMime } from './utils.js';
|
||||||
@@ -304,7 +304,7 @@ export function previewFormatter(style, data, values, key) {
|
|||||||
break;
|
break;
|
||||||
case 'EX':
|
case 'EX':
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data.mime == 'application/json') {
|
if (Drafty.isFormResponseType(data.mime)) {
|
||||||
// Ignore JSON attachments: they are form response payloads.
|
// Ignore JSON attachments: they are form response payloads.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -426,7 +426,7 @@ function quoteFormatter(style, data, values, key) {
|
|||||||
case 'EX':
|
case 'EX':
|
||||||
let fname;
|
let fname;
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data.mime == 'application/json') {
|
if (Drafty.isFormResponseType(data.mime)) {
|
||||||
// Ignore JSON attachments: they are form response payloads.
|
// Ignore JSON attachments: they are form response payloads.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
import {
|
||||||
|
shortDateFormat,
|
||||||
|
relativeDateFormat,
|
||||||
|
secondsToTime,
|
||||||
|
bytesToHumanSize,
|
||||||
|
shortenFileName,
|
||||||
|
idToColorClass,
|
||||||
|
flagEmoji
|
||||||
|
} from './strformat.js';
|
||||||
|
|
||||||
|
describe('shortDateFormat', () => {
|
||||||
|
jest
|
||||||
|
.useFakeTimers()
|
||||||
|
.setSystemTime(new Date('2025-07-25T12:30:00Z'));
|
||||||
|
|
||||||
|
test('should format for same day', () => {
|
||||||
|
const then = new Date('2025-07-25T10:15:00');
|
||||||
|
// Note: output depends on the test runner's locale and timezone.
|
||||||
|
// Assuming a locale where the format is HH:mm.
|
||||||
|
expect(shortDateFormat(then, 'en-US')).toMatch(/10:15/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format for same year, different day', () => {
|
||||||
|
const then = new Date('2025-06-20T10:15:00');
|
||||||
|
expect(shortDateFormat(then, 'en-US')).toMatch(/Jun 20, 10:15/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format for different year', () => {
|
||||||
|
const then = new Date('2024-07-25T10:15:00');
|
||||||
|
expect(shortDateFormat(then, 'en-US')).toBe('Jul 25, 2024');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('relativeDateFormat', () => {
|
||||||
|
// Mock current date to a fixed point for consistent testing.
|
||||||
|
jest
|
||||||
|
.useFakeTimers()
|
||||||
|
.setSystemTime(new Date('2025-07-25T12:30:00Z'));
|
||||||
|
|
||||||
|
test('should format for today', () => {
|
||||||
|
const then = new Date('2025-07-25T10:00:00Z');
|
||||||
|
expect(relativeDateFormat(then, 'en-US')).toBe('today');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format for yesterday', () => {
|
||||||
|
const then = new Date('2025-07-24T10:00:00Z');
|
||||||
|
expect(relativeDateFormat(then, 'en-US')).toBe('yesterday');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format for a past date', () => {
|
||||||
|
const then = new Date('2025-07-20T10:00:00Z');
|
||||||
|
expect(relativeDateFormat(then, 'en-US')).toBe('7/20/2025');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format for tomorrow', () => {
|
||||||
|
const then = new Date('2025-07-26T10:00:00Z');
|
||||||
|
expect(relativeDateFormat(then, 'en-US')).toBe('tomorrow');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('secondsToTime', () => {
|
||||||
|
test('should format seconds only', () => {
|
||||||
|
expect(secondsToTime(45)).toBe('0:45');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format minutes and seconds', () => {
|
||||||
|
expect(secondsToTime(156)).toBe('2:36');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format hours, minutes, and seconds', () => {
|
||||||
|
expect(secondsToTime(3756)).toBe('1:02:36');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format with fixed minutes', () => {
|
||||||
|
expect(secondsToTime(3726, true)).toBe('1:02:06');
|
||||||
|
expect(secondsToTime(126, true)).toBe('02:06');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle non-number input', () => {
|
||||||
|
expect(secondsToTime('abc')).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('bytesToHumanSize', () => {
|
||||||
|
test('should format 0 bytes', () => {
|
||||||
|
expect(bytesToHumanSize(0)).toBe('0 Bytes');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format bytes', () => {
|
||||||
|
expect(bytesToHumanSize(500)).toBe('500 Bytes');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format kilobytes', () => {
|
||||||
|
expect(bytesToHumanSize(1500)).toBe('1.46 KB');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format megabytes', () => {
|
||||||
|
expect(bytesToHumanSize(1500000)).toBe('1.43 MB');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format gigabytes', () => {
|
||||||
|
expect(bytesToHumanSize(1500000000)).toBe('1.40 GB');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('shortenFileName', () => {
|
||||||
|
test('should not shorten if not needed', () => {
|
||||||
|
expect(shortenFileName('shortname.txt', 20)).toBe('shortname.txt');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should shorten a long file name', () => {
|
||||||
|
const longName = 'this_is_a_very_long_file_name_that_needs_shortening.txt';
|
||||||
|
expect(shortenFileName(longName, 20)).toBe('this_is_a…ening.txt');
|
||||||
|
expect(shortenFileName(longName, 20).length).toBeLessThanOrEqual(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle non-string input', () => {
|
||||||
|
expect(shortenFileName(null, 20)).toBeNull();
|
||||||
|
expect(shortenFileName(undefined, 20)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('idToColorClass', () => {
|
||||||
|
test('should generate light background color class', () => {
|
||||||
|
expect(idToColorClass('usr123', true, false)).toMatch(/lt-bg-\d+/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate dark background color class', () => {
|
||||||
|
expect(idToColorClass('usr123', false, false)).toMatch(/dk-bg-\d+/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate light foreground color class', () => {
|
||||||
|
expect(idToColorClass('usr123', true, true)).toMatch(/lt-fg-\d+/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should generate dark foreground color class', () => {
|
||||||
|
expect(idToColorClass('usr123', false, true)).toMatch(/dk-fg-\d+/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be deterministic', () => {
|
||||||
|
expect(idToColorClass('usr123', true, false)).toBe('lt-bg-2');
|
||||||
|
expect(idToColorClass('grpABC', false, true)).toBe('dk-fg-3');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('flagEmoji', () => {
|
||||||
|
test('should convert country code to flag', () => {
|
||||||
|
expect(flagEmoji('US')).toBe('🇺🇸');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle lowercase country code', () => {
|
||||||
|
expect(flagEmoji('jp')).toBe('🇯🇵');
|
||||||
|
});
|
||||||
|
});
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// This is a generated file. Don't edit.
|
// This is a generated file. Don't edit.
|
||||||
|
|
||||||
export const PACKAGE_VERSION = "0.24.0-rc1";
|
export const PACKAGE_VERSION = "0.25.0";
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Edit account parameters.
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
export default class AccAppearanceView extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
colorSchema: props.colorSchema || 'light dark',
|
||||||
|
textSize: (props.textSize * 10) || '100',
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleColorSchemaSelected = this.handleColorSchemaSelected.bind(this);
|
||||||
|
this.handleTextSizeChanged = this.handleTextSizeChanged.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleColorSchemaSelected(e) {
|
||||||
|
this.setState({colorSchema: e.currentTarget.value});
|
||||||
|
this.props.onChangeColorSchema(e.currentTarget.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTextSizeChanged(e) {
|
||||||
|
// In %%.
|
||||||
|
this.setState({textSize: e.currentTarget.value});
|
||||||
|
// Convert %% to pt, 100% = 10pt.
|
||||||
|
this.props.onTextSizeChanged((e.currentTarget.value / 10) | 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div id="settings-form" className="scrollable-panel">
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<label className="small">
|
||||||
|
<FormattedMessage id="label_color_schema" defaultMessage="Theme:"
|
||||||
|
description="Label for selecting color scheme (dark, light) in Settings" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<ul className="quoted">
|
||||||
|
<li key="system">
|
||||||
|
<input type="radio" id="system" name="color-scheme-select" value="light dark"
|
||||||
|
checked={this.state.colorSchema === 'light dark'}
|
||||||
|
onChange={this.handleColorSchemaSelected} />
|
||||||
|
<label htmlFor="system">
|
||||||
|
<FormattedMessage id="color_schema_system" defaultMessage="System default" description="Name of the color schema"/>
|
||||||
|
<img src="../img/routine.svg" style={{verticalAlign: 'top', width: '1.6rem', height: '1.6rem'}}/>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li key="light">
|
||||||
|
<input type="radio" id="light" name="color-scheme-select" value="light"
|
||||||
|
checked={this.state.colorSchema === 'light'}
|
||||||
|
onChange={this.handleColorSchemaSelected} />
|
||||||
|
<label htmlFor="light">
|
||||||
|
<FormattedMessage id="color_schema_light" defaultMessage="Light" description="Name of the color schema"/>
|
||||||
|
<i className="material-icons orange large">light_mode</i>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li key="dark">
|
||||||
|
<input type="radio" id="dark" name="color-scheme-select" value="dark"
|
||||||
|
checked={this.state.colorSchema === 'dark'}
|
||||||
|
onChange={this.handleColorSchemaSelected} />
|
||||||
|
<label htmlFor="dark">
|
||||||
|
<FormattedMessage id="color_schema_dark" defaultMessage="Dark" description="Name of the color schema"/>
|
||||||
|
<i className="material-icons blue large">dark_mode</i>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="hr" />
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<label className="small">
|
||||||
|
<FormattedMessage id="label_text_size" defaultMessage="Text size:"
|
||||||
|
description="Label adjusting text size" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="panel-form-column">
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<input type="range" id="text_size" name="text_size" min="80" max="120" step="10"
|
||||||
|
list="text_size_options" value={this.state.textSize} onChange={this.handleTextSizeChanged} />
|
||||||
|
</div>
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<datalist id="text_size_options">
|
||||||
|
<option value="80" label="80%" />
|
||||||
|
<option value="90" label="90%" />
|
||||||
|
<option value="100" label="100%" />
|
||||||
|
<option value="110" label="110%" />
|
||||||
|
<option value="120" label="120%" />
|
||||||
|
</datalist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -4,7 +4,7 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import CheckBox from '../widgets/checkbox.jsx';
|
import CheckBox from '../widgets/checkbox.jsx';
|
||||||
|
|
||||||
export default class EditAccountView extends React.PureComponent {
|
export default class AccNotificationsView extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -16,8 +16,6 @@ export default class EditAccountView extends React.PureComponent {
|
|||||||
this.props.onToggleMessageSounds(checked);
|
this.props.onToggleMessageSounds(checked);
|
||||||
} else if (what == 'alert') {
|
} else if (what == 'alert') {
|
||||||
this.props.onTogglePushNotifications(checked);
|
this.props.onTogglePushNotifications(checked);
|
||||||
} else if (what == 'incognito') {
|
|
||||||
this.props.onToggleIncognitoMode(checked);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,14 +45,6 @@ export default class EditAccountView extends React.PureComponent {
|
|||||||
checked={this.props.desktopAlerts}
|
checked={this.props.desktopAlerts}
|
||||||
onChange={this.props.desktopAlertsEnabled ? this.handleCheckboxClick : null} />
|
onChange={this.props.desktopAlertsEnabled ? this.handleCheckboxClick : null} />
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-form-row">
|
|
||||||
<label htmlFor="incognito-mode">
|
|
||||||
<FormattedMessage id="label_incognito_mode" defaultMessage="Incognito mode:"
|
|
||||||
description="Label for incognito mode toggle" />
|
|
||||||
</label>
|
|
||||||
<CheckBox name="incognito" id="incognito-mode"
|
|
||||||
checked={this.props.incognitoMode} onChange={this.handleCheckboxClick} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import CheckBox from '../widgets/checkbox.jsx';
|
||||||
import InPlaceEdit from '../widgets/in-place-edit.jsx';
|
import InPlaceEdit from '../widgets/in-place-edit.jsx';
|
||||||
import PermissionsEditor from '../widgets/permissions-editor.jsx';
|
import PermissionsEditor from '../widgets/permissions-editor.jsx';
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ class AccSecurityView extends React.Component {
|
|||||||
this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);
|
this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);
|
||||||
this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);
|
this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);
|
||||||
this.handleDeleteAccount = this.handleDeleteAccount.bind(this);
|
this.handleDeleteAccount = this.handleDeleteAccount.bind(this);
|
||||||
|
this.handleCheckboxClick = this.handleCheckboxClick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePasswordUpdate(pwd) {
|
handlePasswordUpdate(pwd) {
|
||||||
@@ -70,6 +72,10 @@ class AccSecurityView extends React.Component {
|
|||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCheckboxClick(what, checked) {
|
||||||
|
this.props.onToggleIncognitoMode(checked);
|
||||||
|
}
|
||||||
|
|
||||||
handleDeleteAccount(e) {
|
handleDeleteAccount(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const {formatMessage} = this.props.intl;
|
const {formatMessage} = this.props.intl;
|
||||||
@@ -109,6 +115,21 @@ class AccSecurityView extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="hr" />
|
<div className="hr" />
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<label htmlFor="incognito-mode">
|
||||||
|
<FormattedMessage id="label_incognito_mode" defaultMessage="Incognito mode:"
|
||||||
|
description="Label for incognito mode toggle" />
|
||||||
|
</label>
|
||||||
|
<CheckBox name="incognito" id="incognito-mode"
|
||||||
|
checked={this.props.incognitoMode} onChange={this.handleCheckboxClick} />
|
||||||
|
</div>
|
||||||
|
<div className="panel-form-row">
|
||||||
|
<span className="small">
|
||||||
|
<FormattedMessage id="incognito_mode_description" defaultMessage="Hide your online status and typing indicators."
|
||||||
|
description="Description for incognito mode toggle" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="hr" />
|
||||||
<div className="panel-form-column">
|
<div className="panel-form-column">
|
||||||
<a href="#" className="danger flat-button" onClick={(e) => {e.preventDefault(); this.props.onLogout();}}>
|
<a href="#" className="danger flat-button" onClick={(e) => {e.preventDefault(); this.props.onLogout();}}>
|
||||||
<i className="material-icons">exit_to_app</i> <FormattedMessage id="button_logout"
|
<i className="material-icons">exit_to_app</i> <FormattedMessage id="button_logout"
|
||||||
|
|||||||
@@ -148,6 +148,10 @@ export default class AccountSettingsView extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="hr" />
|
<div className="hr" />
|
||||||
<div className="panel-form-column">
|
<div className="panel-form-column">
|
||||||
|
<a href="#" className="flat-button" onClick={e => {e.preventDefault(); this.props.onNavigate('appearance');}}>
|
||||||
|
<i className="material-icons">tune</i> <FormattedMessage id="sidepanel_title_appearance"
|
||||||
|
defaultMessage="Appearance" description="Sidepanel title for AccAppearanceView." />
|
||||||
|
</a>
|
||||||
<a href="#" className="flat-button" onClick={e => {e.preventDefault(); this.props.onNavigate('notif');}}>
|
<a href="#" className="flat-button" onClick={e => {e.preventDefault(); this.props.onNavigate('notif');}}>
|
||||||
<i className="material-icons">notifications</i> <FormattedMessage id="sidepanel_title_acc_notifications"
|
<i className="material-icons">notifications</i> <FormattedMessage id="sidepanel_title_acc_notifications"
|
||||||
defaultMessage="Notifications" description="Sidepanel title for AccNotificationsView." />
|
defaultMessage="Notifications" description="Sidepanel title for AccNotificationsView." />
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import SendMessage from '../widgets/send-message.jsx';
|
|||||||
import VideoPreview from '../widgets/video-preview.jsx';
|
import VideoPreview from '../widgets/video-preview.jsx';
|
||||||
|
|
||||||
import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, IMMEDIATE_P2P_SUBSCRIPTION,
|
import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, IMMEDIATE_P2P_SUBSCRIPTION,
|
||||||
KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE,
|
DRAFTY_FR_MIME_TYPE_LEGACY, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM,
|
||||||
READ_DELAY, QUOTED_REPLY_LENGTH, VIDEO_PREVIEW_DIM } from '../config.js';
|
MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY, QUOTED_REPLY_LENGTH, VIDEO_PREVIEW_DIM } from '../config.js';
|
||||||
import { CALL_STATE_OUTGOING_INITATED, CALL_STATE_IN_PROGRESS } from '../constants.js';
|
import { CALL_STATE_OUTGOING_INITATED, CALL_STATE_IN_PROGRESS } from '../constants.js';
|
||||||
import { blobToBase64, fileToBase64, imageScaled, makeImageUrl } from '../lib/blob-helpers.js';
|
import { blobToBase64, fileToBase64, imageScaled, makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
import HashNavigation from '../lib/navigation.js';
|
import HashNavigation from '../lib/navigation.js';
|
||||||
@@ -1022,6 +1022,11 @@ class MessagesView extends React.Component {
|
|||||||
const maxInbandAttachmentSize = (this.props.tinode.getServerParam('maxMessageSize',
|
const maxInbandAttachmentSize = (this.props.tinode.getServerParam('maxMessageSize',
|
||||||
MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;
|
MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;
|
||||||
|
|
||||||
|
// If the attachment is a JSON file, then use 'application/octet-stream' instead of 'application/json'.
|
||||||
|
// This is a temporary workaround for the collision with the 'application/json' MIME type of form responses.
|
||||||
|
// Remove this code in 2026 or so.
|
||||||
|
const jsonMimeConverter = (fileType) => fileType === DRAFTY_FR_MIME_TYPE_LEGACY ? 'application/octet-stream' : fileType;
|
||||||
|
|
||||||
if (file.size > maxInbandAttachmentSize) {
|
if (file.size > maxInbandAttachmentSize) {
|
||||||
// Too large to send inband - uploading out of band and sending as a link.
|
// Too large to send inband - uploading out of band and sending as a link.
|
||||||
const uploader = this.props.tinode.getLargeFileHelper();
|
const uploader = this.props.tinode.getLargeFileHelper();
|
||||||
@@ -1031,7 +1036,7 @@ class MessagesView extends React.Component {
|
|||||||
}
|
}
|
||||||
const uploadCompletionPromise = uploader.upload(file);
|
const uploadCompletionPromise = uploader.upload(file);
|
||||||
const msg = Drafty.attachFile(null, {
|
const msg = Drafty.attachFile(null, {
|
||||||
mime: file.type,
|
mime: jsonMimeConverter(file.type),
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
urlPromise: uploadCompletionPromise
|
urlPromise: uploadCompletionPromise
|
||||||
@@ -1042,7 +1047,7 @@ class MessagesView extends React.Component {
|
|||||||
// Small enough to send inband.
|
// Small enough to send inband.
|
||||||
fileToBase64(file)
|
fileToBase64(file)
|
||||||
.then(b64 => this.sendMessage(Drafty.attachFile(null, {
|
.then(b64 => this.sendMessage(Drafty.attachFile(null, {
|
||||||
mime: b64.mime,
|
mime: jsonMimeConverter(b64.mime),
|
||||||
data: b64.bits,
|
data: b64.bits,
|
||||||
filename: b64.name,
|
filename: b64.name,
|
||||||
size: file.size
|
size: file.size
|
||||||
@@ -1673,7 +1678,7 @@ class MessagesView extends React.Component {
|
|||||||
onClick={this.goToLatestMessage}>
|
onClick={this.goToLatestMessage}>
|
||||||
<i className="material-icons">arrow_downward</i>
|
<i className="material-icons">arrow_downward</i>
|
||||||
</button>
|
</button>
|
||||||
<div id="messages-panel" ref={this.handleScrollReference}>
|
<div id="messages-panel" ref={this.handleScrollReference} color-scheme={this.props.colorSchema}>
|
||||||
<ul id="scroller" className={chatBoxClass}>
|
<ul id="scroller" className={chatBoxClass}>
|
||||||
{messageNodes}
|
{messageNodes}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -57,21 +57,23 @@ class NewTopicView extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSearchContacts(query) {
|
handleSearchContacts(query) {
|
||||||
query = query.trim();
|
query = query.trim() || Tinode.DEL_CHAR;
|
||||||
if (!query) {
|
if (!/[\s,:]/.test(query) && query != Tinode.DEL_CHAR) {
|
||||||
query = Tinode.DEL_CHAR;
|
// No colons, spaces or commas, not DEL char. Try as email, phone, or alias.
|
||||||
} else if (!/[\s,:]/.test(query) && query != Tinode.DEL_CHAR) {
|
|
||||||
// No colons, spaces or commas, not DEL char. Try as email or phone.
|
|
||||||
// If not email or phone, treat as alias.
|
|
||||||
const email = asEmail(query);
|
const email = asEmail(query);
|
||||||
if (email) {
|
if (email) {
|
||||||
query = `email:${email}`;
|
query = `${Tinode.TAG_EMAIL}${email}`;
|
||||||
} else {
|
} else {
|
||||||
const tel = asPhone(query);
|
const tel = asPhone(query);
|
||||||
if (tel) {
|
if (tel) {
|
||||||
query = `tel:${tel}`;
|
query = `${Tinode.TAG_PHONE}${tel}`;
|
||||||
} else {
|
} else {
|
||||||
query = `alias:${query}`;
|
if (query[0] == '@') {
|
||||||
|
// Strip the leading '@' sign if present.
|
||||||
|
query = query.substring(1);
|
||||||
|
}
|
||||||
|
// 'alice' -> 'alias:alice,alice'
|
||||||
|
query = `${Tinode.TAG_ALIAS}${query},${query}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import TopicCommonView from './topic-common-view.jsx';
|
|||||||
const AccountSettingsView = React.lazy(_ => import('./account-settings-view.jsx'));
|
const AccountSettingsView = React.lazy(_ => import('./account-settings-view.jsx'));
|
||||||
import ContactsView from './contacts-view.jsx';
|
import ContactsView from './contacts-view.jsx';
|
||||||
const CreateAccountView = React.lazy(_ => import('./create-account-view.jsx'));
|
const CreateAccountView = React.lazy(_ => import('./create-account-view.jsx'));
|
||||||
|
import AccAppearanceView from './acc-appearance-view.jsx';
|
||||||
import AccNotificationsView from './acc-notifications-view.jsx';
|
import AccNotificationsView from './acc-notifications-view.jsx';
|
||||||
import AccSecurityView from './acc-security-view.jsx';
|
const AccSecurityView = React.lazy(_ => import('./acc-security-view.jsx'));
|
||||||
import AccSupportView from './acc-support-view.jsx';
|
import AccSupportView from './acc-support-view.jsx';
|
||||||
import LoginView from './login-view.jsx';
|
import LoginView from './login-view.jsx';
|
||||||
import NewTopicView from './new-topic-view.jsx';
|
import NewTopicView from './new-topic-view.jsx';
|
||||||
@@ -60,6 +61,11 @@ const messages = defineMessages({
|
|||||||
description: 'Sidepanel title for AccNotificationsView.',
|
description: 'Sidepanel title for AccNotificationsView.',
|
||||||
defaultMessage: 'Notifications'
|
defaultMessage: 'Notifications'
|
||||||
},
|
},
|
||||||
|
appearance: {
|
||||||
|
id: 'sidepanel_title_appearance',
|
||||||
|
description: 'Sidepanel title for AccAppearanceView.',
|
||||||
|
defaultMessage: 'Appearance'
|
||||||
|
},
|
||||||
support: {
|
support: {
|
||||||
id: 'sidepanel_title_acc_support',
|
id: 'sidepanel_title_acc_support',
|
||||||
description: 'Sidepanel title for AccSupportView.',
|
description: 'Sidepanel title for AccSupportView.',
|
||||||
@@ -200,25 +206,35 @@ class SidepanelView extends React.PureComponent {
|
|||||||
onUpdateTagsRequest={this.props.onUpdateAccountTags}
|
onUpdateTagsRequest={this.props.onUpdateAccountTags}
|
||||||
onError={this.props.onError} /> :
|
onError={this.props.onError} /> :
|
||||||
|
|
||||||
|
view === 'appearance' ?
|
||||||
|
<AccAppearanceView
|
||||||
|
colorSchema={this.props.colorSchema}
|
||||||
|
textSize={this.props.textSize}
|
||||||
|
onChangeColorSchema={this.props.onChangeColorSchema}
|
||||||
|
onTextSizeChanged={this.props.onTextSizeChanged} /> :
|
||||||
|
|
||||||
view === 'notif' ?
|
view === 'notif' ?
|
||||||
<AccNotificationsView
|
<AccNotificationsView
|
||||||
messageSounds={this.props.messageSounds}
|
messageSounds={this.props.messageSounds}
|
||||||
desktopAlerts={this.props.desktopAlerts}
|
desktopAlerts={this.props.desktopAlerts}
|
||||||
desktopAlertsEnabled={this.props.desktopAlertsEnabled}
|
desktopAlertsEnabled={this.props.desktopAlertsEnabled}
|
||||||
incognitoMode={this.props.incognitoMode}
|
|
||||||
onTogglePushNotifications={this.props.onTogglePushNotifications}
|
onTogglePushNotifications={this.props.onTogglePushNotifications}
|
||||||
onToggleMessageSounds={this.props.onToggleMessageSounds}
|
onToggleMessageSounds={this.props.onToggleMessageSounds} /> :
|
||||||
onToggleIncognitoMode={this.props.onToggleIncognitoMode} /> :
|
|
||||||
|
|
||||||
view === 'security' ?
|
view === 'security' ?
|
||||||
|
<Suspense fallback={<div className="panel-form-row"><FormattedMessage id="loading_note"
|
||||||
|
defaultMessage="Loading..." description="Message shown when component is loading"/></div>}>
|
||||||
<AccSecurityView
|
<AccSecurityView
|
||||||
tinode={this.props.tinode}
|
tinode={this.props.tinode}
|
||||||
|
incognitoMode={this.props.incognitoMode}
|
||||||
onUpdateAccountDesc={this.props.onUpdateAccountDesc}
|
onUpdateAccountDesc={this.props.onUpdateAccountDesc}
|
||||||
onUpdatePassword={this.props.onUpdatePassword}
|
onUpdatePassword={this.props.onUpdatePassword}
|
||||||
onLogout={this.props.onLogout}
|
onLogout={this.props.onLogout}
|
||||||
onDeleteAccount={this.props.onDeleteAccount}
|
onDeleteAccount={this.props.onDeleteAccount}
|
||||||
onShowAlert={this.props.onShowAlert}
|
onShowAlert={this.props.onShowAlert}
|
||||||
onShowBlocked={this.props.onShowBlocked} /> :
|
onShowBlocked={this.props.onShowBlocked}
|
||||||
|
onToggleIncognitoMode={this.props.onToggleIncognitoMode} />
|
||||||
|
</Suspense> :
|
||||||
|
|
||||||
view === 'support' ?
|
view === 'support' ?
|
||||||
<AccSupportView
|
<AccSupportView
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import ForwardDialog from '../widgets/forward-dialog.jsx';
|
|||||||
import CallIncoming from '../widgets/call-incoming.jsx';
|
import CallIncoming from '../widgets/call-incoming.jsx';
|
||||||
const PhoneCountrySelector = React.lazy(_ => import('../widgets/phone-country-selector.jsx'));
|
const PhoneCountrySelector = React.lazy(_ => import('../widgets/phone-country-selector.jsx'));
|
||||||
|
|
||||||
import InfoView from './info-view.jsx';
|
const InfoView = React.lazy(_ => import('./info-view.jsx'));
|
||||||
import MessagesView from './messages-view.jsx';
|
import MessagesView from './messages-view.jsx';
|
||||||
import SidepanelView from './sidepanel-view.jsx';
|
import SidepanelView from './sidepanel-view.jsx';
|
||||||
|
|
||||||
import { API_KEY, APP_NAME, DEFAULT_P2P_ACCESS_MODE, FORWARDED_PREVIEW_LENGTH, LOGGING_ENABLED,
|
import { API_KEY, APP_NAME, DEFAULT_COLOR_SCHEME, DEFAULT_P2P_ACCESS_MODE, DEFAULT_TEXT_SIZE, FORWARDED_PREVIEW_LENGTH,
|
||||||
MEDIA_BREAKPOINT, WAKE_UP_TICK, WAKE_UP_TIMEOUT } from '../config.js';
|
LOGGING_ENABLED, MEDIA_BREAKPOINT, WAKE_UP_TICK, WAKE_UP_TIMEOUT } from '../config.js';
|
||||||
import { CALL_STATE_NONE, CALL_STATE_OUTGOING_INITATED,
|
import { CALL_STATE_NONE, CALL_STATE_OUTGOING_INITATED,
|
||||||
CALL_STATE_INCOMING_RECEIVED, CALL_STATE_IN_PROGRESS,
|
CALL_STATE_INCOMING_RECEIVED, CALL_STATE_IN_PROGRESS,
|
||||||
CALL_HEAD_STARTED } from '../constants.js';
|
CALL_HEAD_STARTED } from '../constants.js';
|
||||||
@@ -134,6 +134,8 @@ class TinodeWeb extends React.Component {
|
|||||||
this.handleUpdatePasswordRequest = this.handleUpdatePasswordRequest.bind(this);
|
this.handleUpdatePasswordRequest = this.handleUpdatePasswordRequest.bind(this);
|
||||||
this.handleUpdateAccountTagsRequest = this.handleUpdateAccountTagsRequest.bind(this);
|
this.handleUpdateAccountTagsRequest = this.handleUpdateAccountTagsRequest.bind(this);
|
||||||
this.handleToggleIncognitoMode = this.handleToggleIncognitoMode.bind(this);
|
this.handleToggleIncognitoMode = this.handleToggleIncognitoMode.bind(this);
|
||||||
|
this.handleChangeColorSchema = this.handleChangeColorSchema.bind(this);
|
||||||
|
this.handleChangeTextSize = this.handleChangeTextSize.bind(this);
|
||||||
this.handleSettings = this.handleSettings.bind(this);
|
this.handleSettings = this.handleSettings.bind(this);
|
||||||
this.handleGlobalSettings = this.handleGlobalSettings.bind(this);
|
this.handleGlobalSettings = this.handleGlobalSettings.bind(this);
|
||||||
this.handleShowArchive = this.handleShowArchive.bind(this);
|
this.handleShowArchive = this.handleShowArchive.bind(this);
|
||||||
@@ -213,6 +215,8 @@ class TinodeWeb extends React.Component {
|
|||||||
// "On" is the default, so saving the "off" state.
|
// "On" is the default, so saving the "off" state.
|
||||||
messageSounds: !settings.messageSoundsOff,
|
messageSounds: !settings.messageSoundsOff,
|
||||||
incognitoMode: false,
|
incognitoMode: false,
|
||||||
|
colorSchema: settings.colorSchema || DEFAULT_COLOR_SCHEME, // default: system default
|
||||||
|
textSize: settings.textSize || DEFAULT_TEXT_SIZE, // default size: 10pt
|
||||||
// Persistent request to enable alerts.
|
// Persistent request to enable alerts.
|
||||||
desktopAlerts: persist && !!settings.desktopAlerts,
|
desktopAlerts: persist && !!settings.desktopAlerts,
|
||||||
// Enable / disable checkbox.
|
// Enable / disable checkbox.
|
||||||
@@ -293,6 +297,9 @@ class TinodeWeb extends React.Component {
|
|||||||
window.addEventListener('offline', this.handleOnlineOff);
|
window.addEventListener('offline', this.handleOnlineOff);
|
||||||
window.addEventListener('hashchange', this.handleHashRoute);
|
window.addEventListener('hashchange', this.handleHashRoute);
|
||||||
|
|
||||||
|
document.documentElement.style.colorScheme = this.state.colorSchema;
|
||||||
|
document.documentElement.style.setProperty('--message-text-size', `${this.state.textSize}pt`);
|
||||||
|
|
||||||
// Process background notifications from the service worker.
|
// Process background notifications from the service worker.
|
||||||
if (typeof BroadcastChannel == 'function') {
|
if (typeof BroadcastChannel == 'function') {
|
||||||
const serviceWorkerChannel = new BroadcastChannel('tinode-sw');
|
const serviceWorkerChannel = new BroadcastChannel('tinode-sw');
|
||||||
@@ -517,7 +524,7 @@ class TinodeWeb extends React.Component {
|
|||||||
|
|
||||||
if (hash.path && hash.path.length > 0) {
|
if (hash.path && hash.path.length > 0) {
|
||||||
// Left-side panel selector.
|
// Left-side panel selector.
|
||||||
if (['register','settings','edit','notif','security','support','general','crop',
|
if (['register','settings','edit','notif','appearance','security','support','general','crop',
|
||||||
'cred','reset','newtpk','archive','blocked','contacts',''].includes(hash.path[0])) {
|
'cred','reset','newtpk','archive','blocked','contacts',''].includes(hash.path[0])) {
|
||||||
newState.sidePanelSelected = hash.path[0];
|
newState.sidePanelSelected = hash.path[0];
|
||||||
} else {
|
} else {
|
||||||
@@ -1204,6 +1211,18 @@ class TinodeWeb extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleChangeColorSchema(scheme) {
|
||||||
|
this.setState({colorSchema: scheme});
|
||||||
|
LocalStorageUtil.updateObject('settings', {colorSchema: scheme});
|
||||||
|
document.documentElement.style.colorScheme = scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeTextSize(size) {
|
||||||
|
this.setState({textSize: size | 0});
|
||||||
|
LocalStorageUtil.updateObject('settings', {textSize: size});
|
||||||
|
document.documentElement.style.setProperty('--message-text-size', `${size}pt`);
|
||||||
|
}
|
||||||
|
|
||||||
handleUpdateAccountTagsRequest(_, tags) {
|
handleUpdateAccountTagsRequest(_, tags) {
|
||||||
this.tinode.getMeTopic().setMeta({tags: tags})
|
this.tinode.getMeTopic().setMeta({tags: tags})
|
||||||
.catch(err => this.handleError(err.message, 'err'));
|
.catch(err => this.handleError(err.message, 'err'));
|
||||||
@@ -1316,7 +1335,7 @@ class TinodeWeb extends React.Component {
|
|||||||
handleSidepanelCancel() {
|
handleSidepanelCancel() {
|
||||||
const parsed = HashNavigation.parseUrlHash(window.location.hash);
|
const parsed = HashNavigation.parseUrlHash(window.location.hash);
|
||||||
let path = '';
|
let path = '';
|
||||||
if (['security','support','general','notif'].includes(parsed.path[0])) {
|
if (['security','support','general','notif','appearance'].includes(parsed.path[0])) {
|
||||||
path = 'edit';
|
path = 'edit';
|
||||||
} else if ('crop' == parsed.path[0]) {
|
} else if ('crop' == parsed.path[0]) {
|
||||||
path = 'general';
|
path = 'general';
|
||||||
@@ -1470,6 +1489,10 @@ class TinodeWeb extends React.Component {
|
|||||||
firebaseDelToken(this.fcm);
|
firebaseDelToken(this.fcm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset color scheme.
|
||||||
|
document.documentElement.style.colorScheme = DEFAULT_COLOR_SCHEME;
|
||||||
|
document.documentElement.style.setProperty('--message-text-size', `${DEFAULT_TEXT_SIZE}pt`);
|
||||||
|
|
||||||
clearInterval(this.reconnectCountdown);
|
clearInterval(this.reconnectCountdown);
|
||||||
|
|
||||||
let cleared;
|
let cleared;
|
||||||
@@ -2059,6 +2082,8 @@ class TinodeWeb extends React.Component {
|
|||||||
secureConnection={this.state.secureConnection}
|
secureConnection={this.state.secureConnection}
|
||||||
serverVersion={this.state.serverVersion}
|
serverVersion={this.state.serverVersion}
|
||||||
reqCredMethod={this.state.reqCredMethod}
|
reqCredMethod={this.state.reqCredMethod}
|
||||||
|
textSize={this.state.textSize}
|
||||||
|
colorSchema={this.state.colorSchema}
|
||||||
|
|
||||||
onGlobalSettings={this.handleGlobalSettings}
|
onGlobalSettings={this.handleGlobalSettings}
|
||||||
onSignUp={this.handleNewAccount}
|
onSignUp={this.handleNewAccount}
|
||||||
@@ -2073,6 +2098,8 @@ class TinodeWeb extends React.Component {
|
|||||||
onTogglePushNotifications={this.toggleFCMToken}
|
onTogglePushNotifications={this.toggleFCMToken}
|
||||||
onToggleMessageSounds={this.handleToggleMessageSounds}
|
onToggleMessageSounds={this.handleToggleMessageSounds}
|
||||||
onToggleIncognitoMode={this.handleToggleIncognitoMode}
|
onToggleIncognitoMode={this.handleToggleIncognitoMode}
|
||||||
|
onChangeColorSchema={this.handleChangeColorSchema}
|
||||||
|
onTextSizeChanged={this.handleChangeTextSize}
|
||||||
onCredAdd={this.handleCredAdd}
|
onCredAdd={this.handleCredAdd}
|
||||||
onCredDelete={this.handleCredDelete}
|
onCredDelete={this.handleCredDelete}
|
||||||
onCredConfirm={this.handleCredConfirm}
|
onCredConfirm={this.handleCredConfirm}
|
||||||
@@ -2114,6 +2141,7 @@ class TinodeWeb extends React.Component {
|
|||||||
serverVersion={this.state.serverVersion}
|
serverVersion={this.state.serverVersion}
|
||||||
serverAddress={this.state.serverAddress}
|
serverAddress={this.state.serverAddress}
|
||||||
applicationVisible={this.state.applicationVisible}
|
applicationVisible={this.state.applicationVisible}
|
||||||
|
colorSchema={this.state.colorSchema}
|
||||||
|
|
||||||
forwardMessage={this.state.forwardMessage}
|
forwardMessage={this.state.forwardMessage}
|
||||||
onCancelForwardMessage={this.handleHideForwardDialog}
|
onCancelForwardMessage={this.handleHideForwardDialog}
|
||||||
@@ -2148,6 +2176,8 @@ class TinodeWeb extends React.Component {
|
|||||||
: null}
|
: null}
|
||||||
|
|
||||||
{this.state.infoPanel ?
|
{this.state.infoPanel ?
|
||||||
|
<Suspense fallback={<div><FormattedMessage id="loading_note" defaultMessage="Loading..."
|
||||||
|
description="Message shown when component is loading"/></div>}>
|
||||||
<InfoView
|
<InfoView
|
||||||
tinode={this.tinode}
|
tinode={this.tinode}
|
||||||
connected={this.state.connected}
|
connected={this.state.connected}
|
||||||
@@ -2180,6 +2210,7 @@ class TinodeWeb extends React.Component {
|
|||||||
|
|
||||||
showContextMenu={this.handleShowContextMenu}
|
showContextMenu={this.handleShowContextMenu}
|
||||||
/>
|
/>
|
||||||
|
</Suspense>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,8 +162,8 @@ class BaseChatMessage extends React.PureComponent {
|
|||||||
const attachments = [];
|
const attachments = [];
|
||||||
if (this.props.mimeType == Drafty.getContentType() && Drafty.isValid(content)) {
|
if (this.props.mimeType == Drafty.getContentType() && Drafty.isValid(content)) {
|
||||||
Drafty.attachments(content, (att, i) => {
|
Drafty.attachments(content, (att, i) => {
|
||||||
if (att.mime == 'application/json') {
|
if (Drafty.isFormResponseType(att.mime)) {
|
||||||
// Don't show json objects as attachments.
|
// Don't show json drafty form response objects as attachments.
|
||||||
// They are not meant for users.
|
// They are not meant for users.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default class DocPreview extends React.PureComponent {
|
|||||||
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
|
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
|
||||||
</div>
|
</div>
|
||||||
<div id="image-preview-container">
|
<div id="image-preview-container">
|
||||||
<div className="flex-column narrow">
|
<div className="doc-card">
|
||||||
<i className="material-icons gray">{iconFromMime(this.props.content.type)}</i>
|
<i className="material-icons gray">{iconFromMime(this.props.content.type)}</i>
|
||||||
<div><b><FormattedMessage id="label_file_name" defaultMessage="File name:"
|
<div><b><FormattedMessage id="label_file_name" defaultMessage="File name:"
|
||||||
description="Label for a file name" /></b> {shortenFileName(this.props.content.name, 24) || '-'}</div>
|
description="Label for a file name" /></b> {shortenFileName(this.props.content.name, 24) || '-'}</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export default class TagManager extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
tags: this.props.tags || [],
|
tags: this.props.tags || [],
|
||||||
|
alias: Tinode.tagByPrefix(this.props.tags, Tinode.TAG_ALIAS) || '',
|
||||||
tagInput: '',
|
tagInput: '',
|
||||||
activated: this.props.activated
|
activated: this.props.activated
|
||||||
};
|
};
|
||||||
@@ -29,7 +30,10 @@ export default class TagManager extends React.Component {
|
|||||||
static getDerivedStateFromProps(nextProps, prevState) {
|
static getDerivedStateFromProps(nextProps, prevState) {
|
||||||
const tags = nextProps.tags || [];
|
const tags = nextProps.tags || [];
|
||||||
if (!arrayEqual(tags, prevState.tags) && !prevState.activated) {
|
if (!arrayEqual(tags, prevState.tags) && !prevState.activated) {
|
||||||
return {tags: tags};
|
return {
|
||||||
|
tags: tags || [],
|
||||||
|
alias: Tinode.tagByPrefix(tags, Tinode.TAG_ALIAS) || '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -76,8 +80,12 @@ export default class TagManager extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit() {
|
handleSubmit() {
|
||||||
// Add unprocessed input to tags, submit the list.
|
// Add unprocessed input to tags.
|
||||||
this.props.onSubmit(this.handleAddTag(this.state.tagInput.trim()));
|
let tags = this.handleAddTag(this.state.tagInput.trim());
|
||||||
|
// Add back the alias (and overwrite one possibly added by hand).
|
||||||
|
tags = Tinode.setUniqueTag(tags, this.state.alias);
|
||||||
|
// Submit the updated list.
|
||||||
|
this.props.onSubmit(tags);
|
||||||
this.setState({activated: false, tags: this.props.tags || []});
|
this.setState({activated: false, tags: this.props.tags || []});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,11 +103,16 @@ export default class TagManager extends React.Component {
|
|||||||
let tags = [];
|
let tags = [];
|
||||||
if (this.state.activated) {
|
if (this.state.activated) {
|
||||||
this.state.tags.forEach(tag => {
|
this.state.tags.forEach(tag => {
|
||||||
|
if (tag != this.state.alias) {
|
||||||
tags.push({user: tag, invalid: (tag.length < minTagLength || tag.length > maxTagLength)});
|
tags.push({user: tag, invalid: (tag.length < minTagLength || tag.length > maxTagLength)});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.state.tags.forEach(tag => {
|
this.state.tags.forEach(tag => {
|
||||||
|
if (tag != this.state.alias) {
|
||||||
tags.push(<span className="badge" key={tags.length}>{tag}</span>);
|
tags.push(<span className="badge" key={tags.length}>{tag}</span>);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (tags.length == 0) {
|
if (tags.length == 0) {
|
||||||
tags = (
|
tags = (
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ const messages = defineMessages({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const ALIAS_REGEX = /^[a-z0-9_\-]{4,24}$/;
|
|
||||||
const ALIAS_AVAILABILITY_CHECK_DELAY = 1000; // milliseconds
|
const ALIAS_AVAILABILITY_CHECK_DELAY = 1000; // milliseconds
|
||||||
|
|
||||||
class TopicDescEdit extends React.Component {
|
class TopicDescEdit extends React.Component {
|
||||||
@@ -79,6 +78,7 @@ class TopicDescEdit extends React.Component {
|
|||||||
this.handleDescriptionUpdate = this.handleDescriptionUpdate.bind(this);
|
this.handleDescriptionUpdate = this.handleDescriptionUpdate.bind(this);
|
||||||
this.handleTagsUpdated = this.handleTagsUpdated.bind(this);
|
this.handleTagsUpdated = this.handleTagsUpdated.bind(this);
|
||||||
this.validateAlias = this.validateAlias.bind(this);
|
this.validateAlias = this.validateAlias.bind(this);
|
||||||
|
this.cancelValidator = this.cancelValidator.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -90,6 +90,7 @@ class TopicDescEdit extends React.Component {
|
|||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
const topic = this.props.tinode.getTopic(this.props.topic);
|
const topic = this.props.tinode.getTopic(this.props.topic);
|
||||||
topic.onTagsUpdated = this.previousOnTags;
|
topic.onTagsUpdated = this.previousOnTags;
|
||||||
|
this.cancelValidator();
|
||||||
}
|
}
|
||||||
|
|
||||||
tnNewTags(tags) {
|
tnNewTags(tags) {
|
||||||
@@ -177,28 +178,24 @@ class TopicDescEdit extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleAliasUpdate(alias) {
|
handleAliasUpdate(alias) {
|
||||||
alias = alias.trim() || '';
|
alias = (alias || '').trim();
|
||||||
const tags = this.state.tags.filter(t => !t.startsWith('alias:'));
|
const tags = alias ?
|
||||||
if (alias) {
|
Tinode.setUniqueTag(this.state.tags, Tinode.TAG_ALIAS + alias.toLowerCase()) :
|
||||||
tags.push('alias:' + alias.toLowerCase());
|
Tinode.clearTagPrefix(this.state.tags, Tinode.TAG_ALIAS);
|
||||||
}
|
|
||||||
this.handleTagsUpdated(tags);
|
this.handleTagsUpdated(tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
validateAlias(alias) {
|
validateAlias(alias) {
|
||||||
// Reset pending delayed check.
|
// Reset pending delayed check.
|
||||||
if (this.aliasCheckTimer) {
|
this.cancelValidator();
|
||||||
this.aliasCheckPromise.reject(null);
|
|
||||||
clearTimeout(this.aliasCheckTimer);
|
|
||||||
this.aliasCheckTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
alias = alias.trim();
|
||||||
if (!alias) {
|
if (!alias) {
|
||||||
this.setState({aliasError: ''});
|
this.setState({aliasError: ''});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = ALIAS_REGEX.test(alias);
|
const valid = Tinode.isValidTagValue(alias);
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
this.setState({aliasError: this.props.intl.formatMessage(messages.alias_invalid)});
|
this.setState({aliasError: this.props.intl.formatMessage(messages.alias_invalid)});
|
||||||
return false;
|
return false;
|
||||||
@@ -206,6 +203,12 @@ class TopicDescEdit extends React.Component {
|
|||||||
this.setState({aliasError: ''});
|
this.setState({aliasError: ''});
|
||||||
|
|
||||||
// Setup a delayed check for alias availability.
|
// Setup a delayed check for alias availability.
|
||||||
|
const fnd = this.props.tinode.getFndTopic();
|
||||||
|
if (!fnd) {
|
||||||
|
// Unable to check alias availability.
|
||||||
|
this.setState({aliasError: ''});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
this.aliasCheckPromise = {};
|
this.aliasCheckPromise = {};
|
||||||
const validationPromise = new Promise((resolve, reject) => {
|
const validationPromise = new Promise((resolve, reject) => {
|
||||||
this.aliasCheckPromise.resolve = resolve;
|
this.aliasCheckPromise.resolve = resolve;
|
||||||
@@ -213,15 +216,10 @@ class TopicDescEdit extends React.Component {
|
|||||||
});
|
});
|
||||||
this.aliasCheckTimer = setTimeout(_ => {
|
this.aliasCheckTimer = setTimeout(_ => {
|
||||||
this.aliasCheckTimer = null;
|
this.aliasCheckTimer = null;
|
||||||
const fnd = this.props.tinode.getFndTopic();
|
fnd.checkTagUniqueness(`${Tinode.TAG_ALIAS}${alias}`, this.props.topic)
|
||||||
fnd.subscribe()
|
.then(ok => {
|
||||||
.then(_ => fnd.setMeta({desc: {public: `alias:${alias}`}}))
|
this.aliasCheckPromise.resolve(ok)
|
||||||
.then(_ => fnd.getMeta(fnd.startMetaQuery().withTags().build()))
|
this.setState({aliasError: ok ? '' : this.props.intl.formatMessage(messages.alias_already_taken)});
|
||||||
.then(meta => {
|
|
||||||
const taken = Array.isArray(meta.tags);
|
|
||||||
this.setState({aliasError: taken ?
|
|
||||||
this.props.intl.formatMessage(messages.alias_already_taken) : ''});
|
|
||||||
this.aliasCheckPromise.resolve(!taken)
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.aliasCheckPromise.reject(err);
|
this.aliasCheckPromise.reject(err);
|
||||||
@@ -231,6 +229,15 @@ class TopicDescEdit extends React.Component {
|
|||||||
return validationPromise;
|
return validationPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelValidator() {
|
||||||
|
// Reset pending delayed check.
|
||||||
|
if (this.aliasCheckTimer) {
|
||||||
|
clearTimeout(this.aliasCheckTimer);
|
||||||
|
this.aliasCheckTimer = null;
|
||||||
|
this.aliasCheckPromise.reject(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleTagsUpdated(tags) {
|
handleTagsUpdated(tags) {
|
||||||
// Check if tags have actually changed.
|
// Check if tags have actually changed.
|
||||||
if (arrayEqual(this.state.tags.slice(0), tags.slice(0))) {
|
if (arrayEqual(this.state.tags.slice(0), tags.slice(0))) {
|
||||||
@@ -373,7 +380,7 @@ class TopicDescEdit extends React.Component {
|
|||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{editable ?
|
{editable && !this.state.isMe ?
|
||||||
<>
|
<>
|
||||||
<div className="hr" />
|
<div className="hr" />
|
||||||
<FormattedMessage id="title_tag_manager" defaultMessage="Tags (search & discovery)"
|
<FormattedMessage id="title_tag_manager" defaultMessage="Tags (search & discovery)"
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"file":"316.prod.js","mappings":"qkNASA,MAAMA,UAA6BC,IAAAA,cACjCC,WAAAA,CAAYC,GACVC,MAAMD,GAENE,KAAKC,UAAY,GACjB,MAAM,kBAAEC,GAAsBJ,EAAMK,KACpCC,EAAeC,SAAQC,IACPA,EAAGC,KAAKC,MAAM,KACtBH,SAAQI,IACZT,KAAKC,UAAUS,KAAK,CAClBH,KAAME,EAAKE,OACXC,KAAMN,EAAGM,KACTC,MAAMC,EAAAA,EAAAA,IAAUR,EAAGM,MACnBG,KAAMb,EAAkBI,EAAGM,KAAM,CAACI,KAAM,YACxC,GACF,IAGJhB,KAAKC,UAAUgB,MAAK,CAACC,EAAGC,IAAMD,EAAEH,KAAKK,cAAcD,EAAEJ,OACvD,CAEAM,iBAAAA,GACMrB,KAAKsB,aACPtB,KAAKsB,YAAYC,eAAe,CAACC,MAAO,SAAUC,OAAQ,WAE9D,CAEAC,MAAAA,GACE,MAAMzB,EAAY,GACZ0B,EAAW3B,KAAKF,MAAM6B,UAAY,KAYxC,OAXA3B,KAAKC,UAAUI,SAAQ,CAACuB,EAAGC,KACzB,MAAMC,EAASF,EAAEhB,MAAQe,EAAW,YAAc,GAClD1B,EAAUS,KAAKd,IAAAA,cAAA,MAAImC,UAAWD,EAAOE,IAAKH,EACxCI,IAAKA,IAAYL,EAAEhB,MAAQe,IAAW3B,KAAKsB,YAAcW,EAAG,EAC5DC,QAASC,GAAKnC,KAAKF,MAAMsC,SAASR,EAAEhB,KAAMgB,EAAErB,OAC5CX,IAAAA,cAAA,QAAMmC,UAAU,gBAAgBH,EAAEf,MAClCjB,IAAAA,cAAA,QAAMmC,UAAU,WAAU,IAAOH,EAAEb,MACnCnB,IAAAA,cAAA,QAAMmC,UAAU,aAAY,KAAQH,EAAErB,OAClC,IAINX,IAAAA,cAAA,OAAKmC,UAAU,mBAAmBD,MAAO,CAACO,OAAQ,UAChDzC,IAAAA,cAAA,MAAImC,UAAU,0BACX9B,GAIT,EAGF,WAAeqC,EAAAA,EAAAA,YAAW3C,E","sources":["webpack://tinode-webapp/./src/widgets/phone-country-selector.jsx"],"sourcesContent":["// Selector for country dialing code.\n\nimport React from 'react';\nimport { injectIntl } from 'react-intl';\n\nimport * as dcodes from '../dcodes.json';\n\nimport { flagEmoji } from '../lib/strformat';\n\nclass PhoneCountrySelector extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.countries = [];\n const { formatDisplayName } = props.intl;\n dcodes.default.forEach(dc => {\n const parts = dc.dial.split(',');\n parts.forEach(part => {\n this.countries.push({\n dial: part.trim(),\n code: dc.code,\n flag: flagEmoji(dc.code),\n name: formatDisplayName(dc.code, {type: 'region'})\n });\n });\n });\n\n this.countries.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n componentDidMount() {\n if (this.selectedRef) {\n this.selectedRef.scrollIntoView({block: 'center', inline: 'nearest'});\n }\n }\n\n render() {\n const countries = [];\n const selected = this.props.selected || 'US';\n this.countries.forEach((c, idx) => {\n const style = (c.code == selected ? 'selected ' : '');\n countries.push(<li className={style} key={idx}\n ref={ref => {if (c.code == selected) {this.selectedRef = ref}}}\n onClick={_ => this.props.onSubmit(c.code, c.dial)}>\n <span className=\"country-flag\">{c.flag}</span>\n <span className=\"country\"> {c.name}</span>\n <span className=\"dial-code\"> +{c.dial}</span>\n </li>);\n });\n\n return (\n <div className=\"scrollable-panel\" style={{height: '30rem'}}>\n <ul className=\"phone-country-selector\">\n {countries}\n </ul>\n </div>\n );\n }\n}\n\nexport default injectIntl(PhoneCountrySelector);\n"],"names":["PhoneCountrySelector","React","constructor","props","super","this","countries","formatDisplayName","intl","dcodes","forEach","dc","dial","split","part","push","trim","code","flag","flagEmoji","name","type","sort","a","b","localeCompare","componentDidMount","selectedRef","scrollIntoView","block","inline","render","selected","c","idx","style","className","key","ref","onClick","_","onSubmit","height","injectIntl"],"sourceRoot":""}
|
{"version":3,"file":"316.prod.js","mappings":"qkNASA,MAAMA,UAA6BC,IAAAA,cACjCC,WAAAA,CAAYC,GACVC,MAAMD,GAENE,KAAKC,UAAY,GACjB,MAAM,kBAAEC,GAAsBJ,EAAMK,KACpCC,EAAeC,QAAQC,IACPA,EAAGC,KAAKC,MAAM,KACtBH,QAAQI,IACZT,KAAKC,UAAUS,KAAK,CAClBH,KAAME,EAAKE,OACXC,KAAMN,EAAGM,KACTC,MAAMC,EAAAA,EAAAA,IAAUR,EAAGM,MACnBG,KAAMb,EAAkBI,EAAGM,KAAM,CAACI,KAAM,iBAK9ChB,KAAKC,UAAUgB,KAAK,CAACC,EAAGC,IAAMD,EAAEH,KAAKK,cAAcD,EAAEJ,MACvD,CAEAM,iBAAAA,GACMrB,KAAKsB,aACPtB,KAAKsB,YAAYC,eAAe,CAACC,MAAO,SAAUC,OAAQ,WAE9D,CAEAC,MAAAA,GACE,MAAMzB,EAAY,GACZ0B,EAAW3B,KAAKF,MAAM6B,UAAY,KAYxC,OAXA3B,KAAKC,UAAUI,QAAQ,CAACuB,EAAGC,KACzB,MAAMC,EAASF,EAAEhB,MAAQe,EAAW,YAAc,GAClD1B,EAAUS,KAAKd,IAAAA,cAAA,MAAImC,UAAWD,EAAOE,IAAKH,EACxCI,IAAKA,IAAYL,EAAEhB,MAAQe,IAAW3B,KAAKsB,YAAcW,IACzDC,QAASC,GAAKnC,KAAKF,MAAMsC,SAASR,EAAEhB,KAAMgB,EAAErB,OAC5CX,IAAAA,cAAA,QAAMmC,UAAU,gBAAgBH,EAAEf,MAClCjB,IAAAA,cAAA,QAAMmC,UAAU,WAAU,IAAOH,EAAEb,MACnCnB,IAAAA,cAAA,QAAMmC,UAAU,aAAY,KAAQH,EAAErB,UAKxCX,IAAAA,cAAA,OAAKmC,UAAU,mBAAmBD,MAAO,CAACO,OAAQ,UAChDzC,IAAAA,cAAA,MAAImC,UAAU,0BACX9B,GAIT,EAGF,WAAeqC,EAAAA,EAAAA,YAAW3C,E","sources":["webpack://tinode-webapp/./src/widgets/phone-country-selector.jsx"],"sourcesContent":["// Selector for country dialing code.\n\nimport React from 'react';\nimport { injectIntl } from 'react-intl';\n\nimport * as dcodes from '../dcodes.json';\n\nimport { flagEmoji } from '../lib/strformat';\n\nclass PhoneCountrySelector extends React.PureComponent {\n constructor(props) {\n super(props);\n\n this.countries = [];\n const { formatDisplayName } = props.intl;\n dcodes.default.forEach(dc => {\n const parts = dc.dial.split(',');\n parts.forEach(part => {\n this.countries.push({\n dial: part.trim(),\n code: dc.code,\n flag: flagEmoji(dc.code),\n name: formatDisplayName(dc.code, {type: 'region'})\n });\n });\n });\n\n this.countries.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n componentDidMount() {\n if (this.selectedRef) {\n this.selectedRef.scrollIntoView({block: 'center', inline: 'nearest'});\n }\n }\n\n render() {\n const countries = [];\n const selected = this.props.selected || 'US';\n this.countries.forEach((c, idx) => {\n const style = (c.code == selected ? 'selected ' : '');\n countries.push(<li className={style} key={idx}\n ref={ref => {if (c.code == selected) {this.selectedRef = ref}}}\n onClick={_ => this.props.onSubmit(c.code, c.dial)}>\n <span className=\"country-flag\">{c.flag}</span>\n <span className=\"country\"> {c.name}</span>\n <span className=\"dial-code\"> +{c.dial}</span>\n </li>);\n });\n\n return (\n <div className=\"scrollable-panel\" style={{height: '30rem'}}>\n <ul className=\"phone-country-selector\">\n {countries}\n </ul>\n </div>\n );\n }\n}\n\nexport default injectIntl(PhoneCountrySelector);\n"],"names":["PhoneCountrySelector","React","constructor","props","super","this","countries","formatDisplayName","intl","dcodes","forEach","dc","dial","split","part","push","trim","code","flag","flagEmoji","name","type","sort","a","b","localeCompare","componentDidMount","selectedRef","scrollIntoView","block","inline","render","selected","c","idx","style","className","key","ref","onClick","_","onSubmit","height","injectIntl"],"sourceRoot":""}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+634
-2037
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,510 @@
|
|||||||
|
"use strict";
|
||||||
|
(self["webpackChunktinode_webapp"] = self["webpackChunktinode_webapp"] || []).push([["src_views_acc-security-view_jsx"],{
|
||||||
|
|
||||||
|
/***/ "./src/views/acc-security-view.jsx":
|
||||||
|
/*!*****************************************!*\
|
||||||
|
!*** ./src/views/acc-security-view.jsx ***!
|
||||||
|
\*****************************************/
|
||||||
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
__webpack_require__.r(__webpack_exports__);
|
||||||
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
|
||||||
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
||||||
|
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-intl */ "react-intl");
|
||||||
|
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
|
||||||
|
/* harmony import */ var _widgets_checkbox_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../widgets/checkbox.jsx */ "./src/widgets/checkbox.jsx");
|
||||||
|
/* harmony import */ var _widgets_in_place_edit_jsx__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../widgets/in-place-edit.jsx */ "./src/widgets/in-place-edit.jsx");
|
||||||
|
/* harmony import */ var _widgets_permissions_editor_jsx__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../widgets/permissions-editor.jsx */ "./src/widgets/permissions-editor.jsx");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
|
||||||
|
delete_account: {
|
||||||
|
id: "delete_account",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Delete account"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
delete_account_warning: {
|
||||||
|
id: "delete_account_warning",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Are you sure you want to delete your account? It cannot be undone."
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
class AccSecurityView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component) {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const me = this.props.tinode.getMeTopic();
|
||||||
|
let blockedCount = 0;
|
||||||
|
me.contacts(c => {
|
||||||
|
if (c.acs && !c.acs.isJoiner()) {
|
||||||
|
blockedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const defacs = me.getDefaultAccess();
|
||||||
|
this.state = {
|
||||||
|
auth: defacs ? defacs.auth : null,
|
||||||
|
anon: defacs ? defacs.anon : null,
|
||||||
|
showPermissionEditorFor: undefined,
|
||||||
|
blockedCount: blockedCount
|
||||||
|
};
|
||||||
|
this.handlePasswordUpdate = this.handlePasswordUpdate.bind(this);
|
||||||
|
this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);
|
||||||
|
this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);
|
||||||
|
this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);
|
||||||
|
this.handleDeleteAccount = this.handleDeleteAccount.bind(this);
|
||||||
|
this.handleCheckboxClick = this.handleCheckboxClick.bind(this);
|
||||||
|
}
|
||||||
|
handlePasswordUpdate(pwd) {
|
||||||
|
this.setState({
|
||||||
|
password: pwd
|
||||||
|
});
|
||||||
|
this.props.onUpdatePassword(pwd);
|
||||||
|
}
|
||||||
|
handleLaunchPermissionsEditor(which) {
|
||||||
|
this.setState({
|
||||||
|
showPermissionEditorFor: which,
|
||||||
|
editedPermissions: this.state[which]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleHidePermissionsEditor() {
|
||||||
|
this.setState({
|
||||||
|
showPermissionEditorFor: undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handlePermissionsChanged(perm) {
|
||||||
|
let defacs = {};
|
||||||
|
defacs[this.state.showPermissionEditorFor] = perm;
|
||||||
|
this.props.onUpdateAccountDesc('me', undefined, undefined, defacs);
|
||||||
|
let newState = {
|
||||||
|
showPermissionEditorFor: undefined
|
||||||
|
};
|
||||||
|
newState[this.state.showPermissionEditorFor] = perm;
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
handleCheckboxClick(what, checked) {
|
||||||
|
this.props.onToggleIncognitoMode(checked);
|
||||||
|
}
|
||||||
|
handleDeleteAccount(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const {
|
||||||
|
formatMessage
|
||||||
|
} = this.props.intl;
|
||||||
|
this.props.onShowAlert(formatMessage(messages.delete_account), formatMessage(messages.delete_account_warning), _ => this.props.onDeleteAccount(), null, true, null);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, this.state.showPermissionEditorFor ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_permissions_editor_jsx__WEBPACK_IMPORTED_MODULE_4__["default"], {
|
||||||
|
mode: this.state.editedPermissions,
|
||||||
|
skip: "O",
|
||||||
|
onSubmit: this.handlePermissionsChanged,
|
||||||
|
onCancel: this.handleHidePermissionsEditor
|
||||||
|
}) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "scrollable-panel"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "panel-form-column"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
|
||||||
|
className: "small"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "label_password",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Password"
|
||||||
|
}]
|
||||||
|
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "password_unchanged_prompt",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Unchanged"
|
||||||
|
}]
|
||||||
|
}, password_unchanged => react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_in_place_edit_jsx__WEBPACK_IMPORTED_MODULE_3__["default"], {
|
||||||
|
placeholder: password_unchanged,
|
||||||
|
type: "password",
|
||||||
|
onFinished: this.handlePasswordUpdate
|
||||||
|
})))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "hr"
|
||||||
|
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "panel-form-row"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
|
||||||
|
htmlFor: "incognito-mode"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "label_incognito_mode",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Incognito mode:"
|
||||||
|
}]
|
||||||
|
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_widgets_checkbox_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
|
||||||
|
name: "incognito",
|
||||||
|
id: "incognito-mode",
|
||||||
|
checked: this.props.incognitoMode,
|
||||||
|
onChange: this.handleCheckboxClick
|
||||||
|
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "panel-form-row"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {
|
||||||
|
className: "small"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "incognito_mode_description",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Hide your online status and typing indicators."
|
||||||
|
}]
|
||||||
|
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "hr"
|
||||||
|
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "panel-form-column"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
|
href: "#",
|
||||||
|
className: "danger flat-button",
|
||||||
|
onClick: e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.onLogout();
|
||||||
|
}
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
|
className: "material-icons"
|
||||||
|
}, "exit_to_app"), " \xA0", react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "button_logout",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Logout"
|
||||||
|
}]
|
||||||
|
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
|
href: "#",
|
||||||
|
className: "danger flat-button",
|
||||||
|
onClick: e => {
|
||||||
|
this.handleDeleteAccount(e);
|
||||||
|
}
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
|
className: "material-icons"
|
||||||
|
}, "delete"), " \xA0", react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "button_delete_account",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Delete account"
|
||||||
|
}]
|
||||||
|
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "hr"
|
||||||
|
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "panel-form-column"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
|
||||||
|
className: "small"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "label_default_access_mode",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Default access mode:"
|
||||||
|
}]
|
||||||
|
}))), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "quoted"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, "Auth: ", react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tt", {
|
||||||
|
className: "clickable",
|
||||||
|
onClick: this.handleLaunchPermissionsEditor.bind(this, 'auth')
|
||||||
|
}, this.state.auth)), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, "Anon: ", react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tt", {
|
||||||
|
className: "clickable",
|
||||||
|
onClick: this.handleLaunchPermissionsEditor.bind(this, 'anon')
|
||||||
|
}, this.state.anon)))), this.state.blockedCount > 0 ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "hr"
|
||||||
|
}), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "panel-form-row"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("i", {
|
||||||
|
className: "material-icons"
|
||||||
|
}, "block"), "\xA0", react__WEBPACK_IMPORTED_MODULE_0___default().createElement("a", {
|
||||||
|
href: "#",
|
||||||
|
className: "gray",
|
||||||
|
onClick: e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.props.onShowBlocked();
|
||||||
|
}
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "blocked_contacts_link",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Blocked contacts ("
|
||||||
|
}, {
|
||||||
|
"type": 1,
|
||||||
|
"value": "count"
|
||||||
|
}, {
|
||||||
|
"type": 0,
|
||||||
|
"value": ")"
|
||||||
|
}],
|
||||||
|
values: {
|
||||||
|
count: this.state.blockedCount
|
||||||
|
}
|
||||||
|
})))) : null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
/* harmony default export */ __webpack_exports__["default"] = ((0,react_intl__WEBPACK_IMPORTED_MODULE_1__.injectIntl)(AccSecurityView));
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ "./src/widgets/permissions-editor.jsx":
|
||||||
|
/*!********************************************!*\
|
||||||
|
!*** ./src/widgets/permissions-editor.jsx ***!
|
||||||
|
\********************************************/
|
||||||
|
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
|
__webpack_require__.r(__webpack_exports__);
|
||||||
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react");
|
||||||
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
||||||
|
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-intl */ "react-intl");
|
||||||
|
/* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__);
|
||||||
|
/* harmony import */ var _checkbox_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./checkbox.jsx */ "./src/widgets/checkbox.jsx");
|
||||||
|
/* harmony import */ var _contact_jsx__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./contact.jsx */ "./src/widgets/contact.jsx");
|
||||||
|
/* harmony import */ var _lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../lib/blob-helpers.js */ "./src/lib/blob-helpers.js");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({
|
||||||
|
joiner: {
|
||||||
|
id: "permission_join",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Join ("
|
||||||
|
}, {
|
||||||
|
"type": 1,
|
||||||
|
"value": "val"
|
||||||
|
}, {
|
||||||
|
"type": 0,
|
||||||
|
"value": ")"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
reader: {
|
||||||
|
id: "permission_read",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Read ("
|
||||||
|
}, {
|
||||||
|
"type": 1,
|
||||||
|
"value": "val"
|
||||||
|
}, {
|
||||||
|
"type": 0,
|
||||||
|
"value": ")"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
writer: {
|
||||||
|
id: "permission_write",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Write ("
|
||||||
|
}, {
|
||||||
|
"type": 1,
|
||||||
|
"value": "val"
|
||||||
|
}, {
|
||||||
|
"type": 0,
|
||||||
|
"value": ")"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
preser: {
|
||||||
|
id: "permission_pres",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Get notified ("
|
||||||
|
}, {
|
||||||
|
"type": 1,
|
||||||
|
"value": "val"
|
||||||
|
}, {
|
||||||
|
"type": 0,
|
||||||
|
"value": ")"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
approver: {
|
||||||
|
id: "permission_admin",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Approve ("
|
||||||
|
}, {
|
||||||
|
"type": 1,
|
||||||
|
"value": "val"
|
||||||
|
}, {
|
||||||
|
"type": 0,
|
||||||
|
"value": ")"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
sharer: {
|
||||||
|
id: "permission_share",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Share ("
|
||||||
|
}, {
|
||||||
|
"type": 1,
|
||||||
|
"value": "val"
|
||||||
|
}, {
|
||||||
|
"type": 0,
|
||||||
|
"value": ")"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
deleter: {
|
||||||
|
id: "permission_delete",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Delete ("
|
||||||
|
}, {
|
||||||
|
"type": 1,
|
||||||
|
"value": "val"
|
||||||
|
}, {
|
||||||
|
"type": 0,
|
||||||
|
"value": ")"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
id: "permission_owner",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Owner ("
|
||||||
|
}, {
|
||||||
|
"type": 1,
|
||||||
|
"value": "val"
|
||||||
|
}, {
|
||||||
|
"type": 0,
|
||||||
|
"value": ")"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
class PermissionsEditor extends (react__WEBPACK_IMPORTED_MODULE_0___default().Component) {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
mode: (props.mode || '').replace('N', '')
|
||||||
|
};
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
this.handleCancel = this.handleCancel.bind(this);
|
||||||
|
}
|
||||||
|
handleChange(val) {
|
||||||
|
let mode = this.state.mode;
|
||||||
|
const idx = mode.indexOf(val);
|
||||||
|
if (idx == -1) {
|
||||||
|
mode += val;
|
||||||
|
} else {
|
||||||
|
mode = mode.replace(val, '');
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
mode: mode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleSubmit() {
|
||||||
|
const mode = (this.state.mode || 'N').split('').sort().join('');
|
||||||
|
const before = (this.props.mode || 'N').split('').sort().join('');
|
||||||
|
if (mode !== before) {
|
||||||
|
this.props.onSubmit(mode);
|
||||||
|
} else {
|
||||||
|
this.props.onCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleCancel() {
|
||||||
|
this.props.onCancel();
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
formatMessage
|
||||||
|
} = this.props.intl;
|
||||||
|
const all = 'JRWPASDO';
|
||||||
|
const names = {
|
||||||
|
'J': formatMessage(messages.joiner, {
|
||||||
|
val: 'J'
|
||||||
|
}),
|
||||||
|
'R': formatMessage(messages.reader, {
|
||||||
|
val: 'R'
|
||||||
|
}),
|
||||||
|
'W': formatMessage(messages.writer, {
|
||||||
|
val: 'W'
|
||||||
|
}),
|
||||||
|
'P': formatMessage(messages.preser, {
|
||||||
|
val: 'P'
|
||||||
|
}),
|
||||||
|
'A': formatMessage(messages.approver, {
|
||||||
|
val: 'A'
|
||||||
|
}),
|
||||||
|
'S': formatMessage(messages.sharer, {
|
||||||
|
val: 'S'
|
||||||
|
}),
|
||||||
|
'D': formatMessage(messages.deleter, {
|
||||||
|
val: 'D'
|
||||||
|
}),
|
||||||
|
'O': formatMessage(messages.owner, {
|
||||||
|
val: 'O'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let skip = this.props.skip || '';
|
||||||
|
let mode = this.state.mode;
|
||||||
|
let compare = (this.props.compare || '').replace('N', '');
|
||||||
|
let items = [];
|
||||||
|
for (let i = 0; i < all.length; i++) {
|
||||||
|
let c = all.charAt(i);
|
||||||
|
if (skip.indexOf(c) >= 0 && mode.indexOf(c) < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
items.push(react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tr", {
|
||||||
|
key: c
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", null, names[c]), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", {
|
||||||
|
className: "checkbox"
|
||||||
|
}, skip.indexOf(c) < 0 ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_checkbox_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
|
||||||
|
name: c,
|
||||||
|
checked: mode.indexOf(c) >= 0,
|
||||||
|
onChange: this.handleChange
|
||||||
|
}) : react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_checkbox_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
|
||||||
|
name: c,
|
||||||
|
checked: mode.indexOf(c) >= 0
|
||||||
|
})), this.props.compare ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", {
|
||||||
|
className: "checkbox"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_checkbox_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], {
|
||||||
|
name: c,
|
||||||
|
checked: compare.indexOf(c) >= 0
|
||||||
|
})) : null));
|
||||||
|
}
|
||||||
|
return react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "panel-form-column"
|
||||||
|
}, this.props.userTitle ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("ul", {
|
||||||
|
className: "contact-box small"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_contact_jsx__WEBPACK_IMPORTED_MODULE_3__["default"], {
|
||||||
|
tinode: this.props.tinode,
|
||||||
|
item: this.props.item,
|
||||||
|
title: this.props.userTitle,
|
||||||
|
small: true,
|
||||||
|
avatar: (0,_lib_blob_helpers_js__WEBPACK_IMPORTED_MODULE_4__.makeImageUrl)(this.props.userAvatar ? this.props.userAvatar : null)
|
||||||
|
})) : null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("label", {
|
||||||
|
className: "small"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "title_permissions",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Permissions"
|
||||||
|
}]
|
||||||
|
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("table", {
|
||||||
|
className: "permission-editor"
|
||||||
|
}, this.props.compare ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement("thead", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tr", null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("th", null), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("th", null, this.props.modeTitle), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("th", null, this.props.compareTitle))) : null, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tbody", null, items)), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("br", null), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||||
|
className: "dialog-buttons"
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
|
||||||
|
className: "outline",
|
||||||
|
onClick: this.handleCancel
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "button_cancel",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "Cancel"
|
||||||
|
}]
|
||||||
|
})), react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
|
||||||
|
className: "primary",
|
||||||
|
onClick: this.handleSubmit
|
||||||
|
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, {
|
||||||
|
id: "button_ok",
|
||||||
|
defaultMessage: [{
|
||||||
|
"type": 0,
|
||||||
|
"value": "OK"
|
||||||
|
}]
|
||||||
|
}))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
/* harmony default export */ __webpack_exports__["default"] = ((0,react_intl__WEBPACK_IMPORTED_MODULE_1__.injectIntl)(PermissionsEditor));
|
||||||
|
|
||||||
|
/***/ })
|
||||||
|
|
||||||
|
}]);
|
||||||
|
//# sourceMappingURL=src_views_acc-security-view_jsx.dev.js.map
|
||||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user