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:
parent
95e40c9509
commit
28c7445dce
10
Gruntfile.js
10
Gruntfile.js
|
@ -151,14 +151,6 @@ module.exports = grunt => {
|
||||||
grunt.file.write(configPath, `${JSON.stringify(localConfig)}\n`);
|
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.registerTask('getCommitHash', () => {
|
||||||
grunt.task.requires('gitinfo');
|
grunt.task.requires('gitinfo');
|
||||||
const gitinfo = grunt.config.get('gitinfo');
|
const gitinfo = grunt.config.get('gitinfo');
|
||||||
|
@ -167,7 +159,7 @@ module.exports = grunt => {
|
||||||
});
|
});
|
||||||
|
|
||||||
grunt.registerTask('dev', ['default', 'watch']);
|
grunt.registerTask('dev', ['default', 'watch']);
|
||||||
grunt.registerTask('date', ['gitinfo', 'getExpireTime']);
|
grunt.registerTask('date', ['gitinfo']);
|
||||||
grunt.registerTask('default', [
|
grunt.registerTask('default', [
|
||||||
'exec:build-protobuf',
|
'exec:build-protobuf',
|
||||||
'exec:transpile',
|
'exec:transpile',
|
||||||
|
|
|
@ -327,7 +327,7 @@
|
||||||
"addAsModerator": "Add as Moderator",
|
"addAsModerator": "Add as Moderator",
|
||||||
"removeFromModerators": "Remove From Moderators",
|
"removeFromModerators": "Remove From Moderators",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"addingContacts": "Adding contacts to",
|
"addingContacts": "Adding contacts to $name$",
|
||||||
"noContactsToAdd": "No contacts to add",
|
"noContactsToAdd": "No contacts to add",
|
||||||
"noMembersInThisGroup": "No other members in this group",
|
"noMembersInThisGroup": "No other members in this group",
|
||||||
"noModeratorsToRemove": "no moderators to remove",
|
"noModeratorsToRemove": "no moderators to remove",
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
],
|
],
|
||||||
"updatesEnabled": false,
|
"updatesEnabled": false,
|
||||||
"openDevTools": false,
|
"openDevTools": false,
|
||||||
"buildExpiration": 0,
|
|
||||||
"commitHash": "",
|
"commitHash": "",
|
||||||
"import": false,
|
"import": false
|
||||||
"serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,14 @@ const OS = require('../../ts/OS');
|
||||||
const Settings = require('./settings');
|
const Settings = require('./settings');
|
||||||
const Util = require('../../ts/util');
|
const Util = require('../../ts/util');
|
||||||
const LinkPreviews = require('./link_previews');
|
const LinkPreviews = require('./link_previews');
|
||||||
const { Message } = require('../../ts/components/conversation/Message');
|
const { Message } = require('../../ts/components/conversation/message/message-item/Message');
|
||||||
|
|
||||||
// Components
|
// 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
|
// Types
|
||||||
const AttachmentType = require('./types/attachment');
|
const AttachmentType = require('./types/attachment');
|
||||||
|
|
2
main.js
2
main.js
|
@ -154,7 +154,6 @@ function prepareURL(pathSegments, moreKeys) {
|
||||||
name: packageJson.productName,
|
name: packageJson.productName,
|
||||||
locale: locale.name,
|
locale: locale.name,
|
||||||
version: app.getVersion(),
|
version: app.getVersion(),
|
||||||
buildExpiration: config.get('buildExpiration'),
|
|
||||||
commitHash: config.get('commitHash'),
|
commitHash: config.get('commitHash'),
|
||||||
serverUrl: config.get('serverUrl'),
|
serverUrl: config.get('serverUrl'),
|
||||||
localUrl: config.get('localUrl'),
|
localUrl: config.get('localUrl'),
|
||||||
|
@ -167,7 +166,6 @@ function prepareURL(pathSegments, moreKeys) {
|
||||||
appInstance: process.env.NODE_APP_INSTANCE,
|
appInstance: process.env.NODE_APP_INSTANCE,
|
||||||
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
|
proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy,
|
||||||
contentProxyUrl: config.contentProxyUrl,
|
contentProxyUrl: config.contentProxyUrl,
|
||||||
serverTrustRoot: config.get('serverTrustRoot'),
|
|
||||||
appStartInitialSpellcheckSetting,
|
appStartInitialSpellcheckSetting,
|
||||||
...moreKeys,
|
...moreKeys,
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,7 +21,7 @@ window.getEnvironment = () => config.environment;
|
||||||
window.getVersion = () => config.version;
|
window.getVersion = () => config.version;
|
||||||
window.getAppInstance = () => config.appInstance;
|
window.getAppInstance = () => config.appInstance;
|
||||||
|
|
||||||
const { SessionPasswordPrompt } = require('./ts/components/session/SessionPasswordPrompt');
|
const { SessionPasswordPrompt } = require('./ts/components/SessionPasswordPrompt');
|
||||||
|
|
||||||
window.Signal = {
|
window.Signal = {
|
||||||
Components: {
|
Components: {
|
||||||
|
|
|
@ -30,11 +30,9 @@ window.getEnvironment = () => config.environment;
|
||||||
window.getAppInstance = () => config.appInstance;
|
window.getAppInstance = () => config.appInstance;
|
||||||
window.getVersion = () => config.version;
|
window.getVersion = () => config.version;
|
||||||
window.isDev = () => config.environment === 'development';
|
window.isDev = () => config.environment === 'development';
|
||||||
window.getExpiration = () => config.buildExpiration;
|
|
||||||
window.getCommitHash = () => config.commitHash;
|
window.getCommitHash = () => config.commitHash;
|
||||||
window.getNodeVersion = () => config.node_version;
|
window.getNodeVersion = () => config.node_version;
|
||||||
window.getHostName = () => config.hostname;
|
window.getHostName = () => config.hostname;
|
||||||
window.getServerTrustRoot = () => config.serverTrustRoot;
|
|
||||||
window.isBehindProxy = () => Boolean(config.proxyUrl);
|
window.isBehindProxy = () => Boolean(config.proxyUrl);
|
||||||
|
|
||||||
window.lokiFeatureFlags = {
|
window.lokiFeatureFlags = {
|
||||||
|
@ -214,7 +212,7 @@ window.Signal = Signal.setup({
|
||||||
logger: window.log,
|
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');
|
const WorkerInterface = require('./js/modules/util_worker_interface');
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ $borderAvatarColor: unquote(
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
object-fit: cover;
|
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 {
|
.module-avatar__icon--crown-wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0%;
|
bottom: 0%;
|
||||||
|
@ -129,7 +115,8 @@ $borderAvatarColor: unquote(
|
||||||
|
|
||||||
.module-avatar-clickable {
|
.module-avatar-clickable {
|
||||||
transition: $session-transition-duration;
|
transition: $session-transition-duration;
|
||||||
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: $session-subtle-factor;
|
filter: grayscale(0.7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,12 @@
|
||||||
padding: 1.1em;
|
padding: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.session-modal {
|
||||||
|
.contact-selection-list {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.create-group-dialog,
|
.create-group-dialog,
|
||||||
.add-moderators-dialog,
|
.add-moderators-dialog,
|
||||||
.remove-moderators-dialog,
|
.remove-moderators-dialog,
|
||||||
|
|
|
@ -494,6 +494,7 @@ label {
|
||||||
|
|
||||||
.group-member-list__selection {
|
.group-member-list__selection {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__centered {
|
&__centered {
|
||||||
|
|
|
@ -158,10 +158,8 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
.session-icon-button {
|
.session-icon-button {
|
||||||
// & > .session-icon-button {
|
|
||||||
margin-right: $session-margin-sm;
|
margin-right: $session-margin-sm;
|
||||||
}
|
|
||||||
.session-icon-button {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -169,7 +167,7 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(0.93);
|
filter: brightness(0.9);
|
||||||
transition: $session-transition-duration;
|
transition: $session-transition-duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ import * as GoogleChrome from '../util/GoogleChrome';
|
||||||
|
|
||||||
import { AttachmentType } from '../types/Attachment';
|
import { AttachmentType } from '../types/Attachment';
|
||||||
|
|
||||||
import { SessionInput } from './session/SessionInput';
|
|
||||||
import { SessionButton, SessionButtonColor, SessionButtonType } from './session/SessionButton';
|
|
||||||
import autoBind from 'auto-bind';
|
import autoBind from 'auto-bind';
|
||||||
|
import { SessionButton, SessionButtonColor, SessionButtonType } from './basic/SessionButton';
|
||||||
|
import { SessionInput } from './basic/SessionInput';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
attachment: AttachmentType;
|
attachment: AttachmentType;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { ContactType } from './session/SessionMemberListItem';
|
|
||||||
import { ToastUtils } from '../session/utils';
|
import { ToastUtils } from '../session/utils';
|
||||||
import { createClosedGroup as createClosedGroupV2 } from '../receiver/closedGroups';
|
import { createClosedGroup as createClosedGroupV2 } from '../receiver/closedGroups';
|
||||||
import { VALIDATION } from '../session/constants';
|
import { VALIDATION } from '../session/constants';
|
||||||
|
@ -38,7 +37,7 @@ export class MessageView extends React.Component {
|
||||||
*/
|
*/
|
||||||
async function createClosedGroup(
|
async function createClosedGroup(
|
||||||
groupName: string,
|
groupName: string,
|
||||||
groupMembers: Array<ContactType>
|
groupMemberIds: Array<string>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// Validate groupName and groupMembers length
|
// Validate groupName and groupMembers length
|
||||||
if (groupName.length === 0) {
|
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
|
// >= 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
|
// the same is valid with groups count < 1
|
||||||
|
|
||||||
if (groupMembers.length < 1) {
|
if (groupMemberIds.length < 1) {
|
||||||
ToastUtils.pushToastError('pickClosedGroupMember', window.i18n('pickClosedGroupMember'));
|
ToastUtils.pushToastError('pickClosedGroupMember', window.i18n('pickClosedGroupMember'));
|
||||||
return false;
|
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'));
|
ToastUtils.pushToastError('closedGroupMaxSize', window.i18n('closedGroupMaxSize'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupMemberIds = groupMembers.map(m => m.id);
|
|
||||||
|
|
||||||
await createClosedGroupV2(groupName, groupMemberIds);
|
await createClosedGroupV2(groupName, groupMemberIds);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,32 +1,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { getConversationController } from '../../session/conversations';
|
import { LeftPane } from './leftpane/LeftPane';
|
||||||
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';
|
|
||||||
|
|
||||||
// tslint:disable-next-line: no-submodule-imports
|
// tslint:disable-next-line: no-submodule-imports
|
||||||
import { PersistGate } from 'redux-persist/integration/react';
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import { persistStore } from 'redux-persist';
|
import { persistStore } from 'redux-persist';
|
||||||
import { TimerOptionsArray } from '../../state/ducks/timerOptions';
|
import { getConversationController } from '../session/conversations';
|
||||||
import { getEmptyStagedAttachmentsState } from '../../state/ducks/stagedAttachments';
|
import { UserUtils } from '../session/utils';
|
||||||
import { initialCallState } from '../../state/ducks/call';
|
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()
|
// Workaround: A react component's required properties are filtering up through connect()
|
||||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
|
@ -4,7 +4,7 @@ import { useAppIsFocused } from '../hooks/useAppFocused';
|
||||||
import { getFocusedSettingsSection } from '../state/selectors/section';
|
import { getFocusedSettingsSection } from '../state/selectors/section';
|
||||||
|
|
||||||
import { SmartSessionConversation } from '../state/smart/SessionConversation';
|
import { SmartSessionConversation } from '../state/smart/SessionConversation';
|
||||||
import { SessionSettingsView } from './session/settings/SessionSettings';
|
import { SessionSettingsView } from './settings/SessionSettings';
|
||||||
|
|
||||||
const FilteredSettingsView = SessionSettingsView as any;
|
const FilteredSettingsView = SessionSettingsView as any;
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { SessionIcon } from './icon';
|
import { SessionIcon } from './icon';
|
||||||
import { SessionButton, SessionButtonColor, SessionButtonType } from './SessionButton';
|
|
||||||
import { Constants } from '../../session';
|
|
||||||
import { withTheme } from 'styled-components';
|
import { withTheme } from 'styled-components';
|
||||||
import autoBind from 'auto-bind';
|
import autoBind from 'auto-bind';
|
||||||
|
import { SessionButton, SessionButtonColor, SessionButtonType } from './basic/SessionButton';
|
||||||
|
import { Constants } from '../session';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
error: string;
|
error: string;
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { getShowScrollButton } from '../../state/selectors/conversations';
|
import { getShowScrollButton } from '../state/selectors/conversations';
|
||||||
|
|
||||||
import { SessionIconButton } from './icon';
|
import { SessionIconButton } from './icon';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { getConversationsCount } from '../../state/selectors/conversations';
|
import { getConversationsCount } from '../state/selectors/conversations';
|
||||||
import { SessionIconButton } from './icon';
|
import { SessionIconButton } from './icon';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
|
@ -2,10 +2,10 @@ import React, { useEffect, useRef } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { SessionIconButton } from './icon/';
|
import { SessionIconButton } from './icon/';
|
||||||
import { SessionButton } from './SessionButton';
|
|
||||||
|
|
||||||
// tslint:disable-next-line: no-submodule-imports
|
// tslint:disable-next-line: no-submodule-imports
|
||||||
import useKey from 'react-use/lib/useKey';
|
import useKey from 'react-use/lib/useKey';
|
||||||
|
import { SessionButton } from './basic/SessionButton';
|
||||||
|
|
||||||
export type SessionWrapperModalType = {
|
export type SessionWrapperModalType = {
|
||||||
title?: string;
|
title?: string;
|
|
@ -1,15 +1,15 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useEncryptedFileFetch } from '../hooks/useEncryptedFileFetch';
|
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import {
|
import {
|
||||||
useAvatarPath,
|
useAvatarPath,
|
||||||
useConversationUsername,
|
useConversationUsername,
|
||||||
useIsClosedGroup,
|
useIsClosedGroup,
|
||||||
} from '../hooks/useParamSelector';
|
} from '../../hooks/useParamSelector';
|
||||||
import { AvatarPlaceHolder } from './AvatarPlaceHolder/AvatarPlaceHolder';
|
import { AvatarPlaceHolder } from './AvatarPlaceHolder/AvatarPlaceHolder';
|
||||||
import { ClosedGroupAvatar } from './AvatarPlaceHolder/ClosedGroupAvatar';
|
import { ClosedGroupAvatar } from './AvatarPlaceHolder/ClosedGroupAvatar';
|
||||||
import { useDisableDrag } from '../hooks/useDisableDrag';
|
import { useDisableDrag } from '../../hooks/useDisableDrag';
|
||||||
|
|
||||||
export enum AvatarSize {
|
export enum AvatarSize {
|
||||||
XS = 28,
|
XS = 28,
|
||||||
|
@ -111,10 +111,12 @@ const AvatarInner = (props: Props) => {
|
||||||
hasImage ? 'module-avatar--with-image' : 'module-avatar--no-image',
|
hasImage ? 'module-avatar--with-image' : 'module-avatar--no-image',
|
||||||
isClickable && 'module-avatar-clickable'
|
isClickable && 'module-avatar-clickable'
|
||||||
)}
|
)}
|
||||||
onClick={e => {
|
onMouseDown={e => {
|
||||||
e.stopPropagation();
|
if (props.onAvatarClick) {
|
||||||
e.preventDefault();
|
e.stopPropagation();
|
||||||
props.onAvatarClick?.();
|
e.preventDefault();
|
||||||
|
props.onAvatarClick?.();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
role="button"
|
role="button"
|
||||||
data-testid={dataTestId}
|
data-testid={dataTestId}
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { getInitials } from '../../util/getInitials';
|
import { getInitials } from '../../../util/getInitials';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
diameter: number;
|
diameter: number;
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useMembersAvatars } from '../../hooks/useMembersAvatars';
|
import { useMembersAvatars } from '../../../hooks/useMembersAvatars';
|
||||||
import { Avatar, AvatarSize } from '../Avatar';
|
import { Avatar, AvatarSize } from '../Avatar';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { SessionIcon, SessionIconType } from '../icon';
|
||||||
|
|
||||||
import { SessionIcon, SessionIconType } from './icon/';
|
|
||||||
import { SessionDropdownItem, SessionDropDownItemType } from './SessionDropdownItem';
|
import { SessionDropdownItem, SessionDropDownItemType } from './SessionDropdownItem';
|
||||||
|
|
||||||
// THIS IS DROPDOWN ACCORDIAN STYLE OPTIONS SELECTOR ELEMENT, NOT A CONTEXTMENU
|
// THIS IS DROPDOWN ACCORDIAN STYLE OPTIONS SELECTOR ELEMENT, NOT A CONTEXTMENU
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { SessionIcon, SessionIconType } from '../icon';
|
||||||
import { SessionIcon, SessionIconType } from './icon/';
|
|
||||||
|
|
||||||
export enum SessionDropDownItemType {
|
export enum SessionDropDownItemType {
|
||||||
Default = 'default',
|
Default = 'default',
|
|
@ -20,6 +20,7 @@ export const SessionHtmlRenderer: React.SFC<Props> = ({ tag = 'div', key, html,
|
||||||
return React.createElement(tag, {
|
return React.createElement(tag, {
|
||||||
key,
|
key,
|
||||||
className,
|
className,
|
||||||
|
// tslint:disable-next-line: react-no-dangerous-html
|
||||||
dangerouslySetInnerHTML: { __html: clean },
|
dangerouslySetInnerHTML: { __html: clean },
|
||||||
});
|
});
|
||||||
};
|
};
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { SessionIconButton } from './icon';
|
import { SessionIconButton } from '../icon';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label?: string;
|
label?: string;
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { SessionIcon, SessionIconType } from './icon/';
|
|
||||||
import { Flex } from '../basic/Flex';
|
import { Flex } from '../basic/Flex';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
import { SessionIcon, SessionIconType } from '../icon';
|
||||||
|
|
||||||
export enum SessionToastType {
|
export enum SessionToastType {
|
||||||
Info = 'info',
|
Info = 'info',
|
|
@ -1,11 +1,11 @@
|
||||||
import { SessionIconButton } from '../icon';
|
import { SessionIconButton } from '../icon';
|
||||||
import { animation, contextMenu, Item, Menu } from 'react-contexify';
|
import { animation, contextMenu, Item, Menu } from 'react-contexify';
|
||||||
import { InputItem } from '../../../session/utils/calling/CallManager';
|
import { InputItem } from '../../session/utils/calling/CallManager';
|
||||||
import { setFullScreenCall } from '../../../state/ducks/call';
|
import { setFullScreenCall } from '../../state/ducks/call';
|
||||||
import { CallManager, ToastUtils } from '../../../session/utils';
|
import { CallManager, ToastUtils } from '../../session/utils';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { getHasOngoingCallWithPubkey } from '../../../state/selectors/call';
|
import { getHasOngoingCallWithPubkey } from '../../state/selectors/call';
|
||||||
import { DropDownAndToggleButton } from '../icon/DropDownAndToggleButton';
|
import { DropDownAndToggleButton } from '../icon/DropDownAndToggleButton';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
|
@ -3,12 +3,12 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||||
// tslint:disable-next-line: no-submodule-imports
|
// tslint:disable-next-line: no-submodule-imports
|
||||||
import useKey from 'react-use/lib/useKey';
|
import useKey from 'react-use/lib/useKey';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener';
|
import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
|
||||||
import { setFullScreenCall } from '../../../state/ducks/call';
|
import { setFullScreenCall } from '../../state/ducks/call';
|
||||||
import {
|
import {
|
||||||
getCallIsInFullScreen,
|
getCallIsInFullScreen,
|
||||||
getHasOngoingCallWithFocusedConvo,
|
getHasOngoingCallWithFocusedConvo,
|
||||||
} from '../../../state/selectors/call';
|
} from '../../state/selectors/call';
|
||||||
import { CallWindowControls } from './CallButtons';
|
import { CallWindowControls } from './CallButtons';
|
||||||
import { StyledVideoElement } from './DraggableCallContainer';
|
import { StyledVideoElement } from './DraggableCallContainer';
|
||||||
|
|
|
@ -4,11 +4,11 @@ import Draggable, { DraggableData, DraggableEvent } from 'react-draggable';
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import { getSelectedConversationKey } from '../../../state/selectors/conversations';
|
import { getSelectedConversationKey } from '../../state/selectors/conversations';
|
||||||
import { getHasOngoingCall, getHasOngoingCallWith } from '../../../state/selectors/call';
|
import { getHasOngoingCall, getHasOngoingCallWith } from '../../state/selectors/call';
|
||||||
import { openConversationWithMessages } from '../../../state/ducks/conversations';
|
import { openConversationWithMessages } from '../../state/ducks/conversations';
|
||||||
import { Avatar, AvatarSize } from '../../Avatar';
|
import { Avatar, AvatarSize } from '../avatar/Avatar';
|
||||||
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener';
|
import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
|
||||||
import { VideoLoadingSpinner } from './InConversationCallContainer';
|
import { VideoLoadingSpinner } from './InConversationCallContainer';
|
||||||
|
|
||||||
export const DraggableCallWindow = styled.div`
|
export const DraggableCallWindow = styled.div`
|
|
@ -3,7 +3,7 @@ import React, { useRef, useState } from 'react';
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import { CallManager, UserUtils } from '../../../session/utils';
|
import { CallManager, UserUtils } from '../../session/utils';
|
||||||
import {
|
import {
|
||||||
getCallIsInFullScreen,
|
getCallIsInFullScreen,
|
||||||
getCallWithFocusedConvoIsOffering,
|
getCallWithFocusedConvoIsOffering,
|
||||||
|
@ -11,18 +11,18 @@ import {
|
||||||
getCallWithFocusedConvosIsConnecting,
|
getCallWithFocusedConvosIsConnecting,
|
||||||
getHasOngoingCallWithFocusedConvo,
|
getHasOngoingCallWithFocusedConvo,
|
||||||
getHasOngoingCallWithPubkey,
|
getHasOngoingCallWithPubkey,
|
||||||
} from '../../../state/selectors/call';
|
} from '../../state/selectors/call';
|
||||||
import { StyledVideoElement } from './DraggableCallContainer';
|
import { StyledVideoElement } from './DraggableCallContainer';
|
||||||
import { Avatar, AvatarSize } from '../../Avatar';
|
import { Avatar, AvatarSize } from '../avatar/Avatar';
|
||||||
|
|
||||||
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener';
|
import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
|
||||||
import { useModuloWithTripleDots } from '../../../hooks/useModuloWithTripleDots';
|
import { useModuloWithTripleDots } from '../../hooks/useModuloWithTripleDots';
|
||||||
import { CallWindowControls } from './CallButtons';
|
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
|
// tslint:disable-next-line: no-submodule-imports
|
||||||
import useInterval from 'react-use/lib/useInterval';
|
import useInterval from 'react-use/lib/useInterval';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { SessionSpinner } from '../basic/SessionSpinner';
|
||||||
|
|
||||||
const VideoContainer = styled.div`
|
const VideoContainer = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
|
@ -3,13 +3,13 @@ import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import { useConversationUsername } from '../../../hooks/useParamSelector';
|
import { useConversationUsername } from '../../hooks/useParamSelector';
|
||||||
import { ed25519Str } from '../../../session/onions/onionPath';
|
import { ed25519Str } from '../../session/onions/onionPath';
|
||||||
import { CallManager } from '../../../session/utils';
|
import { CallManager } from '../../session/utils';
|
||||||
import { callTimeoutMs } from '../../../session/utils/calling/CallManager';
|
import { callTimeoutMs } from '../../session/utils/calling/CallManager';
|
||||||
import { getHasIncomingCall, getHasIncomingCallFrom } from '../../../state/selectors/call';
|
import { getHasIncomingCall, getHasIncomingCallFrom } from '../../state/selectors/call';
|
||||||
import { Avatar, AvatarSize } from '../../Avatar';
|
import { Avatar, AvatarSize } from '../avatar/Avatar';
|
||||||
import { SessionButton, SessionButtonColor } from '../SessionButton';
|
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
|
||||||
import { SessionWrapperModal } from '../SessionWrapperModal';
|
import { SessionWrapperModal } from '../SessionWrapperModal';
|
||||||
|
|
||||||
export const CallWindow = styled.div`
|
export const CallWindow = styled.div`
|
|
@ -1,11 +1,7 @@
|
||||||
import React from 'react';
|
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 { contextMenu } from 'react-contexify';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { ConversationNotificationSettingType } from '../../models/conversation';
|
import { ConversationNotificationSettingType } from '../../models/conversation';
|
||||||
|
@ -38,6 +34,9 @@ import {
|
||||||
import { callRecipient } from '../../interactions/conversationInteractions';
|
import { callRecipient } from '../../interactions/conversationInteractions';
|
||||||
import { getHasIncomingCall, getHasOngoingCall } from '../../state/selectors/call';
|
import { getHasIncomingCall, getHasOngoingCall } from '../../state/selectors/call';
|
||||||
import { useConversationUsername } from '../../hooks/useParamSelector';
|
import { useConversationUsername } from '../../hooks/useParamSelector';
|
||||||
|
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
|
||||||
|
import { SessionIconButton } from '../icon';
|
||||||
|
import { MemoConversationHeaderMenu } from '../menu/ConversationHeaderMenu';
|
||||||
|
|
||||||
export interface TimerOption {
|
export interface TimerOption {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React, { useCallback, useState } from 'react';
|
||||||
import { getTimerBucketIcon } from '../../util/timer';
|
import { getTimerBucketIcon } from '../../util/timer';
|
||||||
import { useInterval } from '../../hooks/useInterval';
|
import { useInterval } from '../../hooks/useInterval';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { SessionIcon } from '../session/icon';
|
import { SessionIcon } from '../icon/SessionIcon';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
expirationLength: number;
|
expirationLength: number;
|
||||||
|
|
|
@ -8,8 +8,8 @@ import {
|
||||||
PropsForGroupUpdateType,
|
PropsForGroupUpdateType,
|
||||||
} from '../../state/ducks/conversations';
|
} from '../../state/ducks/conversations';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import { ReadableMessage } from './ReadableMessage';
|
import { NotificationBubble } from './message/message-item/notification-bubble/NotificationBubble';
|
||||||
import { NotificationBubble } from './notification-bubble/NotificationBubble';
|
import { ReadableMessage } from './message/message-item/ReadableMessage';
|
||||||
|
|
||||||
// This component is used to display group updates in the conversation view.
|
// 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
|
// This is a not a "notification" as the name suggests, but a message inside the conversation
|
||||||
|
|
|
@ -9,8 +9,8 @@ import {
|
||||||
getSortedMessagesOfSelectedConversation,
|
getSortedMessagesOfSelectedConversation,
|
||||||
} from '../../state/selectors/conversations';
|
} from '../../state/selectors/conversations';
|
||||||
import { getAudioAutoplay } from '../../state/selectors/userConfig';
|
import { getAudioAutoplay } from '../../state/selectors/userConfig';
|
||||||
import { SessionIcon } from '../session/icon';
|
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
|
||||||
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
|
import { SessionIcon } from '../icon';
|
||||||
|
|
||||||
export const AudioPlayerWithEncryptedFile = (props: {
|
export const AudioPlayerWithEncryptedFile = (props: {
|
||||||
src: string;
|
src: string;
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
} from '../../types/Attachment';
|
} from '../../types/Attachment';
|
||||||
|
|
||||||
import { Image } from './Image';
|
import { Image } from './Image';
|
||||||
import { IsMessageVisibleContext } from './message/MessageContent';
|
import { IsMessageVisibleContext } from './message/message-content/MessageContent';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
attachments: Array<AttachmentTypeWithPath>;
|
attachments: Array<AttachmentTypeWithPath>;
|
||||||
|
|
|
@ -8,36 +8,38 @@ import {
|
||||||
StagedAttachmentType,
|
StagedAttachmentType,
|
||||||
} from './composition/CompositionBox';
|
} from './composition/CompositionBox';
|
||||||
|
|
||||||
import { Constants } from '../../../session';
|
|
||||||
import _ from 'lodash';
|
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 { SessionMessagesListContainer } from './SessionMessagesListContainer';
|
||||||
import { ToastUtils, UserUtils } from '../../../session/utils';
|
|
||||||
import * as MIME from '../../../types/MIME';
|
|
||||||
import { SessionFileDropzone } from './SessionFileDropzone';
|
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 {
|
import {
|
||||||
quoteMessage,
|
quoteMessage,
|
||||||
ReduxConversationType,
|
ReduxConversationType,
|
||||||
resetSelectedMessageIds,
|
resetSelectedMessageIds,
|
||||||
SortedMessageModelProps,
|
SortedMessageModelProps,
|
||||||
updateMentionsMembers,
|
updateMentionsMembers,
|
||||||
} from '../../../state/ducks/conversations';
|
} from '../../state/ducks/conversations';
|
||||||
import { MessageView } from '../../MainViewController';
|
import { updateConfirmModal } from '../../state/ducks/modalDialog';
|
||||||
import { MessageDetail } from '../../conversation/MessageDetail';
|
import { SessionTheme } from '../../state/ducks/SessionTheme';
|
||||||
import { getConversationController } from '../../../session/conversations';
|
import { addStagedAttachmentsInConversation } from '../../state/ducks/stagedAttachments';
|
||||||
import { getPubkeysInPublicConversation } from '../../../data/data';
|
import { MIME } from '../../types';
|
||||||
import autoBind from 'auto-bind';
|
import { AttachmentTypeWithPath } from '../../types/Attachment';
|
||||||
import { SessionButtonColor } from '../SessionButton';
|
import { AttachmentUtil, GoogleChrome } from '../../util';
|
||||||
import { updateConfirmModal } from '../../../state/ducks/modalDialog';
|
import { SessionButtonColor } from '../basic/SessionButton';
|
||||||
import { addStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments';
|
import { MessageView } from '../MainViewController';
|
||||||
import { InConversationCallContainer } from '../calling/InConversationCallContainer';
|
import { ConversationHeaderWithDetails } from './ConversationHeader';
|
||||||
import { SplitViewContainer } from '../SplitViewContainer';
|
import { MessageDetail } from './message/message-item/MessageDetail';
|
||||||
|
import { SessionRightPanelWithDetails } from './SessionRightPanel';
|
||||||
// tslint:disable: jsx-curly-spacing
|
// tslint:disable: jsx-curly-spacing
|
||||||
|
|
||||||
interface State {
|
interface State {
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Picker } from 'emoji-mart';
|
import { Picker } from 'emoji-mart';
|
||||||
import { Constants } from '../../../session';
|
import { Constants } from '../../session';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onEmojiClicked: (emoji: any) => void;
|
onEmojiClicked: (emoji: any) => void;
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Flex } from '../../basic/Flex';
|
import { Flex } from '../basic/Flex';
|
||||||
import { SessionIcon } from '../icon';
|
import { SessionIcon } from '../icon';
|
||||||
|
|
||||||
const DropZoneContainer = styled.div`
|
const DropZoneContainer = styled.div`
|
|
@ -2,22 +2,23 @@ import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
// tslint:disable-next-line: no-submodule-imports
|
// tslint:disable-next-line: no-submodule-imports
|
||||||
import useKey from 'react-use/lib/useKey';
|
import useKey from 'react-use/lib/useKey';
|
||||||
import { PropsForDataExtractionNotification, QuoteClickOptions } from '../../../models/messageType';
|
import { PropsForDataExtractionNotification, QuoteClickOptions } from '../../models/messageType';
|
||||||
import {
|
import {
|
||||||
PropsForCallNotification,
|
PropsForCallNotification,
|
||||||
PropsForExpirationTimer,
|
PropsForExpirationTimer,
|
||||||
PropsForGroupInvitation,
|
PropsForGroupInvitation,
|
||||||
PropsForGroupUpdate,
|
PropsForGroupUpdate,
|
||||||
} from '../../../state/ducks/conversations';
|
} from '../../state/ducks/conversations';
|
||||||
import { getSortedMessagesTypesOfSelectedConversation } from '../../../state/selectors/conversations';
|
import { getSortedMessagesTypesOfSelectedConversation } from '../../state/selectors/conversations';
|
||||||
import { CallNotification } from '../../conversation/notification-bubble/CallNotification';
|
import { GroupNotification } from './GroupNotification';
|
||||||
import { DataExtractionNotification } from '../../conversation/DataExtractionNotification';
|
import { DataExtractionNotification } from './message/message-item/DataExtractionNotification';
|
||||||
import { GroupInvitation } from '../../conversation/GroupInvitation';
|
import { MessageDateBreak } from './message/message-item/DateBreak';
|
||||||
import { GroupNotification } from '../../conversation/GroupNotification';
|
import { GroupInvitation } from './message/message-item/GroupInvitation';
|
||||||
import { Message } from '../../conversation/Message';
|
import { Message } from './message/message-item/Message';
|
||||||
import { MessageDateBreak } from '../../conversation/message/DateBreak';
|
import { CallNotification } from './message/message-item/notification-bubble/CallNotification';
|
||||||
import { TimerNotification } from '../../conversation/TimerNotification';
|
|
||||||
import { SessionLastSeenIndicator } from './SessionLastSeenIndicator';
|
import { SessionLastSeenIndicator } from './SessionLastSeenIndicator';
|
||||||
|
import { TimerNotification } from './TimerNotification';
|
||||||
|
|
||||||
export const SessionMessagesList = (props: {
|
export const SessionMessagesList = (props: {
|
||||||
scrollToQuoteMessage: (options: QuoteClickOptions) => Promise<void>;
|
scrollToQuoteMessage: (options: QuoteClickOptions) => Promise<void>;
|
|
@ -2,23 +2,26 @@ import React from 'react';
|
||||||
|
|
||||||
import { SessionScrollButton } from '../SessionScrollButton';
|
import { SessionScrollButton } from '../SessionScrollButton';
|
||||||
import { contextMenu } from 'react-contexify';
|
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 {
|
import {
|
||||||
quotedMessageToAnimate,
|
quotedMessageToAnimate,
|
||||||
ReduxConversationType,
|
ReduxConversationType,
|
||||||
showScrollToBottomButton,
|
showScrollToBottomButton,
|
||||||
SortedMessageModelProps,
|
SortedMessageModelProps,
|
||||||
updateHaveDoneFirstScroll,
|
updateHaveDoneFirstScroll,
|
||||||
} from '../../../state/ducks/conversations';
|
} from '../../state/ducks/conversations';
|
||||||
import { ToastUtils } from '../../../session/utils';
|
import { StateType } from '../../state/reducer';
|
||||||
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';
|
|
||||||
import {
|
import {
|
||||||
getFirstUnreadMessageId,
|
getFirstUnreadMessageId,
|
||||||
getQuotedMessageToAnimate,
|
getQuotedMessageToAnimate,
|
||||||
|
@ -27,9 +30,8 @@ import {
|
||||||
getShowScrollButton,
|
getShowScrollButton,
|
||||||
getSortedMessagesOfSelectedConversation,
|
getSortedMessagesOfSelectedConversation,
|
||||||
isFirstUnreadMessageIdAbove,
|
isFirstUnreadMessageIdAbove,
|
||||||
} from '../../../state/selectors/conversations';
|
} from '../../state/selectors/conversations';
|
||||||
import { SessionMessagesList } from './SessionMessagesList';
|
import { TypingBubble } from './TypingBubble';
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
export type SessionMessageListProps = {
|
export type SessionMessageListProps = {
|
||||||
messageContainerRef: React.RefObject<HTMLDivElement>;
|
messageContainerRef: React.RefObject<HTMLDivElement>;
|
|
@ -1,13 +1,13 @@
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { Flex } from '../../basic/Flex';
|
|
||||||
import { SessionIcon, SessionIconButton } from '../icon';
|
import { SessionIcon, SessionIconButton } from '../icon';
|
||||||
import styled from 'styled-components';
|
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 { 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`
|
const QuotedMessageComposition = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
|
@ -3,11 +3,11 @@ import classNames from 'classnames';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import { SessionIconButton } from '../icon';
|
import { SessionIconButton } from '../icon';
|
||||||
import { Constants } from '../../../session';
|
|
||||||
import { ToastUtils } from '../../../session/utils';
|
|
||||||
import autoBind from 'auto-bind';
|
import autoBind from 'auto-bind';
|
||||||
import MicRecorder from 'mic-recorder-to-mp3';
|
import MicRecorder from 'mic-recorder-to-mp3';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Constants } from '../../session';
|
||||||
|
import { ToastUtils } from '../../session/utils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onExitVoiceNoteView: () => void;
|
onExitVoiceNoteView: () => void;
|
||||||
|
@ -124,7 +124,7 @@ export class SessionRecording extends React.Component<Props, State> {
|
||||||
<StyledFlexWrapper marginHorizontal={Constants.UI.SPACING.marginXs}>
|
<StyledFlexWrapper marginHorizontal={Constants.UI.SPACING.marginXs}>
|
||||||
{isRecording && (
|
{isRecording && (
|
||||||
<SessionIconButton
|
<SessionIconButton
|
||||||
iconType="pause"
|
iconType="stop"
|
||||||
iconSize="medium"
|
iconSize="medium"
|
||||||
iconColor={Constants.UI.COLORS.DANGER_ALT}
|
iconColor={Constants.UI.COLORS.DANGER_ALT}
|
||||||
onClick={actionPauseFn}
|
onClick={actionPauseFn}
|
|
@ -1,17 +1,13 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { SessionIconButton } from '../icon';
|
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 _ from 'lodash';
|
||||||
import { Constants } from '../../../session';
|
// tslint:disable-next-line: no-submodule-imports
|
||||||
import { AttachmentTypeWithPath } from '../../../types/Attachment';
|
import useInterval from 'react-use/lib/useInterval';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getMessagesWithFileAttachments,
|
getMessagesWithFileAttachments,
|
||||||
getMessagesWithVisualMediaAttachments,
|
getMessagesWithVisualMediaAttachments,
|
||||||
} from '../../../data/data';
|
} from '../../data/data';
|
||||||
import { SpacerLG } from '../../basic/Text';
|
|
||||||
import {
|
import {
|
||||||
deleteAllMessagesByConvoIdWithConfirmation,
|
deleteAllMessagesByConvoIdWithConfirmation,
|
||||||
setDisappearingMessagesByConvoId,
|
setDisappearingMessagesByConvoId,
|
||||||
|
@ -21,17 +17,18 @@ import {
|
||||||
showRemoveModeratorsByConvoId,
|
showRemoveModeratorsByConvoId,
|
||||||
showUpdateGroupMembersByConvoId,
|
showUpdateGroupMembersByConvoId,
|
||||||
showUpdateGroupNameByConvoId,
|
showUpdateGroupNameByConvoId,
|
||||||
} from '../../../interactions/conversationInteractions';
|
} from '../../interactions/conversationInteractions';
|
||||||
import { MediaItemType } from '../../LightboxGallery';
|
import { Constants } from '../../session';
|
||||||
// tslint:disable-next-line: no-submodule-imports
|
import { closeRightPanel } from '../../state/ducks/conversations';
|
||||||
import useInterval from 'react-use/lib/useInterval';
|
import { getSelectedConversation, isRightPanelShowing } from '../../state/selectors/conversations';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { getTimerOptions } from '../../state/selectors/timerOptions';
|
||||||
import { getTimerOptions } from '../../../state/selectors/timerOptions';
|
import { AttachmentTypeWithPath } from '../../types/Attachment';
|
||||||
import {
|
import { Avatar, AvatarSize } from '../avatar/Avatar';
|
||||||
getSelectedConversation,
|
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
|
||||||
isRightPanelShowing,
|
import { SessionDropdown } from '../basic/SessionDropdown';
|
||||||
} from '../../../state/selectors/conversations';
|
import { SpacerLG } from '../basic/Text';
|
||||||
import { closeRightPanel } from '../../../state/ducks/conversations';
|
import { MediaItemType } from '../lightbox/LightboxGallery';
|
||||||
|
import { MediaGallery } from './media-gallery/MediaGallery';
|
||||||
|
|
||||||
async function getMediaGalleryProps(
|
async function getMediaGalleryProps(
|
||||||
conversationId: string
|
conversationId: string
|
|
@ -1,11 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { arrayBufferFromFile } from '../../../types/Attachment';
|
|
||||||
import { AttachmentUtil, LinkPreviewUtil } from '../../../util';
|
|
||||||
import { StagedLinkPreviewData } from './composition/CompositionBox';
|
import { StagedLinkPreviewData } from './composition/CompositionBox';
|
||||||
import { default as insecureNodeFetch } from 'node-fetch';
|
import { default as insecureNodeFetch } from 'node-fetch';
|
||||||
import { fetchLinkPreviewImage } from '../../../util/linkPreviewFetch';
|
|
||||||
import { AbortSignal } from 'abort-controller';
|
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 {
|
export interface StagedLinkPreviewProps extends StagedLinkPreviewData {
|
||||||
onClose: (url: string) => void;
|
onClose: (url: string) => void;
|
|
@ -4,7 +4,7 @@ import classNames from 'classnames';
|
||||||
import { Image } from './Image';
|
import { Image } from './Image';
|
||||||
|
|
||||||
import { AttachmentType, isImageAttachment } from '../../types/Attachment';
|
import { AttachmentType, isImageAttachment } from '../../types/Attachment';
|
||||||
import { SessionSpinner } from '../session/SessionSpinner';
|
import { SessionSpinner } from '../basic/SessionSpinner';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isLoaded: boolean;
|
isLoaded: boolean;
|
||||||
|
|
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||||
|
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
import { PropsForExpirationTimer } from '../../state/ducks/conversations';
|
import { PropsForExpirationTimer } from '../../state/ducks/conversations';
|
||||||
import { ReadableMessage } from './ReadableMessage';
|
import { NotificationBubble } from './message/message-item/notification-bubble/NotificationBubble';
|
||||||
import { NotificationBubble } from './notification-bubble/NotificationBubble';
|
import { ReadableMessage } from './message/message-item/ReadableMessage';
|
||||||
|
|
||||||
export const TimerNotification = (props: PropsForExpirationTimer) => {
|
export const TimerNotification = (props: PropsForExpirationTimer) => {
|
||||||
const { messageId, receivedAt, isUnread, pubkey, profileName, timespan, type, disabled } = props;
|
const { messageId, receivedAt, isUnread, pubkey, profileName, timespan, type, disabled } = props;
|
||||||
|
|
|
@ -1,19 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import _, { debounce } from 'lodash';
|
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 { SessionEmojiPanel } from '../SessionEmojiPanel';
|
||||||
import { SessionRecording } from '../SessionRecording';
|
import { SessionRecording } from '../SessionRecording';
|
||||||
|
|
||||||
import { Constants } from '../../../../session';
|
|
||||||
|
|
||||||
import { toArray } from 'react-emoji-render';
|
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 {
|
import {
|
||||||
getPreview,
|
getPreview,
|
||||||
LINK_PREVIEW_TIMEOUT,
|
LINK_PREVIEW_TIMEOUT,
|
||||||
|
@ -22,31 +15,37 @@ import {
|
||||||
import { AbortController } from 'abort-controller';
|
import { AbortController } from 'abort-controller';
|
||||||
import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition';
|
import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition';
|
||||||
import { Mention, MentionsInput } from 'react-mentions';
|
import { Mention, MentionsInput } from 'react-mentions';
|
||||||
import { CaptionEditor } from '../../../CaptionEditor';
|
import { MemberListItem } from '../../MemberListItem';
|
||||||
import { getConversationController } from '../../../../session/conversations';
|
|
||||||
import { ReduxConversationType } from '../../../../state/ducks/conversations';
|
|
||||||
import { SessionMemberListItem } from '../../SessionMemberListItem';
|
|
||||||
import autoBind from 'auto-bind';
|
import autoBind from 'auto-bind';
|
||||||
import { getMediaPermissionsSettings } from '../../settings/SessionSettings';
|
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 { getDraftForConversation, updateDraftForConversation } from '../SessionConversationDrafts';
|
||||||
import { showLinkSharingConfirmationModalDialog } from '../../../../interactions/conversationInteractions';
|
|
||||||
import {
|
import {
|
||||||
AddStagedAttachmentButton,
|
AddStagedAttachmentButton,
|
||||||
SendMessageButton,
|
SendMessageButton,
|
||||||
StartRecordingButton,
|
StartRecordingButton,
|
||||||
ToggleEmojiButton,
|
ToggleEmojiButton,
|
||||||
} from './CompositionButtons';
|
} 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 {
|
export interface ReplyingToMessageProps {
|
||||||
convoId: string;
|
convoId: string;
|
||||||
|
@ -413,7 +412,6 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
||||||
? i18n('unblockGroupToSend')
|
? i18n('unblockGroupToSend')
|
||||||
: i18n('sendMessage');
|
: i18n('sendMessage');
|
||||||
const { typingEnabled } = this.props;
|
const { typingEnabled } = this.props;
|
||||||
let index = 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MentionsInput
|
<MentionsInput
|
||||||
|
@ -439,21 +437,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
||||||
displayTransform={(_id, display) => `@${display}`}
|
displayTransform={(_id, display) => `@${display}`}
|
||||||
data={this.fetchUsersForGroup}
|
data={this.fetchUsersForGroup}
|
||||||
renderSuggestion={(suggestion, _search, _highlightedDisplay, _index, focused) => (
|
renderSuggestion={(suggestion, _search, _highlightedDisplay, _index, focused) => (
|
||||||
<SessionMemberListItem
|
<MemberListItem isSelected={focused} key={suggestion.id} pubkey={`${suggestion.id}`} />
|
||||||
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: '',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</MentionsInput>
|
</MentionsInput>
|
|
@ -2,8 +2,8 @@ import React from 'react';
|
||||||
|
|
||||||
import { DocumentListItem } from './DocumentListItem';
|
import { DocumentListItem } from './DocumentListItem';
|
||||||
import { MediaGridItem } from './MediaGridItem';
|
import { MediaGridItem } from './MediaGridItem';
|
||||||
import { MediaItemType } from '../../LightboxGallery';
|
|
||||||
import { missingCaseError } from '../../../util/missingCaseError';
|
import { missingCaseError } from '../../../util/missingCaseError';
|
||||||
|
import { MediaItemType } from '../../lightbox/LightboxGallery';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type: 'media' | 'documents';
|
type: 'media' | 'documents';
|
||||||
|
|
|
@ -4,10 +4,10 @@ import classNames from 'classnames';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
// tslint:disable-next-line:match-default-export-name
|
// tslint:disable-next-line:match-default-export-name
|
||||||
import formatFileSize from 'filesize';
|
import formatFileSize from 'filesize';
|
||||||
import { MediaItemType } from '../../LightboxGallery';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { getSelectedConversationKey } from '../../../state/selectors/conversations';
|
import { getSelectedConversationKey } from '../../../state/selectors/conversations';
|
||||||
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
|
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
|
||||||
|
import { MediaItemType } from '../../lightbox/LightboxGallery';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
// Required
|
// Required
|
||||||
|
|
|
@ -3,8 +3,8 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import { AttachmentSection } from './AttachmentSection';
|
import { AttachmentSection } from './AttachmentSection';
|
||||||
import { EmptyState } from './EmptyState';
|
import { EmptyState } from './EmptyState';
|
||||||
|
import { MediaItemType } from '../../lightbox/LightboxGallery';
|
||||||
|
|
||||||
import { MediaItemType } from '../../LightboxGallery';
|
|
||||||
type Props = {
|
type Props = {
|
||||||
documents: Array<MediaItemType>;
|
documents: Array<MediaItemType>;
|
||||||
media: Array<MediaItemType>;
|
media: Array<MediaItemType>;
|
||||||
|
|
|
@ -2,11 +2,11 @@ import React, { useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome';
|
import { isImageTypeSupported, isVideoTypeSupported } from '../../../util/GoogleChrome';
|
||||||
import { MediaItemType } from '../../LightboxGallery';
|
|
||||||
import { useEncryptedFileFetch } from '../../../hooks/useEncryptedFileFetch';
|
import { useEncryptedFileFetch } from '../../../hooks/useEncryptedFileFetch';
|
||||||
import { showLightBox } from '../../../state/ducks/conversations';
|
import { showLightBox } from '../../../state/ducks/conversations';
|
||||||
import { LightBoxOptions } from '../../session/conversation/SessionConversation';
|
|
||||||
import { useDisableDrag } from '../../../hooks/useDisableDrag';
|
import { useDisableDrag } from '../../../hooks/useDisableDrag';
|
||||||
|
import { LightBoxOptions } from '../SessionConversation';
|
||||||
|
import { MediaItemType } from '../../lightbox/LightboxGallery';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
mediaItem: MediaItemType;
|
mediaItem: MediaItemType;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { compact, groupBy, sortBy } from 'lodash';
|
import { compact, groupBy, sortBy } from 'lodash';
|
||||||
|
import { MediaItemType } from '../../lightbox/LightboxGallery';
|
||||||
import { MediaItemType } from '../../LightboxGallery';
|
|
||||||
|
|
||||||
// import { missingCaseError } from '../../../util/missingCaseError';
|
// import { missingCaseError } from '../../../util/missingCaseError';
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { getMessageById, getMessagesByConversation } from '../../../data/data';
|
import { getMessageById, getMessagesByConversation } from '../../../../data/data';
|
||||||
import { getConversationController } from '../../../session/conversations';
|
import { getConversationController } from '../../../../session/conversations';
|
||||||
import { AttachmentDownloads } from '../../../session/utils';
|
import { AttachmentDownloads } from '../../../../session/utils';
|
||||||
import { updateConfirmModal } from '../../../state/ducks/modalDialog';
|
import { updateConfirmModal } from '../../../../state/ducks/modalDialog';
|
||||||
import { SessionIcon } from '../../session/icon';
|
import { SessionButtonColor } from '../../../basic/SessionButton';
|
||||||
import { SessionButtonColor } from '../../session/SessionButton';
|
import { SessionIcon } from '../../../icon';
|
||||||
|
|
||||||
const StyledTrustSenderUI = styled.div`
|
const StyledTrustSenderUI = styled.div`
|
||||||
padding-inline: var(--margins-xs);
|
padding-inline: var(--margins-xs);
|
|
@ -2,17 +2,17 @@ import classNames from 'classnames';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import { getMessageById } from '../../../data/data';
|
import { getMessageById } from '../../../../data/data';
|
||||||
import { MessageRenderingProps } from '../../../models/messageType';
|
import { MessageRenderingProps } from '../../../../models/messageType';
|
||||||
import {
|
import {
|
||||||
PropsForAttachment,
|
PropsForAttachment,
|
||||||
showLightBox,
|
showLightBox,
|
||||||
toggleSelectedMessageId,
|
toggleSelectedMessageId,
|
||||||
} from '../../../state/ducks/conversations';
|
} from '../../../../state/ducks/conversations';
|
||||||
import {
|
import {
|
||||||
getMessageAttachmentProps,
|
getMessageAttachmentProps,
|
||||||
isMessageSelectionMode,
|
isMessageSelectionMode,
|
||||||
} from '../../../state/selectors/conversations';
|
} from '../../../../state/selectors/conversations';
|
||||||
import {
|
import {
|
||||||
AttachmentType,
|
AttachmentType,
|
||||||
AttachmentTypeWithPath,
|
AttachmentTypeWithPath,
|
||||||
|
@ -23,13 +23,13 @@ import {
|
||||||
isAudio,
|
isAudio,
|
||||||
isImage,
|
isImage,
|
||||||
isVideo,
|
isVideo,
|
||||||
} from '../../../types/Attachment';
|
} from '../../../../types/Attachment';
|
||||||
import { isFileDangerous } from '../../../util';
|
import { isFileDangerous } from '../../../../util';
|
||||||
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
|
import { saveAttachmentToDisk } from '../../../../util/attachmentsUtil';
|
||||||
import { Spinner } from '../../basic/Spinner';
|
import { Spinner } from '../../../basic/Spinner';
|
||||||
import { LightBoxOptions } from '../../session/conversation/SessionConversation';
|
import { AudioPlayerWithEncryptedFile } from '../../H5AudioPlayer';
|
||||||
import { AudioPlayerWithEncryptedFile } from '../H5AudioPlayer';
|
import { ImageGrid } from '../../ImageGrid';
|
||||||
import { ImageGrid } from '../ImageGrid';
|
import { LightBoxOptions } from '../../SessionConversation';
|
||||||
import { ClickToTrustSender } from './ClickToTrustSender';
|
import { ClickToTrustSender } from './ClickToTrustSender';
|
||||||
|
|
||||||
export type MessageAttachmentSelectorProps = Pick<
|
export type MessageAttachmentSelectorProps = Pick<
|
|
@ -1,14 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { MessageRenderingProps } from '../../../models/messageType';
|
import { MessageRenderingProps } from '../../../../models/messageType';
|
||||||
import { PubKey } from '../../../session/types/PubKey';
|
import { PubKey } from '../../../../session/types';
|
||||||
import {
|
import {
|
||||||
getMessageAuthorProps,
|
getMessageAuthorProps,
|
||||||
isGroupConversation,
|
isGroupConversation,
|
||||||
isPublicGroupConversation,
|
isPublicGroupConversation,
|
||||||
} from '../../../state/selectors/conversations';
|
} from '../../../../state/selectors/conversations';
|
||||||
import { Flex } from '../../basic/Flex';
|
import { Flex } from '../../../basic/Flex';
|
||||||
import { ContactName } from '../ContactName';
|
import { ContactName } from '../../ContactName';
|
||||||
|
|
||||||
export type MessageAuthorSelectorProps = Pick<
|
export type MessageAuthorSelectorProps = Pick<
|
||||||
MessageRenderingProps,
|
MessageRenderingProps,
|
|
@ -1,9 +1,10 @@
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { MessageRenderingProps } from '../../../models/messageType';
|
import { MessageRenderingProps } from '../../../../models/messageType';
|
||||||
import { updateUserDetailsModal } from '../../../state/ducks/modalDialog';
|
import { updateUserDetailsModal } from '../../../../state/ducks/modalDialog';
|
||||||
import { getMessageAvatarProps } from '../../../state/selectors/conversations';
|
import { getMessageAvatarProps } from '../../../../state/selectors/conversations';
|
||||||
import { Avatar, AvatarSize } from '../../Avatar';
|
import { Avatar, AvatarSize } from '../../../avatar/Avatar';
|
||||||
|
// tslint:disable: use-simple-attributes
|
||||||
|
|
||||||
export type MessageAvatarSelectorProps = Pick<
|
export type MessageAvatarSelectorProps = Pick<
|
||||||
MessageRenderingProps,
|
MessageRenderingProps,
|
||||||
|
@ -62,7 +63,11 @@ export const MessageAvatar = (props: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-message__author-avatar" key={`msg-avatar-${authorPhoneNumber}`}>
|
<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 && (
|
{isPublic && isSenderAdmin && (
|
||||||
<div className="module-avatar__icon--crown-wrapper">
|
<div className="module-avatar__icon--crown-wrapper">
|
||||||
<div className="module-avatar__icon--crown" />
|
<div className="module-avatar__icon--crown" />
|
|
@ -1,12 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { RenderTextCallbackType } from '../../../../types/Util';
|
||||||
import { getSizeClass, SizeClassType } from '../../util/emoji';
|
import { getSizeClass, SizeClassType } from '../../../../util/emoji';
|
||||||
import { Emojify } from './Emojify';
|
import { AddMentions } from '../../AddMentions';
|
||||||
import { AddNewLines } from './AddNewLines';
|
import { AddNewLines } from '../../AddNewLines';
|
||||||
import { AddMentions } from './AddMentions';
|
import { Emojify } from '../../Emojify';
|
||||||
import { Linkify } from './Linkify';
|
import { Linkify } from '../../Linkify';
|
||||||
|
|
||||||
import { RenderTextCallbackType } from '../../types/Util';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
text: string;
|
text: string;
|
|
@ -4,11 +4,11 @@ import React, { createContext, useCallback, useState } from 'react';
|
||||||
import { InView } from 'react-intersection-observer';
|
import { InView } from 'react-intersection-observer';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType';
|
import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
|
||||||
import {
|
import {
|
||||||
getMessageContentSelectorProps,
|
getMessageContentSelectorProps,
|
||||||
getMessageTextProps,
|
getMessageTextProps,
|
||||||
} from '../../../state/selectors/conversations';
|
} from '../../../../state/selectors/conversations';
|
||||||
import {
|
import {
|
||||||
canDisplayImage,
|
canDisplayImage,
|
||||||
getGridDimensions,
|
getGridDimensions,
|
||||||
|
@ -18,9 +18,9 @@ import {
|
||||||
isImage,
|
isImage,
|
||||||
isImageAttachment,
|
isImageAttachment,
|
||||||
isVideo,
|
isVideo,
|
||||||
} from '../../../types/Attachment';
|
} from '../../../../types/Attachment';
|
||||||
import { Flex } from '../../basic/Flex';
|
import { Flex } from '../../../basic/Flex';
|
||||||
import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../Message';
|
import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../message-item/Message';
|
||||||
import { MessageAttachment } from './MessageAttachment';
|
import { MessageAttachment } from './MessageAttachment';
|
||||||
import { MessagePreview } from './MessagePreview';
|
import { MessagePreview } from './MessagePreview';
|
||||||
import { MessageQuote } from './MessageQuote';
|
import { MessageQuote } from './MessageQuote';
|
|
@ -2,13 +2,14 @@ import classNames from 'classnames';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import { replyToMessage } from '../../../interactions/conversationInteractions';
|
import { replyToMessage } from '../../../../interactions/conversationInteractions';
|
||||||
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType';
|
import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
|
||||||
import { toggleSelectedMessageId } from '../../../state/ducks/conversations';
|
import { toggleSelectedMessageId } from '../../../../state/ducks/conversations';
|
||||||
import {
|
import {
|
||||||
getMessageContentWithStatusesSelectorProps,
|
getMessageContentWithStatusesSelectorProps,
|
||||||
isMessageSelectionMode,
|
isMessageSelectionMode,
|
||||||
} from '../../../state/selectors/conversations';
|
} from '../../../../state/selectors/conversations';
|
||||||
|
|
||||||
import { MessageAuthorText } from './MessageAuthorText';
|
import { MessageAuthorText } from './MessageAuthorText';
|
||||||
import { MessageContent } from './MessageContent';
|
import { MessageContent } from './MessageContent';
|
||||||
import { MessageContextMenu } from './MessageContextMenu';
|
import { MessageContextMenu } from './MessageContextMenu';
|
|
@ -2,26 +2,26 @@ import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { animation, Item, Menu } from 'react-contexify';
|
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 { 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 {
|
import {
|
||||||
deleteMessagesById,
|
deleteMessagesById,
|
||||||
deleteMessagesByIdForEveryone,
|
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<
|
export type MessageContextMenuSelectorProps = Pick<
|
||||||
MessageRenderingProps,
|
MessageRenderingProps,
|
||||||
|
@ -183,7 +183,9 @@ export const MessageContextMenu = (props: Props) => {
|
||||||
|
|
||||||
<Item onClick={copyText}>{window.i18n('copyMessage')}</Item>
|
<Item onClick={copyText}>{window.i18n('copyMessage')}</Item>
|
||||||
{(isSent || !isOutgoing) && <Item onClick={onReply}>{window.i18n('replyToMessage')}</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}
|
{showRetry ? <Item onClick={onRetry}>{window.i18n('resend')}</Item> : null}
|
||||||
{isDeletable ? (
|
{isDeletable ? (
|
||||||
<>
|
<>
|
|
@ -1,13 +1,13 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { isImageAttachment } from '../../../types/Attachment';
|
import { isImageAttachment } from '../../../../types/Attachment';
|
||||||
import { SessionIcon } from '../../session/icon';
|
import { ImageGrid } from '../../ImageGrid';
|
||||||
import { ImageGrid } from '../ImageGrid';
|
import { Image } from '../../Image';
|
||||||
import { Image } from '../Image';
|
import { MessageRenderingProps } from '../../../../models/messageType';
|
||||||
import { MINIMUM_LINK_PREVIEW_IMAGE_WIDTH } from '../Message';
|
|
||||||
import { MessageRenderingProps } from '../../../models/messageType';
|
|
||||||
import { useSelector } from 'react-redux';
|
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'>;
|
export type MessagePreviewSelectorProps = Pick<MessageRenderingProps, 'attachments' | 'previews'>;
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType';
|
import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
|
||||||
import { PubKey } from '../../../session/types';
|
import { PubKey } from '../../../../session/types';
|
||||||
import { toggleSelectedMessageId } from '../../../state/ducks/conversations';
|
import { toggleSelectedMessageId } from '../../../../state/ducks/conversations';
|
||||||
import {
|
import {
|
||||||
getMessageQuoteProps,
|
getMessageQuoteProps,
|
||||||
isMessageSelectionMode,
|
isMessageSelectionMode,
|
||||||
} from '../../../state/selectors/conversations';
|
} from '../../../../state/selectors/conversations';
|
||||||
import { Quote } from '../Quote';
|
import { Quote } from './Quote';
|
||||||
|
|
||||||
// tslint:disable: use-simple-attributes
|
// tslint:disable: use-simple-attributes
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { MessageRenderingProps } from '../../../models/messageType';
|
import { MessageRenderingProps } from '../../../../models/messageType';
|
||||||
import { getMessageStatusProps } from '../../../state/selectors/conversations';
|
import { getMessageStatusProps } from '../../../../state/selectors/conversations';
|
||||||
import { OutgoingMessageStatus } from './OutgoingMessageStatus';
|
import { OutgoingMessageStatus } from './OutgoingMessageStatus';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
|
@ -1,13 +1,13 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { MessageRenderingProps } from '../../../models/messageType';
|
import { MessageRenderingProps } from '../../../../models/messageType';
|
||||||
import {
|
import {
|
||||||
getMessageTextProps,
|
getMessageTextProps,
|
||||||
isMessageSelectionMode,
|
isMessageSelectionMode,
|
||||||
} from '../../../state/selectors/conversations';
|
} from '../../../../state/selectors/conversations';
|
||||||
import { SessionIcon } from '../../session/icon';
|
import { SessionIcon } from '../../../icon';
|
||||||
import { MessageBody } from '../MessageBody';
|
import { MessageBody } from './MessageBody';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
messageId: string;
|
messageId: string;
|
|
@ -1,8 +1,8 @@
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { MessageDeliveryStatus } from '../../../models/messageType';
|
import { MessageDeliveryStatus } from '../../../../models/messageType';
|
||||||
import { SessionIcon } from '../../session/icon';
|
import { SessionIcon } from '../../../icon';
|
||||||
|
|
||||||
const MessageStatusSendingContainer = styled.div`
|
const MessageStatusSendingContainer = styled.div`
|
||||||
display: inline-block;
|
display: inline-block;
|
|
@ -1,22 +1,22 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import * as MIME from '../../../ts/types/MIME';
|
import * as MIME from '../../../../../ts/types/MIME';
|
||||||
import * as GoogleChrome from '../../../ts/util/GoogleChrome';
|
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 { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { noop } from 'underscore';
|
||||||
|
import { useDisableDrag } from '../../../../hooks/useDisableDrag';
|
||||||
|
import { useEncryptedFileFetch } from '../../../../hooks/useEncryptedFileFetch';
|
||||||
|
import { PubKey } from '../../../../session/types';
|
||||||
import {
|
import {
|
||||||
getSelectedConversationKey,
|
getSelectedConversationKey,
|
||||||
isGroupConversation,
|
isGroupConversation,
|
||||||
isPublicGroupConversation,
|
isPublicGroupConversation,
|
||||||
} from '../../state/selectors/conversations';
|
} from '../../../../state/selectors/conversations';
|
||||||
import { noop } from 'underscore';
|
import { ContactName } from '../../ContactName';
|
||||||
import { useDisableDrag } from '../../hooks/useDisableDrag';
|
import { MessageBody } from './MessageBody';
|
||||||
|
|
||||||
export type QuotePropsWithoutListener = {
|
export type QuotePropsWithoutListener = {
|
||||||
attachment?: QuotedAttachmentType;
|
attachment?: QuotedAttachmentType;
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { PropsForDataExtractionNotification } from '../../models/messageType';
|
import { PropsForDataExtractionNotification } from '../../../../models/messageType';
|
||||||
import { SignalService } from '../../protobuf';
|
import { SignalService } from '../../../../protobuf';
|
||||||
import { Flex } from '../basic/Flex';
|
import { Flex } from '../../../basic/Flex';
|
||||||
import { SessionIcon } from '../session/icon';
|
import { SpacerSM, Text } from '../../../basic/Text';
|
||||||
import { SpacerSM, Text } from '../basic/Text';
|
import { SessionIcon } from '../../../icon';
|
||||||
import { ReadableMessage } from './ReadableMessage';
|
import { ReadableMessage } from './ReadableMessage';
|
||||||
|
|
||||||
export const DataExtractionNotification = (props: PropsForDataExtractionNotification) => {
|
export const DataExtractionNotification = (props: PropsForDataExtractionNotification) => {
|
|
@ -5,21 +5,21 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||||
// tslint:disable-next-line: no-submodule-imports
|
// tslint:disable-next-line: no-submodule-imports
|
||||||
import useInterval from 'react-use/lib/useInterval';
|
import useInterval from 'react-use/lib/useInterval';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { removeMessage } from '../../../data/data';
|
import { removeMessage } from '../../../../data/data';
|
||||||
import { MessageRenderingProps, QuoteClickOptions } from '../../../models/messageType';
|
import { MessageRenderingProps, QuoteClickOptions } from '../../../../models/messageType';
|
||||||
import { getConversationController } from '../../../session/conversations';
|
import { getConversationController } from '../../../../session/conversations';
|
||||||
import { messageExpired } from '../../../state/ducks/conversations';
|
import { messageExpired } from '../../../../state/ducks/conversations';
|
||||||
import {
|
import {
|
||||||
getGenericReadableMessageSelectorProps,
|
getGenericReadableMessageSelectorProps,
|
||||||
getIsMessageSelected,
|
getIsMessageSelected,
|
||||||
getQuotedMessageToAnimate,
|
getQuotedMessageToAnimate,
|
||||||
isMessageSelectionMode,
|
isMessageSelectionMode,
|
||||||
} from '../../../state/selectors/conversations';
|
} from '../../../../state/selectors/conversations';
|
||||||
import { getIncrement } from '../../../util/timer';
|
import { getIncrement } from '../../../../util/timer';
|
||||||
import { ExpireTimer } from '../ExpireTimer';
|
import { ExpireTimer } from '../../ExpireTimer';
|
||||||
import { ReadableMessage } from '../ReadableMessage';
|
import { MessageAvatar } from '../message-content/MessageAvatar';
|
||||||
import { MessageAvatar } from './MessageAvatar';
|
import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus';
|
||||||
import { MessageContentWithStatuses } from './MessageContentWithStatus';
|
import { ReadableMessage } from './ReadableMessage';
|
||||||
|
|
||||||
export type GenericReadableMessageSelectorProps = Pick<
|
export type GenericReadableMessageSelectorProps = Pick<
|
||||||
MessageRenderingProps,
|
MessageRenderingProps,
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { SessionIconButton } from '../session/icon';
|
import { PropsForGroupInvitation } from '../../../../state/ducks/conversations';
|
||||||
import { PropsForGroupInvitation } from '../../state/ducks/conversations';
|
import { acceptOpenGroupInvitation } from '../../../../interactions/messageInteractions';
|
||||||
import { acceptOpenGroupInvitation } from '../../interactions/messageInteractions';
|
import { SessionIconButton } from '../../../icon';
|
||||||
import { ReadableMessage } from './ReadableMessage';
|
import { ReadableMessage } from './ReadableMessage';
|
||||||
|
|
||||||
export const GroupInvitation = (props: PropsForGroupInvitation) => {
|
export const GroupInvitation = (props: PropsForGroupInvitation) => {
|
|
@ -2,10 +2,10 @@ import React from 'react';
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
import { QuoteClickOptions } from '../../models/messageType';
|
|
||||||
import { GenericReadableMessage } from './message/GenericReadableMessage';
|
|
||||||
import { useSelector } from 'react-redux';
|
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
|
// Same as MIN_WIDTH in ImageGrid.tsx
|
||||||
export const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
|
export const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
|
|
@ -2,16 +2,16 @@ import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import { Avatar, AvatarSize } from '../Avatar';
|
|
||||||
import { ContactName } from './ContactName';
|
|
||||||
import { Message } from './Message';
|
import { Message } from './Message';
|
||||||
import { useSelector } from 'react-redux';
|
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 {
|
import {
|
||||||
getMessageDetailsViewProps,
|
getMessageDetailsViewProps,
|
||||||
getMessageIsDeletable,
|
getMessageIsDeletable,
|
||||||
} from '../../state/selectors/conversations';
|
} from '../../../../state/selectors/conversations';
|
||||||
import { deleteMessagesById } from '../../interactions/conversations/unsendingInteractions';
|
import { ContactName } from '../../ContactName';
|
||||||
|
|
||||||
const AvatarItem = (props: { pubkey: string }) => {
|
const AvatarItem = (props: { pubkey: string }) => {
|
||||||
const { pubkey } = props;
|
const { pubkey } = props;
|
|
@ -2,14 +2,14 @@ import _, { noop } from 'lodash';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { InView } from 'react-intersection-observer';
|
import { InView } from 'react-intersection-observer';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { getMessageById } from '../../data/data';
|
import { getMessageById } from '../../../../data/data';
|
||||||
import { Constants } from '../../session';
|
import { Constants } from '../../../../session';
|
||||||
import { getConversationController } from '../../session/conversations';
|
import { getConversationController } from '../../../../session/conversations';
|
||||||
import {
|
import {
|
||||||
fetchMessagesForConversation,
|
fetchMessagesForConversation,
|
||||||
markConversationFullyRead,
|
markConversationFullyRead,
|
||||||
showScrollToBottomButton,
|
showScrollToBottomButton,
|
||||||
} from '../../state/ducks/conversations';
|
} from '../../../../state/ducks/conversations';
|
||||||
import {
|
import {
|
||||||
areMoreMessagesBeingFetched,
|
areMoreMessagesBeingFetched,
|
||||||
getHaveDoneFirstScroll,
|
getHaveDoneFirstScroll,
|
||||||
|
@ -17,8 +17,8 @@ import {
|
||||||
getMostRecentMessageId,
|
getMostRecentMessageId,
|
||||||
getOldestMessageId,
|
getOldestMessageId,
|
||||||
getSelectedConversationKey,
|
getSelectedConversationKey,
|
||||||
} from '../../state/selectors/conversations';
|
} from '../../../../state/selectors/conversations';
|
||||||
import { getIsAppFocused } from '../../state/selectors/section';
|
import { getIsAppFocused } from '../../../../state/selectors/section';
|
||||||
|
|
||||||
type ReadableMessageProps = {
|
type ReadableMessageProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
|
@ -1,11 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { PubKey } from '../../../session/types';
|
import { PubKey } from '../../../../../session/types';
|
||||||
|
|
||||||
import { CallNotificationType, PropsForCallNotification } from '../../../state/ducks/conversations';
|
import {
|
||||||
import { getSelectedConversation } from '../../../state/selectors/conversations';
|
CallNotificationType,
|
||||||
import { LocalizerKeys } from '../../../types/LocalizerKeys';
|
PropsForCallNotification,
|
||||||
import { SessionIconType } from '../../session/icon';
|
} from '../../../../../state/ducks/conversations';
|
||||||
|
import { getSelectedConversation } from '../../../../../state/selectors/conversations';
|
||||||
|
import { LocalizerKeys } from '../../../../../types/LocalizerKeys';
|
||||||
|
import { SessionIconType } from '../../../../icon';
|
||||||
import { ReadableMessage } from '../ReadableMessage';
|
import { ReadableMessage } from '../ReadableMessage';
|
||||||
import { NotificationBubble } from './NotificationBubble';
|
import { NotificationBubble } from './NotificationBubble';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { SessionIcon, SessionIconType } from '../../session/icon';
|
import { SessionIcon, SessionIconType } from '../../../../icon';
|
||||||
|
|
||||||
const NotificationBubbleFlex = styled.div`
|
const NotificationBubbleFlex = styled.div`
|
||||||
display: flex;
|
display: flex;
|
|
@ -1,10 +1,9 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
|
|
||||||
import { SessionWrapperModal } from '../session/SessionWrapperModal';
|
|
||||||
import { SpacerLG } from '../basic/Text';
|
import { SpacerLG } from '../basic/Text';
|
||||||
import { getConversationController } from '../../session/conversations';
|
import { getConversationController } from '../../session/conversations';
|
||||||
import { adminLeaveClosedGroup } from '../../state/ducks/modalDialog';
|
import { adminLeaveClosedGroup } from '../../state/ducks/modalDialog';
|
||||||
|
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
|
||||||
|
import { SessionWrapperModal } from '../SessionWrapperModal';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { ed25519Str } from '../../session/onions/onionPath';
|
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 { forceSyncConfigurationNowIfNeeded } from '../../session/utils/syncUtils';
|
||||||
import { updateConfirmModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog';
|
import { updateConfirmModal, updateDeleteAccountModal } from '../../state/ducks/modalDialog';
|
||||||
import { SpacerLG } from '../basic/Text';
|
import { SpacerLG } from '../basic/Text';
|
||||||
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
|
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
|
||||||
import { SessionHtmlRenderer } from '../session/SessionHTMLRenderer';
|
import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
|
||||||
import { SessionSpinner } from '../session/SessionSpinner';
|
import { SessionSpinner } from '../basic/SessionSpinner';
|
||||||
import { SessionWrapperModal } from '../session/SessionWrapperModal';
|
import { SessionWrapperModal } from '../SessionWrapperModal';
|
||||||
|
|
||||||
const deleteDbLocally = async () => {
|
const deleteDbLocally = async () => {
|
||||||
window?.log?.info('last message sent successfully. Deleting everything');
|
window?.log?.info('last message sent successfully. Deleting everything');
|
||||||
|
|
|
@ -2,24 +2,24 @@ import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { QRCode } from 'react-qr-svg';
|
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 { PillDivider } from '../basic/PillDivider';
|
||||||
|
|
||||||
import { SessionIconButton } from '../session/icon';
|
|
||||||
import { PillDivider } from '../session/PillDivider';
|
|
||||||
import { SyncUtils, ToastUtils, UserUtils } from '../../session/utils';
|
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 { ConversationModel, ConversationTypeEnum } from '../../models/conversation';
|
||||||
|
|
||||||
import { SessionWrapperModal } from '../session/SessionWrapperModal';
|
|
||||||
import { AttachmentUtil } from '../../util';
|
import { AttachmentUtil } from '../../util';
|
||||||
import { getConversationController } from '../../session/conversations';
|
import { getConversationController } from '../../session/conversations';
|
||||||
import { SpacerLG, SpacerMD } from '../basic/Text';
|
import { SpacerLG, SpacerMD } from '../basic/Text';
|
||||||
import autoBind from 'auto-bind';
|
import autoBind from 'auto-bind';
|
||||||
import { editProfileModal } from '../../state/ducks/modalDialog';
|
import { editProfileModal } from '../../state/ducks/modalDialog';
|
||||||
import { uploadOurAvatar } from '../../interactions/conversationInteractions';
|
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 {
|
interface State {
|
||||||
profileName: string;
|
profileName: string;
|
||||||
|
|
|
@ -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 { getConversationController } from '../../session/conversations';
|
||||||
import { ToastUtils, UserUtils } from '../../session/utils';
|
import { ToastUtils, UserUtils } from '../../session/utils';
|
||||||
import { initiateGroupUpdate } from '../../session/group';
|
import { initiateGroupUpdate } from '../../session/group';
|
||||||
import { ConversationModel, ConversationTypeEnum } from '../../models/conversation';
|
import { ConversationTypeEnum } from '../../models/conversation';
|
||||||
import { getCompleteUrlForV2ConvoId } from '../../interactions/conversationInteractions';
|
import { getCompleteUrlForV2ConvoId } from '../../interactions/conversationInteractions';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { VALIDATION } from '../../session/constants';
|
import { VALIDATION } from '../../session/constants';
|
||||||
import { SessionWrapperModal } from '../session/SessionWrapperModal';
|
|
||||||
import { SpacerLG } from '../basic/Text';
|
import { SpacerLG } from '../basic/Text';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { updateInviteContactModal } from '../../state/ducks/modalDialog';
|
import { updateInviteContactModal } from '../../state/ducks/modalDialog';
|
||||||
// tslint:disable-next-line: no-submodule-imports
|
// tslint:disable-next-line: no-submodule-imports
|
||||||
import useKey from 'react-use/lib/useKey';
|
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 = {
|
type Props = {
|
||||||
conversationId: string;
|
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 InviteContactsDialogInner = (props: Props) => {
|
||||||
const { conversationId } = props;
|
const { conversationId } = props;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const convo = getConversationController().get(conversationId);
|
const privateContactPubkeys = useSelector(getPrivateContactsPubkeys);
|
||||||
// tslint:disable-next-line: max-func-body-length
|
let validContactsForInvite = _.clone(privateContactPubkeys);
|
||||||
|
|
||||||
let contacts = getConversationController()
|
const convoProps = useConversationPropsById(conversationId);
|
||||||
.getConversations()
|
|
||||||
.filter(d => !!d && !d.isBlocked() && d.isPrivate() && !d.isMe() && !!d.get('active_at'));
|
const { uniqueValues: selectedContacts, addTo, removeFrom } = useSet<string>();
|
||||||
if (!convo.isPublic()) {
|
|
||||||
|
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
|
// filter our zombies and current members from the list of contact we can add
|
||||||
|
const members = convoProps.members || [];
|
||||||
const members = convo.get('members') || [];
|
const zombies = convoProps.zombies || [];
|
||||||
const zombies = convo.get('zombies') || [];
|
validContactsForInvite = validContactsForInvite.filter(
|
||||||
contacts = contacts.filter(d => !members.includes(d.id) && !zombies.includes(d.id));
|
d => !members.includes(d) && !zombies.includes(d)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const chatName = convo.get('name');
|
const chatName = convoProps.name;
|
||||||
const isPublicConvo = convo.isPublic();
|
const isPublicConvo = convoProps.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 closeDialog = () => {
|
const closeDialog = () => {
|
||||||
dispatch(updateInviteContactModal(null));
|
dispatch(updateInviteContactModal(null));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickOK = () => {
|
const onClickOK = () => {
|
||||||
const selectedContacts = contactList
|
|
||||||
.filter((d: ContactType) => d.checkmarked)
|
|
||||||
.map((d: ContactType) => d.id);
|
|
||||||
|
|
||||||
if (selectedContacts.length > 0) {
|
if (selectedContacts.length > 0) {
|
||||||
if (isPublicConvo) {
|
if (isPublicConvo) {
|
||||||
void submitForOpenGroup(selectedContacts);
|
void submitForOpenGroup(conversationId, selectedContacts);
|
||||||
} else {
|
} else {
|
||||||
void submitForClosedGroup(selectedContacts);
|
void submitForClosedGroup(conversationId, selectedContacts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,123 +143,37 @@ const InviteContactsDialogInner = (props: Props) => {
|
||||||
return event.key === 'Esc' || event.key === 'Escape';
|
return event.key === 'Esc' || event.key === 'Escape';
|
||||||
}, closeDialog);
|
}, 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 cancelText = window.i18n('cancel');
|
||||||
const okText = window.i18n('ok');
|
const okText = window.i18n('ok');
|
||||||
|
|
||||||
const hasContacts = contactList.length !== 0;
|
const hasContacts = validContactsForInvite.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);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SessionWrapperModal title={titleText} onClose={closeDialog}>
|
<SessionWrapperModal title={titleText} onClose={closeDialog}>
|
||||||
<SpacerLG />
|
<SpacerLG />
|
||||||
|
|
||||||
<div className="contact-selection-list">{renderMemberList()}</div>
|
<div className="contact-selection-list">
|
||||||
{hasContacts ? null : (
|
{hasContacts ? (
|
||||||
<>
|
validContactsForInvite.map((member: string) => (
|
||||||
<SpacerLG />
|
<MemberListItem
|
||||||
<p className="no-contacts">{window.i18n('noContactsToAdd')}</p>
|
key={member}
|
||||||
<SpacerLG />
|
pubkey={member}
|
||||||
</>
|
isSelected={selectedContacts.includes(member)}
|
||||||
)}
|
onSelect={addTo}
|
||||||
|
onUnselect={removeFrom}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<SpacerLG />
|
||||||
|
<p className="no-contacts">{window.i18n('noContactsToAdd')}</p>
|
||||||
|
<SpacerLG />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<SpacerLG />
|
<SpacerLG />
|
||||||
|
|
||||||
<div className="session-modal__button-group">
|
<div className="session-modal__button-group">
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton';
|
|
||||||
import { PubKey } from '../../session/types';
|
import { PubKey } from '../../session/types';
|
||||||
import { ToastUtils } from '../../session/utils';
|
import { ToastUtils } from '../../session/utils';
|
||||||
import { SessionSpinner } from '../session/SessionSpinner';
|
|
||||||
import { Flex } from '../basic/Flex';
|
import { Flex } from '../basic/Flex';
|
||||||
import { ApiV2 } from '../../opengroup/opengroupV2';
|
import { ApiV2 } from '../../session/apis/open_group_api/opengroupV2';
|
||||||
import { SessionWrapperModal } from '../session/SessionWrapperModal';
|
|
||||||
import { getConversationController } from '../../session/conversations';
|
import { getConversationController } from '../../session/conversations';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { updateAddModeratorsModal } from '../../state/ducks/modalDialog';
|
import { updateAddModeratorsModal } from '../../state/ducks/modalDialog';
|
||||||
|
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
|
||||||
|
import { SessionSpinner } from '../basic/SessionSpinner';
|
||||||
|
import { SessionWrapperModal } from '../SessionWrapperModal';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
|
|
@ -1,217 +1,134 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ApiV2 } from '../../opengroup/opengroupV2';
|
import { ApiV2 } from '../../session/apis/open_group_api/opengroupV2';
|
||||||
import { getConversationController } from '../../session/conversations';
|
import { getConversationController } from '../../session/conversations';
|
||||||
import { PubKey } from '../../session/types';
|
import { PubKey } from '../../session/types';
|
||||||
import { ToastUtils } from '../../session/utils';
|
import { ToastUtils } from '../../session/utils';
|
||||||
import { Flex } from '../basic/Flex';
|
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 _ from 'lodash';
|
||||||
import { SessionWrapperModal } from '../session/SessionWrapperModal';
|
|
||||||
import { updateRemoveModeratorsModal } from '../../state/ducks/modalDialog';
|
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;
|
conversationId: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
interface State {
|
async function removeMods(convoId: string, modsToRemove: Array<string>) {
|
||||||
modList: Array<ContactType>;
|
if (modsToRemove.length === 0) {
|
||||||
removingInProgress: boolean;
|
window?.log?.info('No moderators removed. Nothing todo');
|
||||||
firstLoading: boolean;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
window?.log?.info(`asked to remove moderators: ${modsToRemove}`);
|
||||||
|
|
||||||
public componentDidMount() {
|
try {
|
||||||
this.refreshModList();
|
let res;
|
||||||
}
|
const convo = getConversationController().get(convoId);
|
||||||
|
|
||||||
public render() {
|
const roomInfos = convo.toOpenGroupV2();
|
||||||
const { i18n } = window;
|
const modsToRemovePubkey = _.compact(modsToRemove.map(m => PubKey.from(m)));
|
||||||
const { removingInProgress, firstLoading } = this.state;
|
res = await Promise.all(
|
||||||
const hasMods = this.state.modList.length !== 0;
|
modsToRemovePubkey.map(async m => {
|
||||||
|
return ApiV2.removeModerator(m, roomInfos);
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
// all moderators are removed means all promise resolved with bool= true
|
||||||
|
res = res.every(r => !!r);
|
||||||
|
|
||||||
private closeDialog() {
|
if (!res) {
|
||||||
window.inboxStore?.dispatch(updateRemoveModeratorsModal(null));
|
window?.log?.warn('failed to remove moderators:', res);
|
||||||
}
|
|
||||||
|
|
||||||
private renderMemberList() {
|
ToastUtils.pushFailedToRemoveFromModerator();
|
||||||
const members = this.state.modList;
|
return false;
|
||||||
const selectedContacts = members.filter(d => d.checkmarked).map(d => d.id);
|
} else {
|
||||||
|
window?.log?.info(`${modsToRemove} removed from moderators...`);
|
||||||
return members.map((member: ContactType, index: number) => (
|
ToastUtils.pushUserRemovedFromModerators();
|
||||||
<SessionMemberListItem
|
return true;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
} 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -15,11 +15,11 @@ import {
|
||||||
getOnionPathsCount,
|
getOnionPathsCount,
|
||||||
} from '../../state/selectors/onions';
|
} from '../../state/selectors/onions';
|
||||||
import { Flex } from '../basic/Flex';
|
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
|
// tslint:disable-next-line: no-submodule-imports
|
||||||
import useHover from 'react-use/lib/useHover';
|
import useHover from 'react-use/lib/useHover';
|
||||||
|
import { SessionSpinner } from '../basic/SessionSpinner';
|
||||||
|
import { SessionIcon, SessionIconButton } from '../icon';
|
||||||
|
import { SessionWrapperModal } from '../SessionWrapperModal';
|
||||||
|
|
||||||
export type StatusLightType = {
|
export type StatusLightType = {
|
||||||
glowStartDelay: number;
|
glowStartDelay: number;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
|
import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
|
||||||
import { SessionHtmlRenderer } from '../session/SessionHTMLRenderer';
|
|
||||||
import { SessionIcon, SessionIconSize, SessionIconType } from '../session/icon';
|
|
||||||
import { SessionWrapperModal } from '../session/SessionWrapperModal';
|
|
||||||
import { updateConfirmModal } from '../../state/ducks/modalDialog';
|
import { updateConfirmModal } from '../../state/ducks/modalDialog';
|
||||||
import { SpacerLG } from '../basic/Text';
|
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 {
|
export interface SessionConfirmDialogProps {
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
|
||||||
import { SessionIconButton, SessionIconType } from '../session/icon';
|
import { SessionIconButton, SessionIconType } from '../icon';
|
||||||
import { SessionButtonColor, SessionButtonType } from '../session/SessionButton';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
@ -5,8 +5,8 @@ import _ from 'lodash';
|
||||||
import { SpacerLG } from '../basic/Text';
|
import { SpacerLG } from '../basic/Text';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { changeNickNameModal } from '../../state/ducks/modalDialog';
|
import { changeNickNameModal } from '../../state/ducks/modalDialog';
|
||||||
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
|
import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
|
||||||
import { SessionWrapperModal } from '../session/SessionWrapperModal';
|
import { SessionWrapperModal } from '../SessionWrapperModal';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue