Compare commits

...
43 Commits
Author SHA1 Message Date
or-else 87c73869af releasing v0.17.10 2021-08-26 18:19:52 -07:00
or-else 763a525292 fix message scrolling on initial load 2021-08-23 16:54:52 -07:00
or-else f3e54e46b5 fix a race condition in handleScrollEvent (#85) 2021-08-23 15:30:08 -07:00
or-else e73a920154 rebuilt with translations 2021-08-22 12:23:29 -07:00
or-else 9c4db41b76 fixing unarchive menu, translations 2021-08-22 12:22:47 -07:00
or-else 7ba20c78b1 adding unarchive menu item 2021-08-22 11:45:50 -07:00
or-else a6c29e3e5d rebuilt 2021-08-22 11:01:16 -07:00
or-else 1d0c7e1c73 fixing "archive" menu item 2021-08-22 10:52:21 -07:00
or-else 36aa610b8e tweaking color 2021-08-20 20:39:24 -07:00
or-else 8822c2892e added a "password reset email has been sent" screen 2021-08-20 20:32:56 -07:00
or-else ddfbfdcbb3 releasing 0.17.9 2021-08-19 18:56:36 -07:00
or-else faf644d9dd bump version to v0.17.9-rc2 2021-08-17 10:44:38 -07:00
or-else c34ba46563 rebuilt 2021-08-16 16:05:27 -07:00
or-else b783ec2949 remove loading... spinner on database fetch 2021-08-16 16:04:32 -07:00
or-else 0840a79024 use topic.messages directly instead of creating a copy in this.state 2021-08-16 11:40:30 -07:00
or-else 4399428fdf rebuilt with translations 2021-08-13 15:08:42 -07:00
or-else e0e25fc6d3 updated dependencies 2021-08-13 15:01:42 -07:00
or-else 25e9c1a885 added missing translations 2021-08-13 15:01:17 -07:00
or-else b59fd06381 fix some visual breakage with long strings in messages 2021-08-13 15:00:38 -07:00
or-else b540ac44df make owner delete chat instead of leaving 2021-08-13 14:59:49 -07:00
or-else 6c739d9c43 bump version and rebuild 2021-08-11 11:53:49 -07:00
or-else b2daf6b58e bump version and rebuild 2021-08-03 11:18:48 -07:00
or-else 063245e665 refreshed Translations section 2021-07-31 15:02:42 -07:00
or-else e3bdd5bb45 zh-TW translations in service worker 2021-07-30 09:40:10 -07:00
or-else 122fd018d0 fixing #80 in service-worker 2021-07-30 09:37:25 -07:00
or-else febc75da17 ensure sort order of contacts by [touched] 2021-07-29 19:16:38 -07:00
or-else eff8bbabc5 bump version and rebuild 2021-07-29 17:12:09 -07:00
or-else c7f4fa0165 rebuilt with chinese traditional translation 2021-07-29 17:11:52 -07:00
or-else a0312f8b52 fix leaving a conversation 2021-07-29 17:11:08 -07:00
or-else f8879f7b68 rename the new translation according to ISO 639-1 2021-07-27 10:11:25 -07:00
Gene 39c8b9a4a1 Merge pull request #81 from SiderealArt/patch-1
Add Traditional Chinese translation
2021-07-27 09:45:29 -07:00
SiderealArt 812cdd970c Add Traditional Chinese translation 2021-07-27 19:22:34 +08:00
or-else a24ee01b37 remove crash in Safari, #80 2021-07-17 10:28:35 -07:00
or-else 6b8595750a merge devel 2021-07-10 17:26:20 -07:00
or-else aa451946a5 releasing 0.17.6 2021-07-10 17:25:00 -07:00
or-else d1ea1d7658 add React.StrictMode checking for devel 2021-07-07 16:35:39 -07:00
or-else 626484d8cb releasing 0.17.6-rc1 2021-07-07 15:24:12 -07:00
or-else 864753942f rebuilt with indexedDB initialization fix 2021-07-06 09:33:21 -07:00
or-else 66c2db86d9 rebuilt with the fix & bumped versionfor new release 2021-07-02 10:47:05 -07:00
or-else bf9ba90ed3 clear reconnect timer on logout 2021-06-24 19:54:58 -07:00
or-else 07aa38e162 rebuilt with sdk fixes 2021-06-23 17:32:38 -07:00
or-else 181161250a rebuilt with the sdk fixes 2021-06-23 15:57:24 -07:00
or-else 019b270242 wrong order of params in file uploads 2021-06-23 14:03:41 -07:00
32 changed files with 2620 additions and 2152 deletions
+3 -2
View File
@@ -28,16 +28,17 @@ This is NOT a standalone app, this is just a frontend, a client. It requires a [
The app is fully internationalized using [React-Intl](https://github.com/formatjs/react-intl). The UI language is selected automatically from the language specified by the browser. A specific language can be forced by adding `#?hl=XX` parameter to the URL when starting the app, e.g. https://web.tinode.co/#?hl=ru.
As of the time of this writing the following translations exist:
The app is currently available in the following languages:
* English
* Chinese (simplified)
* Chinese (traditional)
* German
* Korean
* Romanian
* Russian
* Spanish
More translations are welcome. The source file with the original English strings and context explanations is located at [/src/i18n/ex/base-en.json](/src/i18n/ex/base-en.json). Only `defaultMessage` needs to be translated. Send a pull request with a json file with translated strings. Take a look at [Spanish](/src/i18n/es.json), [Russian](/src/i18n/ru.json), or [Simplified Chinese](/src/i18n/zh.json) translations for guidance.
More translations are welcome. See [instructions](https://github.com/tinode/chat/blob/devel/docs/translations.md#webapp).
## Not done yet
+9 -2
View File
@@ -373,6 +373,10 @@ div.alert .content {
color: #2196f3; /* blue 500 */
}
.green {
color: #388e3c; /* green 700 */
}
.red {
color: #D32F2F; /* red 700 */
}
@@ -462,7 +466,7 @@ div.alert .content {
position: relative;
display: flex;
height: 100%;
max-width: 80rem;
max-width: 84rem;
overflow: hidden;
margin-left: auto;
margin-right: auto;
@@ -1416,7 +1420,7 @@ i.search {
.chat-box .bubble {
position: relative;
display: flex;
max-width: 40rem;
max-width: 36rem;
min-width: 9rem;
height: auto;
padding: 0.75rem 0.5rem 0.75rem 0.5rem;
@@ -1936,6 +1940,9 @@ ul.tabbar li.active > a {
.material-icons.big {
font-size: 2.4rem;
}
.material-icons.huge {
font-size: 4rem;
}
#self-avatar .material-icons {
font-size: 2.4rem;
+1 -1
View File
File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -22,11 +22,11 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,500,700,700i" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<link rel="stylesheet" href="css/base.min.css" />
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react@17.0.1/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@17.0.1/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@5.18.3/react-intl.iife.min.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@8.6.7/firebase-app.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@8.6.7/firebase-messaging.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react@17.0.2/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/react-intl@5.20.7/react-intl.iife.min.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@8.9.1/firebase-app.js"></script>
<script crossorigin="anonymous" src="https://cdn.jsdelivr.net/npm/firebase@8.9.1/firebase-messaging.js"></script>
</head>
<body>
<noscript><div id="noscript-message"><div id="noscript-title">Javascript support is required.</div>
+1398 -1793
View File
File diff suppressed because it is too large Load Diff
+7 -6
View File
@@ -1,7 +1,7 @@
{
"name": "tinode-webapp",
"description": "Tinode messenger for the web",
"version": "0.17.2",
"version": "0.17.10",
"repository": {
"type": "git",
"url": "https://github.com/tinode/example-react-js.git"
@@ -52,7 +52,8 @@
"ko",
"ro",
"ru",
"zh"
"zh",
"zh-TW"
],
"main": "umd/index.prod.js",
"bugs": {
@@ -63,11 +64,11 @@
"author": "Tinode Authors <info@tinode.co>",
"license": "Apache-2.0",
"dependencies": {
"firebase": "^8.6.7",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"firebase": "^8.9.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-intl": "^5.20.2",
"tinode-sdk": "^0.17.2"
"tinode-sdk": "^0.17.10"
},
"devDependencies": {
"@babel/core": "^7.14.5",
+12 -5
View File
@@ -1,11 +1,12 @@
// Must be located at the root.
importScripts('https://cdn.jsdelivr.net/npm/firebase@8.6.7/firebase-app.js');
importScripts('https://cdn.jsdelivr.net/npm/firebase@8.6.7/firebase-messaging.js');
importScripts('https://cdn.jsdelivr.net/npm/firebase@8.9.1/firebase-app.js');
importScripts('https://cdn.jsdelivr.net/npm/firebase@8.9.1/firebase-messaging.js');
importScripts('firebase-init.js');
importScripts('version.js');
// Channel to notify the webapp.
const webAppChannel = new BroadcastChannel('tinode-sw');
// Channel to notify the webapp. There is no BroadcastChannel in Safari.
const webAppChannel = (typeof BroadcastChannel == 'function') ?
new BroadcastChannel('tinode-sw') : null;
// Basic internationalization.
const i18n = {
@@ -36,6 +37,10 @@ const i18n = {
'zh': {
'new_message': "新讯息",
'new_chat': "新聊天",
},
'zh-TW': {
'new_message': "新訊息",
'new_chat': "新聊天",
}
};
self.i18nMessage = function(id) {
@@ -56,7 +61,9 @@ fbMessaging.onBackgroundMessage((payload) => {
}
// Notify webapp that a message was received.
webAppChannel.postMessage(payload.data);
if (webAppChannel) {
webAppChannel.postMessage(payload.data);
}
const pushType = payload.data.what || 'msg';
const title = payload.data.title || self.i18nMessage(pushType == 'msg' ? 'new_message' : 'new_chat');
+4 -1
View File
@@ -175,5 +175,8 @@
"channel": "Sender",
"drafty_attachment": "Dateianhang",
"drafty_form": "Formular:",
"drafty_image": "Bild"
"drafty_image": "Bild",
"topic_delete": "Chat löschen",
"password_reset_email_sent": "Eine E-Mail wurde an {email} gesendet. Folgen Sie den Anweisungen in der E-Mail, um Ihr Passwort zurückzusetzen.",
"menu_item_restore_topic": "Wiederherstellen"
}
+4 -1
View File
@@ -105,6 +105,7 @@
"menu_item_info": "Info",
"menu_item_member_delete": "Remove",
"menu_item_mute": "Mute",
"menu_item_restore_topic": "Restore",
"menu_item_send_retry": "Retry",
"menu_item_unblock": "Unblock",
"menu_item_unmute": "Unmute",
@@ -120,6 +121,7 @@
"numeric_confirmation_code_prompt": "Numbers only",
"online_now": "online now",
"password_prompt": "Password",
"password_reset_email_sent": "An email has been sent to {email}. Follow the directions in the email to reset your password.",
"password_unchanged_prompt": "Unchanged",
"peers_messaging_disabled": "Peer's messaging is disabled.",
"permission_admin": "Approve ({val})",
@@ -169,7 +171,8 @@
"title_permissions": "Permissions",
"title_tag_manager": "Tags (user discovery)",
"topic_block_warning": "Are you sure you want to block this conversation?",
"topic_delete_warning": "Are you sure you want to delete this conversation?",
"topic_delete": "Delete Conversation",
"topic_delete_warning": "Are you sure you want to delete this conversation? It cannot be undone.",
"topic_name_editing_placeholder": "Freeform name of the group",
"unnamed_topic": "Unnamed",
"update_available": "Update available.",
+5 -2
View File
@@ -162,7 +162,7 @@
"title_permissions": "Permisos",
"message_sending": "enviando...",
"message_sending_failed": "no se pudo enviar el mensaje",
"search_placeholder": "Ej. email:alice@example.com, tel:17025550003...",
"search_placeholder": "Ej. email:alba@example.com, tel:17025550003...",
"messaging_disabled_prompt": "El envío de mensajes está deshabilitado",
"new_message_prompt": "Nuevo mensaje",
"image_caption_prompt": "Añade un comentario",
@@ -175,5 +175,8 @@
"channel": "canal",
"drafty_attachment": "Archivo",
"drafty_form": "Formulario:",
"drafty_image": "Imagen"
"drafty_image": "Imagen",
"topic_delete": "Eliminar chat",
"password_reset_email_sent": "Se ha enviado un correo electrónico a {email}. Siga las instrucciones del correo electrónico para restablecer su contraseña.",
"menu_item_restore_topic": "Restaurar"
}
+13 -1
View File
@@ -421,6 +421,10 @@
"defaultMessage": "Mute",
"description": "Turn off notifications"
},
"menu_item_restore_topic": {
"defaultMessage": "Restore",
"description": "Restore topic from archive"
},
"menu_item_send_retry": {
"defaultMessage": "Retry",
"description": "Retry sending message"
@@ -481,6 +485,10 @@
"defaultMessage": "Password",
"description": "Placeholder/prompt for entering password"
},
"password_reset_email_sent": {
"defaultMessage": "An email has been sent to {email}. Follow the directions in the email to reset your password.",
"description": "Notification that the email with password reset instructions has been sent"
},
"password_unchanged_prompt": {
"defaultMessage": "Unchanged",
"description": "Message in editor while password is unchanged"
@@ -677,8 +685,12 @@
"defaultMessage": "Are you sure you want to block this conversation?",
"description": "Alert warning when blocking a topic."
},
"topic_delete": {
"defaultMessage": "Delete Conversation",
"description": "Alert title when deleting the topic."
},
"topic_delete_warning": {
"defaultMessage": "Are you sure you want to delete this conversation?",
"defaultMessage": "Are you sure you want to delete this conversation? It cannot be undone.",
"description": "Alert warning when deleting entire topic"
},
"topic_name_editing_placeholder": {
+5 -2
View File
@@ -162,7 +162,7 @@
"title_permissions": "권한",
"message_sending": "보내기...",
"message_sending_failed": "실패",
"search_placeholder": "email:alice@example.com,tel:17025550003...와같은목록",
"search_placeholder": "email:kim@example.com,tel:17025550003...와같은목록",
"messaging_disabled_prompt": "메시지비활성화",
"new_message_prompt": "새메시지",
"image_caption_prompt": "이미지설명",
@@ -178,5 +178,8 @@
"channel": "채널",
"drafty_attachment": "부착",
"drafty_form": "형태:",
"drafty_image": "이미지"
"drafty_image": "이미지",
"topic_delete": "채팅 삭제",
"password_reset_email_sent": "이메일이 {email}(으)로 전송되었습니다. 이메일의 지침에 따라 비밀번호를 재설정하세요.",
"menu_item_restore_topic": "복원"
}
+4 -1
View File
@@ -175,5 +175,8 @@
"title_manage_tags": "Administrează",
"drafty_attachment": "Atasament",
"drafty_form": "Formă:",
"drafty_image": "Imagine"
"drafty_image": "Imagine",
"topic_delete": "Ștergeți Chat",
"password_reset_email_sent": "Un e-mail a fost trimis către {email}. Urmați instrucțiunile din e-mail pentru a vă reseta parola.",
"menu_item_restore_topic": "Restabili"
}
+4 -1
View File
@@ -175,5 +175,8 @@
"channel": "канал",
"drafty_attachment": "Аттачмент",
"drafty_form": "Форма:",
"drafty_image": "Картинка"
"drafty_image": "Картинка",
"topic_delete": "Удалить чат",
"password_reset_email_sent": "Сообщение было отправлено на адрес {email}. Следуйте инструкциям в сообщении, чтобы изменить пароль.",
"menu_item_restore_topic": "Восстановить"
}
+182
View File
@@ -0,0 +1,182 @@
{
"action_block_contact": "封鎖聯絡人",
"action_cancel": "取消",
"action_clear_messages": "清除訊息",
"action_delete_messages": "為所有人清除訊息",
"action_leave_chat": "離開對話",
"action_more": "更多",
"action_report_chat": "檢舉對話",
"archived_contacts": "已封存的聯絡人 ({count})",
"badge_owner": "擁有者",
"badge_you": "您",
"block_contact_warning": "您確定要封鎖此聯絡人嗎?",
"blocked_contacts_link": "已封鎖的聯絡人 ({count})",
"button_add_another": "新增其他人",
"button_add_members": "新增成員",
"button_cancel": "取消",
"button_confirm": "確認",
"button_create": "建立",
"button_delete_account": "刪除帳號",
"button_edit": "編輯",
"button_logout": "登出",
"button_ok": "OK",
"button_reset": "重設",
"button_send_request": "傳送要求",
"button_sign_in": "登入",
"button_sign_up": "註冊",
"button_subscribe": "訂閱",
"button_update": "更新",
"cannot_initiate_file_upload": "無法為檔案上傳初始化。",
"channel": "頻道",
"channel_prompt": "這是頻道",
"chat_invitation": "您已被邀請開始新對話。您接下來要做什麼呢?",
"chat_invitation_accept": "接受",
"chat_invitation_block": "封鎖",
"chat_invitation_ignore": "忽略",
"clear_messages_warning": "您確定要清除所有訊息嗎?此操作無法復原。",
"code_doesnot_match": "代碼不相符",
"contacts_not_found": "您沒有任何對話<br />¯∖_(ツ)_/¯",
"contacts_not_found_short": "沒有符合 ''{query}'' 的聯絡人",
"credential_email_prompt": "您的註冊電子郵件地址",
"delete_account": "刪除帳號",
"delete_account_arning": "您確定要刪除您的帳號嗎?此操作無法復原。",
"delete_messages_warning": "您確定要為所有人刪除所有訊息嗎?此操作無法復原。",
"deleted_content": "內容已被刪除",
"download_action": "下載",
"drafty_attachment": "附加檔案",
"drafty_form": "表單:",
"drafty_image": "圖片",
"email_dative": "電子郵件地址",
"email_prompt": "電子郵件地址,例如:jdoe@example.com",
"enable_peers_messaging": "啟用",
"enter_confirmation_code_prompt": "輸入透過 {method} 傳送給您的確認碼:",
"error_invalid_id": "ID 無效",
"file_attachment_too_large": "檔案大小 {size} 超出 {limit} 限制。",
"forgot_password_link": "忘記密碼?",
"full_name_prompt": "全名,例如:王小明",
"granted_permissions": "已授權",
"group_has_no_members": "無成員",
"group_user_id_prompt": "群組或使用者 ID",
"image_caption_prompt": "圖片說明",
"invalid_content": "內容無效",
"invalid_security_token": "安全權杖無效",
"label_client": "客戶端:",
"label_content_type": "類型:",
"label_default_access": "預設存取模式:",
"label_default_access_mode": "預設存取模式:",
"label_file_name": "檔名:",
"label_group_members": "群組成員:",
"label_incognito_mode": "無痕模式:",
"label_message_sound": "訊息提示聲:",
"label_muting_topic": "靜音:",
"label_other_user": "其他",
"label_password": "密碼",
"label_permissions": "權限:",
"label_private": "私人留言",
"label_push_notifications": "通知:",
"label_push_notifications_disabled": "通知 (需要 HTTPS):",
"label_reset_password": "傳送重設密碼郵件:",
"label_sdk": "SDK:",
"label_server": "伺服器:",
"label_server_address": "伺服器位址:",
"label_server_to_use": "使用的伺服器:",
"label_size": "大小:",
"label_topic_name": "名稱",
"label_user_contacts": "聯絡人:",
"label_user_id": "ID:",
"label_wire_transport": "Wire transport:",
"label_you": "您:",
"label_your_name": "您的名字",
"label_your_permissions": "您的權限:",
"last_seen_timestamp": "最後上線",
"leave_chat_warning": "您確定要離開此對話嗎?",
"link_contact_us": "聯絡我們",
"link_privacy_policy": "隱私權政策",
"link_terms_of_service": "服務條款",
"login_prompt": "登入",
"menu_item_archive_topic": "封存",
"menu_item_block": "封鎖",
"menu_item_clear_messages": "清除訊息",
"menu_item_clear_messages_for_all": "為所有人清除訊息",
"menu_item_delete": "刪除",
"menu_item_delete_for_all": "為所有人刪除",
"menu_item_delete_topic": "刪除",
"menu_item_edit_permissions": "編輯權限",
"menu_item_info": "Info",
"menu_item_member_delete": "移除",
"menu_item_mute": "靜音",
"menu_item_send_retry": "重試",
"menu_item_unblock": "解除封鎖",
"menu_item_unmute": "解除靜音",
"message_sending": "正在傳送...",
"message_sending_failed": "失敗",
"messages_not_readable": "沒有存取訊息的權限",
"messaging_disabled_prompt": "停用訊息",
"more_online_members": "+{overflow}",
"new_message_prompt": "新訊息",
"new_password_placeholder": "輸入新的密碼",
"no_connection": "無連線",
"no_contacts": "您沒有任何聯絡人 :-(",
"numeric_confirmation_code_prompt": "僅數字",
"online_now": "上線中",
"password_prompt": "密碼",
"password_unchanged_prompt": "未修改",
"peers_messaging_disabled": "Peer 已停用訊息。",
"permission_admin": "核可 ({val})",
"permission_delete": "刪除 ({val})",
"permission_join": "加入 ({val})",
"permission_owner": "擁有者 ({val})",
"permission_pres": "收到通知 ({val})",
"permission_read": "讀取 ({val})",
"permission_share": "分享 ({val})",
"permission_write": "撰寫 ({val})",
"phone_dative": "phone",
"private_editing_placeholder": "僅您可見",
"push_init_failed": "初始化通知失敗。",
"reconnect_countdown": "已斷線。將在 {seconds} 秒後重新連線…",
"reconnect_now": "現在重試",
"reload_update": "重新載入",
"report_chat_warning": "您確定要封鎖並檢舉此對話嗎?",
"requested_permissions": "重新請求",
"save_attachment": "儲存",
"search_for_contacts": "使用搜尋來尋找聯絡人",
"search_no_results": "搜尋無結果",
"search_placeholder": "例如 email:alice@example.com, tel:17025550003... 的清單",
"sidepanel_title_acc_general": "一般",
"sidepanel_title_acc_notifications": "通知",
"sidepanel_title_acc_security": "安全性",
"sidepanel_title_acc_support": "支援",
"sidepanel_title_account_settings": "帳號設定",
"sidepanel_title_archive": "已封存的對話",
"sidepanel_title_blocked": "已封鎖的對話",
"sidepanel_title_cred": "確認認證",
"sidepanel_title_login": "登入",
"sidepanel_title_newtpk": "開始新對話",
"sidepanel_title_register": "建立帳號",
"sidepanel_title_reset": "重設密碼",
"sidepanel_title_settings": "設定",
"stay_logged_in": "保持登入",
"tabtitle_find_user": "尋找",
"tabtitle_group_by_id": "以 id",
"tabtitle_new_group": "新群組",
"tags_editor_no_tags": "新增一些標籤",
"tags_not_found": "未定義任何標籤。請新增一些標籤。",
"title_all_contacts": "所有聯絡人",
"title_group_members": "群組成員",
"title_info": "資訊",
"title_manage_tags": "管理",
"title_not_found": "找不到",
"title_permissions": "權限",
"title_tag_manager": "標籤 (使用者探索)",
"topic_block_warning": "您確定要封鎖此對話嗎?",
"topic_delete_warning": "您確定要刪除此對話嗎?",
"topic_name_editing_placeholder": "群組的任意名稱",
"unnamed_topic": "未命名",
"update_available": "有可用的更新。",
"upload_finishing": "正在完成...",
"user_not_found": "找不到",
"validate_credential_action": "確認",
"topic_delete": "刪除聊天",
"password_reset_email_sent": "一封電子郵件已發送至 {email}。按照電子郵件中的說明重置密碼。",
"menu_item_restore_topic": "恢復"
}
+4 -1
View File
@@ -175,5 +175,8 @@
"channel": "频道",
"drafty_attachment": "附件",
"drafty_form": "形式:",
"drafty_image": "图像"
"drafty_image": "图像",
"topic_delete": "删除聊天",
"password_reset_email_sent": "一封电子邮件已发送至 {email}。按照电子邮件中的说明重置密码。",
"menu_item_restore_topic": "从存档中恢复"
}
+5 -3
View File
@@ -29,8 +29,10 @@ const messages =
allMessages.en;
ReactDOM.render(
<IntlProvider locale={language} messages={messages} textComponent={React.Fragment}>
<TinodeWeb />
</IntlProvider>,
<React.StrictMode>
<IntlProvider locale={language} messages={messages} textComponent={React.Fragment}>
<TinodeWeb />
</IntlProvider>
</React.StrictMode>,
document.getElementById('mountPoint')
);
+212 -9
View File
@@ -176,7 +176,10 @@
"channel": "Sender",
"drafty_attachment": "Dateianhang",
"drafty_form": "Formular:",
"drafty_image": "Bild"
"drafty_image": "Bild",
"topic_delete": "Chat löschen",
"password_reset_email_sent": "Eine E-Mail wurde an {email} gesendet. Folgen Sie den Anweisungen in der E-Mail, um Ihr Passwort zurückzusetzen.",
"menu_item_restore_topic": "Wiederherstellen"
},
"en": {
"action_block_contact": "Block Contact",
@@ -285,6 +288,7 @@
"menu_item_info": "Info",
"menu_item_member_delete": "Remove",
"menu_item_mute": "Mute",
"menu_item_restore_topic": "Restore",
"menu_item_send_retry": "Retry",
"menu_item_unblock": "Unblock",
"menu_item_unmute": "Unmute",
@@ -300,6 +304,7 @@
"numeric_confirmation_code_prompt": "Numbers only",
"online_now": "online now",
"password_prompt": "Password",
"password_reset_email_sent": "An email has been sent to {email}. Follow the directions in the email to reset your password.",
"password_unchanged_prompt": "Unchanged",
"peers_messaging_disabled": "Peer's messaging is disabled.",
"permission_admin": "Approve ({val})",
@@ -349,7 +354,8 @@
"title_permissions": "Permissions",
"title_tag_manager": "Tags (user discovery)",
"topic_block_warning": "Are you sure you want to block this conversation?",
"topic_delete_warning": "Are you sure you want to delete this conversation?",
"topic_delete": "Delete Conversation",
"topic_delete_warning": "Are you sure you want to delete this conversation? It cannot be undone.",
"topic_name_editing_placeholder": "Freeform name of the group",
"unnamed_topic": "Unnamed",
"update_available": "Update available.",
@@ -521,7 +527,7 @@
"title_permissions": "Permisos",
"message_sending": "enviando...",
"message_sending_failed": "no se pudo enviar el mensaje",
"search_placeholder": "Ej. email:alice@example.com, tel:17025550003...",
"search_placeholder": "Ej. email:alba@example.com, tel:17025550003...",
"messaging_disabled_prompt": "El envío de mensajes está deshabilitado",
"new_message_prompt": "Nuevo mensaje",
"image_caption_prompt": "Añade un comentario",
@@ -534,7 +540,10 @@
"channel": "canal",
"drafty_attachment": "Archivo",
"drafty_form": "Formulario:",
"drafty_image": "Imagen"
"drafty_image": "Imagen",
"topic_delete": "Eliminar chat",
"password_reset_email_sent": "Se ha enviado un correo electrónico a {email}. Siga las instrucciones del correo electrónico para restablecer su contraseña.",
"menu_item_restore_topic": "Restaurar"
},
"ko": {
"validate_credential_action": "확인",
@@ -697,7 +706,7 @@
"title_permissions": "권한",
"message_sending": "보내기...",
"message_sending_failed": "실패",
"search_placeholder": "email:alice@example.com,tel:17025550003...와같은목록",
"search_placeholder": "email:kim@example.com,tel:17025550003...와같은목록",
"messaging_disabled_prompt": "메시지비활성화",
"new_message_prompt": "새메시지",
"image_caption_prompt": "이미지설명",
@@ -713,7 +722,10 @@
"channel": "채널",
"drafty_attachment": "부착",
"drafty_form": "형태:",
"drafty_image": "이미지"
"drafty_image": "이미지",
"topic_delete": "채팅 삭제",
"password_reset_email_sent": "이메일이 {email}(으)로 전송되었습니다. 이메일의 지침에 따라 비밀번호를 재설정하세요.",
"menu_item_restore_topic": "복원"
},
"ro": {
"validate_credential_action": "confirmă",
@@ -892,7 +904,10 @@
"title_manage_tags": "Administrează",
"drafty_attachment": "Atasament",
"drafty_form": "Formă:",
"drafty_image": "Imagine"
"drafty_image": "Imagine",
"topic_delete": "Ștergeți Chat",
"password_reset_email_sent": "Un e-mail a fost trimis către {email}. Urmați instrucțiunile din e-mail pentru a vă reseta parola.",
"menu_item_restore_topic": "Restabili"
},
"ru": {
"contacts_not_found": "Чатов нет<br />¯∖_(ツ)_/¯",
@@ -1071,7 +1086,192 @@
"channel": "канал",
"drafty_attachment": "Аттачмент",
"drafty_form": "Форма:",
"drafty_image": "Картинка"
"drafty_image": "Картинка",
"topic_delete": "Удалить чат",
"password_reset_email_sent": "Сообщение было отправлено на адрес {email}. Следуйте инструкциям в сообщении, чтобы изменить пароль.",
"menu_item_restore_topic": "Восстановить"
},
"zh-TW": {
"action_block_contact": "封鎖聯絡人",
"action_cancel": "取消",
"action_clear_messages": "清除訊息",
"action_delete_messages": "為所有人清除訊息",
"action_leave_chat": "離開對話",
"action_more": "更多",
"action_report_chat": "檢舉對話",
"archived_contacts": "已封存的聯絡人 ({count})",
"badge_owner": "擁有者",
"badge_you": "您",
"block_contact_warning": "您確定要封鎖此聯絡人嗎?",
"blocked_contacts_link": "已封鎖的聯絡人 ({count})",
"button_add_another": "新增其他人",
"button_add_members": "新增成員",
"button_cancel": "取消",
"button_confirm": "確認",
"button_create": "建立",
"button_delete_account": "刪除帳號",
"button_edit": "編輯",
"button_logout": "登出",
"button_ok": "OK",
"button_reset": "重設",
"button_send_request": "傳送要求",
"button_sign_in": "登入",
"button_sign_up": "註冊",
"button_subscribe": "訂閱",
"button_update": "更新",
"cannot_initiate_file_upload": "無法為檔案上傳初始化。",
"channel": "頻道",
"channel_prompt": "這是頻道",
"chat_invitation": "您已被邀請開始新對話。您接下來要做什麼呢?",
"chat_invitation_accept": "接受",
"chat_invitation_block": "封鎖",
"chat_invitation_ignore": "忽略",
"clear_messages_warning": "您確定要清除所有訊息嗎?此操作無法復原。",
"code_doesnot_match": "代碼不相符",
"contacts_not_found": "您沒有任何對話<br />¯∖_(ツ)_/¯",
"contacts_not_found_short": "沒有符合 ''{query}'' 的聯絡人",
"credential_email_prompt": "您的註冊電子郵件地址",
"delete_account": "刪除帳號",
"delete_account_arning": "您確定要刪除您的帳號嗎?此操作無法復原。",
"delete_messages_warning": "您確定要為所有人刪除所有訊息嗎?此操作無法復原。",
"deleted_content": "內容已被刪除",
"download_action": "下載",
"drafty_attachment": "附加檔案",
"drafty_form": "表單:",
"drafty_image": "圖片",
"email_dative": "電子郵件地址",
"email_prompt": "電子郵件地址,例如:jdoe@example.com",
"enable_peers_messaging": "啟用",
"enter_confirmation_code_prompt": "輸入透過 {method} 傳送給您的確認碼:",
"error_invalid_id": "ID 無效",
"file_attachment_too_large": "檔案大小 {size} 超出 {limit} 限制。",
"forgot_password_link": "忘記密碼?",
"full_name_prompt": "全名,例如:王小明",
"granted_permissions": "已授權",
"group_has_no_members": "無成員",
"group_user_id_prompt": "群組或使用者 ID",
"image_caption_prompt": "圖片說明",
"invalid_content": "內容無效",
"invalid_security_token": "安全權杖無效",
"label_client": "客戶端:",
"label_content_type": "類型:",
"label_default_access": "預設存取模式:",
"label_default_access_mode": "預設存取模式:",
"label_file_name": "檔名:",
"label_group_members": "群組成員:",
"label_incognito_mode": "無痕模式:",
"label_message_sound": "訊息提示聲:",
"label_muting_topic": "靜音:",
"label_other_user": "其他",
"label_password": "密碼",
"label_permissions": "權限:",
"label_private": "私人留言",
"label_push_notifications": "通知:",
"label_push_notifications_disabled": "通知 (需要 HTTPS):",
"label_reset_password": "傳送重設密碼郵件:",
"label_sdk": "SDK:",
"label_server": "伺服器:",
"label_server_address": "伺服器位址:",
"label_server_to_use": "使用的伺服器:",
"label_size": "大小:",
"label_topic_name": "名稱",
"label_user_contacts": "聯絡人:",
"label_user_id": "ID:",
"label_wire_transport": "Wire transport:",
"label_you": "您:",
"label_your_name": "您的名字",
"label_your_permissions": "您的權限:",
"last_seen_timestamp": "最後上線",
"leave_chat_warning": "您確定要離開此對話嗎?",
"link_contact_us": "聯絡我們",
"link_privacy_policy": "隱私權政策",
"link_terms_of_service": "服務條款",
"login_prompt": "登入",
"menu_item_archive_topic": "封存",
"menu_item_block": "封鎖",
"menu_item_clear_messages": "清除訊息",
"menu_item_clear_messages_for_all": "為所有人清除訊息",
"menu_item_delete": "刪除",
"menu_item_delete_for_all": "為所有人刪除",
"menu_item_delete_topic": "刪除",
"menu_item_edit_permissions": "編輯權限",
"menu_item_info": "Info",
"menu_item_member_delete": "移除",
"menu_item_mute": "靜音",
"menu_item_send_retry": "重試",
"menu_item_unblock": "解除封鎖",
"menu_item_unmute": "解除靜音",
"message_sending": "正在傳送...",
"message_sending_failed": "失敗",
"messages_not_readable": "沒有存取訊息的權限",
"messaging_disabled_prompt": "停用訊息",
"more_online_members": "+{overflow}",
"new_message_prompt": "新訊息",
"new_password_placeholder": "輸入新的密碼",
"no_connection": "無連線",
"no_contacts": "您沒有任何聯絡人 :-(",
"numeric_confirmation_code_prompt": "僅數字",
"online_now": "上線中",
"password_prompt": "密碼",
"password_unchanged_prompt": "未修改",
"peers_messaging_disabled": "Peer 已停用訊息。",
"permission_admin": "核可 ({val})",
"permission_delete": "刪除 ({val})",
"permission_join": "加入 ({val})",
"permission_owner": "擁有者 ({val})",
"permission_pres": "收到通知 ({val})",
"permission_read": "讀取 ({val})",
"permission_share": "分享 ({val})",
"permission_write": "撰寫 ({val})",
"phone_dative": "phone",
"private_editing_placeholder": "僅您可見",
"push_init_failed": "初始化通知失敗。",
"reconnect_countdown": "已斷線。將在 {seconds} 秒後重新連線…",
"reconnect_now": "現在重試",
"reload_update": "重新載入",
"report_chat_warning": "您確定要封鎖並檢舉此對話嗎?",
"requested_permissions": "重新請求",
"save_attachment": "儲存",
"search_for_contacts": "使用搜尋來尋找聯絡人",
"search_no_results": "搜尋無結果",
"search_placeholder": "例如 email:alice@example.com, tel:17025550003... 的清單",
"sidepanel_title_acc_general": "一般",
"sidepanel_title_acc_notifications": "通知",
"sidepanel_title_acc_security": "安全性",
"sidepanel_title_acc_support": "支援",
"sidepanel_title_account_settings": "帳號設定",
"sidepanel_title_archive": "已封存的對話",
"sidepanel_title_blocked": "已封鎖的對話",
"sidepanel_title_cred": "確認認證",
"sidepanel_title_login": "登入",
"sidepanel_title_newtpk": "開始新對話",
"sidepanel_title_register": "建立帳號",
"sidepanel_title_reset": "重設密碼",
"sidepanel_title_settings": "設定",
"stay_logged_in": "保持登入",
"tabtitle_find_user": "尋找",
"tabtitle_group_by_id": "以 id",
"tabtitle_new_group": "新群組",
"tags_editor_no_tags": "新增一些標籤",
"tags_not_found": "未定義任何標籤。請新增一些標籤。",
"title_all_contacts": "所有聯絡人",
"title_group_members": "群組成員",
"title_info": "資訊",
"title_manage_tags": "管理",
"title_not_found": "找不到",
"title_permissions": "權限",
"title_tag_manager": "標籤 (使用者探索)",
"topic_block_warning": "您確定要封鎖此對話嗎?",
"topic_delete_warning": "您確定要刪除此對話嗎?",
"topic_name_editing_placeholder": "群組的任意名稱",
"unnamed_topic": "未命名",
"update_available": "有可用的更新。",
"upload_finishing": "正在完成...",
"user_not_found": "找不到",
"validate_credential_action": "確認",
"topic_delete": "刪除聊天",
"password_reset_email_sent": "一封電子郵件已發送至 {email}。按照電子郵件中的說明重置密碼。",
"menu_item_restore_topic": "恢復"
},
"zh": {
"archived_contacts": "已归档联系人 ({count})",
@@ -1250,6 +1450,9 @@
"channel": "频道",
"drafty_attachment": "附件",
"drafty_form": "形式:",
"drafty_image": "图像"
"drafty_image": "图像",
"topic_delete": "删除聊天",
"password_reset_email_sent": "一封电子邮件已发送至 {email}。按照电子邮件中的说明重置密码。",
"menu_item_restore_topic": "从存档中恢复"
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
// This is a generated file. Don't edit.
export const PACKAGE_VERSION = "0.17.2";
export const PACKAGE_VERSION = "0.17.10";
+33 -3
View File
@@ -61,6 +61,16 @@ const messages = defineMessages({
defaultMessage: 'Are you sure you want to delete all messages for everyone? It cannot be undone.',
description: 'Alert dialog warning when hard-deleting all messages.'
},
topic_delete: {
id: 'topic_delete',
defaultMessage: 'Delete Conversation',
description: 'Alert title when deleting the topic.'
},
topic_delete_warning: {
id: 'topic_delete_warning',
defaultMessage: 'Are you sure you want to delete this conversation? It cannot be undone.',
description: 'Alert warning when deleting entire topic'
},
leave_chat: {
id: 'action_leave_chat',
defaultMessage: 'Leave Conversation',
@@ -143,6 +153,7 @@ class InfoView extends React.Component {
this.handleShowAddMembers = this.handleShowAddMembers.bind(this);
this.handleHideAddMembers = this.handleHideAddMembers.bind(this);
this.handleMemberUpdateRequest = this.handleMemberUpdateRequest.bind(this);
this.handleDeleteTopic = this.handleDeleteTopic.bind(this);
this.handleDeleteMessages = this.handleDeleteMessages.bind(this);
this.handleLeave = this.handleLeave.bind(this);
this.handleBlock = this.handleBlock.bind(this);
@@ -422,6 +433,19 @@ class InfoView extends React.Component {
this.setState({showMemberPanel: false});
}
handleDeleteTopic(e) {
e.preventDefault();
const {formatMessage} = this.props.intl;
this.props.onShowAlert(
formatMessage(messages.topic_delete), // title
formatMessage(messages.topic_delete_warning), // content
(() => { this.props.onDeleteTopic(this.props.topic); }), // onConfirm
null, // "OK"
true, // Show Reject button
null // "Cancel"
);
}
handleDeleteMessages(e) {
e.preventDefault();
const {formatMessage} = this.props.intl;
@@ -717,9 +741,15 @@ class InfoView extends React.Component {
:
null
}
<a href="#" className="danger flat-button" onClick={this.handleLeave}>
<i className="material-icons">exit_to_app</i> &nbsp;{formatMessage(messages.leave_chat)}
</a>
{this.state.owner ?
<a href="#" className="danger flat-button" onClick={this.handleDeleteTopic}>
<i className="material-icons">delete</i> &nbsp;{formatMessage(messages.topic_delete)}
</a>
:
<a href="#" className="danger flat-button" onClick={this.handleLeave}>
<i className="material-icons">exit_to_app</i> &nbsp;{formatMessage(messages.leave_chat)}
</a>
}
{!this.state.groupTopic ?
<a href="#" className="danger flat-button" onClick={this.handleBlock}>
<i className="material-icons">block</i> &nbsp;{formatMessage(messages.block_contact)}
+52 -63
View File
@@ -84,7 +84,7 @@ class MessagesView extends React.Component {
this.handleScrollEvent = this.handleScrollEvent.bind(this);
this.handleDescChange = this.handleDescChange.bind(this);
this.handleSubsUpdated = this.handleSubsUpdated.bind(this);
this.handleNewMessage = this.handleNewMessage.bind(this);
this.handleMessageUpdate = this.handleMessageUpdate.bind(this);
this.handleAllMessagesReceived = this.handleAllMessagesReceived.bind(this);
this.handleInfoReceipt = this.handleInfoReceipt.bind(this);
this.handleImagePostview = this.handleImagePostview.bind(this);
@@ -124,7 +124,7 @@ class MessagesView extends React.Component {
// or vertical shrinking.
componentDidUpdate(prevProps, prevState) {
if (this.messagesScroller) {
if (prevState.topic != this.state.topic || prevState.messages.length != this.state.messages.length) {
if (prevState.topic != this.state.topic || prevState.messageCount != this.state.messageCount) {
// New message
this.messagesScroller.scrollTop = this.messagesScroller.scrollHeight - this.state.scrollPosition;
} else if (prevProps.viewportHeight > this.props.viewportHeight) {
@@ -140,7 +140,7 @@ class MessagesView extends React.Component {
}
if (topic) {
topic.onData = this.handleNewMessage;
topic.onData = this.handleMessageUpdate;
topic.onAllMessagesReceived = this.handleAllMessagesReceived;
topic.onInfo = this.handleInfoReceipt;
topic.onMetaDesc = this.handleDescChange;
@@ -208,7 +208,8 @@ class MessagesView extends React.Component {
if (!nextProps.topic) {
// Default state: no topic.
nextState = {
messages: [],
messageCount: 0,
latestClearId: -1,
onlineSubs: [],
topic: null,
title: '',
@@ -236,7 +237,6 @@ class MessagesView extends React.Component {
if (topic) {
// Topic exists.
const msgs = [];
const subs = [];
if (nextProps.connected) {
@@ -247,14 +247,7 @@ class MessagesView extends React.Component {
});
}
topic.messages(function(msg) {
if (!msg.deleted) {
msgs.push(msg);
}
});
Object.assign(nextState, {
messages: msgs,
onlineSubs: subs
});
@@ -281,12 +274,15 @@ class MessagesView extends React.Component {
});
}
Object.assign(nextState, {
messageCount: topic.messageCount(),
latestClearId: topic.maxClearId(),
channel: topic.isChannelType()
});
} else {
// Invalid topic.
Object.assign(nextState, {
messages: [],
messageCount: 0,
latestClearId: -1,
onlineSubs: [],
title: '',
avatar: null,
@@ -369,21 +365,19 @@ class MessagesView extends React.Component {
// Get older messages
handleScrollEvent(event) {
this.setState({scrollPosition: event.target.scrollHeight - event.target.scrollTop});
if (this.state.fetchingMessages) {
return;
}
if (event.target.scrollTop <= 0) {
this.setState((prevState, props) => {
const newState = {};
if (!prevState.fetchingMessages) {
const topic = this.props.tinode.getTopic(this.state.topic);
if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {
newState.fetchingMessages = true;
topic.getMessagesPage(MESSAGES_PAGE).catch((err) => {
this.setState({fetchingMessages: false});
this.props.onError(err.message, 'err');
});
}
}
return newState;
});
const topic = this.props.tinode.getTopic(this.state.topic);
if (topic && topic.isSubscribed() && topic.msgHasMoreMessages()) {
this.setState({fetchingMessages: true}, () => {
topic.getMessagesPage(MESSAGES_PAGE)
.catch((err) => this.props.onError(err.message, 'err'))
.finally(() => this.setState({fetchingMessages: false}));
});
}
}
}
@@ -496,33 +490,32 @@ class MessagesView extends React.Component {
}
}
handleNewMessage(msg) {
// Regenerate messages list
// The 'msg' could be false-ish if some message ranges were deleted.
handleMessageUpdate(msg) {
const topic = this.props.tinode.getTopic(this.state.topic);
const newState = {messages: []};
topic.messages((m) => {
if (!m.deleted) {
newState.messages.push(m);
}
});
// msg could be null if one or more messages were deleted.
if (msg && !msg.deleted) {
// If the message is added to the end of the message list,
// scroll to the bottom.
if (topic.isNewMessage(msg.seq)) {
newState.scrollPosition = 0;
}
// Aknowledge messages except own messages. They are
// automatically assumed to be read and recived.
const status = topic.msgStatus(msg, true);
if (status >= Tinode.MESSAGE_STATUS_SENT && msg.from != this.props.myUserId) {
this.postReadNotification(msg.seq);
}
this.props.onData(msg);
if (!msg) {
// msg could be null if one or more messages were deleted.
// Updating state to force redraw.
this.setState({latestClearId: topic.maxClearId()});
return;
}
this.setState(newState);
this.setState({messageCount: topic.messageCount()});
// Scroll to the bottom if the message is added to the end of the message list.
// TODO: This should be replaced by showing a "scroll to bottom" button.
if (topic.isNewMessage(msg.seq)) {
this.setState({scrollPosition: 0});
}
// Aknowledge messages except own messages. They are
// automatically assumed to be read and recived.
const status = topic.msgStatus(msg, true);
if (status >= Tinode.MESSAGE_STATUS_SENT && msg.from != this.props.myUserId) {
this.postReadNotification(msg.seq);
}
// This will send "received" notifications right away.
this.props.onData(msg);
}
handleAllMessagesReceived(count) {
@@ -705,7 +698,7 @@ class MessagesView extends React.Component {
// Make small preview to show while uploading.
imageScaled(blob, IMAGE_PREVIEW_DIM, IMAGE_PREVIEW_DIM, -1, false,
(tinyBlob) => {
(tinyMine, tinyBlob) => {
// Convert tiny image into base64 for serialization and previewing.
blobToBase64(tinyBlob, (blobMime, tinyBits64) => {
let msg = Drafty.insertImage(null, 0, {
@@ -758,7 +751,7 @@ class MessagesView extends React.Component {
// Get image dimensions and size, optionally scale it down.
imageScaled(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, maxExternAttachmentSize, false,
// Success
(blob, mime, width, height, fname) => {
(mime, blob, width, height, fname) => {
this.setState({imagePreview: {
url: URL.createObjectURL(blob),
blob: blob,
@@ -777,7 +770,8 @@ class MessagesView extends React.Component {
}
handleCancelUpload(seq, uploader) {
const found = this.state.messages.find(msg => msg.seq == seq);
const topic = this.props.tinode.getTopic(this.state.topic);
const found = topic.findMessage(seq);
if (found) {
found._cancelled = true;
}
@@ -828,13 +822,8 @@ class MessagesView extends React.Component {
let messageNodes = [];
let previousFrom = null;
let chatBoxClass = null;
for (let i=0; i<this.state.messages.length; i++) {
let msg = this.state.messages[i];
let nextFrom = null;
if (i + 1 < this.state.messages.length) {
nextFrom = this.state.messages[i+1].from || 'chan';
}
topic.messages((msg, prev, next, i) => {
let nextFrom = next ? (next.from || null) : 'chan';
let sequence = 'single';
let thisFrom = msg.from || 'chan';
@@ -888,7 +877,7 @@ class MessagesView extends React.Component {
onCancelUpload={this.handleCancelUpload}
key={msg.seq} />
);
}
});
let lastSeen = null;
if (isChannel) {
+29 -8
View File
@@ -12,7 +12,8 @@ export default class PasswordResetView extends React.PureComponent {
this.state = {
email: '',
password: ''
password: '',
sent: false
};
this.handleSubmit = this.handleSubmit.bind(this);
@@ -21,7 +22,7 @@ export default class PasswordResetView extends React.PureComponent {
}
componentDidMount() {
let parsed = HashNavigation.parseUrlHash(window.location.hash);
const parsed = HashNavigation.parseUrlHash(window.location.hash);
this.setState({token: parsed.params.token, scheme: parsed.params.scheme});
}
@@ -29,8 +30,14 @@ export default class PasswordResetView extends React.PureComponent {
e.preventDefault();
if (this.state.token) {
this.props.onReset(this.state.scheme, this.state.password.trim(), this.state.token);
} else if (this.state.sent) {
this.props.onCancel();
} else {
this.props.onRequest('email', this.state.email.trim());
const email = this.state.email.trim();
this.setState({email: email});
this.props.onRequest('email', email).then(() => {
this.setState({sent: true});
});
}
}
@@ -43,10 +50,9 @@ export default class PasswordResetView extends React.PureComponent {
}
render() {
let reset = (this.state.token && this.state.scheme);
return (
<form id="password-reset-form" onSubmit={this.handleSubmit}>
{reset ?
{(this.state.token && this.state.scheme) ?
<FormattedMessage id="new_password_placeholder" defaultMessage="Enter new password"
description="Placeholder for entering new password">{
(placeholder) => <VisiblePassword
@@ -56,7 +62,17 @@ export default class PasswordResetView extends React.PureComponent {
required={true} autoFocus={true}
onChange={this.handlePasswordChange} />
}</FormattedMessage>
:
: this.state.sent ?
<>
<br/>
<center><i className="material-icons huge green">task_alt</i></center>
<br/>
<center><FormattedMessage id="password_reset_email_sent"
defaultMessage="An email has been sent to {email}. Follow the directions in the email to reset your password."
values={{ email: <tt>{this.state.email}</tt> }}
description="Notification that the email with password reset instructions has been sent" /></center>
</>
:
<>
<label htmlFor="inputEmail">
<FormattedMessage id="label_reset_password"
@@ -75,8 +91,13 @@ export default class PasswordResetView extends React.PureComponent {
</>
}
<div className="dialog-buttons">
<button className="primary" type="submit">{reset ?
<FormattedMessage id="button_reset" defaultMessage="Reset" description="Button [Reset]" /> :
<button className="primary" type="submit">{
(this.state.token && this.state.scheme) ?
<FormattedMessage id="button_reset" defaultMessage="Reset" description="Button [Reset]" />
: this.state.sent ?
<FormattedMessage id="button_ok" defaultMessage="OK"
description="Button [OK]" />
:
<FormattedMessage id="button_send_request" defaultMessage="Send request"
description="Button [Send request]" />
}</button>
+57 -24
View File
@@ -123,6 +123,7 @@ class TinodeWeb extends React.Component {
this.handleTagsUpdated = this.handleTagsUpdated.bind(this);
this.handleLogout = this.handleLogout.bind(this);
this.handleDeleteAccount = this.handleDeleteAccount.bind(this);
this.handleDeleteTopicRequest = this.handleDeleteTopicRequest.bind(this);
this.handleDeleteMessagesRequest = this.handleDeleteMessagesRequest.bind(this);
this.handleLeaveUnsubRequest = this.handleLeaveUnsubRequest.bind(this);
this.handleBlockTopicRequest = this.handleBlockTopicRequest.bind(this);
@@ -216,9 +217,15 @@ class TinodeWeb extends React.Component {
window.addEventListener('online', (e) => { this.handleOnline(true); });
window.addEventListener('offline', (e) => { this.handleOnline(false); });
window.addEventListener('hashchange', this.handleHashRoute);
// Process background notifications from the service worker.
const serviceWorkerChannel = new BroadcastChannel('tinode-sw');
serviceWorkerChannel.addEventListener('message', this.handlePushMessage);
if (typeof BroadcastChannel == 'function') {
const serviceWorkerChannel = new BroadcastChannel('tinode-sw');
serviceWorkerChannel.addEventListener('message', this.handlePushMessage);
} else {
// Safari is broken by design.
console.warn('Your browser does not support BroadcastChannel. Some features will not be available');
}
// Window/tab visible or invisible for pausing timers.
document.addEventListener('visibilitychange', this.handleVisibilityEvent);
@@ -307,7 +314,7 @@ class TinodeWeb extends React.Component {
initFCMessaging() {
const {formatMessage, locale} = this.props.intl;
const onError = (msg, err) => {
console.log(msg, err);
console.error(msg, err);
this.handleError(formatMessage(messages.push_init_failed), 'err');
this.setState({desktopAlertsEnabled: false});
}
@@ -383,24 +390,28 @@ class TinodeWeb extends React.Component {
'cred','reset','newtpk','archive','blocked','contacts',''].includes(hash.path[0])) {
this.setState({sidePanelSelected: hash.path[0]});
} else {
console.log("Unknown sidepanel view", hash.path[0]);
console.warn("Unknown sidepanel view", hash.path[0]);
}
// Topic for MessagesView selector.
const topicName = hash.path[1];
if (Tinode.topicType(topicName) && topicName != this.state.topicSelected) {
const newState = {
topicSelected: topicName
};
const acs = this.tinode.getTopicAccessMode(topicName);
if (acs) {
newState.topicSelectedAcs = acs;
}
this.setState(newState);
let topicName = hash.path[1] || null;
if (topicName != this.state.topicSelected) {
if (!Tinode.topicType(topicName)) {
// Clear invalid topic name.
topicName = null;
}
const newState = {
topicSelected: topicName
};
const acs = this.tinode.getTopicAccessMode(topicName);
if (acs) {
newState.topicSelectedAcs = acs;
}
this.setState(newState);
}
} else {
// Empty hashpath
this.setState({sidePanelSelected: ''});
this.setState({sidePanelSelected: '', topicSelected: null});
}
// Save validation credentials, if available.
@@ -463,13 +474,11 @@ class TinodeWeb extends React.Component {
handlePersistenceChange(persist) {
if (persist) {
this.tinode.initStorage().then(() => {
console.log("storage initialized");
LocalStorageUtil.setObject('keep-logged-in', true);
this.setState({persist: true});
});
} else {
this.tinode.clearStorage().then(() => {
console.log("storage cleared");
LocalStorageUtil.setObject('keep-logged-in', false);
this.setState({persist: false});
});
@@ -709,7 +718,7 @@ class TinodeWeb extends React.Component {
} else {
// TODO(gene): handle other types of notifications:
// * ua -- user agent changes (maybe display a pictogram for mobile/desktop).
console.log("Unsupported (yet) presence update:" + what + " in: " + cont.topic);
console.info("Unsupported (yet) presence update:" + what + " in: " + cont.topic);
}
}
@@ -766,6 +775,12 @@ class TinodeWeb extends React.Component {
newState.topicSelectedAcs = c.acs;
}
});
const past = new Date(0);
newState.chatList.sort((a, b) => {
return (a.touched || past).getTime() - (b.touched || past).getTime();
});
// Merge search results and chat list.
newState.searchableContacts = TinodeWeb.prepareSearchableContacts(newState.chatList, this.state.searchResults);
this.setState(newState);
@@ -932,7 +947,7 @@ class TinodeWeb extends React.Component {
});
break;
default:
console.log("Unknown invitation action", '"' + action + '""');
console.warn("Unknown invitation action", '"' + action + '""');
}
if (response != null) {
@@ -1064,7 +1079,7 @@ class TinodeWeb extends React.Component {
}).then(() => {
this.requestPushToken();
}).catch((err) => {
console.log("Failed to get notification permission.", err);
console.warn("Failed to get notification permission.", err);
this.handleError(err.message, 'err');
this.setState({desktopAlerts: false, firebaseToken: null});
LocalStorageUtil.updateObject('settings', {desktopAlerts: false});
@@ -1077,7 +1092,7 @@ class TinodeWeb extends React.Component {
}
} else if (this.state.firebaseToken && this.fbPush) {
this.fbPush.deleteToken(this.state.firebaseToken).catch((err) => {
console.log("Unable to delete token.", err);
console.warn("Unable to delete token.", err);
}).finally(() => {
LocalStorageUtil.updateObject('settings', {desktopAlerts: false});
localStorage.removeItem('firebase-token');
@@ -1106,7 +1121,7 @@ class TinodeWeb extends React.Component {
}
}).catch((err) => {
this.handleError(err.message, 'err');
console.log("Failed to retrieve firebase token", err);
console.warn("Failed to retrieve firebase token", err);
});
}
@@ -1253,6 +1268,8 @@ class TinodeWeb extends React.Component {
this.fbPush.deleteToken(this.state.firebaseToken)
}
clearInterval(this.reconnectCountdown);
if (this.tinode) {
this.tinode.clearStorage();
this.tinode.onDisconnect = undefined;
@@ -1274,6 +1291,21 @@ class TinodeWeb extends React.Component {
});
}
handleDeleteTopicRequest(topicName) {
const topic = this.tinode.getTopic(topicName);
if (!topic) {
return;
}
// Request to hard-delete topic.
topic.delTopic(true).then((ctrl) => {
// Hide MessagesView and InfoView panels.
HashNavigation.navigateTo(HashNavigation.setUrlTopic(window.location.hash, ''));
}).catch((err) => {
this.handleError(err.message, 'err');
});
}
handleDeleteMessagesRequest(topicName) {
const topic = this.tinode.getTopic(topicName);
if (!topic) {
@@ -1371,7 +1403,7 @@ class TinodeWeb extends React.Component {
subscribed && deleter ? 'messages_clear_hard' : null,
muted ? (blocked ? null : 'topic_unmute') : 'topic_mute',
self_blocked ? 'topic_unblock' : 'topic_block',
!archived ? 'topic_archive' : null,
archived ? 'topic_restore' : 'topic_archive',
'topic_delete'
];
}
@@ -1464,7 +1496,7 @@ class TinodeWeb extends React.Component {
handlePasswordResetRequest(method, value) {
// If already connected, connnect() will return a resolved promise.
this.tinode.connect()
return this.tinode.connect()
.then(() => {
return this.tinode.requestResetAuthSecret('basic', method, value);
})
@@ -1639,6 +1671,7 @@ class TinodeWeb extends React.Component {
onShowAlert={this.handleShowAlert}
onChangePermissions={this.handleChangePermissions}
onMemberUpdateRequest={this.handleMemberUpdateRequest}
onDeleteTopic={this.handleDeleteTopicRequest}
onDeleteMessages={this.handleDeleteMessagesRequest}
onLeaveTopic={this.handleLeaveUnsubRequest}
onBlockTopic={this.handleBlockTopicRequest}
+4 -2
View File
@@ -92,8 +92,10 @@ export default class ChatMessage extends React.Component {
attachments.push(<Attachment
tinode={this.props.tinode}
downloadUrl={Drafty.getDownloadUrl(att)}
filename={att.name} uploading={Drafty.isProcessing(att)}
mimetype={att.mime} size={Drafty.getEntitySize(att)}
filename={att.name}
uploading={Drafty.isProcessing(att)}
mimetype={att.mime}
size={Drafty.getEntitySize(att)}
progress={this.state.progress}
onCancelUpload={this.handleCancelUpload}
onError={this.props.onError}
+22 -1
View File
@@ -54,7 +54,7 @@ const messages = defineMessages({
},
topic_delete_warning: {
id: 'topic_delete_warning',
defaultMessage: 'Are you sure you want to delete this conversation?',
defaultMessage: 'Are you sure you want to delete this conversation? It cannot be undone.',
description: 'Alert warning when deleting entire topic'
},
delete_messages_warning: {
@@ -88,6 +88,11 @@ const messages = defineMessages({
defaultMessage: 'Archive',
description: 'Move topic from the list of active chats to archive'
},
unarchive: {
id: 'menu_item_restore_topic',
defaultMessage: 'Restore',
description: 'Restore topic from archive'
},
edit_permissions: {
id: 'menu_item_edit_permissions',
defaultMessage: 'Edit permissions',
@@ -241,6 +246,22 @@ class ContextMenu extends React.Component {
});
}
},
'topic_restore': {
id: 'topic_restore',
title: formatMessage(messages.unarchive),
handler: (params, errorHandler) => {
const topic = this.props.tinode.getTopic(params.topicName);
if (!topic) {
console.log("Topic not found: ", params.topicName);
return;
}
return topic.archive(false).catch((err) => {
if (errorHandler) {
errorHandler(err.message, 'err');
}
});
}
},
// menu_item_edit_permissions is defined elsewhere.
'permissions': {
id: 'permissions',
+211 -104
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+329 -105
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,3 +1,3 @@
// This is a generated file. Don't edit.
const PACKAGE_VERSION = "0.17.2";
const PACKAGE_VERSION = "0.17.10";