refactor most of the components to outside of their Session folder (#2072)

* refactor most of the components to outside of their Session folder

* finish moving overlay and memberListItem to react hook

* fix bug with kicked member len >2 not being displayed

also sort admins first in UpdateGroupMembers dialog

* fix admin leaving text of groupNotification

* add a useFocusMount hook to focus input fields on mount

* make click avatar convo item open only user dialog

* cleanup config default.json

* make sure to use convoController to build sync message

* disable showing pubkey on opengroups

* add a pause on audio playback

Fixes #2079
This commit is contained in:
Audric Ackermann 2021-12-14 15:15:12 +11:00 committed by GitHub
parent 95e40c9509
commit 28c7445dce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
230 changed files with 2276 additions and 2421 deletions

View File

@ -151,14 +151,6 @@ module.exports = grunt => {
grunt.file.write(configPath, `${JSON.stringify(localConfig)}\n`);
}
grunt.registerTask('getExpireTime', () => {
grunt.task.requires('gitinfo');
const gitinfo = grunt.config.get('gitinfo');
const committed = gitinfo.local.branch.current.lastCommitTime;
const time = Date.parse(committed) + 1000 * 60 * 60 * 24 * 90;
updateLocalConfig({ buildExpiration: time });
});
grunt.registerTask('getCommitHash', () => {
grunt.task.requires('gitinfo');
const gitinfo = grunt.config.get('gitinfo');
@ -167,7 +159,7 @@ module.exports = grunt => {
});
grunt.registerTask('dev', ['default', 'watch']);
grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
grunt.registerTask('date', ['gitinfo']);
grunt.registerTask('default', [
'exec:build-protobuf',
'exec:transpile',

View File

@ -327,7 +327,7 @@
"addAsModerator": "Add as Moderator",
"removeFromModerators": "Remove From Moderators",
"add": "Add",
"addingContacts": "Adding contacts to",
"addingContacts": "Adding contacts to $name$",
"noContactsToAdd": "No contacts to add",
"noMembersInThisGroup": "No other members in this group",
"noModeratorsToRemove": "no moderators to remove",

View File

@ -16,8 +16,6 @@
],
"updatesEnabled": false,
"openDevTools": false,
"buildExpiration": 0,
"commitHash": "",
"import": false,
"serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"
"import": false
}

View File

@ -8,12 +8,14 @@ const OS = require('../../ts/OS');
const Settings = require('./settings');
const Util = require('../../ts/util');
const LinkPreviews = require('./link_previews');
const { Message } = require('../../ts/components/conversation/Message');
const { Message } = require('../../ts/components/conversation/message/message-item/Message');
// Components
const { SessionRegistrationView } = require('../../ts/components/session/SessionRegistrationView');
const {
SessionRegistrationView,
} = require('../../ts/components/registration/SessionRegistrationView');
const { SessionInboxView } = require('../../ts/components/session/SessionInboxView');
const { SessionInboxView } = require('../../ts/components/SessionInboxView');
// Types
const AttachmentType = require('./types/attachment');

View File

@ -154,7 +154,6 @@ function prepareURL(pathSegments, moreKeys) {
name: packageJson.productName,
locale: locale.name,
version: app.getVersion(),
buildExpiration: config.get('buildExpiration'),
commitHash: config.get('commitHash'),
serverUrl: config.get('serverUrl'),
localUrl: config.get('localUrl'),
@ -167,7 +166,6 @@ function prepareURL(pathSegments, moreKeys) {
appInstance: process.env.NODE_APP_INSTANCE,
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
contentProxyUrl: config.contentProxyUrl,
serverTrustRoot: config.get('serverTrustRoot'),
appStartInitialSpellcheckSetting,
...moreKeys,
},

View File

@ -21,7 +21,7 @@ window.getEnvironment = () => config.environment;
window.getVersion = () => config.version;
window.getAppInstance = () => config.appInstance;
const { SessionPasswordPrompt } = require('./ts/components/session/SessionPasswordPrompt');
const { SessionPasswordPrompt } = require('./ts/components/SessionPasswordPrompt');
window.Signal = {
Components: {

View File

@ -30,11 +30,9 @@ window.getEnvironment = () => config.environment;
window.getAppInstance = () => config.appInstance;
window.getVersion = () => config.version;
window.isDev = () => config.environment === 'development';
window.getExpiration = () => config.buildExpiration;
window.getCommitHash = () => config.commitHash;
window.getNodeVersion = () => config.node_version;
window.getHostName = () => config.hostname;
window.getServerTrustRoot = () => config.serverTrustRoot;
window.isBehindProxy = () => Boolean(config.proxyUrl);
window.lokiFeatureFlags = {
@ -214,7 +212,7 @@ window.Signal = Signal.setup({
logger: window.log,
});
window.getSwarmPollingInstance = require('./ts/session/snode_api/').getSwarmPollingInstance;
window.getSwarmPollingInstance = require('./ts/session/apis/snode_api/').getSwarmPollingInstance;
const WorkerInterface = require('./js/modules/util_worker_interface');

View File

@ -8,6 +8,7 @@ $borderAvatarColor: unquote(
vertical-align: middle;
display: inline-block;
border-radius: 50%;
flex-shrink: 0;
img {
object-fit: cover;
@ -16,21 +17,6 @@ $borderAvatarColor: unquote(
}
}
.module-avatar__label {
width: 100%;
text-align: center;
font-weight: 300;
text-transform: uppercase;
color: $color-white;
}
.module-avatar__icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.module-avatar__icon--crown-wrapper {
position: absolute;
bottom: 0%;
@ -129,7 +115,8 @@ $borderAvatarColor: unquote(
.module-avatar-clickable {
transition: $session-transition-duration;
cursor: pointer;
&:hover {
opacity: $session-subtle-factor;
filter: grayscale(0.7);
}
}

View File

@ -26,6 +26,12 @@
padding: 1.1em;
}
.session-modal {
.contact-selection-list {
width: 100%;
}
}
.create-group-dialog,
.add-moderators-dialog,
.remove-moderators-dialog,

View File

@ -494,6 +494,7 @@ label {
.group-member-list__selection {
overflow-y: auto;
width: 100%;
}
&__centered {

View File

@ -158,10 +158,8 @@
z-index: 1;
.session-icon-button {
// & > .session-icon-button {
margin-right: $session-margin-sm;
}
.session-icon-button {
display: flex;
justify-content: center;
align-items: center;
@ -169,7 +167,7 @@
&:hover {
opacity: 1;
transform: scale(0.93);
filter: brightness(0.9);
transition: $session-transition-duration;
}

View File

@ -5,9 +5,9 @@ import * as GoogleChrome from '../util/GoogleChrome';
import { AttachmentType } from '../types/Attachment';
import { SessionInput } from './session/SessionInput';
import { SessionButton, SessionButtonColor, SessionButtonType } from './session/SessionButton';
import autoBind from 'auto-bind';
import { SessionButton, SessionButtonColor, SessionButtonType } from './basic/SessionButton';
import { SessionInput } from './basic/SessionInput';
interface Props {
attachment: AttachmentType;

View File

@ -1,6 +1,5 @@
import React from 'react';
import { ContactType } from './session/SessionMemberListItem';
import { ToastUtils } from '../session/utils';
import { createClosedGroup as createClosedGroupV2 } from '../receiver/closedGroups';
import { VALIDATION } from '../session/constants';
@ -38,7 +37,7 @@ export class MessageView extends React.Component {
*/
async function createClosedGroup(
groupName: string,
groupMembers: Array<ContactType>
groupMemberIds: Array<string>
): Promise<boolean> {
// Validate groupName and groupMembers length
if (groupName.length === 0) {
@ -53,16 +52,14 @@ async function createClosedGroup(
// >= because we add ourself as a member AFTER this. so a 10 group is already invalid as it will be 11 with ourself
// the same is valid with groups count < 1
if (groupMembers.length < 1) {
if (groupMemberIds.length < 1) {
ToastUtils.pushToastError('pickClosedGroupMember', window.i18n('pickClosedGroupMember'));
return false;
} else if (groupMembers.length >= VALIDATION.CLOSED_GROUP_SIZE_LIMIT) {
} else if (groupMemberIds.length >= VALIDATION.CLOSED_GROUP_SIZE_LIMIT) {
ToastUtils.pushToastError('closedGroupMaxSize', window.i18n('closedGroupMaxSize'));
return false;
}
const groupMemberIds = groupMembers.map(m => m.id);
await createClosedGroupV2(groupName, groupMemberIds);
return true;

View File

@ -0,0 +1,43 @@
import React from 'react';
import classNames from 'classnames';
import { Avatar, AvatarSize } from './avatar/Avatar';
import { Constants } from '../session';
import { SessionIcon } from './icon';
import { useConversationUsernameOrShorten } from '../hooks/useParamSelector';
const AvatarItem = (props: { memberPubkey: string }) => {
return <Avatar size={AvatarSize.XS} pubkey={props.memberPubkey} />;
};
export const MemberListItem = (props: {
pubkey: string;
isSelected: boolean;
// this bool is used to make a zombie appear with less opacity than a normal member
isZombie?: boolean;
onSelect?: (pubkey: string) => void;
onUnselect?: (pubkey: string) => void;
}) => {
const { isSelected, pubkey, isZombie, onSelect, onUnselect } = props;
const memberName = useConversationUsernameOrShorten(pubkey);
return (
<div
className={classNames('session-member-item', isSelected && 'selected', isZombie && 'zombie')}
onClick={() => {
isSelected ? onUnselect?.(pubkey) : onSelect?.(pubkey);
}}
role="button"
>
<div className="session-member-item__info">
<span className="session-member-item__avatar">
<AvatarItem memberPubkey={pubkey} />
</span>
<span className="session-member-item__name">{memberName}</span>
</div>
<span className={classNames('session-member-item__checkmark', isSelected && 'selected')}>
<SessionIcon iconType="check" iconSize="medium" iconColor={Constants.UI.COLORS.GREEN} />
</span>
</div>
);
};

View File

@ -1,32 +1,32 @@
import React from 'react';
import { Provider } from 'react-redux';
import { bindActionCreators } from 'redux';
import { getConversationController } from '../../session/conversations';
import { UserUtils } from '../../session/utils';
import { createStore } from '../../state/createStore';
import {
actions as conversationActions,
getEmptyConversationState,
openConversationWithMessages,
} from '../../state/ducks/conversations';
import { initialDefaultRoomState } from '../../state/ducks/defaultRooms';
import { initialModalState } from '../../state/ducks/modalDialog';
import { initialOnionPathState } from '../../state/ducks/onion';
import { initialSearchState } from '../../state/ducks/search';
import { initialSectionState } from '../../state/ducks/section';
import { initialThemeState } from '../../state/ducks/theme';
import { initialUserConfigState } from '../../state/ducks/userConfig';
import { StateType } from '../../state/reducer';
import { makeLookup } from '../../util';
import { LeftPane } from '../LeftPane';
import { SessionMainPanel } from '../SessionMainPanel';
import { LeftPane } from './leftpane/LeftPane';
// tslint:disable-next-line: no-submodule-imports
import { PersistGate } from 'redux-persist/integration/react';
import { persistStore } from 'redux-persist';
import { TimerOptionsArray } from '../../state/ducks/timerOptions';
import { getEmptyStagedAttachmentsState } from '../../state/ducks/stagedAttachments';
import { initialCallState } from '../../state/ducks/call';
import { getConversationController } from '../session/conversations';
import { UserUtils } from '../session/utils';
import { initialCallState } from '../state/ducks/call';
import {
actions as conversationActions,
getEmptyConversationState,
openConversationWithMessages,
} from '../state/ducks/conversations';
import { initialDefaultRoomState } from '../state/ducks/defaultRooms';
import { initialModalState } from '../state/ducks/modalDialog';
import { initialOnionPathState } from '../state/ducks/onion';
import { initialSearchState } from '../state/ducks/search';
import { initialSectionState } from '../state/ducks/section';
import { getEmptyStagedAttachmentsState } from '../state/ducks/stagedAttachments';
import { initialThemeState } from '../state/ducks/theme';
import { TimerOptionsArray } from '../state/ducks/timerOptions';
import { initialUserConfigState } from '../state/ducks/userConfig';
import { StateType } from '../state/reducer';
import { makeLookup } from '../util';
import { SessionMainPanel } from './SessionMainPanel';
import { createStore } from '../state/createStore';
// Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363

View File

@ -4,7 +4,7 @@ import { useAppIsFocused } from '../hooks/useAppFocused';
import { getFocusedSettingsSection } from '../state/selectors/section';
import { SmartSessionConversation } from '../state/smart/SessionConversation';
import { SessionSettingsView } from './session/settings/SessionSettings';
import { SessionSettingsView } from './settings/SessionSettings';
const FilteredSettingsView = SessionSettingsView as any;

View File

@ -2,10 +2,10 @@ import React from 'react';
import classNames from 'classnames';
import { SessionIcon } from './icon';
import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton';
import { Constants } from '../../session';
import { withTheme } from 'styled-components';
import autoBind from 'auto-bind';
import { SessionButton, SessionButtonColor, SessionButtonType } from './basic/SessionButton';
import { Constants } from '../session';
interface State {
error: string;

View File

@ -1,7 +1,7 @@
import React from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { getShowScrollButton } from '../../state/selectors/conversations';
import { getShowScrollButton } from '../state/selectors/conversations';
import { SessionIconButton } from './icon';

View File

@ -1,6 +1,6 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { getConversationsCount } from '../../state/selectors/conversations';
import { getConversationsCount } from '../state/selectors/conversations';
import { SessionIconButton } from './icon';
type Props = {

View File

@ -2,10 +2,10 @@ import React, { useEffect, useRef } from 'react';
import classNames from 'classnames';
import { SessionIconButton } from './icon/';
import { SessionButton } from './SessionButton';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { SessionButton } from './basic/SessionButton';
export type SessionWrapperModalType = {
title?: string;

View File

@ -1,15 +1,15 @@
import React, { useState } from 'react';
import classNames from 'classnames';
import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import _ from 'underscore';
import {
useAvatarPath,
useConversationUsername,
useIsClosedGroup,
} from '../hooks/useParamSelector';
} from '../../hooks/useParamSelector';
import { AvatarPlaceHolder } from './AvatarPlaceHolder/AvatarPlaceHolder';
import { ClosedGroupAvatar } from './AvatarPlaceHolder/ClosedGroupAvatar';
import { useDisableDrag } from '../hooks/useDisableDrag';
import { useDisableDrag } from '../../hooks/useDisableDrag';
export enum AvatarSize {
XS = 28,
@ -111,10 +111,12 @@ const AvatarInner = (props: Props) => {
hasImage ? 'module-avatar--with-image' : 'module-avatar--no-image',
isClickable && 'module-avatar-clickable'
)}
onClick={e => {
e.stopPropagation();
e.preventDefault();
props.onAvatarClick?.();
onMouseDown={e => {
if (props.onAvatarClick) {
e.stopPropagation();
e.preventDefault();
props.onAvatarClick?.();
}
}}
role="button"
data-testid={dataTestId}

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
import { getInitials } from '../../util/getInitials';
import { getInitials } from '../../../util/getInitials';
type Props = {
diameter: number;

View File

@ -1,5 +1,5 @@
import React from 'react';
import { useMembersAvatars } from '../../hooks/useMembersAvatars';
import { useMembersAvatars } from '../../../hooks/useMembersAvatars';
import { Avatar, AvatarSize } from '../Avatar';
type Props = {

View File

@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { SessionIcon, SessionIconType } from '../icon';
import { SessionIcon, SessionIconType } from './icon/';
import { SessionDropdownItem, SessionDropDownItemType } from './SessionDropdownItem';
// THIS IS DROPDOWN ACCORDIAN STYLE OPTIONS SELECTOR ELEMENT, NOT A CONTEXTMENU

View File

@ -1,7 +1,6 @@
import React from 'react';
import classNames from 'classnames';
import { SessionIcon, SessionIconType } from './icon/';
import { SessionIcon, SessionIconType } from '../icon';
export enum SessionDropDownItemType {
Default = 'default',

View File

@ -20,6 +20,7 @@ export const SessionHtmlRenderer: React.SFC<Props> = ({ tag = 'div', key, html,
return React.createElement(tag, {
key,
className,
// tslint:disable-next-line: react-no-dangerous-html
dangerouslySetInnerHTML: { __html: clean },
});
};

View File

@ -0,0 +1,54 @@
import React, { ChangeEvent, KeyboardEvent, useRef } from 'react';
import classNames from 'classnames';
import { useFocusMount } from '../../hooks/useFocusMount';
type Props = {
placeholder?: string;
value?: string;
text?: string;
editable?: boolean;
onChange?: (value: string) => void;
onPressEnter?: any;
maxLength?: number;
isGroup?: boolean;
};
export const SessionIdEditable = (props: Props) => {
const { placeholder, onPressEnter, onChange, editable, text, value, maxLength, isGroup } = props;
const inputRef = useRef(null);
useFocusMount(inputRef, editable);
function handleChange(e: ChangeEvent<HTMLTextAreaElement>) {
if (editable && onChange) {
const eventValue = e.target.value?.replace(/(\r\n|\n|\r)/gm, '');
onChange(eventValue);
}
}
function handleKeyDown(e: KeyboardEvent<HTMLTextAreaElement>) {
if (editable && e.key === 'Enter') {
e.preventDefault();
// tslint:disable-next-line: no-unused-expression
onPressEnter && onPressEnter();
}
}
return (
<div className={classNames('session-id-editable', !editable && 'session-id-editable-disabled')}>
<textarea
className={classNames(
isGroup ? 'group-id-editable-textarea' : 'session-id-editable-textarea'
)}
ref={inputRef}
placeholder={placeholder}
disabled={!editable}
spellCheck={false}
onKeyDown={handleKeyDown}
onChange={handleChange}
onBlur={handleChange}
value={value || text}
maxLength={maxLength}
/>
</div>
);
};

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import classNames from 'classnames';
import { SessionIconButton } from './icon';
import { SessionIconButton } from '../icon';
type Props = {
label?: string;

View File

@ -1,9 +1,9 @@
import React from 'react';
import { SessionIcon, SessionIconType } from './icon/';
import { Flex } from '../basic/Flex';
import styled from 'styled-components';
import { noop } from 'lodash';
import { SessionIcon, SessionIconType } from '../icon';
export enum SessionToastType {
Info = 'info',

View File

@ -1,11 +1,11 @@
import { SessionIconButton } from '../icon';
import { animation, contextMenu, Item, Menu } from 'react-contexify';
import { InputItem } from '../../../session/utils/calling/CallManager';
import { setFullScreenCall } from '../../../state/ducks/call';
import { CallManager, ToastUtils } from '../../../session/utils';
import { InputItem } from '../../session/utils/calling/CallManager';
import { setFullScreenCall } from '../../state/ducks/call';
import { CallManager, ToastUtils } from '../../session/utils';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getHasOngoingCallWithPubkey } from '../../../state/selectors/call';
import { getHasOngoingCallWithPubkey } from '../../state/selectors/call';
import { DropDownAndToggleButton } from '../icon/DropDownAndToggleButton';
import styled from 'styled-components';

View File

@ -3,12 +3,12 @@ import { useDispatch, useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import styled from 'styled-components';
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener';
import { setFullScreenCall } from '../../../state/ducks/call';
import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
import { setFullScreenCall } from '../../state/ducks/call';
import {
getCallIsInFullScreen,
getHasOngoingCallWithFocusedConvo,
} from '../../../state/selectors/call';
} from '../../state/selectors/call';
import { CallWindowControls } from './CallButtons';
import { StyledVideoElement } from './DraggableCallContainer';

View File

@ -4,11 +4,11 @@ import Draggable, { DraggableData, DraggableEvent } from 'react-draggable';
import styled from 'styled-components';
import _ from 'underscore';
import { getSelectedConversationKey } from '../../../state/selectors/conversations';
import { getHasOngoingCall, getHasOngoingCallWith } from '../../../state/selectors/call';
import { openConversationWithMessages } from '../../../state/ducks/conversations';
import { Avatar, AvatarSize } from '../../Avatar';
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener';
import { getSelectedConversationKey } from '../../state/selectors/conversations';
import { getHasOngoingCall, getHasOngoingCallWith } from '../../state/selectors/call';
import { openConversationWithMessages } from '../../state/ducks/conversations';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
import { VideoLoadingSpinner } from './InConversationCallContainer';
export const DraggableCallWindow = styled.div`

View File

@ -3,7 +3,7 @@ import React, { useRef, useState } from 'react';
import styled from 'styled-components';
import _ from 'underscore';
import { CallManager, UserUtils } from '../../../session/utils';
import { CallManager, UserUtils } from '../../session/utils';
import {
getCallIsInFullScreen,
getCallWithFocusedConvoIsOffering,
@ -11,18 +11,18 @@ import {
getCallWithFocusedConvosIsConnecting,
getHasOngoingCallWithFocusedConvo,
getHasOngoingCallWithPubkey,
} from '../../../state/selectors/call';
} from '../../state/selectors/call';
import { StyledVideoElement } from './DraggableCallContainer';
import { Avatar, AvatarSize } from '../../Avatar';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener';
import { useModuloWithTripleDots } from '../../../hooks/useModuloWithTripleDots';
import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
import { useModuloWithTripleDots } from '../../hooks/useModuloWithTripleDots';
import { CallWindowControls } from './CallButtons';
import { SessionSpinner } from '../SessionSpinner';
import { DEVICE_DISABLED_DEVICE_ID } from '../../../session/utils/calling/CallManager';
import { DEVICE_DISABLED_DEVICE_ID } from '../../session/utils/calling/CallManager';
// tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval';
import moment from 'moment';
import { SessionSpinner } from '../basic/SessionSpinner';
const VideoContainer = styled.div`
height: 100%;

View File

@ -3,13 +3,13 @@ import { useSelector } from 'react-redux';
import styled from 'styled-components';
import _ from 'underscore';
import { useConversationUsername } from '../../../hooks/useParamSelector';
import { ed25519Str } from '../../../session/onions/onionPath';
import { CallManager } from '../../../session/utils';
import { callTimeoutMs } from '../../../session/utils/calling/CallManager';
import { getHasIncomingCall, getHasIncomingCallFrom } from '../../../state/selectors/call';
import { Avatar, AvatarSize } from '../../Avatar';
import { SessionButton, SessionButtonColor } from '../SessionButton';
import { useConversationUsername } from '../../hooks/useParamSelector';
import { ed25519Str } from '../../session/onions/onionPath';
import { CallManager } from '../../session/utils';
import { callTimeoutMs } from '../../session/utils/calling/CallManager';
import { getHasIncomingCall, getHasIncomingCallFrom } from '../../state/selectors/call';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal';
export const CallWindow = styled.div`

View File

@ -1,11 +1,7 @@
import React from 'react';
import { Avatar, AvatarSize } from '../Avatar';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SessionIconButton } from '../session/icon';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { MemoConversationHeaderMenu } from '../session/menu/ConversationHeaderMenu';
import { contextMenu } from 'react-contexify';
import styled from 'styled-components';
import { ConversationNotificationSettingType } from '../../models/conversation';
@ -38,6 +34,9 @@ import {
import { callRecipient } from '../../interactions/conversationInteractions';
import { getHasIncomingCall, getHasOngoingCall } from '../../state/selectors/call';
import { useConversationUsername } from '../../hooks/useParamSelector';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionIconButton } from '../icon';
import { MemoConversationHeaderMenu } from '../menu/ConversationHeaderMenu';
export interface TimerOption {
name: string;

View File

@ -3,7 +3,7 @@ import React, { useCallback, useState } from 'react';
import { getTimerBucketIcon } from '../../util/timer';
import { useInterval } from '../../hooks/useInterval';
import styled from 'styled-components';
import { SessionIcon } from '../session/icon';
import { SessionIcon } from '../icon/SessionIcon';
type Props = {
expirationLength: number;

View File

@ -8,8 +8,8 @@ import {
PropsForGroupUpdateType,
} from '../../state/ducks/conversations';
import _ from 'underscore';
import { ReadableMessage } from './ReadableMessage';
import { NotificationBubble } from './notification-bubble/NotificationBubble';
import { NotificationBubble } from './message/message-item/notification-bubble/NotificationBubble';
import { ReadableMessage } from './message/message-item/ReadableMessage';
// This component is used to display group updates in the conversation view.
// This is a not a "notification" as the name suggests, but a message inside the conversation

View File

@ -9,8 +9,8 @@ import {
getSortedMessagesOfSelectedConversation,
} from '../../state/selectors/conversations';
import { getAudioAutoplay } from '../../state/selectors/userConfig';
import { SessionIcon } from '../session/icon';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionIcon } from '../icon';
export const AudioPlayerWithEncryptedFile = (props: {
src: string;

View File

@ -12,7 +12,7 @@ import {
} from '../../types/Attachment';
import { Image } from './Image';
import { IsMessageVisibleContext } from './message/MessageContent';
import { IsMessageVisibleContext } from './message/message-content/MessageContent';
type Props = {
attachments: Array<AttachmentTypeWithPath>;

View File

@ -8,36 +8,38 @@ import {
StagedAttachmentType,
} from './composition/CompositionBox';
import { Constants } from '../../../session';
import _ from 'lodash';
import { AttachmentUtil, GoogleChrome } from '../../../util';
import { ConversationHeaderWithDetails } from '../../conversation/ConversationHeader';
import { SessionRightPanelWithDetails } from './SessionRightPanel';
import { SessionTheme } from '../../../state/ducks/SessionTheme';
import { SessionMessagesListContainer } from './SessionMessagesListContainer';
import { LightboxGallery, MediaItemType } from '../../LightboxGallery';
import { AttachmentTypeWithPath } from '../../../types/Attachment';
import { ToastUtils, UserUtils } from '../../../session/utils';
import * as MIME from '../../../types/MIME';
import { SessionMessagesListContainer } from './SessionMessagesListContainer';
import { SessionFileDropzone } from './SessionFileDropzone';
import autoBind from 'auto-bind';
import { InConversationCallContainer } from '../calling/InConversationCallContainer';
import { SplitViewContainer } from '../SplitViewContainer';
import { LightboxGallery, MediaItemType } from '../lightbox/LightboxGallery';
import { getPubkeysInPublicConversation } from '../../data/data';
import { Constants } from '../../session';
import { getConversationController } from '../../session/conversations';
import { ToastUtils, UserUtils } from '../../session/utils';
import {
quoteMessage,
ReduxConversationType,
resetSelectedMessageIds,
SortedMessageModelProps,
updateMentionsMembers,
} from '../../../state/ducks/conversations';
import { MessageView } from '../../MainViewController';
import { MessageDetail } from '../../conversation/MessageDetail';
import { getConversationController } from '../../../session/conversations';
import { getPubkeysInPublicConversation } from '../../../data/data';
import autoBind from 'auto-bind';
import { SessionButtonColor } from '../SessionButton';
import { updateConfirmModal } from '../../../state/ducks/modalDialog';
import { addStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments';
import { InConversationCallContainer } from '../calling/InConversationCallContainer';
import { SplitViewContainer } from '../SplitViewContainer';
} from '../../state/ducks/conversations';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { SessionTheme } from '../../state/ducks/SessionTheme';
import { addStagedAttachmentsInConversation } from '../../state/ducks/stagedAttachments';
import { MIME } from '../../types';
import { AttachmentTypeWithPath } from '../../types/Attachment';
import { AttachmentUtil, GoogleChrome } from '../../util';
import { SessionButtonColor } from '../basic/SessionButton';
import { MessageView } from '../MainViewController';
import { ConversationHeaderWithDetails } from './ConversationHeader';
import { MessageDetail } from './message/message-item/MessageDetail';
import { SessionRightPanelWithDetails } from './SessionRightPanel';
// tslint:disable: jsx-curly-spacing
interface State {

View File

@ -1,7 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import { Picker } from 'emoji-mart';
import { Constants } from '../../../session';
import { Constants } from '../../session';
type Props = {
onEmojiClicked: (emoji: any) => void;

View File

@ -1,6 +1,6 @@
import React from 'react';
import styled from 'styled-components';
import { Flex } from '../../basic/Flex';
import { Flex } from '../basic/Flex';
import { SessionIcon } from '../icon';
const DropZoneContainer = styled.div`

View File

@ -2,22 +2,23 @@ import React from 'react';
import { useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { PropsForDataExtractionNotification, QuoteClickOptions } from '../../../models/messageType';
import { PropsForDataExtractionNotification, QuoteClickOptions } from '../../models/messageType';
import {
PropsForCallNotification,
PropsForExpirationTimer,
PropsForGroupInvitation,
PropsForGroupUpdate,
} from '../../../state/ducks/conversations';
import { getSortedMessagesTypesOfSelectedConversation } from '../../../state/selectors/conversations';
import { CallNotification } from '../../conversation/notification-bubble/CallNotification';
import { DataExtractionNotification } from '../../conversation/DataExtractionNotification';
import { GroupInvitation } from '../../conversation/GroupInvitation';
import { GroupNotification } from '../../conversation/GroupNotification';
import { Message } from '../../conversation/Message';
import { MessageDateBreak } from '../../conversation/message/DateBreak';
import { TimerNotification } from '../../conversation/TimerNotification';
} from '../../state/ducks/conversations';
import { getSortedMessagesTypesOfSelectedConversation } from '../../state/selectors/conversations';
import { GroupNotification } from './GroupNotification';
import { DataExtractionNotification } from './message/message-item/DataExtractionNotification';
import { MessageDateBreak } from './message/message-item/DateBreak';
import { GroupInvitation } from './message/message-item/GroupInvitation';
import { Message } from './message/message-item/Message';
import { CallNotification } from './message/message-item/notification-bubble/CallNotification';
import { SessionLastSeenIndicator } from './SessionLastSeenIndicator';
import { TimerNotification } from './TimerNotification';
export const SessionMessagesList = (props: {
scrollToQuoteMessage: (options: QuoteClickOptions) => Promise<void>;

View File

@ -2,23 +2,26 @@ import React from 'react';
import { SessionScrollButton } from '../SessionScrollButton';
import { contextMenu } from 'react-contexify';
import { connect, useSelector } from 'react-redux';
import { SessionMessagesList } from './SessionMessagesList';
import styled from 'styled-components';
import autoBind from 'auto-bind';
import { getMessagesBySentAt } from '../../data/data';
import { ConversationTypeEnum } from '../../models/conversation';
import { MessageModel } from '../../models/message';
import { QuoteClickOptions } from '../../models/messageType';
import { getConversationController } from '../../session/conversations';
import { ToastUtils } from '../../session/utils';
import {
quotedMessageToAnimate,
ReduxConversationType,
showScrollToBottomButton,
SortedMessageModelProps,
updateHaveDoneFirstScroll,
} from '../../../state/ducks/conversations';
import { ToastUtils } from '../../../session/utils';
import { TypingBubble } from '../../conversation/TypingBubble';
import { getConversationController } from '../../../session/conversations';
import { MessageModel } from '../../../models/message';
import { QuoteClickOptions } from '../../../models/messageType';
import { getMessagesBySentAt } from '../../../data/data';
import autoBind from 'auto-bind';
import { ConversationTypeEnum } from '../../../models/conversation';
import { StateType } from '../../../state/reducer';
import { connect, useSelector } from 'react-redux';
} from '../../state/ducks/conversations';
import { StateType } from '../../state/reducer';
import {
getFirstUnreadMessageId,
getQuotedMessageToAnimate,
@ -27,9 +30,8 @@ import {
getShowScrollButton,
getSortedMessagesOfSelectedConversation,
isFirstUnreadMessageIdAbove,
} from '../../../state/selectors/conversations';
import { SessionMessagesList } from './SessionMessagesList';
import styled from 'styled-components';
} from '../../state/selectors/conversations';
import { TypingBubble } from './TypingBubble';
export type SessionMessageListProps = {
messageContainerRef: React.RefObject<HTMLDivElement>;

View File

@ -1,13 +1,13 @@
import React, { useCallback } from 'react';
import { Flex } from '../../basic/Flex';
import { SessionIcon, SessionIconButton } from '../icon';
import styled from 'styled-components';
import { getAlt, isAudio } from '../../../types/Attachment';
import { Image } from '../../conversation/Image';
import { AUDIO_MP3 } from '../../../types/MIME';
import { useDispatch, useSelector } from 'react-redux';
import { getQuotedMessage } from '../../../state/selectors/conversations';
import { quoteMessage } from '../../../state/ducks/conversations';
import { quoteMessage } from '../../state/ducks/conversations';
import { getQuotedMessage } from '../../state/selectors/conversations';
import { getAlt, isAudio } from '../../types/Attachment';
import { AUDIO_MP3 } from '../../types/MIME';
import { Flex } from '../basic/Flex';
import { Image } from '../../../ts/components/conversation/Image';
const QuotedMessageComposition = styled.div`
width: 100%;

View File

@ -3,11 +3,11 @@ import classNames from 'classnames';
import moment from 'moment';
import { SessionIconButton } from '../icon';
import { Constants } from '../../../session';
import { ToastUtils } from '../../../session/utils';
import autoBind from 'auto-bind';
import MicRecorder from 'mic-recorder-to-mp3';
import styled from 'styled-components';
import { Constants } from '../../session';
import { ToastUtils } from '../../session/utils';
interface Props {
onExitVoiceNoteView: () => void;
@ -124,7 +124,7 @@ export class SessionRecording extends React.Component<Props, State> {
<StyledFlexWrapper marginHorizontal={Constants.UI.SPACING.marginXs}>
{isRecording && (
<SessionIconButton
iconType="pause"
iconType="stop"
iconSize="medium"
iconColor={Constants.UI.COLORS.DANGER_ALT}
onClick={actionPauseFn}

View File

@ -1,17 +1,13 @@
import React, { useEffect, useState } from 'react';
import { SessionIconButton } from '../icon';
import { Avatar, AvatarSize } from '../../Avatar';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton';
import { SessionDropdown } from '../SessionDropdown';
import { MediaGallery } from '../../conversation/media-gallery/MediaGallery';
import _ from 'lodash';
import { Constants } from '../../../session';
import { AttachmentTypeWithPath } from '../../../types/Attachment';
// tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval';
import { useDispatch, useSelector } from 'react-redux';
import {
getMessagesWithFileAttachments,
getMessagesWithVisualMediaAttachments,
} from '../../../data/data';
import { SpacerLG } from '../../basic/Text';
} from '../../data/data';
import {
deleteAllMessagesByConvoIdWithConfirmation,
setDisappearingMessagesByConvoId,
@ -21,17 +17,18 @@ import {
showRemoveModeratorsByConvoId,
showUpdateGroupMembersByConvoId,
showUpdateGroupNameByConvoId,
} from '../../../interactions/conversationInteractions';
import { MediaItemType } from '../../LightboxGallery';
// tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval';
import { useDispatch, useSelector } from 'react-redux';
import { getTimerOptions } from '../../../state/selectors/timerOptions';
import {
getSelectedConversation,
isRightPanelShowing,
} from '../../../state/selectors/conversations';
import { closeRightPanel } from '../../../state/ducks/conversations';
} from '../../interactions/conversationInteractions';
import { Constants } from '../../session';
import { closeRightPanel } from '../../state/ducks/conversations';
import { getSelectedConversation, isRightPanelShowing } from '../../state/selectors/conversations';
import { getTimerOptions } from '../../state/selectors/timerOptions';
import { AttachmentTypeWithPath } from '../../types/Attachment';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionDropdown } from '../basic/SessionDropdown';
import { SpacerLG } from '../basic/Text';
import { MediaItemType } from '../lightbox/LightboxGallery';
import { MediaGallery } from './media-gallery/MediaGallery';
async function getMediaGalleryProps(
conversationId: string

View File

@ -1,11 +1,11 @@
import React from 'react';
import { arrayBufferFromFile } from '../../../types/Attachment';
import { AttachmentUtil, LinkPreviewUtil } from '../../../util';
import { StagedLinkPreviewData } from './composition/CompositionBox';
import { default as insecureNodeFetch } from 'node-fetch';
import { fetchLinkPreviewImage } from '../../../util/linkPreviewFetch';
import { AbortSignal } from 'abort-controller';
import { StagedLinkPreview } from '../../conversation/StagedLinkPreview';
import { arrayBufferFromFile } from '../../types/Attachment';
import { AttachmentUtil, LinkPreviewUtil } from '../../util';
import { fetchLinkPreviewImage } from '../../util/linkPreviewFetch';
import { StagedLinkPreview } from './StagedLinkPreview';
export interface StagedLinkPreviewProps extends StagedLinkPreviewData {
onClose: (url: string) => void;

View File

@ -4,7 +4,7 @@ import classNames from 'classnames';
import { Image } from './Image';
import { AttachmentType, isImageAttachment } from '../../types/Attachment';
import { SessionSpinner } from '../session/SessionSpinner';
import { SessionSpinner } from '../basic/SessionSpinner';
type Props = {
isLoaded: boolean;

View File

@ -2,8 +2,8 @@ import React from 'react';
import { missingCaseError } from '../../util/missingCaseError';
import { PropsForExpirationTimer } from '../../state/ducks/conversations';
import { ReadableMessage } from './ReadableMessage';
import { NotificationBubble } from './notification-bubble/NotificationBubble';
import { NotificationBubble } from './message/message-item/notification-bubble/NotificationBubble';
import { ReadableMessage } from './message/message-item/ReadableMessage';
export const TimerNotification = (props: PropsForExpirationTimer) => {
const { messageId, receivedAt, isUnread, pubkey, profileName, timespan, type, disabled } = props;

View File

@ -1,19 +1,12 @@
import React from 'react';
import _, { debounce } from 'lodash';
import { AttachmentType } from '../../../../types/Attachment';
import * as MIME from '../../../../types/MIME';
import * as MIME from '../../../types/MIME';
import { SessionEmojiPanel } from '../SessionEmojiPanel';
import { SessionRecording } from '../SessionRecording';
import { Constants } from '../../../../session';
import { toArray } from 'react-emoji-render';
import { Flex } from '../../../basic/Flex';
import { StagedAttachmentList } from '../../../conversation/StagedAttachmentList';
import { ToastUtils } from '../../../../session/utils';
import { AttachmentUtil } from '../../../../util';
import {
getPreview,
LINK_PREVIEW_TIMEOUT,
@ -22,31 +15,37 @@ import {
import { AbortController } from 'abort-controller';
import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition';
import { Mention, MentionsInput } from 'react-mentions';
import { CaptionEditor } from '../../../CaptionEditor';
import { getConversationController } from '../../../../session/conversations';
import { ReduxConversationType } from '../../../../state/ducks/conversations';
import { SessionMemberListItem } from '../../SessionMemberListItem';
import { MemberListItem } from '../../MemberListItem';
import autoBind from 'auto-bind';
import { getMediaPermissionsSettings } from '../../settings/SessionSettings';
import {
getIsTypingEnabled,
getMentionsInput,
getQuotedMessage,
getSelectedConversation,
getSelectedConversationKey,
} from '../../../../state/selectors/conversations';
import { connect } from 'react-redux';
import { StateType } from '../../../../state/reducer';
import { getTheme } from '../../../../state/selectors/theme';
import { removeAllStagedAttachmentsInConversation } from '../../../../state/ducks/stagedAttachments';
import { getDraftForConversation, updateDraftForConversation } from '../SessionConversationDrafts';
import { showLinkSharingConfirmationModalDialog } from '../../../../interactions/conversationInteractions';
import {
AddStagedAttachmentButton,
SendMessageButton,
StartRecordingButton,
ToggleEmojiButton,
} from './CompositionButtons';
import { AttachmentType } from '../../../types/Attachment';
import { connect } from 'react-redux';
import { showLinkSharingConfirmationModalDialog } from '../../../interactions/conversationInteractions';
import { Constants } from '../../../session';
import { getConversationController } from '../../../session/conversations';
import { ToastUtils } from '../../../session/utils';
import { ReduxConversationType } from '../../../state/ducks/conversations';
import { removeAllStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments';
import { StateType } from '../../../state/reducer';
import {
getIsTypingEnabled,
getMentionsInput,
getQuotedMessage,
getSelectedConversation,
getSelectedConversationKey,
} from '../../../state/selectors/conversations';
import { getTheme } from '../../../state/selectors/theme';
import { AttachmentUtil } from '../../../util';
import { Flex } from '../../basic/Flex';
import { CaptionEditor } from '../../CaptionEditor';
import { StagedAttachmentList } from '../StagedAttachmentList';
export interface ReplyingToMessageProps {
convoId: string;
@ -413,7 +412,6 @@ class CompositionBoxInner extends React.Component<Props, State> {
? i18n('unblockGroupToSend')
: i18n('sendMessage');
const { typingEnabled } = this.props;
let index = 0;
return (
<MentionsInput
@ -439,21 +437,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
displayTransform={(_id, display) => `@${display}`}
data={this.fetchUsersForGroup}
renderSuggestion={(suggestion, _search, _highlightedDisplay, _index, focused) => (
<SessionMemberListItem
isSelected={focused}
index={index++}
key={suggestion.id}
member={{
id: `${suggestion.id}`,
authorPhoneNumber: `${suggestion.id}`,
selected: focused,
authorProfileName: `${suggestion.display}`,
authorName: `${suggestion.display}`,
existingMember: false,
checkmarked: false,
authorAvatarPath: '',
}}
/>
<MemberListItem isSelected={focused} key={suggestion.id} pubkey={`${suggestion.id}`} />
)}
/>
</MentionsInput>

View File

@ -2,8 +2,8 @@ import React from 'react';
import { DocumentListItem } from './DocumentListItem';
import { MediaGridItem } from './MediaGridItem';
import { MediaItemType } from '../../LightboxGallery';
import { missingCaseError } from '../../../util/missingCaseError';
import { MediaItemType } from '../../lightbox/LightboxGallery';
type Props = {
type: 'media' | 'documents';

View File

@ -4,10 +4,10 @@ import classNames from 'classnames';
import moment from 'moment';
// tslint:disable-next-line:match-default-export-name
import formatFileSize from 'filesize';
import { MediaItemType } from '../../LightboxGallery';
import { useSelector } from 'react-redux';
import { getSelectedConversationKey } from '../../../state/selectors/conversations';
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
import { MediaItemType } from '../../lightbox/LightboxGallery';
type Props = {
// Required

View File

@ -3,8 +3,8 @@ import classNames from 'classnames';
import { AttachmentSection } from './AttachmentSection';
import { EmptyState } from './EmptyState';
import { MediaItemType } from '../../lightbox/LightboxGallery';
import { MediaItemType } from '../../LightboxGallery';
type Props = {
documents: Array<MediaItemType>;
media: Array<MediaItemType>;

View File

@ -2,11 +2,11 @@ import React, { useState } from 'react';
import classNames from 'classnames';
import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome';
import { MediaItemType } from '../../LightboxGallery';
import { useEncryptedFileFetch } from '../../../hooks/useEncryptedFileFetch';
import { showLightBox } from '../../../state/ducks/conversations';
import { LightBoxOptions } from '../../session/conversation/SessionConversation';
import { useDisableDrag } from '../../../hooks/useDisableDrag';
import { LightBoxOptions } from '../SessionConversation';
import { MediaItemType } from '../../lightbox/LightboxGallery';
type Props = {
mediaItem: MediaItemType;

View File

@ -1,7 +1,6 @@
import moment from 'moment';
import { compact, groupBy, sortBy } from 'lodash';
import { MediaItemType } from '../../LightboxGallery';
import { MediaItemType } from '../../lightbox/LightboxGallery';
// import { missingCaseError } from '../../../util/missingCaseError';

View File

@ -1,11 +1,11 @@
import React from 'react';
import styled from 'styled-components';
import { getMessageById, getMessagesByConversation } from '../../../data/data';
import { getConversationController } from '../../../session/conversations';
import { AttachmentDownloads } from '../../../session/utils';
import { updateConfirmModal } from '../../../state/ducks/modalDialog';
import { SessionIcon } from '../../session/icon';
import { SessionButtonColor } from '../../session/SessionButton';
import { getMessageById, getMessagesByConversation } from '../../../../data/data';
import { getConversationController } from '../../../../session/conversations';
import { AttachmentDownloads } from '../../../../session/utils';
import { updateConfirmModal } from '../../../../state/ducks/modalDialog';
import { SessionButtonColor } from '../../../basic/SessionButton';
import { SessionIcon } from '../../../icon';
const StyledTrustSenderUI = styled.div`
padding-inline: var(--margins-xs);

View File

@ -2,17 +2,17 @@ import classNames from 'classnames';
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import _ from 'underscore';
import { getMessageById } from '../../../data/data';
import { MessageRenderingProps } from '../../../models/messageType';
import { getMessageById } from '../../../../data/data';
import { MessageRenderingProps } from '../../../../models/messageType';
import {
PropsForAttachment,
showLightBox,
toggleSelectedMessageId,
} from '../../../state/ducks/conversations';
} from '../../../../state/ducks/conversations';
import {
getMessageAttachmentProps,
isMessageSelectionMode,
} from '../../../state/selectors/conversations';
} from '../../../../state/selectors/conversations';
import {
AttachmentType,
AttachmentTypeWithPath,
@ -23,13 +23,13 @@ import {
isAudio,
isImage,
isVideo,
} from '../../../types/Attachment';
import { isFileDangerous } from '../../../util';
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
import { Spinner } from '../../basic/Spinner';
import { LightBoxOptions } from '../../session/conversation/SessionConversation';
import { AudioPlayerWithEncryptedFile } from '../H5AudioPlayer';
import { ImageGrid } from '../ImageGrid';
} from '../../../../types/Attachment';
import { isFileDangerous } from '../../../../util';
import { saveAttachmentToDisk } from '../../../../util/attachmentsUtil';
import { Spinner } from '../../../basic/Spinner';
import { AudioPlayerWithEncryptedFile } from '../../H5AudioPlayer';
import { ImageGrid } from '../../ImageGrid';
import { LightBoxOptions } from '../../SessionConversation';
import { ClickToTrustSender } from './ClickToTrustSender';
export type MessageAttachmentSelectorProps = Pick<

View File

@ -1,14 +1,14 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { MessageRenderingProps } from '../../../models/messageType';
import { PubKey } from '../../../session/types/PubKey';
import { MessageRenderingProps } from '../../../../models/messageType';
import { PubKey } from '../../../../session/types';
import {
getMessageAuthorProps,
isGroupConversation,
isPublicGroupConversation,
} from '../../../state/selectors/conversations';
import { Flex } from '../../basic/Flex';
import { ContactName } from '../ContactName';
} from '../../../../state/selectors/conversations';
import { Flex } from '../../../basic/Flex';
import { ContactName } from '../../ContactName';
export type MessageAuthorSelectorProps = Pick<
MessageRenderingProps,

View File

@ -1,9 +1,10 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { MessageRenderingProps } from '../../../models/messageType';
import { updateUserDetailsModal } from '../../../state/ducks/modalDialog';
import { getMessageAvatarProps } from '../../../state/selectors/conversations';
import { Avatar, AvatarSize } from '../../Avatar';
import { MessageRenderingProps } from '../../../../models/messageType';
import { updateUserDetailsModal } from '../../../../state/ducks/modalDialog';
import { getMessageAvatarProps } from '../../../../state/selectors/conversations';
import { Avatar, AvatarSize } from '../../../avatar/Avatar';
// tslint:disable: use-simple-attributes
export type MessageAvatarSelectorProps = Pick<
MessageRenderingProps,
@ -62,7 +63,11 @@ export const MessageAvatar = (props: Props) => {
return (
<div className="module-message__author-avatar" key={`msg-avatar-${authorPhoneNumber}`}>
<Avatar size={AvatarSize.S} onAvatarClick={onMessageAvatarClick} pubkey={authorPhoneNumber} />
<Avatar
size={AvatarSize.S}
onAvatarClick={(!isPublic && onMessageAvatarClick) || undefined}
pubkey={authorPhoneNumber}
/>
{isPublic && isSenderAdmin && (
<div className="module-avatar__icon--crown-wrapper">
<div className="module-avatar__icon--crown" />

View File

@ -1,12 +1,10 @@
import React from 'react';
import { getSizeClass, SizeClassType } from '../../util/emoji';
import { Emojify } from './Emojify';
import { AddNewLines } from './AddNewLines';
import { AddMentions } from './AddMentions';
import { Linkify } from './Linkify';
import { RenderTextCallbackType } from '../../types/Util';
import { RenderTextCallbackType } from '../../../../types/Util';
import { getSizeClass, SizeClassType } from '../../../../util/emoji';
import { AddMentions } from '../../AddMentions';
import { AddNewLines } from '../../AddNewLines';
import { Emojify } from '../../Emojify';
import { Linkify } from '../../Linkify';
interface Props {
text: string;

View File

@ -4,11 +4,11 @@ import React, { createContext, useCallback, useState } from 'react';
import { InView } from 'react-intersection-observer';
import { useSelector } from 'react-redux';
import _ from 'underscore';
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType';
import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
import {
getMessageContentSelectorProps,
getMessageTextProps,
} from '../../../state/selectors/conversations';
} from '../../../../state/selectors/conversations';
import {
canDisplayImage,
getGridDimensions,
@ -18,9 +18,9 @@ import {
isImage,
isImageAttachment,
isVideo,
} from '../../../types/Attachment';
import { Flex } from '../../basic/Flex';
import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../Message';
} from '../../../../types/Attachment';
import { Flex } from '../../../basic/Flex';
import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../message-item/Message';
import { MessageAttachment } from './MessageAttachment';
import { MessagePreview } from './MessagePreview';
import { MessageQuote } from './MessageQuote';

View File

@ -2,13 +2,14 @@ import classNames from 'classnames';
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import _ from 'underscore';
import { replyToMessage } from '../../../interactions/conversationInteractions';
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType';
import { toggleSelectedMessageId } from '../../../state/ducks/conversations';
import { replyToMessage } from '../../../../interactions/conversationInteractions';
import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
import { toggleSelectedMessageId } from '../../../../state/ducks/conversations';
import {
getMessageContentWithStatusesSelectorProps,
isMessageSelectionMode,
} from '../../../state/selectors/conversations';
} from '../../../../state/selectors/conversations';
import { MessageAuthorText } from './MessageAuthorText';
import { MessageContent } from './MessageContent';
import { MessageContextMenu } from './MessageContextMenu';

View File

@ -2,26 +2,26 @@ import React, { useCallback } from 'react';
import { animation, Item, Menu } from 'react-contexify';
import { MessageInteraction } from '../../../interactions';
import { getMessageById } from '../../../data/data';
import { replyToMessage } from '../../../interactions/conversationInteractions';
import {
showMessageDetailsView,
toggleSelectedMessageId,
} from '../../../state/ducks/conversations';
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
import {
addSenderAsModerator,
removeSenderFromModerator,
} from '../../../interactions/messageInteractions';
import { MessageRenderingProps } from '../../../models/messageType';
import { pushUnblockToSend } from '../../../session/utils/Toast';
import { useDispatch, useSelector } from 'react-redux';
import { getMessageContextMenuProps } from '../../../state/selectors/conversations';
import { getMessageById } from '../../../../data/data';
import { MessageInteraction } from '../../../../interactions';
import { replyToMessage } from '../../../../interactions/conversationInteractions';
import {
deleteMessagesById,
deleteMessagesByIdForEveryone,
} from '../../../interactions/conversations/unsendingInteractions';
} from '../../../../interactions/conversations/unsendingInteractions';
import {
addSenderAsModerator,
removeSenderFromModerator,
} from '../../../../interactions/messageInteractions';
import { MessageRenderingProps } from '../../../../models/messageType';
import { pushUnblockToSend } from '../../../../session/utils/Toast';
import {
showMessageDetailsView,
toggleSelectedMessageId,
} from '../../../../state/ducks/conversations';
import { getMessageContextMenuProps } from '../../../../state/selectors/conversations';
import { saveAttachmentToDisk } from '../../../../util/attachmentsUtil';
export type MessageContextMenuSelectorProps = Pick<
MessageRenderingProps,
@ -183,7 +183,9 @@ export const MessageContextMenu = (props: Props) => {
<Item onClick={copyText}>{window.i18n('copyMessage')}</Item>
{(isSent || !isOutgoing) && <Item onClick={onReply}>{window.i18n('replyToMessage')}</Item>}
<Item onClick={onShowDetail}>{window.i18n('moreInformation')}</Item>
{(!isPublic || isOutgoing) && (
<Item onClick={onShowDetail}>{window.i18n('moreInformation')}</Item>
)}
{showRetry ? <Item onClick={onRetry}>{window.i18n('resend')}</Item> : null}
{isDeletable ? (
<>

View File

@ -1,13 +1,13 @@
import classNames from 'classnames';
import React from 'react';
import { isImageAttachment } from '../../../types/Attachment';
import { SessionIcon } from '../../session/icon';
import { ImageGrid } from '../ImageGrid';
import { Image } from '../Image';
import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../Message';
import { MessageRenderingProps } from '../../../models/messageType';
import { isImageAttachment } from '../../../../types/Attachment';
import { ImageGrid } from '../../ImageGrid';
import { Image } from '../../Image';
import { MessageRenderingProps } from '../../../../models/messageType';
import { useSelector } from 'react-redux';
import { getMessagePreviewProps } from '../../../state/selectors/conversations';
import { getMessagePreviewProps } from '../../../../state/selectors/conversations';
import { SessionIcon } from '../../../icon';
import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../message-item/Message';
export type MessagePreviewSelectorProps = Pick<MessageRenderingProps, 'attachments' | 'previews'>;

View File

@ -1,14 +1,15 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import _ from 'lodash';
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType';
import { PubKey } from '../../../session/types';
import { toggleSelectedMessageId } from '../../../state/ducks/conversations';
import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
import { PubKey } from '../../../../session/types';
import { toggleSelectedMessageId } from '../../../../state/ducks/conversations';
import {
getMessageQuoteProps,
isMessageSelectionMode,
} from '../../../state/selectors/conversations';
import { Quote } from '../Quote';
} from '../../../../state/selectors/conversations';
import { Quote } from './Quote';
// tslint:disable: use-simple-attributes
type Props = {

View File

@ -1,7 +1,7 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { MessageRenderingProps } from '../../../models/messageType';
import { getMessageStatusProps } from '../../../state/selectors/conversations';
import { MessageRenderingProps } from '../../../../models/messageType';
import { getMessageStatusProps } from '../../../../state/selectors/conversations';
import { OutgoingMessageStatus } from './OutgoingMessageStatus';
type Props = {

View File

@ -1,13 +1,13 @@
import classNames from 'classnames';
import React from 'react';
import { useSelector } from 'react-redux';
import { MessageRenderingProps } from '../../../models/messageType';
import { MessageRenderingProps } from '../../../../models/messageType';
import {
getMessageTextProps,
isMessageSelectionMode,
} from '../../../state/selectors/conversations';
import { SessionIcon } from '../../session/icon';
import { MessageBody } from '../MessageBody';
} from '../../../../state/selectors/conversations';
import { SessionIcon } from '../../../icon';
import { MessageBody } from './MessageBody';
type Props = {
messageId: string;

View File

@ -1,8 +1,8 @@
import { ipcRenderer } from 'electron';
import React from 'react';
import styled from 'styled-components';
import { MessageDeliveryStatus } from '../../../models/messageType';
import { SessionIcon } from '../../session/icon';
import { MessageDeliveryStatus } from '../../../../models/messageType';
import { SessionIcon } from '../../../icon';
const MessageStatusSendingContainer = styled.div`
display: inline-block;

View File

@ -1,22 +1,22 @@
import React, { useState } from 'react';
import classNames from 'classnames';
import * as MIME from '../../../ts/types/MIME';
import * as GoogleChrome from '../../../ts/util/GoogleChrome';
import * as MIME from '../../../../../ts/types/MIME';
import * as GoogleChrome from '../../../../../ts/util/GoogleChrome';
import { MessageBody } from './MessageBody';
import { ContactName } from './ContactName';
import { PubKey } from '../../session/types';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import { useSelector } from 'react-redux';
import { noop } from 'underscore';
import { useDisableDrag } from '../../../../hooks/useDisableDrag';
import { useEncryptedFileFetch } from '../../../../hooks/useEncryptedFileFetch';
import { PubKey } from '../../../../session/types';
import {
getSelectedConversationKey,
isGroupConversation,
isPublicGroupConversation,
} from '../../state/selectors/conversations';
import { noop } from 'underscore';
import { useDisableDrag } from '../../hooks/useDisableDrag';
} from '../../../../state/selectors/conversations';
import { ContactName } from '../../ContactName';
import { MessageBody } from './MessageBody';
export type QuotePropsWithoutListener = {
attachment?: QuotedAttachmentType;

View File

@ -1,9 +1,9 @@
import React from 'react';
import { PropsForDataExtractionNotification } from '../../models/messageType';
import { SignalService } from '../../protobuf';
import { Flex } from '../basic/Flex';
import { SessionIcon } from '../session/icon';
import { SpacerSM, Text } from '../basic/Text';
import { PropsForDataExtractionNotification } from '../../../../models/messageType';
import { SignalService } from '../../../../protobuf';
import { Flex } from '../../../basic/Flex';
import { SpacerSM, Text } from '../../../basic/Text';
import { SessionIcon } from '../../../icon';
import { ReadableMessage } from './ReadableMessage';
export const DataExtractionNotification = (props: PropsForDataExtractionNotification) => {

View File

@ -5,21 +5,21 @@ import { useDispatch, useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval';
import _ from 'lodash';
import { removeMessage } from '../../../data/data';
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType';
import { getConversationController } from '../../../session/conversations';
import { messageExpired } from '../../../state/ducks/conversations';
import { removeMessage } from '../../../../data/data';
import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
import { getConversationController } from '../../../../session/conversations';
import { messageExpired } from '../../../../state/ducks/conversations';
import {
getGenericReadableMessageSelectorProps,
getIsMessageSelected,
getQuotedMessageToAnimate,
isMessageSelectionMode,
} from '../../../state/selectors/conversations';
import { getIncrement } from '../../../util/timer';
import { ExpireTimer } from '../ExpireTimer';
import { ReadableMessage } from '../ReadableMessage';
import { MessageAvatar } from './MessageAvatar';
import { MessageContentWithStatuses } from './MessageContentWithStatus';
} from '../../../../state/selectors/conversations';
import { getIncrement } from '../../../../util/timer';
import { ExpireTimer } from '../../ExpireTimer';
import { MessageAvatar } from '../message-content/MessageAvatar';
import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus';
import { ReadableMessage } from './ReadableMessage';
export type GenericReadableMessageSelectorProps = Pick<
MessageRenderingProps,

View File

@ -1,8 +1,8 @@
import React from 'react';
import classNames from 'classnames';
import { SessionIconButton } from '../session/icon';
import { PropsForGroupInvitation } from '../../state/ducks/conversations';
import { acceptOpenGroupInvitation } from '../../interactions/messageInteractions';
import { PropsForGroupInvitation } from '../../../../state/ducks/conversations';
import { acceptOpenGroupInvitation } from '../../../../interactions/messageInteractions';
import { SessionIconButton } from '../../../icon';
import { ReadableMessage } from './ReadableMessage';
export const GroupInvitation = (props: PropsForGroupInvitation) => {

View File

@ -2,10 +2,10 @@ import React from 'react';
import _ from 'lodash';
import uuid from 'uuid';
import { QuoteClickOptions } from '../../models/messageType';
import { GenericReadableMessage } from './message/GenericReadableMessage';
import { useSelector } from 'react-redux';
import { getGenericReadableMessageSelectorProps } from '../../state/selectors/conversations';
import { QuoteClickOptions } from '../../../../models/messageType';
import { getGenericReadableMessageSelectorProps } from '../../../../state/selectors/conversations';
import { GenericReadableMessage } from './GenericReadableMessage';
// Same as MIN_WIDTH in ImageGrid.tsx
export const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;

View File

@ -2,16 +2,16 @@ import React from 'react';
import classNames from 'classnames';
import moment from 'moment';
import { Avatar, AvatarSize } from '../Avatar';
import { ContactName } from './ContactName';
import { Message } from './Message';
import { useSelector } from 'react-redux';
import { ContactPropsMessageDetail } from '../../state/ducks/conversations';
import { Avatar, AvatarSize } from '../../../avatar/Avatar';
import { deleteMessagesById } from '../../../../interactions/conversations/unsendingInteractions';
import { ContactPropsMessageDetail } from '../../../../state/ducks/conversations';
import {
getMessageDetailsViewProps,
getMessageIsDeletable,
} from '../../state/selectors/conversations';
import { deleteMessagesById } from '../../interactions/conversations/unsendingInteractions';
} from '../../../../state/selectors/conversations';
import { ContactName } from '../../ContactName';
const AvatarItem = (props: { pubkey: string }) => {
const { pubkey } = props;

View File

@ -2,14 +2,14 @@ import _, { noop } from 'lodash';
import React, { useCallback } from 'react';
import { InView } from 'react-intersection-observer';
import { useDispatch, useSelector } from 'react-redux';
import { getMessageById } from '../../data/data';
import { Constants } from '../../session';
import { getConversationController } from '../../session/conversations';
import { getMessageById } from '../../../../data/data';
import { Constants } from '../../../../session';
import { getConversationController } from '../../../../session/conversations';
import {
fetchMessagesForConversation,
markConversationFullyRead,
showScrollToBottomButton,
} from '../../state/ducks/conversations';
} from '../../../../state/ducks/conversations';
import {
areMoreMessagesBeingFetched,
getHaveDoneFirstScroll,
@ -17,8 +17,8 @@ import {
getMostRecentMessageId,
getOldestMessageId,
getSelectedConversationKey,
} from '../../state/selectors/conversations';
import { getIsAppFocused } from '../../state/selectors/section';
} from '../../../../state/selectors/conversations';
import { getIsAppFocused } from '../../../../state/selectors/section';
type ReadableMessageProps = {
children: React.ReactNode;

View File

@ -1,11 +1,14 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { PubKey } from '../../../session/types';
import { PubKey } from '../../../../../session/types';
import { CallNotificationType, PropsForCallNotification } from '../../../state/ducks/conversations';
import { getSelectedConversation } from '../../../state/selectors/conversations';
import { LocalizerKeys } from '../../../types/LocalizerKeys';
import { SessionIconType } from '../../session/icon';
import {
CallNotificationType,
PropsForCallNotification,
} from '../../../../../state/ducks/conversations';
import { getSelectedConversation } from '../../../../../state/selectors/conversations';
import { LocalizerKeys } from '../../../../../types/LocalizerKeys';
import { SessionIconType } from '../../../../icon';
import { ReadableMessage } from '../ReadableMessage';
import { NotificationBubble } from './NotificationBubble';

View File

@ -1,6 +1,6 @@
import React from 'react';
import styled from 'styled-components';
import { SessionIcon, SessionIconType } from '../../session/icon';
import { SessionIcon, SessionIconType } from '../../../../icon';
const NotificationBubbleFlex = styled.div`
display: flex;

View File

@ -1,10 +1,9 @@
import React, { useState } from 'react';
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { SpacerLG } from '../basic/Text';
import { getConversationController } from '../../session/conversations';
import { adminLeaveClosedGroup } from '../../state/ducks/modalDialog';
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal';
type Props = {
conversationId: string;

View File

@ -1,14 +1,14 @@
import React, { useCallback, useState } from 'react';
import { useDispatch } from 'react-redux';
import { ed25519Str } from '../../session/onions/onionPath';
import { forceNetworkDeletion } from '../../session/snode_api/SNodeAPI';
import { forceNetworkDeletion } from '../../session/apis/snode_api/SNodeAPI';
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils';
import { updateConfirmModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog';
import { SpacerLG } from '../basic/Text';
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
import { SessionHtmlRenderer } from '../session/SessionHTMLRenderer';
import { SessionSpinner } from '../session/SessionSpinner';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionWrapperModal } from '../SessionWrapperModal';
const deleteDbLocally = async () => {
window?.log?.info('last message sent successfully. Deleting everything');

View File

@ -2,24 +2,24 @@ import React from 'react';
import classNames from 'classnames';
import { QRCode } from 'react-qr-svg';
import { Avatar, AvatarSize } from '../Avatar';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { SessionIconButton } from '../session/icon';
import { PillDivider } from '../session/PillDivider';
import { PillDivider } from '../basic/PillDivider';
import { SyncUtils, ToastUtils, UserUtils } from '../../session/utils';
import { MAX_USERNAME_LENGTH } from '../session/registration/RegistrationStages';
import { SessionSpinner } from '../session/SessionSpinner';
import { ConversationModel, ConversationTypeEnum } from '../../models/conversation';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { AttachmentUtil } from '../../util';
import { getConversationController } from '../../session/conversations';
import { SpacerLG, SpacerMD } from '../basic/Text';
import autoBind from 'auto-bind';
import { editProfileModal } from '../../state/ducks/modalDialog';
import { uploadOurAvatar } from '../../interactions/conversationInteractions';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionIconButton } from '../icon';
import { MAX_USERNAME_LENGTH } from '../registration/RegistrationStages';
import { SessionWrapperModal } from '../SessionWrapperModal';
interface State {
profileName: string;

View File

@ -1,87 +1,134 @@
import React, { useState } from 'react';
import React from 'react';
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
import { ContactType, SessionMemberListItem } from '../session/SessionMemberListItem';
import { getConversationController } from '../../session/conversations';
import { ToastUtils, UserUtils } from '../../session/utils';
import { initiateGroupUpdate } from '../../session/group';
import { ConversationModel, ConversationTypeEnum } from '../../models/conversation';
import { ConversationTypeEnum } from '../../models/conversation';
import { getCompleteUrlForV2ConvoId } from '../../interactions/conversationInteractions';
import _ from 'lodash';
import { VALIDATION } from '../../session/constants';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { SpacerLG } from '../basic/Text';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { updateInviteContactModal } from '../../state/ducks/modalDialog';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { MemberListItem } from '../MemberListItem';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { getPrivateContactsPubkeys } from '../../state/selectors/conversations';
import { useConversationPropsById } from '../../hooks/useParamSelector';
import { useSet } from '../../hooks/useSet';
type Props = {
conversationId: string;
};
const submitForOpenGroup = async (conversationId: string, pubkeys: Array<string>) => {
const completeUrl = await getCompleteUrlForV2ConvoId(conversationId);
const convo = getConversationController().get(conversationId);
if (!convo || !convo.isPublic()) {
throw new Error('submitForOpenGroup group not found');
}
const groupInvitation = {
url: completeUrl,
name: convo.getName() || 'Unknown',
};
pubkeys.forEach(async pubkeyStr => {
const privateConvo = await getConversationController().getOrCreateAndWait(
pubkeyStr,
ConversationTypeEnum.PRIVATE
);
if (privateConvo) {
void privateConvo.sendMessage({
body: '',
attachments: undefined,
groupInvitation,
preview: undefined,
quote: undefined,
});
}
});
};
const submitForClosedGroup = async (convoId: string, pubkeys: Array<string>) => {
const convo = getConversationController().get(convoId);
if (!convo || !convo.isGroup()) {
throw new Error('submitForClosedGroup group not found');
}
// closed group chats
const ourPK = UserUtils.getOurPubKeyStrFromCache();
// we only care about real members. If a member is currently a zombie we have to be able to add him back
let existingMembers = convo.get('members') || [];
// at least make sure it's an array
if (!Array.isArray(existingMembers)) {
existingMembers = [];
}
existingMembers = _.compact(existingMembers);
const existingZombies = convo.get('zombies') || [];
const newMembers = pubkeys.filter(d => !existingMembers.includes(d));
if (newMembers.length > 0) {
// Do not trigger an update if there is too many members
// be sure to include current zombies in this count
if (
newMembers.length + existingMembers.length + existingZombies.length >
VALIDATION.CLOSED_GROUP_SIZE_LIMIT
) {
ToastUtils.pushTooManyMembers();
return;
}
const allMembers = _.concat(existingMembers, newMembers, [ourPK]);
const uniqMembers = _.uniq(allMembers);
const groupId = convo.get('id');
const groupName = convo.get('name');
await initiateGroupUpdate(groupId, groupName || window.i18n('unknown'), uniqMembers, undefined);
}
};
// tslint:disable-next-line: max-func-body-length
const InviteContactsDialogInner = (props: Props) => {
const { conversationId } = props;
const dispatch = useDispatch();
const convo = getConversationController().get(conversationId);
// tslint:disable-next-line: max-func-body-length
const privateContactPubkeys = useSelector(getPrivateContactsPubkeys);
let validContactsForInvite = _.clone(privateContactPubkeys);
let contacts = getConversationController()
.getConversations()
.filter(d => !!d && !d.isBlocked() && d.isPrivate() && !d.isMe() && !!d.get('active_at'));
if (!convo.isPublic()) {
const convoProps = useConversationPropsById(conversationId);
const { uniqueValues: selectedContacts, addTo, removeFrom } = useSet<string>();
if (!convoProps) {
throw new Error('InviteContactsDialogInner not a valid convoId given');
}
if (!convoProps.isGroup) {
throw new Error('InviteContactsDialogInner must be a group');
}
if (!convoProps.isPublic) {
// filter our zombies and current members from the list of contact we can add
const members = convo.get('members') || [];
const zombies = convo.get('zombies') || [];
contacts = contacts.filter(d => !members.includes(d.id) && !zombies.includes(d.id));
const members = convoProps.members || [];
const zombies = convoProps.zombies || [];
validContactsForInvite = validContactsForInvite.filter(
d => !members.includes(d) && !zombies.includes(d)
);
}
const chatName = convo.get('name');
const isPublicConvo = convo.isPublic();
const [contactList, setContactList] = useState(
contacts.map((d: ConversationModel) => {
const lokiProfile = d.getLokiProfile();
const nickname = d.getNickname();
const name = nickname
? nickname
: lokiProfile
? lokiProfile.displayName
: window.i18n('anonymous');
// TODO: should take existing members into account
const existingMember = false;
return {
id: d.id,
authorPhoneNumber: d.id,
authorProfileName: name,
authorAvatarPath: d?.getAvatarPath() || '',
selected: false,
authorName: name,
checkmarked: false,
existingMember,
};
})
);
const chatName = convoProps.name;
const isPublicConvo = convoProps.isPublic;
const closeDialog = () => {
dispatch(updateInviteContactModal(null));
};
const onClickOK = () => {
const selectedContacts = contactList
.filter((d: ContactType) => d.checkmarked)
.map((d: ContactType) => d.id);
if (selectedContacts.length > 0) {
if (isPublicConvo) {
void submitForOpenGroup(selectedContacts);
void submitForOpenGroup(conversationId, selectedContacts);
} else {
void submitForClosedGroup(selectedContacts);
void submitForClosedGroup(conversationId, selectedContacts);
}
}
@ -96,123 +143,37 @@ const InviteContactsDialogInner = (props: Props) => {
return event.key === 'Esc' || event.key === 'Escape';
}, closeDialog);
const titleText = `${window.i18n('addingContacts')} ${chatName}`;
const unknown = window.i18n('unknown');
const titleText = `${window.i18n('addingContacts', [chatName || unknown])}`;
const cancelText = window.i18n('cancel');
const okText = window.i18n('ok');
const hasContacts = contactList.length !== 0;
const submitForOpenGroup = async (pubkeys: Array<string>) => {
if (convo.isOpenGroupV2()) {
const completeUrl = await getCompleteUrlForV2ConvoId(convo.id);
const groupInvitation = {
url: completeUrl,
name: convo.getName() || 'Unknown',
};
pubkeys.forEach(async pubkeyStr => {
const privateConvo = await getConversationController().getOrCreateAndWait(
pubkeyStr,
ConversationTypeEnum.PRIVATE
);
if (privateConvo) {
void privateConvo.sendMessage({
body: '',
attachments: undefined,
groupInvitation,
preview: undefined,
quote: undefined,
});
}
});
}
};
const submitForClosedGroup = async (pubkeys: Array<string>) => {
// closed group chats
const ourPK = UserUtils.getOurPubKeyStrFromCache();
// we only care about real members. If a member is currently a zombie we have to be able to add him back
let existingMembers = convo.get('members') || [];
// at least make sure it's an array
if (!Array.isArray(existingMembers)) {
existingMembers = [];
}
existingMembers = _.compact(existingMembers);
const existingZombies = convo.get('zombies') || [];
const newMembers = pubkeys.filter(d => !existingMembers.includes(d));
if (newMembers.length > 0) {
// Do not trigger an update if there is too many members
// be sure to include current zombies in this count
if (
newMembers.length + existingMembers.length + existingZombies.length >
VALIDATION.CLOSED_GROUP_SIZE_LIMIT
) {
ToastUtils.pushTooManyMembers();
return;
}
const allMembers = _.concat(existingMembers, newMembers, [ourPK]);
const uniqMembers = _.uniq(allMembers);
const groupId = convo.get('id');
const groupName = convo.get('name');
await initiateGroupUpdate(
groupId,
groupName || window.i18n('unknown'),
uniqMembers,
undefined
);
}
};
const renderMemberList = () => {
const members = contactList;
const selectedContacts = contactList
.filter((d: ContactType) => d.checkmarked)
.map((d: ContactType) => d.id);
return members.map((member: ContactType, index: number) => (
<SessionMemberListItem
member={member}
key={member.id}
index={index}
isSelected={selectedContacts.some(m => m === member.id)}
onSelect={(selectedMember: ContactType) => {
onMemberClicked(selectedMember);
}}
onUnselect={(selectedMember: ContactType) => {
onMemberClicked(selectedMember);
}}
/>
));
};
const onMemberClicked = (clickedMember: ContactType) => {
const updatedContacts = contactList.map((member: ContactType) => {
if (member.id === clickedMember.id) {
return { ...member, checkmarked: !member.checkmarked };
} else {
return member;
}
});
setContactList(updatedContacts);
};
const hasContacts = validContactsForInvite.length > 0;
return (
<SessionWrapperModal title={titleText} onClose={closeDialog}>
<SpacerLG />
<div className="contact-selection-list">{renderMemberList()}</div>
{hasContacts ? null : (
<>
<SpacerLG />
<p className="no-contacts">{window.i18n('noContactsToAdd')}</p>
<SpacerLG />
</>
)}
<div className="contact-selection-list">
{hasContacts ? (
validContactsForInvite.map((member: string) => (
<MemberListItem
key={member}
pubkey={member}
isSelected={selectedContacts.includes(member)}
onSelect={addTo}
onUnselect={removeFrom}
/>
))
) : (
<>
<SpacerLG />
<p className="no-contacts">{window.i18n('noContactsToAdd')}</p>
<SpacerLG />
</>
)}
</div>
<SpacerLG />
<div className="session-modal__button-group">

View File

@ -1,14 +1,14 @@
import React, { useState } from 'react';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils';
import { SessionSpinner } from '../session/SessionSpinner';
import { Flex } from '../basic/Flex';
import { ApiV2 } from '../../opengroup/opengroupV2';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { ApiV2 } from '../../session/apis/open_group_api/opengroupV2';
import { getConversationController } from '../../session/conversations';
import { useDispatch } from 'react-redux';
import { updateAddModeratorsModal } from '../../state/ducks/modalDialog';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionWrapperModal } from '../SessionWrapperModal';
type Props = {
conversationId: string;

View File

@ -1,217 +1,134 @@
import React from 'react';
import { ApiV2 } from '../../opengroup/opengroupV2';
import React, { useState } from 'react';
import { ApiV2 } from '../../session/apis/open_group_api/opengroupV2';
import { getConversationController } from '../../session/conversations';
import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils';
import { Flex } from '../basic/Flex';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { ContactType, SessionMemberListItem } from '../session/SessionMemberListItem';
import { SessionSpinner } from '../session/SessionSpinner';
import _ from 'lodash';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { updateRemoveModeratorsModal } from '../../state/ducks/modalDialog';
interface Props {
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { MemberListItem } from '../MemberListItem';
import { useDispatch } from 'react-redux';
import { useConversationPropsById } from '../../hooks/useParamSelector';
type Props = {
conversationId: string;
}
};
interface State {
modList: Array<ContactType>;
removingInProgress: boolean;
firstLoading: boolean;
}
export class RemoveModeratorsDialog extends React.Component<Props, State> {
constructor(props: any) {
super(props);
this.onModClicked = this.onModClicked.bind(this);
this.closeDialog = this.closeDialog.bind(this);
this.removeThem = this.removeThem.bind(this);
this.state = {
modList: [],
removingInProgress: false,
firstLoading: true,
};
async function removeMods(convoId: string, modsToRemove: Array<string>) {
if (modsToRemove.length === 0) {
window?.log?.info('No moderators removed. Nothing todo');
return false;
}
window?.log?.info(`asked to remove moderators: ${modsToRemove}`);
public componentDidMount() {
this.refreshModList();
}
try {
let res;
const convo = getConversationController().get(convoId);
public render() {
const { i18n } = window;
const { removingInProgress, firstLoading } = this.state;
const hasMods = this.state.modList.length !== 0;
const convo = getConversationController().get(this.props.conversationId);
const chatName = convo.get('name');
const title = `${i18n('removeModerators')}: ${chatName}`;
const renderContent = !firstLoading;
return (
<SessionWrapperModal title={title} onClose={this.closeDialog}>
<Flex container={true} flexDirection="column" alignItems="center">
{renderContent && (
<>
<p>Existing moderators:</p>
<div className="contact-selection-list">{this.renderMemberList()}</div>
{hasMods ? null : <p>{i18n('noModeratorsToRemove')}</p>}
<SessionSpinner loading={removingInProgress} />
<div className="session-modal__button-group">
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Green}
onClick={this.removeThem}
disabled={removingInProgress}
text={i18n('ok')}
/>
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Primary}
onClick={this.closeDialog}
disabled={removingInProgress}
text={i18n('cancel')}
/>
</div>
</>
)}
<SessionSpinner loading={firstLoading} />
</Flex>
</SessionWrapperModal>
const roomInfos = convo.toOpenGroupV2();
const modsToRemovePubkey = _.compact(modsToRemove.map(m => PubKey.from(m)));
res = await Promise.all(
modsToRemovePubkey.map(async m => {
return ApiV2.removeModerator(m, roomInfos);
})
);
}
// all moderators are removed means all promise resolved with bool= true
res = res.every(r => !!r);
private closeDialog() {
window.inboxStore?.dispatch(updateRemoveModeratorsModal(null));
}
if (!res) {
window?.log?.warn('failed to remove moderators:', res);
private renderMemberList() {
const members = this.state.modList;
const selectedContacts = members.filter(d => d.checkmarked).map(d => d.id);
return members.map((member: ContactType, index: number) => (
<SessionMemberListItem
member={member}
key={member.id}
index={index}
isSelected={selectedContacts.some(m => m === member.id)}
onSelect={(selectedMember: ContactType) => {
this.onModClicked(selectedMember);
}}
onUnselect={(selectedMember: ContactType) => {
this.onModClicked(selectedMember);
}}
/>
));
}
private onModClicked(selected: ContactType) {
const updatedContacts = this.state.modList.map(member => {
if (member.id === selected.id) {
return { ...member, checkmarked: !member.checkmarked };
} else {
return member;
}
});
this.setState(state => {
return {
...state,
modList: updatedContacts,
};
});
}
private refreshModList() {
let modPubKeys: Array<string> = [];
const convo = getConversationController().get(this.props.conversationId);
modPubKeys = convo.getGroupAdmins() || [];
const convos = getConversationController().getConversations();
const moderatorsConvos = modPubKeys
.map(
pubKey =>
convos.find(c => c.id === pubKey) || {
id: pubKey, // memberList need a key
authorPhoneNumber: pubKey,
}
)
.filter(c => !!c);
const mods = moderatorsConvos.map((d: any) => {
let name = '';
if (d.getLokiProfile) {
const lokiProfile = d.getLokiProfile();
name = lokiProfile ? lokiProfile.displayName : 'Anonymous';
}
// TODO: should take existing members into account
const existingMember = false;
return {
id: d.id,
authorPhoneNumber: d.id,
authorProfileName: name,
selected: false,
authorAvatarPath: '',
authorName: name,
checkmarked: true,
existingMember,
};
});
this.setState({
modList: mods,
firstLoading: false,
removingInProgress: false,
});
}
private async removeThem() {
const removedMods = this.state.modList.filter(d => !d.checkmarked).map(d => d.id);
if (removedMods.length === 0) {
window?.log?.info('No moderators removed. Nothing todo');
return;
}
window?.log?.info(`asked to remove moderator: ${removedMods}`);
try {
this.setState({
removingInProgress: true,
});
let res;
const convo = getConversationController().get(this.props.conversationId);
const roomInfos = convo.toOpenGroupV2();
const modsToRemove = _.compact(removedMods.map(m => PubKey.from(m)));
res = await Promise.all(
modsToRemove.map(async m => {
return ApiV2.removeModerator(m, roomInfos);
})
);
// all moderators are removed means all promise resolved with bool= true
res = res.every(r => !!r);
if (!res) {
window?.log?.warn('failed to remove moderators:', res);
ToastUtils.pushFailedToRemoveFromModerator();
} else {
window?.log?.info(`${removedMods} removed from moderators...`);
ToastUtils.pushUserRemovedFromModerators();
this.closeDialog();
}
} catch (e) {
window?.log?.error('Got error while removing moderator:', e);
} finally {
this.refreshModList();
ToastUtils.pushFailedToRemoveFromModerator();
return false;
} else {
window?.log?.info(`${modsToRemove} removed from moderators...`);
ToastUtils.pushUserRemovedFromModerators();
return true;
}
} catch (e) {
window?.log?.error('Got error while removing moderator:', e);
return false;
}
}
export const RemoveModeratorsDialog = (props: Props) => {
const { conversationId } = props;
const [removingInProgress, setRemovingInProgress] = useState(false);
const [modsToRemove, setModsToRemove] = useState<Array<string>>([]);
const { i18n } = window;
const dispatch = useDispatch();
const closeDialog = () => {
dispatch(updateRemoveModeratorsModal(null));
};
const removeModsCall = async () => {
if (modsToRemove.length) {
setRemovingInProgress(true);
const removed = await removeMods(conversationId, modsToRemove);
setRemovingInProgress(false);
if (removed) {
closeDialog();
}
}
};
const convoProps = useConversationPropsById(conversationId);
if (!convoProps || !convoProps.isPublic || !convoProps.weAreAdmin) {
throw new Error('RemoveModeratorsDialog: convoProps invalid');
}
const existingMods = convoProps.groupAdmins || [];
const hasMods = existingMods.length !== 0;
const title = `${i18n('removeModerators')}: ${convoProps.name}`;
return (
<SessionWrapperModal title={title} onClose={closeDialog}>
<Flex container={true} flexDirection="column" alignItems="center">
{hasMods ? (
<div className="contact-selection-list">
{existingMods.map(modId => (
<MemberListItem
key={modId}
pubkey={modId}
isSelected={modsToRemove.some(m => m === modId)}
onSelect={(selectedMember: string) => {
const updatedList = [...modsToRemove, selectedMember];
setModsToRemove(updatedList);
}}
onUnselect={(selectedMember: string) => {
const updatedList = modsToRemove.filter(m => m !== selectedMember);
setModsToRemove(updatedList);
}}
/>
))}
</div>
) : (
<p>{i18n('noModeratorsToRemove')}</p>
)}
<SessionSpinner loading={removingInProgress} />
<div className="session-modal__button-group">
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Green}
onClick={removeModsCall}
disabled={removingInProgress}
text={i18n('ok')}
/>
<SessionButton
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.Primary}
onClick={closeDialog}
disabled={removingInProgress}
text={i18n('cancel')}
/>
</div>
<SessionSpinner loading={removingInProgress} />
</Flex>
</SessionWrapperModal>
);
};

View File

@ -15,11 +15,11 @@ import {
getOnionPathsCount,
} from '../../state/selectors/onions';
import { Flex } from '../basic/Flex';
import { SessionIcon, SessionIconButton } from '../session/icon';
import { SessionSpinner } from '../session/SessionSpinner';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
// tslint:disable-next-line: no-submodule-imports
import useHover from 'react-use/lib/useHover';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionIcon, SessionIconButton } from '../icon';
import { SessionWrapperModal } from '../SessionWrapperModal';
export type StatusLightType = {
glowStartDelay: number;

View File

@ -1,11 +1,11 @@
import React, { useState } from 'react';
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
import { SessionHtmlRenderer } from '../session/SessionHTMLRenderer';
import { SessionIcon, SessionIconSize, SessionIconType } from '../session/icon';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { SpacerLG } from '../basic/Text';
import { SessionSpinner } from '../session/SessionSpinner';
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionIcon, SessionIconSize, SessionIconType } from '../icon';
import { SessionWrapperModal } from '../SessionWrapperModal';
export interface SessionConfirmDialogProps {
message?: string;

View File

@ -1,8 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import { SessionIconButton, SessionIconType } from '../session/icon';
import { SessionButtonColor, SessionButtonType } from '../session/SessionButton';
import { SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionIconButton, SessionIconType } from '../icon';
interface Props {
title: string;

View File

@ -5,8 +5,8 @@ import _ from 'lodash';
import { SpacerLG } from '../basic/Text';
import { useDispatch } from 'react-redux';
import { changeNickNameModal } from '../../state/ducks/modalDialog';
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
import { SessionWrapperModal } from '../session/SessionWrapperModal';
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal';
type Props = {
conversationId: string;

Some files were not shown because too many files have changed in this diff Show More