diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 4f08617ae..d5bd06d78 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -73,6 +73,7 @@ "attemptingReconnection": "Attempting reconnect in $reconnect_duration_in_seconds$ seconds", "submitDebugLog": "Debug log", "debugLog": "Debug Log", + "showDebugLog": "Show Debug Log", "goToReleaseNotes": "Go to Release Notes", "goToSupportPage": "Go to Support Page", "menuReportIssue": "Report an Issue", @@ -421,6 +422,7 @@ "unpinConversation": "Unpin Conversation", "pinConversationLimitTitle": "Pinned conversations limit", "pinConversationLimitToastDescription": "You can only pin $number$ conversations", + "showUserDetails": "Show User Details", "latestUnreadIsAbove": "First unread message is above", "sendRecoveryPhraseTitle": "Sending Recovery Phrase", "sendRecoveryPhraseMessage": "You are attempting to send your recovery phrase which can be used to access your account. Are you sure you want to send this message?", diff --git a/debug_log_preload.js b/debug_log_preload.js index 8121af5d6..dbd23deff 100644 --- a/debug_log_preload.js +++ b/debug_log_preload.js @@ -22,5 +22,9 @@ window.getNodeVersion = () => config.node_version; window.getEnvironment = () => config.environment; require('./js/logging'); +const os = require('os'); + +window.getOSRelease = () => `${os.type()} ${os.release} ${os.platform()}`; +window.getCommitHash = () => config.commitHash; window.closeDebugLog = () => ipcRenderer.send('close-debug-log'); diff --git a/js/views/debug_log_view.js b/js/views/debug_log_view.js index 1ed681538..4ef324362 100644 --- a/js/views/debug_log_view.js +++ b/js/views/debug_log_view.js @@ -26,9 +26,15 @@ this.render(); this.$('textarea').val(i18n('loading')); + const operatingSystemInfo = `Operating System: ${window.getOSRelease()}`; + + const commitHashInfo = window.getCommitHash() ? `Commit Hash: ${window.getCommitHash()}` : ''; + // eslint-disable-next-line more/no-then window.log.fetch().then(text => { - this.$('textarea').val(text); + const debugLogWithSystemInfo = operatingSystemInfo + commitHashInfo + text; + + this.$('textarea').val(debugLogWithSystemInfo); }); }, events: { diff --git a/package.json b/package.json index 14fe768fd..4e6381299 100644 --- a/package.json +++ b/package.json @@ -336,6 +336,8 @@ "node_modules/socks/build/common/*.js", "node_modules/socks/build/client/*.js", "node_modules/smart-buffer/build/*.js", + "node_modules/react-draggable/build/cjs/*.js", + "node_modules/react-draggable/build/cjs/utils/*.js", "!node_modules/@journeyapps/sqlcipher/deps/*", "!node_modules/@journeyapps/sqlcipher/build/*", "!node_modules/@journeyapps/sqlcipher/lib/binding/node-*", diff --git a/ts/components/ConversationListItem.tsx b/ts/components/ConversationListItem.tsx index ec667eb4f..e94606cef 100644 --- a/ts/components/ConversationListItem.tsx +++ b/ts/components/ConversationListItem.tsx @@ -356,6 +356,9 @@ const ConversationListItem = (props: Props) => { left={!!left} type={type} currentNotificationSetting={currentNotificationSetting || 'all'} + avatarPath={avatarPath || null} + name={name} + profileName={profileName} /> diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index f321a6a6a..d8ec8287e 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -360,6 +360,9 @@ export const ConversationHeaderWithDetails = () => { left={left} hasNickname={hasNickname} currentNotificationSetting={currentNotificationSetting} + avatarPath={avatarPath} + name={name} + profileName={profileName} /> diff --git a/ts/components/conversation/message/OutgoingMessageStatus.tsx b/ts/components/conversation/message/OutgoingMessageStatus.tsx index 2a6869b75..0701ce131 100644 --- a/ts/components/conversation/message/OutgoingMessageStatus.tsx +++ b/ts/components/conversation/message/OutgoingMessageStatus.tsx @@ -1,3 +1,4 @@ +import { ipcRenderer } from 'electron'; import React from 'react'; import styled from 'styled-components'; import { MessageDeliveryStatus } from '../../../models/messageType'; @@ -40,8 +41,12 @@ const MessageStatusRead = () => { }; const MessageStatusError = () => { + const showDebugLog = () => { + ipcRenderer.send('show-debug-log'); + }; + return ( - + ); diff --git a/ts/components/dialog/UserDetailsDialog.tsx b/ts/components/dialog/UserDetailsDialog.tsx index 10166436c..64622765f 100644 --- a/ts/components/dialog/UserDetailsDialog.tsx +++ b/ts/components/dialog/UserDetailsDialog.tsx @@ -6,6 +6,7 @@ import useCopyToClipboard from 'react-use/lib/useCopyToClipboard'; import useKey from 'react-use/lib/useKey'; import { ConversationTypeEnum } from '../../models/conversation'; import { getConversationController } from '../../session/conversations'; +import { ToastUtils } from '../../session/utils'; import { openConversationWithMessages } from '../../state/ducks/conversations'; import { updateUserDetailsModal } from '../../state/ducks/modalDialog'; import { Avatar, AvatarSize } from '../Avatar'; @@ -77,6 +78,7 @@ export const UserDetailsDialog = (props: Props) => { buttonColor={SessionButtonColor.Primary} onClick={() => { copyToClipboard(props.conversationId); + ToastUtils.pushCopiedToClipBoard(); }} /> { @@ -50,7 +54,11 @@ const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => { left, hasNickname, currentNotificationSetting, + name, + profileName, + avatarPath, } = props; + const userName = name || profileName || conversationId; return ( @@ -75,9 +83,9 @@ const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => { {getRemoveModeratorsMenuItem(weAreAdmin, isPublic, isKickedFromGroup, conversationId)} {getUpdateGroupNameMenuItem(weAreAdmin, isKickedFromGroup, left, conversationId)} {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)} - {/* TODO: add delete group */} {getInviteContactMenuItem(isGroup, isPublic, conversationId)} {getDeleteContactMenuItem(isGroup, isPublic, left, isKickedFromGroup, conversationId)} + {getShowUserDetailsMenuItem(isPrivate, conversationId, avatarPath, userName)} ); }; diff --git a/ts/components/session/menu/ConversationListItemContextMenu.tsx b/ts/components/session/menu/ConversationListItemContextMenu.tsx index 0cf88923c..0dc4d65bd 100644 --- a/ts/components/session/menu/ConversationListItemContextMenu.tsx +++ b/ts/components/session/menu/ConversationListItemContextMenu.tsx @@ -18,6 +18,7 @@ import { getMarkAllReadMenuItem, getNotificationForConvoMenuItem, getPinConversationMenuItem, + getShowUserDetailsMenuItem, } from './Menu'; export type PropsContextConversationItem = { @@ -33,6 +34,9 @@ export type PropsContextConversationItem = { left: boolean; theme?: any; currentNotificationSetting: ConversationNotificationSettingType; + name: string | undefined; + profileName: string | undefined; + avatarPath: string | null; }; const ConversationListItemContextMenu = (props: PropsContextConversationItem) => { @@ -48,9 +52,14 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) => isKickedFromGroup, currentNotificationSetting, isPrivate, + name, + profileName, + avatarPath, } = props; const isGroup = type === 'group'; + const userName = name || profileName || conversationId; + return ( {getNotificationForConvoMenuItem({ @@ -71,6 +80,7 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) => {getInviteContactMenuItem(isGroup, isPublic, conversationId)} {getDeleteContactMenuItem(isGroup, isPublic, left, isKickedFromGroup, conversationId)} {getLeaveGroupMenuItem(isKickedFromGroup, left, isGroup, isPublic, conversationId)} + {getShowUserDetailsMenuItem(isPrivate, conversationId, avatarPath, userName)} ); }; diff --git a/ts/components/session/menu/Menu.tsx b/ts/components/session/menu/Menu.tsx index 6e9b569c5..b7fb86cd1 100644 --- a/ts/components/session/menu/Menu.tsx +++ b/ts/components/session/menu/Menu.tsx @@ -12,7 +12,11 @@ import { ConversationNotificationSettingType, } from '../../../models/conversation'; import { useDispatch, useSelector } from 'react-redux'; -import { changeNickNameModal, updateConfirmModal } from '../../../state/ducks/modalDialog'; +import { + changeNickNameModal, + updateConfirmModal, + updateUserDetailsModal, +} from '../../../state/ducks/modalDialog'; import { SectionType } from '../../../state/ducks/section'; import { getConversationController } from '../../../session/conversations'; import { @@ -242,6 +246,35 @@ export function getLeaveGroupMenuItem( return null; } +export function getShowUserDetailsMenuItem( + isPrivate: boolean | undefined, + conversationId: string, + avatarPath: string | null, + userName: string +): JSX.Element | null { + const dispatch = useDispatch(); + + if (isPrivate) { + return ( + { + dispatch( + updateUserDetailsModal({ + conversationId: conversationId, + userName, + authorAvatarPath: avatarPath, + }) + ); + }} + > + {window.i18n('showUserDetails')} + + ); + } + + return null; +} + export function getUpdateGroupNameMenuItem( isAdmin: boolean | undefined, isKickedFromGroup: boolean | undefined, diff --git a/ts/components/session/settings/SessionSettings.tsx b/ts/components/session/settings/SessionSettings.tsx index 6db6900ed..b5141c089 100644 --- a/ts/components/session/settings/SessionSettings.tsx +++ b/ts/components/session/settings/SessionSettings.tsx @@ -4,9 +4,8 @@ import { SettingsHeader } from './SessionSettingsHeader'; import { SessionSettingListItem } from './SessionSettingListItem'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton'; import { PasswordUtil } from '../../../util'; -import { ConversationLookupType } from '../../../state/ducks/conversations'; import { StateType } from '../../../state/reducer'; -import { getConversationLookup } from '../../../state/selectors/conversations'; +import { getBlockedPubkeys } from '../../../state/selectors/conversations'; import { connect } from 'react-redux'; import { getPasswordHash } from '../../../../ts/data/data'; import { shell } from 'electron'; @@ -31,9 +30,7 @@ export function getCallMediaPermissionsSettings() { export interface SettingsViewProps { category: SessionSettingCategory; - // pass the conversation as props, so our render is called everytime they change. - // we have to do this to make the list refresh on unblock() - conversations?: ConversationLookupType; + blockedNumbers: Array; } interface State { @@ -121,13 +118,13 @@ class SettingsViewInner extends React.Component { /* tslint:disable-next-line:max-func-body-length */ public renderSettingInCategory() { - const { category } = this.props; + const { category, blockedNumbers } = this.props; let settings: Array; if (category === SessionSettingCategory.Blocked) { // special case for blocked user - settings = getBlockedUserSettings(); + settings = getBlockedUserSettings(blockedNumbers); } else { // Grab initial values from database on startup // ID corresponds to installGetter parameters in preload.js @@ -151,8 +148,6 @@ class SettingsViewInner extends React.Component { ? storedSetting : setting.content && setting.content.defaultValue; - if (setting.id.startsWith('call-media')) console.warn('storedSetting call: ', value); - const sliderFn = setting.type === SessionSettingType.Slider ? (settingValue: any) => window.setSettingValue(setting.id, settingValue) @@ -298,7 +293,7 @@ class SettingsViewInner extends React.Component { const mapStateToProps = (state: StateType) => { return { - conversations: getConversationLookup(state), + blockedNumbers: getBlockedPubkeys(state), }; }; diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index a1847bd8e..794edaae0 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -227,7 +227,7 @@ function updateReadStatus(message: MessageModel, conversation: ConversationModel } } -function handleSyncedReceipts(message: MessageModel, conversation: ConversationModel) { +async function handleSyncedReceipts(message: MessageModel, conversation: ConversationModel) { const readReceipts = window.Whisper.ReadReceipts.forMessage(conversation, message); if (readReceipts.length) { const readBy = readReceipts.map((receipt: any) => receipt.get('reader')); @@ -245,6 +245,12 @@ function handleSyncedReceipts(message: MessageModel, conversation: ConversationM } message.set({ recipients }); + + // If the newly received message is from us, we assume that we've seen the messages up until that point + const sentTimestamp = message.get('sent_at'); + if (sentTimestamp) { + await conversation.markRead(sentTimestamp); + } } async function handleRegularMessage( @@ -312,7 +318,7 @@ async function handleRegularMessage( } if (type === 'outgoing') { - handleSyncedReceipts(message, conversation); + await handleSyncedReceipts(message, conversation); } const conversationActiveAt = conversation.get('active_at');