diff --git a/res/css/structures/_TagPanel.scss b/res/css/structures/_TagPanel.scss index 2683a32dae..cdca1f0764 100644 --- a/res/css/structures/_TagPanel.scss +++ b/res/css/structures/_TagPanel.scss @@ -30,25 +30,6 @@ limitations under the License. cursor: pointer; } -.mx_TagPanel .mx_TagPanel_clearButton_container { - /* Constant height within flex mx_TagPanel */ - height: 70px; - width: 56px; - - flex: none; - - justify-content: center; - align-items: flex-start; - - display: none; -} - -.mx_TagPanel .mx_TagPanel_clearButton object { - /* Same as .mx_SearchBox padding-top */ - margin-top: 24px; - pointer-events: none; -} - .mx_TagPanel .mx_TagPanel_divider { height: 0px; width: 90%; @@ -99,11 +80,11 @@ limitations under the License. } .mx_TagTile:not(.mx_TagTile_selected_prototype) .mx_TagTile_homeIcon { - background-color: $icon-button-color; // XXX: Variable abuse + background-color: $roomheader-addroom-bg-color; border-radius: 48px; &::before { - background-color: $primary-bg-color; // light-on-grey + background-color: $roomheader-addroom-fg-color; } } @@ -116,13 +97,14 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/home.svg'); mask-position: center; mask-repeat: no-repeat; - width: 21px; - height: 21px; + mask-size: 21px; content: ''; display: inline-block; + width: 32px; + height: 32px; position: absolute; - top: calc(50% - 10.5px); - left: calc(50% - 10.5px); + top: calc(50% - 16px); + left: calc(50% - 16px); } } diff --git a/res/img/element-icons/home.svg b/res/img/element-icons/home.svg index d65812cafd..a6c15456ff 100644 --- a/res/img/element-icons/home.svg +++ b/res/img/element-icons/home.svg @@ -1,3 +1,3 @@ - + diff --git a/src/CrossSigningManager.js b/src/CrossSigningManager.js index da09a436e9..0353bfc5ae 100644 --- a/src/CrossSigningManager.js +++ b/src/CrossSigningManager.js @@ -69,19 +69,19 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { if (keyInfoEntries.length > 1) { throw new Error("Multiple storage key requests not implemented"); } - const [name, info] = keyInfoEntries[0]; + const [keyId, keyInfo] = keyInfoEntries[0]; // Check the in-memory cache - if (isCachingAllowed() && secretStorageKeys[name]) { - return [name, secretStorageKeys[name]]; + if (isCachingAllowed() && secretStorageKeys[keyId]) { + return [keyId, secretStorageKeys[keyId]]; } const inputToKey = async ({ passphrase, recoveryKey }) => { if (passphrase) { return deriveKey( passphrase, - info.passphrase.salt, - info.passphrase.iterations, + keyInfo.passphrase.salt, + keyInfo.passphrase.iterations, ); } else { return decodeRecoveryKey(recoveryKey); @@ -93,10 +93,10 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { AccessSecretStorageDialog, /* props= */ { - keyInfo: info, + keyInfo, checkPrivateKey: async (input) => { const key = await inputToKey(input); - return await MatrixClientPeg.get().checkSecretStorageKey(key, info); + return await MatrixClientPeg.get().checkSecretStorageKey(key, keyInfo); }, }, /* className= */ null, @@ -118,11 +118,15 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { const key = await inputToKey(input); // Save to cache to avoid future prompts in the current session - if (isCachingAllowed()) { - secretStorageKeys[name] = key; - } + cacheSecretStorageKey(keyId, key); - return [name, key]; + return [keyId, key]; +} + +function cacheSecretStorageKey(keyId, key) { + if (isCachingAllowed()) { + secretStorageKeys[keyId] = key; + } } const onSecretRequested = async function({ @@ -170,6 +174,7 @@ const onSecretRequested = async function({ export const crossSigningCallbacks = { getSecretStorageKey, + cacheSecretStorageKey, onSecretRequested, }; @@ -218,7 +223,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '', import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"), { - force: forceReset, + forceReset, }, null, /* priority = */ false, @@ -239,7 +244,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f } } else { const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); - await cli.bootstrapSecretStorage({ + await cli.bootstrapCrossSigning({ authUploadDeviceSigningKeys: async (makeRequest) => { const { finished } = Modal.createTrackedDialog( 'Cross-signing keys dialog', '', InteractiveAuthDialog, @@ -254,7 +259,9 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f throw new Error("Cross-signing key upload auth canceled"); } }, - getBackupPassphrase: promptForBackupPassphrase, + }); + await cli.bootstrapSecretStorage({ + getKeyBackupPassphrase: promptForBackupPassphrase, }); } diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts index 6b667ae54d..b05f0fcd68 100644 --- a/src/DeviceListener.ts +++ b/src/DeviceListener.ts @@ -207,9 +207,13 @@ export default class DeviceListener { // (we add a listener on sync to do once check after the initial sync is done) if (!cli.isInitialSyncComplete()) return; + // JRS: This will change again in the next PR which moves secret storage + // later in the process. const crossSigningReady = await cli.isCrossSigningReady(); + const secretStorageReady = await cli.isSecretStorageReady(); + const allSystemsReady = crossSigningReady && secretStorageReady; - if (this.dismissedThisDeviceToast || crossSigningReady) { + if (this.dismissedThisDeviceToast || allSystemsReady) { hideSetupEncryptionToast(); } else if (this.shouldShowSetupEncryptionToast()) { // make sure our keys are finished downloading diff --git a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js index 47faa35df4..0a1a0b02b3 100644 --- a/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js +++ b/src/async-components/views/dialogs/secretstorage/CreateSecretStorageDialog.js @@ -56,12 +56,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent { static propTypes = { hasCancel: PropTypes.bool, accountPassword: PropTypes.string, - force: PropTypes.bool, + forceReset: PropTypes.bool, }; static defaultProps = { hasCancel: true, - force: false, + forceReset: false, }; constructor(props) { @@ -118,8 +118,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent { MatrixClientPeg.get().isCryptoEnabled() && await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo) ); - const { force } = this.props; - const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE; + const { forceReset } = this.props; + const phase = (backupInfo && !forceReset) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE; this.setState({ phase, @@ -277,20 +277,25 @@ export default class CreateSecretStorageDialog extends React.PureComponent { const cli = MatrixClientPeg.get(); - const { force } = this.props; + const { forceReset } = this.props; try { - if (force) { - console.log("Forcing secret storage reset"); // log something so we can debug this later + if (forceReset) { + console.log("Forcing cross-signing and secret storage reset"); await cli.bootstrapSecretStorage({ - authUploadDeviceSigningKeys: this._doBootstrapUIAuth, createSecretStorageKey: async () => this._recoveryKey, setupNewKeyBackup: true, setupNewSecretStorage: true, }); - } else { - await cli.bootstrapSecretStorage({ + await cli.bootstrapCrossSigning({ authUploadDeviceSigningKeys: this._doBootstrapUIAuth, + setupNewCrossSigning: true, + }); + } else { + await cli.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: this._doBootstrapUIAuth, + }); + await cli.bootstrapSecretStorage({ createSecretStorageKey: async () => this._recoveryKey, keyBackupInfo: this.state.backupInfo, setupNewKeyBackup: !this.state.backupInfo, diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js index 4acbc49d4d..a714b126ec 100644 --- a/src/components/structures/TagPanel.js +++ b/src/components/structures/TagPanel.js @@ -95,11 +95,6 @@ const TagPanel = createReactClass({ } }, - onCreateGroupClick(ev) { - ev.stopPropagation(); - dis.dispatch({action: 'view_create_group'}); - }, - onClearFilterClick(ev) { dis.dispatch({action: 'deselect_tags'}); }, @@ -117,9 +112,7 @@ const TagPanel = createReactClass({ render() { const DNDTagTile = sdk.getComponent('elements.DNDTagTile'); - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const ActionButton = sdk.getComponent('elements.ActionButton'); - const TintableSvg = sdk.getComponent('elements.TintableSvg'); const tags = this.state.orderedTags.map((tag, index) => { return 0; - - let clearButton; - if (itemsSelected) { - clearButton = - - ; - } - const classes = classNames('mx_TagPanel', { mx_TagPanel_items_selected: itemsSelected, }); @@ -164,10 +146,7 @@ const TagPanel = createReactClass({ ); } - return
-
- { clearButton } -
+ return
{ - if (result.room_id) { - dis.dispatch({ - action: 'view_room', - room_id: result.room_id, - }); - - // Ensure the tag gets selected now that we've created it - dis.dispatch({action: 'deselect_tags'}, true); - dis.dispatch({ - action: 'select_tag', - tag: result.group_id, - }); - } else { - dis.dispatch({ - action: 'view_group', - group_id: result.group_id, - group_is_new: true, - }); - } + dis.dispatch({ + action: 'view_group', + group_id: result.group_id, + group_is_new: true, + }); this.props.onFinished(true); }).catch((e) => { this.setState({createError: e}); diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index ce7ac6e59c..4890626527 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -25,6 +25,8 @@ import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {Key} from "../../../Keyboard"; import {privateShouldBeEncrypted} from "../../../createRoom"; +import TagOrderStore from "../../../stores/TagOrderStore"; +import GroupStore from "../../../stores/GroupStore"; export default createReactClass({ displayName: 'CreateRoomDialog', @@ -70,6 +72,10 @@ export default createReactClass({ opts.encryption = this.state.isEncrypted; } + if (TagOrderStore.getSelectedPrototypeTag()) { + opts.associatedWithCommunity = TagOrderStore.getSelectedPrototypeTag(); + } + return opts; }, @@ -178,18 +184,25 @@ export default createReactClass({ const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch'); const RoomAliasField = sdk.getComponent('views.elements.RoomAliasField'); - let publicPrivateLabel; let aliasField; if (this.state.isPublic) { - publicPrivateLabel = (

{_t("Set a room address to easily share your room with other people.")}

); const domain = MatrixClientPeg.get().getDomain(); aliasField = (
this._aliasFieldRef = ref} onChange={this.onAliasChange} domain={domain} value={this.state.alias} />
); - } else { - publicPrivateLabel = (

{_t("This room is private, and can only be joined by invitation.")}

); + } + + let publicPrivateLabel =

{_t( + "Private rooms can be found and joined by invitation only. Public rooms can be " + + "found and joined by anyone.", + )}

; + if (TagOrderStore.getSelectedPrototypeTag()) { + publicPrivateLabel =

{_t( + "Private rooms can be found and joined by invitation only. Public rooms can be " + + "found and joined by anyone in this community.", + )}

; } let e2eeSection; @@ -212,7 +225,25 @@ export default createReactClass({ ; } - const title = this.state.isPublic ? _t('Create a public room') : _t('Create a private room'); + let federateLabel = _t( + "You might enable this if the room will only be used for collaborating with internal " + + "teams on your homeserver. This cannot be changed later.", + ); + if (SdkConfig.get().default_federate === false) { + // We only change the label if the default setting is different to avoid jarring text changes to the + // user. They will have read the implications of turning this off/on, so no need to rephrase for them. + federateLabel = _t( + "You might disable this if the room will be used for collaborating with external " + + "teams who have their own homeserver. This cannot be changed later.", + ); + } + + let title = this.state.isPublic ? _t('Create a public room') : _t('Create a private room'); + if (TagOrderStore.getSelectedPrototypeTag()) { + const summary = GroupStore.getSummary(TagOrderStore.getSelectedPrototypeTag()); + const name = summary?.profile?.name || TagOrderStore.getSelectedPrototypeTag(); + title = _t("Create a room in %(communityName)s", {communityName: name}); + } return ( { this.state.detailsOpen ? _t('Hide advanced') : _t('Show advanced') } - + +

{federateLabel}

diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index a52dea3e0a..299025f949 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -735,7 +735,7 @@ export default class AppTile extends React.Component { // Additional iframe feature pemissions // (see - https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-permissions-in-cross-origin-iframes and https://wicg.github.io/feature-policy/) - const iframeFeatures = "microphone; camera; encrypted-media; autoplay;"; + const iframeFeatures = "microphone; camera; encrypted-media; autoplay; display-capture;"; const appTileBodyClass = 'mx_AppTileBody' + (this.props.miniMode ? '_mini ' : ' '); diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 3274e0e49f..92c5982276 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -45,6 +45,7 @@ import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays"; import { objectShallowClone, objectWithOnly } from "../../../utils/objects"; import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu"; import AccessibleButton from "../elements/AccessibleButton"; +import TagOrderStore from "../../../stores/TagOrderStore"; interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; @@ -129,7 +130,9 @@ const TAG_AESTHETICS: { }} /> { e.preventDefault(); diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index 1c6baee9af..847bcf3da3 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -89,6 +89,7 @@ export default class CrossSigningPanel extends React.PureComponent { const homeserverSupportsCrossSigning = await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"); const crossSigningReady = await cli.isCrossSigningReady(); + const secretStorageReady = await cli.isSecretStorageReady(); this.setState({ crossSigningPublicKeysOnDevice, @@ -101,6 +102,7 @@ export default class CrossSigningPanel extends React.PureComponent { secretStorageKeyInAccount, homeserverSupportsCrossSigning, crossSigningReady, + secretStorageReady, }); } @@ -151,6 +153,7 @@ export default class CrossSigningPanel extends React.PureComponent { secretStorageKeyInAccount, homeserverSupportsCrossSigning, crossSigningReady, + secretStorageReady, } = this.state; let errorSection; @@ -166,14 +169,19 @@ export default class CrossSigningPanel extends React.PureComponent { summarisedStatus =

{_t( "Your homeserver does not support cross-signing.", )}

; - } else if (crossSigningReady) { + } else if (crossSigningReady && secretStorageReady) { summarisedStatus =

✅ {_t( - "Cross-signing and secret storage are enabled.", + "Cross-signing and secret storage are ready for use.", + )}

; + } else if (crossSigningReady && !secretStorageReady) { + summarisedStatus =

✅ {_t( + "Cross-signing is ready for use, but secret storage is " + + "currently not being used to backup your keys.", )}

; } else if (crossSigningPrivateKeysInStorage) { summarisedStatus =

{_t( - "Your account has a cross-signing identity in secret storage, but it " + - "is not yet trusted by this session.", + "Your account has a cross-signing identity in secret storage, " + + "but it is not yet trusted by this session.", )}

; } else { summarisedStatus =

{_t( diff --git a/src/createRoom.ts b/src/createRoom.ts index 78d0cf1356..09de265ebc 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -27,6 +27,7 @@ import * as Rooms from "./Rooms"; import DMRoomMap from "./utils/DMRoomMap"; import {getAddressType} from "./UserAddress"; import { getE2EEWellKnown } from "./utils/WellKnownUtils"; +import GroupStore from "./stores/GroupStore"; // we define a number of interfaces which take their names from the js-sdk /* eslint-disable camelcase */ @@ -79,6 +80,7 @@ interface IOpts { encryption?: boolean; inlineErrors?: boolean; andView?: boolean; + associatedWithCommunity?: string; } /** @@ -181,6 +183,10 @@ export default function createRoom(opts: IOpts): Promise { } else { return Promise.resolve(); } + }).then(() => { + if (opts.associatedWithCommunity) { + return GroupStore.addRoomToGroup(opts.associatedWithCommunity, roomId, false); + } }).then(function() { // NB createRoom doesn't block on the client seeing the echo that the // room has been created, so we race here with the client knowing that diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d6ba736a76..442f07499c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -645,7 +645,8 @@ "Confirm password": "Confirm password", "Change Password": "Change Password", "Your homeserver does not support cross-signing.": "Your homeserver does not support cross-signing.", - "Cross-signing and secret storage are enabled.": "Cross-signing and secret storage are enabled.", + "Cross-signing and secret storage are ready for use.": "Cross-signing and secret storage are ready for use.", + "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.": "Cross-signing is ready for use, but secret storage is currently not being used to backup your keys.", "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.": "Your account has a cross-signing identity in secret storage, but it is not yet trusted by this session.", "Cross-signing and secret storage are not yet set up.": "Cross-signing and secret storage are not yet set up.", "Reset cross-signing and secret storage": "Reset cross-signing and secret storage", @@ -1121,6 +1122,7 @@ "Rooms": "Rooms", "Add room": "Add room", "Create new room": "Create new room", + "Explore community rooms": "Explore community rooms", "Explore public rooms": "Explore public rooms", "Low priority": "Low priority", "System Alerts": "System Alerts", @@ -1636,18 +1638,21 @@ "Community ID": "Community ID", "example": "example", "Please enter a name for the room": "Please enter a name for the room", - "Set a room address to easily share your room with other people.": "Set a room address to easily share your room with other people.", - "This room is private, and can only be joined by invitation.": "This room is private, and can only be joined by invitation.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.": "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone.", + "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.": "Private rooms can be found and joined by invitation only. Public rooms can be found and joined by anyone in this community.", "You can’t disable this later. Bridges & most bots won’t work yet.": "You can’t disable this later. Bridges & most bots won’t work yet.", "Enable end-to-end encryption": "Enable end-to-end encryption", + "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.": "You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later.", + "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.", "Create a public room": "Create a public room", "Create a private room": "Create a private room", + "Create a room in %(communityName)s": "Create a room in %(communityName)s", "Name": "Name", "Topic (optional)": "Topic (optional)", "Make this room public": "Make this room public", "Hide advanced": "Hide advanced", "Show advanced": "Show advanced", - "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)", + "Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.", "Create Room": "Create Room", "Sign out": "Sign out", "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this", diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index 74292749b9..448562b68a 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -115,6 +115,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) { body.append("cross_signing_supported_by_hs", String(await client.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing"))); body.append("cross_signing_ready", String(await client.isCrossSigningReady())); + body.append("secret_storage_ready", String(await client.isSecretStorageReady())); } } diff --git a/src/stores/TagOrderStore.js b/src/stores/TagOrderStore.js index f02fce0769..2eb35e6dc2 100644 --- a/src/stores/TagOrderStore.js +++ b/src/stores/TagOrderStore.js @@ -285,6 +285,13 @@ class TagOrderStore extends Store { getSelectedTags() { return this._state.selectedTags; } + + getSelectedPrototypeTag() { + if (SettingsStore.getValue("feature_communities_v2_prototypes")) { + return this.getSelectedTags()[0]; + } + return null; // no selection as far as this function is concerned + } } if (global.singletonTagOrderStore === undefined) {